WME FC Layer

Adds a Functional Class layer for states that publish ArcGIS FC data.

当前为 2020-03-07 提交的版本,查看 最新版本

  1. /* global W */
  2. /* global Promise */
  3. /* global OL */
  4. /* global I18n */
  5. /* global unsafeWindow */
  6. /* global GM_info */
  7. /* global WazeWrap */
  8.  
  9. // // ==UserScript==
  10. // @name WME FC Layer
  11. // @namespace https://greasyfork.org/users/45389
  12. // @version 2020.02.22.001
  13. // @description Adds a Functional Class layer for states that publish ArcGIS FC data.
  14. // @author MapOMatic
  15. // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
  16. // @license GNU GPLv3
  17. // @contributionURL https://github.com/WazeDev/Thank-The-Authors
  18. // @require https://greasyfork.org/scripts/39002-bluebird/code/Bluebird.js?version=255146
  19. // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
  20. // @grant GM_xmlhttpRequest
  21. // @connect arcgis.com
  22. // @connect arkansas.gov
  23. // @connect azdot.gov
  24. // @connect coloradodot.info
  25. // @connect delaware.gov
  26. // @connect dc.gov
  27. // @connect ga.gov
  28. // @connect uga.edu
  29. // @connect hawaii.gov
  30. // @connect idaho.gov
  31. // @connect in.gov
  32. // @connect iowadot.gov
  33. // @connect illinois.gov
  34. // @connect ksdot.org
  35. // @connect ky.gov
  36. // @connect la.gov
  37. // @connect maine.gov
  38. // @connect md.gov
  39. // @connect ma.us
  40. // @connect state.mi.us
  41. // @connect modot.org
  42. // @connect mt.gov
  43. // @connect unh.edu
  44. // @connect ny.gov
  45. // @connect ncdot.gov
  46. // @connect nd.gov
  47. // @connect oh.us
  48. // @connect or.us
  49. // @connect pa.gov
  50. // @connect sd.gov
  51. // @connect shelbycountytn.gov
  52. // @connect utah.gov
  53. // @connect vermont.gov
  54. // @connect wa.gov
  55. // @connect wv.gov
  56. // @connect wyoroad.info
  57. // ==/UserScript==
  58.  
  59. /* eslint-disable */
  60.  
  61. (function () {
  62. 'use strict';
  63.  
  64. var _settingsStoreName = 'wme_fc_layer';
  65. var _alertUpdate = false;
  66. var _debugLevel = 0;
  67. var _scriptVersion = GM_info.script.version;
  68. var _scriptVersionChanges = [
  69. GM_info.script.name,
  70. 'v' + _scriptVersion,
  71. '',
  72. 'What\'s New',
  73. '------------------------------',
  74. '' // Add important stuff here when _alertUpdate = true.
  75. ].join('\n');
  76. var _mapLayer = null;
  77. var _isAM = false;
  78. var _uid;
  79. var _settings = {};
  80. var _r;
  81. var _mapLayerZIndex = 334;
  82. var _betaIDs = [103400892];
  83. var _statesHash = {
  84. 'Alabama': 'AL', 'Alaska': 'AK', 'American Samoa': 'AS', 'Arizona': 'AZ', 'Arkansas': 'AR', 'California': 'CA', 'Colorado': 'CO', 'Connecticut': 'CT', 'Delaware': 'DE', 'District of Columbia': 'DC',
  85. 'Federated States Of Micronesia': 'FM', 'Florida': 'FL', 'Georgia': 'GA', 'Guam': 'GU', 'Hawaii': 'HI', 'Idaho': 'ID', 'Illinois': 'IL', 'Indiana': 'IN', 'Iowa': 'IA', 'Kansas': 'KS',
  86. 'Kentucky': 'KY', 'Louisiana': 'LA', 'Maine': 'ME', 'Marshall Islands': 'MH', 'Maryland': 'MD', 'Massachusetts': 'MA', 'Michigan': 'MI', 'Minnesota': 'MN', 'Mississippi': 'MS', 'Missouri': 'MO',
  87. 'Montana': 'MT', 'Nebraska': 'NE', 'Nevada': 'NV', 'New Hampshire': 'NH', 'New Jersey': 'NJ', 'New Mexico': 'NM', 'New York': 'NY', 'North Carolina': 'NC', 'North Dakota': 'ND',
  88. 'Northern Mariana Islands': 'MP', 'Ohio': 'OH', 'Oklahoma': 'OK', 'Oregon': 'OR', 'Palau': 'PW', 'Pennsylvania': 'PA', 'Puerto Rico': 'PR', 'Rhode Island': 'RI', 'South Carolina': 'SC',
  89. 'South Dakota': 'SD', 'Tennessee': 'TN', 'Texas': 'TX', 'Utah': 'UT', 'Vermont': 'VT', 'Virgin Islands': 'VI', 'Virginia': 'VA', 'Washington': 'WA', 'West Virginia': 'WV', 'Wisconsin': 'WI', 'Wyoming': 'WY'
  90. };
  91.  
  92. function reverseStatesHash(stateAbbr) {
  93. for (var stateName in _statesHash) {
  94. if (_statesHash[stateName] === stateAbbr) return stateName;
  95. }
  96. }
  97. var _stateSettings = {
  98. global: {
  99. roadTypes: ['St', 'PS', 'PS2', 'mH', 'MH', 'Ew', 'Rmp', 'Fw'], // Ew = Expressway. For FC's that make it uncertain if they should be MH or FW.
  100. getFeatureRoadType: function (feature, layer) {
  101. var fc = feature.attributes[layer.fcPropName];
  102. return this.getRoadTypeFromFC(fc, layer);
  103. },
  104. getRoadTypeFromFC: function (fc, layer) {
  105. for (var roadType in layer.roadTypeMap) {
  106. if (layer.roadTypeMap[roadType].indexOf(fc) !== -1) {
  107. return roadType;
  108. }
  109. }
  110. return null;
  111. },
  112. isPermitted: function (stateAbbr) { if (_betaIDs.indexOf(_uid) !== -1) return true; var state = _stateSettings[stateAbbr]; if (state.isPermitted) { return state.isPermitted(); } else { return (_r >= 3 && _isAM) || (_r >= 4); } },
  113. getMapLayer: function (stateAbbr, layerID) {
  114. var returnValue;
  115. _stateSettings[stateAbbr].fcMapLayers.forEach(function (layer) {
  116. if (layer.layerID === layerID) {
  117. returnValue = layer;
  118. }
  119. });
  120. return returnValue;
  121. }
  122. },
  123. AL: {
  124. baseUrl: 'https://services.arcgis.com/LZzQi3xDiclG6XvQ/arcgis/rest/services/HPMS_Year2017_F_System_Data/FeatureServer/',
  125. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  126. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  127. fcMapLayers: [
  128. {
  129. layerID: 0, fcPropName: 'F_SYSTEM_V', idPropName: 'OBJECTID', outFields: ['FID', 'F_SYSTEM_V', 'State_Sys'],
  130. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  131. }
  132. ],
  133. isPermitted: function () { return _r >= 3; },
  134. information: { Source: 'ALDOT', Permission: 'Visible to R3+', Description: 'Federal and State highways set to a minimum of mH.' },
  135. getWhereClause: function (context) {
  136. if (context.mapContext.zoom < 4) {
  137. return context.layer.fcPropName + "<>7";
  138. } else {
  139. return null;
  140. }
  141. },
  142. getFeatureRoadType: function (feature, layer) {
  143. var fc = parseInt(feature.attributes[layer.fcPropName]);
  144. if (fc > 4 && feature.attributes.State_Sys === 'YES') { fc = 4; }
  145. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  146. }
  147. },
  148. AK: {
  149. baseUrl: 'https://services.arcgis.com/r4A0V7UzH9fcLVvv/ArcGIS/rest/services/AKDOTPF_Route_Data/FeatureServer/',
  150. defaultColors: { Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  151. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  152. fcMapLayers: [
  153. {
  154. layerID: 13, fcPropName: 'Functional_Class', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'Functional_Class'],
  155. roadTypeMap: { Ew: [1, 2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  156. }
  157. ],
  158. information: { Source: 'Alaska DOT&PF', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  159. getWhereClause: function (context) {
  160. if (context.mapContext.zoom < 4) {
  161. return context.layer.fcPropName + "<>7";
  162. } else {
  163. return null;
  164. }
  165. },
  166. getFeatureRoadType: function (feature, layer) {
  167. if (layer.getFeatureRoadType) {
  168. return layer.getFeatureRoadType(feature);
  169. } else {
  170. return _stateSettings.global.getFeatureRoadType(feature, layer);
  171. }
  172. }
  173. },
  174. AZ: {
  175. baseUrl: 'https://gis.azdot.gov/gis/rest/services/AGOL/FunClass_NHS/MapServer/',
  176. defaultColors: { Fw: '#ff00c5', Ew: '#ff00c5', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  177. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  178. fcMapLayers: [
  179. {
  180. layerID: 8, fcPropName: 'FunctionalClass', idPropName: 'OBJECTID',
  181. outFields: ['OBJECTID', 'FunctionalClass', 'RouteId'],
  182. roadTypeMap: { Fw: [1, 11], Ew: [2, 3, 12], MH: [4, 14], mH: [6, 16], PS: [7, 17, 8, 18], St: [] }, maxRecordCount: 1000, supportsPagination: false
  183. }
  184. ],
  185. information: { Source: 'ADOT', Permission: 'Visible to R4+ or R3-AM' },
  186. getWhereClause: function (context) {
  187. return context.layer.fcPropName + ' NOT IN (9, 19)';
  188. },
  189. getFeatureRoadType: function (feature, layer) {
  190. var roadID = feature.attributes.RouteId.trim().replace(/ +/g, ' ');
  191. var roadNum = parseInt(roadID.substring(2, 5));
  192. var fc = parseInt(feature.attributes[layer.fcPropName]);
  193. fc = (fc === 2) ? 4 : fc % 10;
  194. var azIH = [8, 10, 11, 17, 19, 40]; // Interstate hwys in AZ
  195. var isUS = RegExp(/^U\D\d{3}\b/).test(roadID);
  196. var isState = RegExp(/^S\D\d{3}\b/).test(roadID);
  197. var isBiz = RegExp(/^SB\d{3}\b/).test(roadID);
  198. if (fc > 4 && isState && azIH.includes(roadNum) && isBiz) {
  199. fc = 4;
  200. } else if (fc > 4 && isUS) {
  201. fc = isBiz ? 6 : 4;
  202. } else if (fc > 6 && isState) {
  203. fc = isBiz ? 7 : 6;
  204. }
  205. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  206. }
  207. },
  208. AR: {
  209. baseUrl: 'https://gis.arkansas.gov/arcgis/rest/services/FEATURESERVICES/Transportation/FeatureServer/',
  210. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  211. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  212. fcMapLayers: [
  213. {
  214. layerID: 8, fcPropName: 'FunctionalClass', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FunctionalClass', 'AH_Route', 'AH_Section'],
  215. roadTypeMap: { Fw: [1, 2], Ew: [], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  216. }
  217. ],
  218. information: { Source: 'ARDOT', Permission: 'Visible to R4+ or R3-AM' },
  219. getWhereClause: function (context) {
  220. return null;
  221. },
  222. getFeatureRoadType: function (feature, layer) {
  223. var attr = feature.attributes;
  224. var fc = parseInt(attr[layer.fcPropName]);
  225. var roadID = parseInt(attr.AH_Route);
  226. var usHwys = [49, 59, 61, 62, 63, 64, 65, 67, 70, 71, 79, 82, 165, 167, 270, 271, 278, 371, 412, 425];
  227. var isUS = usHwys.includes(roadID);
  228. var isState = roadID < 613;
  229. var isBiz = attr.AH_Section[attr.AH_Section.length - 1] === 'B';
  230. if (fc > 3 && isUS) {
  231. fc = isBiz ? 4 : 3;
  232. } else if (fc > 4 && isState) {
  233. fc = isBiz ? 5 : 4;
  234. }
  235. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  236. }
  237. },
  238. CO: {
  239. baseUrl: 'http://dtdapps.coloradodot.info/arcgis/rest/services/MapView/BaseLayers_MapView_ext/MapServer/',
  240. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  241. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  242. fcMapLayers: [
  243. {
  244. layerID: 7, fcPropName: 'FUNCCLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCCLASS', 'ROUTE', 'REFPT'],
  245. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  246. },
  247. {
  248. layerID: 17, fcPropName: 'FUNCCLASSID', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCCLASSID', 'ROUTE', 'FIPSCOUNTY'],
  249. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  250. },
  251. {
  252. layerID: 18, fcPropName: 'FUNCCLASSID', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCCLASSID', 'ROUTE'],
  253. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  254. }
  255. ],
  256. isPermitted: function () { return _r >= 4; },
  257. information: {
  258. Source: 'CDOT', Permission: 'Visible to R4+',
  259. Description: 'Please consult with a state manager before making any changes to road types based on the data presented.'
  260. },
  261. getWhereClause: function (context) {
  262. if (context.mapContext.zoom < 4) {
  263. return context.layer.fcPropName + "<>'7'";
  264. } else {
  265. return null;
  266. }
  267. },
  268. getFeatureRoadType: function (feature, layer) {
  269. var attr = feature.attributes;
  270. var fc = parseInt(attr[layer.fcPropName]);
  271. var route = attr.ROUTE.replace(/ +/g, ' ');
  272. if (layer.layerID === 7) {
  273. var rtnum = parseInt(route.slice(0, 3));
  274. var refpt = attr.REFPT;
  275. var hwys = [6, 24, 25, 34, 36, 40, 50, 70, 84, 85, 87, 138, 160, 285, 287, 350, 385, 400, 491, 550];
  276. var IH = [25, 70];
  277. // Exceptions first, then normal classification
  278. var doNothing = ['024D', '040G'];
  279. var notNothing = ['070K', '070L', '070O', '070Q', '070R'];
  280. var doMin = ['024E', '050D', '070O', '085F', '160D'];
  281. if (doNothing.includes(route) || (rtnum === 70 && route !== '070K' && !notNothing.includes(route))) { }
  282. else if (doMin.includes(route) ||
  283. (rtnum === 40 && refpt > 320 && refpt < 385) ||
  284. (rtnum === 36 && refpt > 79 && refpt < 100.99) ||
  285. (route === '034D' && refpt > 11)) {
  286. fc = 4;
  287. } else if (hwys.includes(rtnum)) {
  288. fc = Math.min(fc, 3);
  289. }
  290. else {
  291. fc = Math.min(fc, 4);
  292. }
  293. }
  294. else {
  295. // All exceptions
  296. var fips = parseInt(attr.FIPSCOUNTY);
  297. if ((fips === 19 && route === 'COLORADO BD') ||
  298. (fips === 37 && (route === 'GRAND AV' || route === 'S H6'))) { fc = 3; }
  299. else if (fips === 67 && route === 'BAYFIELDPAY') { fc = 4; }
  300. }
  301. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  302. }
  303. },
  304. DE: {
  305. baseUrl: 'https://firstmap.delaware.gov/arcgis/rest/services/Transportation/DE_FUNCTIONAL_CLASSIFICATION/MapServer/',
  306. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  307. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  308. fcMapLayers: [
  309. {
  310. layerID: 0, fcPropName: 'VALUE_TEXT', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'VALUE_TEXT'], maxRecordCount: 1000, supportsPagination: false,
  311. roadTypeMap: { Fw: ['Interstate'], Ew: ['Other Expressways & Freeway'], MH: ['Other Principal Arterials'], mH: ['Minor Arterial'], PS: ['Major Collector', 'Minor Collector'], St: ['Local'] }
  312. }
  313. ],
  314. information: { Source: 'Delaware FirstMap', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  315. getWhereClause: function (context) {
  316. if (context.mapContext.zoom < 4) {
  317. return context.layer.fcPropName + " <> 'Local'";
  318. } else {
  319. return null;
  320. }
  321. },
  322. getFeatureRoadType: function (feature, layer) {
  323. if (layer.getFeatureRoadType) {
  324. return layer.getFeatureRoadType(feature);
  325. } else {
  326. return _stateSettings.global.getFeatureRoadType(feature, layer);
  327. }
  328. }
  329. },
  330. DC: {
  331. baseUrl: 'https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Transportation_WebMercator/MapServer/',
  332. supportsPagination: false,
  333. defaultColors: { Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  334. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  335. fetchAllFC: false,
  336. fcMapLayers: [
  337. {
  338. layerID: 48, fcPropName: 'FUNCTIONALCLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCTIONALCLASS'], maxRecordCount: 1000, supportsPagination: false,
  339. roadTypeMap: { Fw: ['Interstate'], Ew: ['Other Freeway and Expressway'], MH: ['Principal Arterial'], mH: ['Minor Arterial'], PS: ['Collector'] }
  340. }
  341. ],
  342. information: { Source: 'DDOT', Permission: 'Visible to R4+ or R3-AM' },
  343. getWhereClause: function (context) {
  344. return null;
  345. },
  346. getFeatureRoadType: function (feature, layer) {
  347. if (layer.getFeatureRoadType) {
  348. return layer.getFeatureRoadType(feature);
  349. } else {
  350. return _stateSettings.global.getFeatureRoadType(feature, layer);
  351. }
  352. }
  353. },
  354. FL: {
  355. baseUrl: 'https://services1.arcgis.com/O1JpcwDW8sjYuddV/ArcGIS/rest/services/Functional_Classification_TDA/FeatureServer/',
  356. supportsPagination: false,
  357. defaultColors: { Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  358. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  359. fetchAllFC: false,
  360. fcMapLayers: [
  361. {
  362. layerID: 0, fcPropName: 'FUNCLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCLASS'], maxRecordCount: 1000, supportsPagination: false,
  363. roadTypeMap: { Fw: ['01', '11'], Ew: ['02', '12'], MH: ['04', '14'], mH: ['06', '16'], PS: ['07', '08', '17', '18'] }
  364. }
  365. ],
  366. information: { Source: 'FDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  367. getWhereClause: function (context) {
  368. return null;
  369. },
  370. getFeatureRoadType: function (feature, layer) {
  371. if (layer.getFeatureRoadType) {
  372. return layer.getFeatureRoadType(feature);
  373. } else {
  374. return _stateSettings.global.getFeatureRoadType(feature, layer);
  375. }
  376. }
  377. },
  378. GA: {
  379. baseUrl: 'https://maps.itos.uga.edu/arcgis/rest/services/GDOT/GDOT_FunctionalClass/mapserver/',
  380. supportsPagination: true,
  381. defaultColors: { Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  382. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  383. fetchAllFC: false,
  384. fcMapLayers: [
  385. { layerID: 0, fcPropName: 'FUNCTIONAL_CLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'SYSTEM_CODE'], maxRecordCount: 1000, supportsPagination: true, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  386. { layerID: 1, fcPropName: 'FUNCTIONAL_CLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'SYSTEM_CODE'], maxRecordCount: 1000, supportsPagination: true, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  387. { layerID: 2, fcPropName: 'FUNCTIONAL_CLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'SYSTEM_CODE'], maxRecordCount: 1000, supportsPagination: true, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  388. { layerID: 3, fcPropName: 'FUNCTIONAL_CLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'SYSTEM_CODE'], maxRecordCount: 1000, supportsPagination: true, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  389. { layerID: 4, fcPropName: 'FUNCTIONAL_CLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'SYSTEM_CODE'], maxRecordCount: 1000, supportsPagination: true, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  390. { layerID: 5, fcPropName: 'FUNCTIONAL_CLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'SYSTEM_CODE'], maxRecordCount: 1000, supportsPagination: true, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  391. { layerID: 6, fcPropName: 'FUNCTIONAL_CLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'SYSTEM_CODE'], maxRecordCount: 1000, supportsPagination: true, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } }
  392. ],
  393. information: { Source: 'GDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Federal and State highways set to a minimum of mH.' },
  394. getWhereClause: function (context) {
  395. return null;
  396. },
  397. getFeatureRoadType: function (feature, layer) {
  398. if (layer.getFeatureRoadType) {
  399. return layer.getFeatureRoadType(feature);
  400. } else {
  401. var attr = feature.attributes;
  402. var fc = attr.FUNCTIONAL_CLASS;
  403. if (attr.SYSTEM_CODE === '1' && fc > 4) {
  404. return _stateSettings.global.getRoadTypeFromFC(4, layer);
  405. } else {
  406. return _stateSettings.global.getFeatureRoadType(feature, layer);
  407. }
  408. }
  409. }
  410. },
  411. HI: {
  412. baseUrl: 'http://geodata.hawaii.gov/arcgis/rest/services/Transportation/MapServer/',
  413. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  414. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  415. fcMapLayers: [
  416. {
  417. layerID: 12, fcPropName: 'funsystem', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'funsystem'],
  418. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  419. }
  420. ],
  421. information: { Source: 'HDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  422. getWhereClause: function (context) {
  423. if (context.mapContext.zoom < 4) {
  424. return context.layer.fcPropName + "<>7";
  425. } else {
  426. return null;
  427. }
  428. },
  429. getFeatureRoadType: function (feature, layer) {
  430. if (layer.getFeatureRoadType) {
  431. return layer.getFeatureRoadType(feature);
  432. } else {
  433. return _stateSettings.global.getFeatureRoadType(feature, layer);
  434. }
  435. }
  436. },
  437. ID: {
  438. baseUrl: 'https://gis.itd.idaho.gov/arcgisprod/rest/services/IPLAN/Functional_Classification/MapServer/',
  439. supportsPagination: false,
  440. defaultColors: { Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  441. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  442. fetchAllFC: true,
  443. fcMapLayers: [
  444. { layerID: 0, fcPropName: 'FCCODE', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FCCODE'], maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  445. { layerID: 1, fcPropName: 'FCCODE', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FCCODE'], maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  446. { layerID: 2, fcPropName: 'FCCODE', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FCCODE'], maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  447. { layerID: 3, fcPropName: 'FCCODE', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FCCODE'], maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  448. { layerID: 4, fcPropName: 'FCCODE', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FCCODE'], maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  449. { layerID: 5, fcPropName: 'FCCODE', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FCCODE'], maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } }
  450. ],
  451. information: { Source: 'ITD', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  452. getWhereClause: function (context) {
  453. return null;
  454. },
  455. getFeatureRoadType: function (feature, layer) {
  456. if (layer.getFeatureRoadType) {
  457. return layer.getFeatureRoadType(feature);
  458. } else {
  459. return _stateSettings.global.getFeatureRoadType(feature, layer);
  460. }
  461. }
  462. },
  463. IL: {
  464. baseUrl: 'http://ags10s1.dot.illinois.gov/ArcGIS/rest/services/IRoads/IRoads_53/MapServer/',
  465. supportsPagination: false,
  466. defaultColors: { Fw: '#ff00c5', Ew: '#ff00c5', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee', CH: '#ff5e0e' },
  467. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  468. fcMapLayers: [
  469. {
  470. layerID: 3, idPropName: 'OBJECTID', fcPropName: 'FC', outFields: ['FC', 'MRK_RT_TYP', 'CH', 'OBJECTID'],
  471. roadTypeMap: { Fw: ['1'], Ew: ['2'], MH: ['3'], mH: ['4'], PS: ['5', '6'], St: ['7'] }, maxRecordCount: 1000, supportsPagination: false
  472. }
  473. ],
  474. isPermitted: function () { return _r >= 4; },
  475. information: { Source: 'IDOT', Permission: 'Visible to R4+' },
  476. getWhereClause: function (context) {
  477. return context.mapContext.zoom < 4 ? "FC<>7" : null;
  478. },
  479. getFeatureRoadType: function (feature, layer) {
  480. var attr = feature.attributes;
  481. var fc = attr.FC;
  482. var type = attr.MRK_RT_TYP;
  483. if (fc > 3 && type === 'U') { // US Route
  484. fc = 3;
  485. } else if (fc > 4 && type === 'S') { // State Route
  486. fc = 4;
  487. } else if (fc > 6 && attr.CH !== '0000') { // County Route
  488. fc = 6;
  489. }
  490. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  491. }
  492. },
  493. IN: {
  494. baseUrl: 'https://gis.in.gov/arcgis/rest/services/DOT/INDOT_LTAP/FeatureServer/',
  495. supportsPagination: false,
  496. overrideUrl: '1Sbwc7e6BfHpZWSTfU3_1otXGSxHrdDYcbn7fOf1VjpA',
  497. defaultColors: { Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  498. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []], hideRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  499. fcMapLayers: [
  500. {
  501. layerID: 10, idPropName: 'OBJECTID', fcPropName: 'FUNCTIONAL_CLASS', outFields: ['FUNCTIONAL_CLASS', 'OBJECTID', 'TO_DATE'],
  502. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 100000, supportsPagination: false
  503. }
  504. ],
  505. isPermitted: function () { return true; },
  506. information: { Source: 'INDOT', Description: 'Raw unmodified FC data.' },
  507. getWhereClause: function (context) {
  508. var whereParts = ['TO_DATE IS NULL'];
  509. if (context.mapContext.zoom < 4) {
  510. whereParts += ' AND ' + context.layer.fcPropName + '<>7';
  511. }
  512. return whereParts;
  513. },
  514. getFeatureRoadType: function (feature, layer) {
  515. if (layer.getFeatureRoadType) {
  516. return layer.getFeatureRoadType(feature);
  517. } else {
  518. return _stateSettings.global.getFeatureRoadType(feature, layer);
  519. }
  520. }
  521. },
  522. IA: {
  523. baseUrl: 'https://gis.iowadot.gov/public/rest/services/RAMS/Road_Network/MapServer/',
  524. defaultColors: { Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee', PSGr: '#cc6533', StGr: '#e99cb6' },
  525. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  526. fcMapLayers: [
  527. {
  528. layerID: 0, fcPropName: 'FED_FUNCTIONAL_CLASS', idPropName: 'OBJECTID',
  529. outFields: ['OBJECTID', 'FED_FUNCTIONAL_CLASS', 'STATE_ROUTE_NAME_1', 'ACCESS_CONTROL', 'SURFACE_TYPE'],
  530. roadTypeMap: { Fw: [1], MH: [2, 3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  531. }
  532. ],
  533. information: { Source: 'Iowa DOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Additional colors denote unpaved PS and LS segements.' },
  534. getWhereClause: function (context) {
  535. var theWhereClause = "FACILITY_TYPE<>'7'"; // Removed proposed roads
  536. if (context.mapContext.zoom < 4) {
  537. theWhereClause += " AND " + context.layer.fcPropName + "<>'7'";
  538. }
  539. return theWhereClause;
  540. },
  541. getFeatureRoadType: function (feature, layer) {
  542. var attr = feature.attributes;
  543. var fc = parseInt(attr[layer.fcPropName]);
  544. var isFw = attr.ACCESS_CONTROL === 1;
  545. var isUS = RegExp('^STATE OF IOWA, US').test(attr.STATE_ROUTE_NAME_1);
  546. var isState = RegExp('^STATE OF IOWA, IA').test(attr.STATE_ROUTE_NAME_1);
  547. if (isFw) {
  548. fc = 1;
  549. } else if (fc > 3 && isUS) {
  550. fc = 3;
  551. } else if (fc > 4 && isState) {
  552. fc = 4;
  553. }
  554. if (fc > 4 && attr.SURFACE_TYPE === 20) {
  555. return fc < 7 ? 'PSGr' : 'StGr';
  556. } else {
  557. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  558. }
  559. }
  560. },
  561. KS: {
  562. baseUrl: 'http://wfs.ksdot.org/arcgis_web_adaptor/rest/services/Transportation/',
  563. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  564. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  565. fcMapLayers: [
  566. {
  567. layerID: 0, layerPath: 'Non_State_System/MapServer/', idPropName: 'ID2', fcPropName: 'FUNCLASS', outFields: ['FUNCLASS', 'ID2', 'ROUTE_ID'],
  568. roadTypeMap: { Fw: [1], MH: [2, 3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  569. },
  570. {
  571. layerID: 0, layerPath: 'State_System/MapServer/', idPropName: 'OBJECTID', fcPropName: 'FUN_CLASS_CD', outFields: ['FUN_CLASS_CD', 'OBJECTID', 'PREFIX', 'ACCESS_CONTROL'],
  572. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  573. }
  574. ],
  575. information: { Source: 'KDOT', Permission: 'Visible to R4+ or R3-AM' },
  576. getWhereClause: function (context) {
  577. if (context.mapContext.zoom < 4) {
  578. return context.layer.fcPropName + "<>'7'";
  579. } else {
  580. return null;
  581. }
  582. },
  583. getFeatureRoadType: function (feature, layer) {
  584. var attr = feature.attributes;
  585. var fc = parseInt(attr[layer.fcPropName]);
  586. var roadPrefix = attr.PREFIX;
  587. var isUS = roadPrefix === 'U';
  588. var isState = roadPrefix === 'K';
  589. if ((fc > 3 && isUS) || (fc === 2 && parseInt(attr.ACCESS_CONTROL) !== 1)) {
  590. fc = 3;
  591. } else if (fc > 4 && isState) {
  592. fc = 4;
  593. }
  594. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  595. },
  596. },
  597. KY: {
  598. baseUrl: 'https://maps.kytc.ky.gov/arcgis/rest/services/BaseMap/System/MapServer/',
  599. supportsPagination: false,
  600. defaultColors: { Fw: '#ff00c5', Ew: '#ff00c5', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  601. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  602. fcMapLayers: [
  603. {
  604. layerID: 0, idPropName: 'OBJECTID', fcPropName: 'FC', outFields: ['FC', 'OBJECTID', 'RT_PREFIX', 'RT_SUFFIX'],
  605. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  606. }
  607. ],
  608. isPermitted: function () { return true; },
  609. information: { Source: 'KYTC' },
  610. getWhereClause: function (context) {
  611. if (context.mapContext.zoom < 4) {
  612. return context.layer.fcPropName + "<>'7'";
  613. } else {
  614. return null;
  615. }
  616. },
  617. getFeatureRoadType: function (feature, layer) {
  618. var attr = feature.attributes;
  619. var fc = parseInt(attr[layer.fcPropName]);
  620. if (fc > 3 && attr.RT_PREFIX === 'US') {
  621. var suffix = attr.RT_SUFFIX;
  622. fc = (suffix && suffix.indexOf('X') > -1) ? 4 : 3;
  623. }
  624. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  625. }
  626. },
  627. LA: {
  628. baseUrl: 'https://giswebnew.dotd.la.gov/arcgis/rest/services/Transportation/LA_RoadwayFunctionalClassification/FeatureServer/',
  629. supportsPagination: false,
  630. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  631. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  632. fcMapLayers: [
  633. { layerID: 0, fcPropName: 'FunctionalSystem', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FunctionalSystem', 'RouteID'], roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false },
  634. { layerID: 1, fcPropName: 'FunctionalSystem', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FunctionalSystem', 'RouteID'], roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false },
  635. { layerID: 2, fcPropName: 'FunctionalSystem', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FunctionalSystem', 'RouteID'], roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false },
  636. { layerID: 3, fcPropName: 'FunctionalSystem', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FunctionalSystem', 'RouteID'], roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false },
  637. { layerID: 4, fcPropName: 'FunctionalSystem', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FunctionalSystem', 'RouteID'], roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false },
  638. { layerID: 5, fcPropName: 'FunctionalSystem', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FunctionalSystem', 'RouteID'], roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false },
  639. { layerID: 6, fcPropName: 'FunctionalSystem', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FunctionalSystem', 'RouteID'], roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false }
  640. ],
  641. information: { Source: 'LaDOTD', Permission: 'Visible to R4+ or R3-AM' },
  642. getWhereClause: function (context) {
  643. if (context.mapContext.zoom < 4) {
  644. return context.layer.fcPropName + "<>'7'"; // OR State_Route LIKE 'US%' OR State_Route LIKE 'LA%'";
  645. } else {
  646. return null;
  647. }
  648. },
  649. getFeatureRoadType: function (feature, layer) {
  650. var fc = feature.attributes[layer.fcPropName];
  651. if (fc === '2a' || fc === '2b') { fc = 2; }
  652. fc = parseInt(fc);
  653. var route = feature.attributes.RouteID.split('_')[1].trim();
  654. var isUS = /^US \d/.test(route);
  655. var isState = /^LA \d/.test(route);
  656. var isBiz = / BUS$/.test(route);
  657. if (fc > 3 && isUS) {
  658. fc = isBiz ? 4 : 3;
  659. } else if (fc > 4 && isState) {
  660. fc = isBiz ? 5 : 4;
  661. }
  662. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  663. }
  664. },
  665. ME: {
  666. baseUrl: 'https://arcgisserver.maine.gov/arcgis/rest/services/mdot/MaineDOT_Dynamic/MapServer/',
  667. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  668. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  669. fcMapLayers: [
  670. {
  671. layerID: 811, fcPropName: 'fedfunccls', idPropName: 'objectid', outFields: ['objectid', 'fedfunccls', 'prirtename'],
  672. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  673. }
  674. ],
  675. information: { Source: 'MaineDOT', Permission: 'Visible to R4+ or R3-AM' },
  676. getWhereClause: function (context) {
  677. if (context.mapContext.zoom < 4) {
  678. return context.layer.fcPropName + "<>'Local'";
  679. } else {
  680. return null;
  681. }
  682. },
  683. getFeatureRoadType: function (feature, layer) {
  684. var attr = feature.attributes;
  685. var fc = attr[layer.fcPropName];
  686. switch (fc) {
  687. case 'Princ art interstate': fc = 1; break;
  688. case 'Princ art other f&e': fc = 2; break;
  689. case 'Other princ arterial': fc = 3; break;
  690. case 'Minor arterial': fc = 4; break;
  691. case 'Major/urb collector':
  692. case 'Minor collector': fc = 5; break;
  693. default: fc = 7;
  694. }
  695. var route = attr.prirtename;
  696. var isUS = RegExp(/^US \d/).test(route);
  697. var isState = RegExp(/^ST RTE \d/).test(route);
  698. var isBiz = (isUS && RegExp(/(1B|1BS)$/).test(route)) || (isState && RegExp(/(15B|24B|25B|137B)$/).test(route));
  699. if (fc > 3 && isUS) {
  700. fc = isBiz ? 4 : 3;
  701. } else if (fc > 4 && isState) {
  702. fc = isBiz ? 5 : 4;
  703. }
  704. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  705. }
  706. },
  707. MD: {
  708. baseUrl: 'https://geodata.md.gov/imap/rest/services/Transportation/MD_HighwayPerformanceMonitoringSystem/MapServer/',
  709. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#ffff00', St: '#eeeeee' },
  710. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  711. fcMapLayers: [
  712. { layerID: 2, fcPropName: 'FUNCTIONAL_CLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'ID_PREFIX', 'MP_SUFFIX'], roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false }
  713. ],
  714. information: { Source: 'MDOT', Permission: 'Visible to R4+ or R3-AM' },
  715. getWhereClause: function (context) {
  716. if (context.mapContext.zoom < 4) {
  717. return "(FUNCTIONAL_CLASS < 7 OR ID_PREFIX IN('MD'))";
  718. } else {
  719. return null;
  720. }
  721. },
  722. getFeatureRoadType: function (feature, layer) {
  723. var attr = feature.attributes;
  724. var fc = parseInt(attr.FUNCTIONAL_CLASS);
  725. var isUS = attr.ID_PREFIX === 'US';
  726. var isState = attr.ID_PREFIX === 'MD';
  727. var isBiz = attr.MP_SUFFIX === 'BU';
  728. if (fc > 3 && isUS) {
  729. fc = isBiz ? 4 : 3;
  730. } else if (fc > 4 && isState) {
  731. fc = isBiz ? 5 : 4;
  732. }
  733. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  734. }
  735. },
  736. MA: {
  737. baseUrl: 'https://gis.massdot.state.ma.us/arcgis/rest/services/Roads/RoadInventory/MapServer/',
  738. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  739. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  740. fcMapLayers: [
  741. {
  742. layerID: 0, fcPropName: 'F_F_Class', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'F_F_Class', 'Route_ID'],
  743. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  744. }
  745. ],
  746. information: { Source: 'MDOT', Permission: 'Visible to R4+ or R3-AM' },
  747. getWhereClause: function (context) {
  748. if (context.mapContext.zoom < 4) {
  749. return context.layer.fcPropName + "<>'7'";
  750. } else {
  751. return null;
  752. }
  753. },
  754. getFeatureRoadType: function (feature, layer) {
  755. var attr = feature.attributes;
  756. var fc = parseInt(attr[layer.fcPropName]);
  757. var route = attr.Route_ID;
  758. var isUS = /^US\d/.test(route);
  759. var isState = /^SR\d/.test(route);
  760. if (fc > 3 && isUS) {
  761. fc = 3;
  762. } else if (fc > 4 && isState) {
  763. fc = 4;
  764. }
  765. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  766. }
  767. },
  768. MI: {
  769. baseUrl: 'https://gisp.mcgi.state.mi.us/arcgis/rest/services/MDOT/NFC/MapServer/',
  770. defaultColors: { Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  771. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  772. fcMapLayers: [
  773. { layerID: 2, idPropName: 'OBJECTID', fcPropName: 'NFC', outFields: ['NFC'], roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false }
  774. ],
  775. isPermitted: function () { return true; },
  776. information: { Source: 'MDOT', Description: 'Raw unmodified FC data.' },
  777. getWhereClause: function (context) {
  778. if (context.mapContext.zoom < 4) {
  779. return context.layer.fcPropName + '<>7';
  780. } else {
  781. return null;
  782. }
  783. },
  784. getFeatureRoadType: function (feature, layer) {
  785. if (layer.getFeatureRoadType) {
  786. return layer.getFeatureRoadType(feature);
  787. } else {
  788. return _stateSettings.global.getFeatureRoadType(feature, layer);
  789. }
  790. }
  791. },
  792. MO: {
  793. baseUrl: 'http://mapping.modot.org/external/rest/services/BaseMap/TmsUtility/MapServer/',
  794. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  795. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  796. fcMapLayers: [
  797. {
  798. layerID: 5, fcPropName: 'FUNC_CLASS_NAME', idPropName: 'SS_PAVEMENT_ID', outFields: ['SS_PAVEMENT_ID', 'FUNC_CLASS_NAME', 'TRAVELWAY_DESG', 'TRAVELWAY_NAME', 'ACCESS_CAT_NAME'],
  799. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  800. }
  801. ],
  802. isPermitted: function () { return _r >= 3 || (_r >= 2 && _isAM); },
  803. information: { Source: 'MoDOT', Permission: 'Visible to R3+ or R2-AM' },
  804. getWhereClause: function (context) {
  805. if (context.mapContext.zoom < 1) {
  806. return '1=0'; //WME very laggy at zoom 0
  807. } else {
  808. // Remove duplicate rows, but suss out interstate business loops
  809. return "FUNC_CLASS_NAME <> ' ' AND (TRAVELWAY_ID = CNTL_TW_ID OR (TRAVELWAY_ID <> CNTL_TW_ID AND TRAVELWAY_DESG = 'LP'))";
  810. }
  811. },
  812. getFeatureRoadType: function (feature, layer) {
  813. var attr = feature.attributes;
  814. var fc = attr[layer.fcPropName];
  815. var rtType = attr.TRAVELWAY_DESG;
  816. var route = attr.TRAVELWAY_NAME;
  817. switch (fc) {
  818. case 'INTERSTATE': fc = 1; break;
  819. case 'FREEWAY': fc = 2; break;
  820. case 'PRINCIPAL ARTERIAL': fc = 3; break;
  821. case 'MINOR ARTERIAL': fc = 4; break;
  822. case 'MAJOR COLLECTOR': fc = 5; break
  823. case 'MINOR COLLECTOR': fc = 6; break;
  824. default: fc = 8; // not a typo
  825. }
  826. var usHwys = ['24', '36', '40', '50', '54', '56', '59', '60', '61', '62', '63', '65', '67', '69', '71', '136', '159', '160', '166', '169', '275', '400', '412'];
  827. var isUS = ['US', 'LP'].includes(rtType); // is US or interstate biz
  828. var isState = ['MO', 'AL'].includes(rtType);
  829. var isSup = rtType === 'RT';
  830. var isBiz = ['BU', 'SP'].includes(rtType) || /BUSINESS .+ \d/.test(route);
  831. var isUSBiz = isBiz && usHwys.includes(route);
  832. if ((fc === 2 && attr.ACCESS_CAT_NAME !== 'FULL') || (fc > 3 && isUS)) {
  833. fc = 3;
  834. } else if (fc > 4 && (isState || isUSBiz)) {
  835. fc = 4;
  836. } else if (fc > 6 && (isSup || isBiz)) {
  837. fc = 6;
  838. }
  839. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  840. }
  841. },
  842. MT: {
  843. baseUrl: 'https://app.mdt.mt.gov/arcgis/rest/services/Standard/ROUTES/MapServer/',
  844. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  845. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  846. fcMapLayers: [
  847. { layerID: 0, fcPropName: 'FC', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FC', 'SIGN_ROUTE', 'ROUTE_NAME'], roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false },
  848. { layerID: 1, fcPropName: 'FC', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FC', 'SIGN_ROUTE', 'ROUTE_NAME'], roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false }
  849. ],
  850. isPermitted: function () { return _r >= 3; },
  851. information: { Source: 'MDT', Permission: 'Visible to R3+' },
  852. getWhereClause: function (context) {
  853. if (context.mapContext.zoom < 4) {
  854. return context.layer.fcPropName + "<>'LOCAL'";
  855. } else {
  856. return null;
  857. }
  858. },
  859. getFeatureRoadType: function (feature, layer) {
  860. var fc = feature.attributes.FC;
  861. switch (fc) {
  862. case 'PRINCIPAL ARTERIAL - INTERSTATE': fc = 1; break;
  863. case 'PRINCIPAL ARTERIAL - NON-INTERSTATE': fc = 3; break;
  864. case 'MINOR ARTERIAL': fc = 4; break;
  865. case 'MAJOR COLLECTOR':
  866. case 'MINOR COLLECTOR': fc = 5; break;
  867. default: fc = 7;
  868. }
  869. var roadID = feature.attributes.SIGN_ROUTE;
  870. if (!roadID) { roadID = feature.attributes.ROUTE_NAME; }
  871. var isUS = RegExp(/^US \d+/).test(roadID);
  872. var isState = RegExp(/^MONTANA \d+|ROUTE \d+|S \d{3}\b/).test(roadID);
  873. if (fc > 3 && isUS) { fc = 3; }
  874. else if (fc > 4 && isState) { fc = 4; }
  875. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  876. }
  877. },
  878. NH: {
  879. baseUrl: 'https://nhgeodata.unh.edu/nhgeodata/rest/services/GRANITView/GV_BaseLayers/MapServer/',
  880. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  881. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  882. fcMapLayers: [
  883. {
  884. layerID: 18, fcPropName: 'FUNCT_SYSTEM', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCT_SYSTEM', 'STREET_ALIASES', 'TIER'],
  885. roadTypeMap: { Fw: [1], Ew: [2], MH: [2, 3], mH: [4], PS: [5, 6], St: [7, 0] }, maxRecordCount: 1000, supportsPagination: false
  886. }
  887. ],
  888. isPermitted: function () { return _r >= 3; },
  889. information: { Source: 'NH GRANIT', Permission: 'Visible to R3+' },
  890. getWhereClause: function (context) {
  891. if (context.mapContext.zoom < 4) {
  892. return context.layer.fcPropName + "<>7 AND " + context.layer.fcPropName + "<>0";
  893. } else {
  894. return null;
  895. }
  896. },
  897. getFeatureRoadType: function (feature, layer) {
  898. var fc = parseInt(feature.attributes[layer.fcPropName]);
  899. if (!(fc > 0)) { fc = 7; }
  900. var route = feature.attributes.STREET_ALIASES;
  901. var isUS = RegExp(/US /).test(route);
  902. var isState = RegExp(/NH /).test(route);
  903. if (fc === 2) { feature.attributes.TIER === 1 ? fc = 1 : fc = 3; }
  904. else if (fc > 3 && isUS) { RegExp(/US 3B/).test(route) ? fc = 4 : fc = 3; }
  905. else if (fc > 4 && isState) { fc = 4; }
  906. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  907. }
  908. },
  909. NM: {
  910. baseUrl: 'https://services.arcgis.com/hOpd7wfnKm16p9D9/ArcGIS/rest/services/NMDOT_Functional_Class/FeatureServer/',
  911. defaultColors: { Fw: '#ff00c5', Ew: '#ff00c5', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  912. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  913. fcMapLayers: [
  914. {
  915. layerID: 0, fcPropName: 'Func_Class', idPropName: 'OBJECTID_1', maxRecordCount: 1000, supportsPagination: false,
  916. outFields: ['OBJECTID_1', 'Func_Class', 'D_RT_ROUTE'], roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }
  917. }
  918. ],
  919. isPermitted: function () { return true; },
  920. information: { Source: 'NMDOT' },
  921. getWhereClause: function (context) {
  922. return null;
  923. },
  924. getFeatureRoadType: function (feature, layer) {
  925. var fc = parseInt(feature.attributes[layer.fcPropName]);
  926. var roadType = feature.attributes.D_RT_ROUTE.split('-', 1).shift();
  927. var isBiz = roadType === 'BL'; // Interstate Business Loop
  928. var isUS = roadType === 'US';
  929. var isState = roadType === 'NM';
  930. if (roadType === 'IX') { fc = 0; }
  931. else if (fc > 3 && (isBiz || isUS)) { fc = 3; }
  932. else if (fc > 4 && isState) { fc = 4; }
  933. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  934. }
  935. },
  936. NY: {//https://gis.dot.ny.gov/hostingny/rest/services/Basemap/MapServer/21
  937. baseUrl: 'https://gis.dot.ny.gov/hostingny/rest/services',
  938. defaultColors: { Fw: '#ff00c5', Ew: '#5f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  939. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  940. fcMapLayers: [
  941. {
  942. layerID: '/Geocortex/FC/MapServer/1', fcPropName: 'FUNC_CLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNC_CLASS', 'SEGMENT_NAME', 'ROUTE_NO'], roadTypeMap: { Fw: [1, 11], Ew: [2, 12], MH: [4, 14], mH: [6, 16], PS: [7, 8, 17, 18] },
  943. maxRecordCount: 1000, supportsPagination: false
  944. },
  945. { layerID: 'Basemap/MapServer/21', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'SHIELD'], maxRecordCount: 1000, supportsPagination: false }
  946. ],
  947. information: { Source: 'NYSDOT', Permission: 'Visible to R4+ or R3-AM' },
  948. getWhereClause: function (context) {
  949. if (context.layer.layerID === 'Basemap/MapServer/21') {
  950. return ("SHIELD IN ('C','CT')");
  951. } else {
  952. return null;
  953. }
  954. },
  955. getFeatureRoadType: function (feature, layer) {
  956. var roadType;
  957. if (layer.layerID === 'Basemap/MapServer/21') {
  958. roadType = 'PS';
  959. } else {
  960. roadType = _stateSettings.global.getFeatureRoadType(feature, layer);
  961. var routeNo = feature.attributes.ROUTE_NO;
  962. if (/^NY.*/.test(routeNo)) {
  963. if (roadType === 'PS') roadType = 'mH';
  964. } else if (/^US.*/.test(routeNo)) {
  965. if (roadType === 'PS' || roadType === 'mH') roadType = 'MH';
  966. }
  967. }
  968. return roadType;
  969. }
  970. },
  971. NC: {
  972. baseUrl: 'https://gis11.services.ncdot.gov/arcgis/rest/services/NCDOT_FunctionalClassQtr/MapServer/',
  973. defaultColors: { Fw: '#ff00c5', Rmp: '#999999', Ew: '#5f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  974. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  975. fcMapLayers: [
  976. { layerID: 0, fcPropName: 'FuncClass', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FuncClass', 'RouteClass'], roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, zoomLevels: [3, 4, 5, 6, 7, 8, 9, 10], maxRecordCount: 1000, supportsPagination: false }
  977. //{ layerID:2, fcPropName:'FC_TYP_CD', idPropName:'OBJECTID', outFields:['OBJECTID','FC_TYP_CD','RTE_1_CLSS_CD'], roadTypeMap:{Fw:[1],Ew:[2],MH:[3],mH:[4],PS:[5,6],St:[7]}, zoomLevels:[2], maxRecordCount:1000, supportsPagination:false },
  978. //{ layerID:3, fcPropName:'FC_TYP_CD', idPropName:'OBJECTID', outFields:['OBJECTID','FC_TYP_CD','RTE_1_CLSS_CD'], roadTypeMap:{Fw:[1],Ew:[2],MH:[3],mH:[4],PS:[5,6],St:[7]}, zoomLevels:[0,1], maxRecordCount:1000, supportsPagination:false },
  979. //{ layerID:4, fcPropName:'FC_TYP_CD', idPropName:'OBJECTID', outFields:['OBJECTID','FC_TYP_CD','RTE_1_CLSS_CD'], roadTypeMap:{Fw:[1],Ew:[2],MH:[3],mH:[4],PS:[5,6],St:[7]}, zoomLevels:[], maxRecordCount:1000, supportsPagination:false },
  980. //{ layerID:5, fcPropName:'FC_TYP_CD', idPropName:'OBJECTID', outFields:['OBJECTID','FC_TYP_CD','RTE_1_CLSS_CD'], roadTypeMap:{Fw:[1],Ew:[2],MH:[3],mH:[4],PS:[5,6],St:[7]}, zoomLevels:[], maxRecordCount:1000, supportsPagination:false },
  981. //{ layerID:6, fcPropName:'FC_TYP_CD', idPropName:'OBJECTID', outFields:['OBJECTID','FC_TYP_CD','RTE_1_CLSS_CD'], roadTypeMap:{Fw:[1],Ew:[2],MH:[3],mH:[4],PS:[5,6],St:[7]}, zoomLevels:[], maxRecordCount:1000, supportsPagination:false }
  982. ],
  983. isPermitted: function () { return _r >= 3; },
  984. information: { Source: 'NCDOT', Permission: 'Visible to R3+' },
  985. getWhereClause: function (context) {
  986. if (context.mapContext.zoom < 4) {
  987. var clause = '(' + context.layer.fcPropName + " < 7 OR RouteClass IN ('I','FED','NC','RMP','US'))";
  988. return clause;
  989. } else {
  990. return null;
  991. }
  992. },
  993. getFeatureRoadType: function (feature, layer) {
  994. var fc = feature.attributes[layer.fcPropName];
  995. var roadType;
  996. switch (this.getHwySys(feature)) {
  997. case 'interstate':
  998. roadType = 'Fw';
  999. break;
  1000. case 'us':
  1001. roadType = fc <= 2 ? 'Ew' : 'MH';
  1002. break;
  1003. case 'state':
  1004. roadType = fc === 2 ? 'Ew' : (fc === 3 ? 'MH' : 'mH');
  1005. break;
  1006. case 'ramp':
  1007. roadType = 'Rmp';
  1008. break;
  1009. default:
  1010. roadType = fc === 2 ? 'Ew' : (fc === 3 ? 'MH' : (fc === 4 ? 'mH' : (fc <= 6 ? 'PS' : 'St')));
  1011. }
  1012. return roadType;
  1013. },
  1014. getHwySys: function (feature) {
  1015. var hwySys;
  1016. switch (feature.attributes.RouteClass) {
  1017. case '1':
  1018. hwySys = 'interstate';
  1019. break;
  1020. case '2':
  1021. hwySys = 'us';
  1022. break;
  1023. case '3':
  1024. hwySys = 'state';
  1025. break;
  1026. case '80':
  1027. hwySys = 'ramp';
  1028. break;
  1029. default:
  1030. hwySys = 'local';
  1031. }
  1032. return hwySys;
  1033. }
  1034. },
  1035. ND: {
  1036. baseUrl: 'https://gis.dot.nd.gov/arcgis/rest/services/external/transinfo/MapServer/',
  1037. defaultColors: { Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  1038. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1039. fcMapLayers: [
  1040. {
  1041. layerID: 10, fcPropName: 'FUNCTION_CLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCTION_CLASS'], roadTypeMap: { Fw: ['Interstate'], MH: ['Principal Arterial'], mH: ['Minor Arterial'], PS: ['Major Collector', 'Collector'], St: ['Local'] },
  1042. maxRecordCount: 1000, supportsPagination: false
  1043. },
  1044. {
  1045. layerID: 11, fcPropName: 'FUNCTION_CLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCTION_CLASS'], roadTypeMap: { Fw: ['Interstate'], MH: ['Principal Arterial'], mH: ['Minor Arterial'], PS: ['Major Collector', 'Collector'], St: ['Local'] },
  1046. maxRecordCount: 1000, supportsPagination: false
  1047. },
  1048. {
  1049. layerID: 12, fcPropName: 'FUNCTION_CLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCTION_CLASS'], roadTypeMap: { PS: ['Major Collector', 'Collector'] },
  1050. maxRecordCount: 1000, supportsPagination: false
  1051. },
  1052. {
  1053. layerID: 16, fcPropName: 'SYSTEM_CD', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'SYSTEM_CD', 'SYSTEM_DESC', 'HIGHWAY', 'HWY_SUFFIX'], roadTypeMap: { Fw: [1, 11], MH: [2, 14], mH: [6, 7, 16, 19] },
  1054. maxRecordCount: 1000, supportsPagination: false
  1055. }
  1056. ],
  1057. information: { Source: 'NDDOT', Permission: 'Visible to R4+ or R3-AM' },
  1058. getWhereClause: function (context) {
  1059. if (context.mapContext.zoom < 4) {
  1060. if (context.layer.layerID !== 16) return context.layer.fcPropName + "<>'Local'";
  1061. } else {
  1062. return null;
  1063. }
  1064. },
  1065. getFeatureRoadType: function (feature, layer) {
  1066. return _stateSettings.global.getFeatureRoadType(feature, layer);
  1067. }
  1068. },
  1069. OH: {
  1070. baseUrl: 'https://gis.dot.state.oh.us/arcgis/rest/services/TIMS/Roadway_Information/MapServer/',
  1071. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  1072. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1073.  
  1074. fcMapLayers: [
  1075. {
  1076. layerID: 8, fcPropName: 'FUNCTION_CLASS', idPropName: 'ObjectID', outFields: ['FUNCTION_CLASS', 'ROUTE_TYPE', 'ROUTE_NBR', 'ObjectID'],
  1077. maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }
  1078. }
  1079. ],
  1080. isPermitted: function () { return true; },
  1081. information: { Source: 'ODOT' },
  1082. getWhereClause: function (context) {
  1083. if (context.mapContext.zoom < 4) {
  1084. var clause = '(' + context.layer.fcPropName + " < 7 OR ROUTE_TYPE IN ('CR','SR','US'))";
  1085. return clause;
  1086. } else {
  1087. return null;
  1088. }
  1089. },
  1090. getFeatureRoadType: function (feature, layer) {
  1091. var fc = feature.attributes[layer.fcPropName];
  1092. var prefix = feature.attributes.ROUTE_TYPE;
  1093. var isUS = prefix === 'US';
  1094. var isState = prefix === 'SR';
  1095. var isCounty = prefix === 'CR';
  1096. if (isUS && fc > 3) { fc = 3; }
  1097. if (isState && fc > 4) { fc = 4; }
  1098. if (isCounty && fc > 6) { fc = 6; }
  1099. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  1100. }
  1101. },
  1102. OK: {
  1103. baseUrl: 'https://services6.arcgis.com/RBtoEUQ2lmN0K3GY/arcgis/rest/services/Roadways/FeatureServer/',
  1104. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  1105. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1106. fcMapLayers: [
  1107. {
  1108. layerID: 0, fcPropName: 'FUNCTIONALCLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCTIONALCLASS', 'FHWAPRIMARYROUTE', 'ODOTROUTECLASS', 'ACCESSCONTROL'],
  1109. maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }
  1110. }
  1111. ],
  1112. information: { Source: 'ODOT', Permission: 'Visible to R4+ or R3-AM' },
  1113. getWhereClause: function (context) {
  1114. if (context.mapContext.zoom < 4) {
  1115. return context.layer.fcPropName + " < 7 OR ODOTROUTECLASS IN ('U','S','I')";
  1116. } else {
  1117. return null;
  1118. }
  1119. },
  1120. getFeatureRoadType: function (feature, layer) {
  1121. var fc = feature.attributes[layer.fcPropName];
  1122. var route = (feature.attributes.FHWAPRIMARYROUTE || '').trim();
  1123. var isBusinessOrSpur = /BUS$|SPR$/i.test(route);
  1124. var prefix = isBusinessOrSpur ? route.substring(0, 1) : feature.attributes.ODOTROUTECLASS;
  1125. var isFw = parseInt(feature.attributes.ACCESSCONTROL) === 1;
  1126. var isInterstate = prefix === 'I';
  1127. var isUS = prefix === 'U';
  1128. var isState = prefix === 'S';
  1129. if (isFw) { fc = 1; }
  1130. else if (fc > 3 && ((isUS && !isBusinessOrSpur) || (isInterstate && isBusinessOrSpur))) { fc = 3; }
  1131. else if (fc > 4 && ((isUS && isBusinessOrSpur) || (isState && !isBusinessOrSpur))) { fc = 4; }
  1132. else if (fc > 5 && isState && isBusinessOrSpur) { fc = 5; }
  1133. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  1134. }
  1135. },
  1136. OR: {
  1137. baseUrl: 'https://gis.odot.state.or.us/arcgis/rest/services/transgis/data_catalog_display/Mapserver/',
  1138. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  1139. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1140. fcMapLayers: [
  1141. {
  1142. layerID: 78, fcPropName: 'NEW_FC_CD', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'NEW_FC_CD'],
  1143. roadTypeMap: { Fw: ['1'], Ew: ['2'], MH: ['3'], mH: ['4'], PS: ['5', '6'], St: ['7'] }, maxRecordCount: 1000, supportsPagination: false
  1144. },
  1145. {
  1146. layerID: 80, fcPropName: 'NEW_FC_CD', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'NEW_FC_CD'],
  1147. roadTypeMap: { Fw: ['1'], Ew: ['2'], MH: ['3'], mH: ['4'], PS: ['5', '6'], St: ['7'] }, maxRecordCount: 1000, supportsPagination: false
  1148. }
  1149. ],
  1150. information: { Source: 'ODOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  1151. getWhereClause: function (context) {
  1152. if (context.mapContext.zoom < 4) {
  1153. return context.layer.fcPropName + " <> '7'";
  1154. } else {
  1155. return null;
  1156. }
  1157. },
  1158. getFeatureRoadType: function (feature, layer) {
  1159. if (layer.getFeatureRoadType) {
  1160. return layer.getFeatureRoadType(feature);
  1161. } else {
  1162. return _stateSettings.global.getFeatureRoadType(feature, layer);
  1163. }
  1164. }
  1165. },
  1166. PA: {
  1167. baseUrl: 'https://www.pdarcgissvr.pa.gov/penndotgis/rest/services/PennShare/PennShare/MapServer/',
  1168. supportsPagination: false,
  1169. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  1170. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1171. fcMapLayers: [
  1172. {
  1173. layerID: 3, features: new Map(), fcPropName: 'FUNC_CLS', idPropName: 'MSLINK', outFields: ['MSLINK', 'FUNC_CLS'],
  1174. maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: ['01', '11'], Ew: ['12'], MH: ['02', '14'], mH: ['06', '16'], PS: ['07', '08', '17'], St: ['09', '19'] }
  1175. }
  1176. ],
  1177. isPermitted: function () { return _r >= 4; },
  1178. information: { Source: 'PennDOT', Permission: 'Visible to R4+', Description: 'Raw unmodified FC data.' },
  1179. getWhereClause: function (context) {
  1180. return (context.mapContext.zoom < 4) ? context.layer.fcPropName + " NOT IN ('09','19')" : null;
  1181. },
  1182. getFeatureRoadType: function (feature, layer) {
  1183. if (layer.getFeatureRoadType) {
  1184. return layer.getFeatureRoadType(feature);
  1185. } else {
  1186. var fc = feature.attributes[layer.fcPropName];
  1187. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  1188. }
  1189. }
  1190. },
  1191. RI: {
  1192. baseUrl: 'https://services2.arcgis.com/S8zZg9pg23JUEexQ/arcgis/rest/services/RIDOT_Roads_2016/FeatureServer/',
  1193. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  1194. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  1195. fcMapLayers: [
  1196. {
  1197. layerID: 0, fcPropName: 'F_SYSTEM', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'F_SYSTEM', 'ROADTYPE', 'RTNO'],
  1198. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7, 0] }, maxRecordCount: 1000, supportsPagination: false
  1199. }
  1200. ],
  1201. isPermitted: function () { return _r >= 3; },
  1202. information: { Source: 'RIDOT', Permission: 'Visible to R3+' },
  1203. getWhereClause: function (context) {
  1204. return (context.mapContext.zoom < 4) ? context.layer.fcPropName + " NOT IN (7,0)" : null;
  1205. },
  1206. getFeatureRoadType: function (feature, layer) {
  1207. var fc = parseInt(feature.attributes[layer.fcPropName]);
  1208. var type = feature.attributes.ROADTYPE;
  1209. var rtnum = feature.attributes.RTNO;
  1210. if (fc === 2 && ['10', '24', '37', '78', '99', '138', '403'].includes(rtnum)) { fc = 1; } //isFw
  1211. else if ((fc > 3 && type === 'US') || rtnum === '1') { fc = 3; } //isUS
  1212. else if (fc > 4 && rtnum.trim() !== '') { fc = 4; } //isState
  1213. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  1214. }
  1215. },
  1216. SC: {
  1217. baseUrl: 'https://services1.arcgis.com/VaY7cY9pvUYUP1Lf/arcgis/rest/services/Functional_Class/FeatureServer/',
  1218. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  1219. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1220. fcMapLayers: [
  1221. {
  1222. layerID: 0, fcPropName: 'FC_GIS', idPropName: 'FID', outFields: ['FID', 'FC_GIS', 'ROUTE_LRS'],
  1223. maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }
  1224. }
  1225. ],
  1226. isPermitted: function () { return _r >= 4; },
  1227. information: { Source: 'SCDOT', Permission: 'Visible to R4+' },
  1228. getWhereClause: function (context) {
  1229. return null;
  1230. },
  1231. getFeatureRoadType: function (feature, layer) {
  1232. var roadID = feature.attributes.ROUTE_LRS;
  1233. var roadType = parseInt(roadID.slice(3, 4));
  1234. var isFw = roadType === 1;
  1235. var isUS = roadType === 2;
  1236. var isState = roadType === 4;
  1237. var isBiz = parseInt(roadID.slice(-2, -1)) === 7;
  1238. var fc = 7;
  1239. switch (feature.attributes[layer.fcPropName]) {
  1240. case 'INT': fc = 1; break;
  1241. case 'EXP': fc = 2; break;
  1242. case 'PRA': fc = 3; break;
  1243. case 'MIA': fc = 4; break;
  1244. case 'MAC':
  1245. case 'MIC': fc = 5; break;
  1246. }
  1247. if (fc > 1 && isFw) {
  1248. fc = 1;
  1249. } else if (fc > 3 && isUS) {
  1250. fc = isBiz ? 4 : 3;
  1251. } else if (fc > 4 && isState) {
  1252. fc = (isBiz ? 5 : 4);
  1253. }
  1254. if (layer.getFeatureRoadType) {
  1255. return layer.getFeatureRoadType(feature);
  1256. } else {
  1257. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  1258. }
  1259. }
  1260. },
  1261. SD: {
  1262. baseUrl: 'https://arcgis.sd.gov/arcgis/rest/services/DOT/LocalRoads/MapServer/',
  1263. defaultColors: { Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee', PSGr: '#cc6533', StGr: '#e99cb6' },
  1264. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1265. fcMapLayers: [{
  1266. layerID: 1, fcPropName: 'FUNC_CLASS', idPropName: 'OBJECTID', maxRecordCount: 1000, supportsPagination: false,
  1267. outFields: ['OBJECTID', 'FUNC_CLASS', 'SURFACE_TYPE', 'ROADNAME'],
  1268. roadTypeMap: { Fw: [1, 11], Ew: [2, 12], MH: [4, 14], mH: [6, 16], PS: [7, 8, 17], St: [9, 19] }
  1269. }],
  1270. information: { Source: 'SDDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Additional colors denote unpaved PS and LS segements.' },
  1271. getWhereClause: function (context) {
  1272. if (context.mapContext.zoom < 4) {
  1273. return context.layer.fcPropName + " NOT IN (9,19)";
  1274. } else {
  1275. return null;
  1276. }
  1277. },
  1278. getFeatureRoadType: function (feature, layer) {
  1279. var attr = feature.attributes;
  1280. var fc = parseInt(attr[layer.fcPropName]) % 10;
  1281. var isFw = attr.ACCESS_CONTROL === 1;
  1282. var isUS = RegExp('^US HWY ', 'i').test(attr.ROADNAME);
  1283. var isState = RegExp('^SD HWY ', 'i').test(attr.ROADNAME);
  1284. var isBiz = RegExp('^(US|SD) HWY .* (E|W)?(B|L)$', 'i').test(attr.ROADNAME);
  1285. var isPaved = parseInt(attr.SURFACE_TYPE) > 5;
  1286. if (isFw) {
  1287. fc = 1;
  1288. } else if (fc > 4 && isUS) {
  1289. fc = (isBiz ? 6 : 4);
  1290. } else if (fc > 6 && isState) {
  1291. fc = (isBiz ? 7 : 6);
  1292. }
  1293. if (fc > 6 && !isPaved) {
  1294. return fc < 9 ? 'PSGr' : 'StGr';
  1295. } else {
  1296. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  1297. }
  1298. }
  1299. },
  1300. TN: {
  1301. baseUrl: 'https://',
  1302. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', PS2: '#cfae0e', St: '#eeeeee' },
  1303. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  1304. fcMapLayers: [
  1305. {
  1306. layerPath: 'testuasiportal.shelbycountytn.gov/arcgis/rest/services/MPO/Webmap_2015_04_20_TMPO/MapServer/', maxRecordCount: 1000, supportsPagination: false,
  1307. layerID: 17, fcPropName: 'FuncClass', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FuncClass'],
  1308. roadTypeMap: { Fw: [1, 11], Ew: [2, 12], MH: [4, 14], mH: [6, 16], PS: [7, 17], PS2: [8, 18], St: [9, 19] }
  1309. },
  1310. {
  1311. layerPath: 'services3.arcgis.com/pXGyp7DHTIE4RXOJ/ArcGIS/rest/services/Functional_Classification/FeatureServer/', maxRecordCount: 1000, supportsPagination: false,
  1312. layerID: 0, fcPropName: 'FC_MPO', idPropName: 'FID', outFields: ['FID', 'FC_MPO'],
  1313. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }
  1314. }
  1315. ],
  1316. information: {
  1317. Source: 'Shelby County, Nashville Area MPO', Permission: 'Visible to R4+ or R3-AM',
  1318. Description: 'Raw unmodified FC data for the Memphis and Nashville regions only.'
  1319. },
  1320. getWhereClause: function (context) {
  1321. if (context.mapContext.zoom < 4) {
  1322. return context.layer.fcPropName + ' NOT IN (0,7,9,19)';
  1323. } else {
  1324. return context.layer.fcPropName + ' <> 0';
  1325. }
  1326. },
  1327. getFeatureRoadType: function (feature, layer) {
  1328. if (layer.getFeatureRoadType) {
  1329. return layer.getFeatureRoadType(feature);
  1330. } else {
  1331. return _stateSettings.global.getFeatureRoadType(feature, layer);
  1332. }
  1333. }
  1334. },
  1335. TX: {
  1336. baseUrl: 'https://services.arcgis.com/KTcxiTD9dsQw4r7Z/ArcGIS/rest/services/TxDOT_Functional_Classification/FeatureServer/',
  1337. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  1338. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  1339. fcMapLayers: [
  1340. { layerID: 0, fcPropName: 'F_SYSTEM', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'F_SYSTEM', 'RTE_PRFX'], maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] } }
  1341. ],
  1342. isPermitted: function () { return _r >= 2; },
  1343. information: { Source: 'TxDOT', Permission: 'Visible to R2+' },
  1344. getWhereClause: function (context) {
  1345. var where = " F_SYSTEM IS NOT NULL AND RTE_PRFX IS NOT NULL";
  1346. if (context.mapContext.zoom < 4) {
  1347. where += ' AND ' + context.layer.fcPropName + " <> 7";
  1348. }
  1349. return where;
  1350. },
  1351. getFeatureRoadType: function (feature, layer) {
  1352. // On-System:
  1353. // IH=Interstate BF=Business FM
  1354. // US=US Highway FM=Farm to Mkt
  1355. // UA=US Alt. RM=Ranch to Mkt
  1356. // UP=US Spur RR=Ranch Road
  1357. // SH=State Highway PR=Park Road
  1358. // SA=State Alt. RE=Rec Road
  1359. // SL=State Loop RP=Rec Rd Spur
  1360. // SS=State Spur FS=FM Spur
  1361. // BI=Business IH RS=RM Spur
  1362. // BU=Business US RU=RR Spur
  1363. // BS=Business State PA=Principal Arterial
  1364. // Off-System:
  1365. // TL=Off-System Tollroad CR=County Road
  1366. // FC=Func. Classified St. LS=Local Street
  1367. if (layer.getFeatureRoadType) {
  1368. return layer.getFeatureRoadType(feature);
  1369. } else {
  1370. var fc = feature.attributes[layer.fcPropName];
  1371. var type = feature.attributes.RTE_PRFX.substring(0, 2).toUpperCase();
  1372. if (type === 'IH' && fc > 1) {
  1373. fc = 1;
  1374. } else if ((type === 'US' || type === 'BI' || type === 'UA') && fc > 3) {
  1375. fc = 3;
  1376. } else if ((type === 'UP' || type === 'BU' || type === 'SH' || type === 'SA') && fc > 4) {
  1377. fc = 4;
  1378. } else if ((type === 'SL' || type === 'SS' || type === 'BS') && fc > 6) {
  1379. fc = 6;
  1380. }
  1381. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  1382. }
  1383. }
  1384. },
  1385. UT: {
  1386. baseUrl: 'https://maps.udot.utah.gov/arcgis/rest/services/Functional_Class/MapServer/',
  1387. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  1388. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1389. fcMapLayers: [
  1390. {
  1391. layerID: 0, fcPropName: 'FC_CODE', idPropName: 'OBJECTID', outFields: ['*'/*'OBJECTID','FC_CODE'*/], roadTypeMap: { Fw: [1], Ew: [2, 20], MH: [3, 30], mH: [4, 40], PS: [5, 50, 6, 60], St: [7, 77] },
  1392. maxRecordCount: 1000, supportsPagination: false
  1393. }
  1394. ],
  1395. information: { Source: 'TxDOT', Permission: 'Visible to R4+ or R3-AM' },
  1396. getWhereClause: function (context) {
  1397. var clause = context.layer.fcPropName + '<=7';
  1398. if (context.mapContext.zoom < 4) {
  1399. clause += ' OR ' + context.layer.fcPropName + '<7';
  1400. }
  1401. return clause;
  1402. },
  1403. getFeatureRoadType: function (feature, layer) {
  1404. var routeId = feature.attributes.ROUTE_ID;
  1405. var fc = feature.attributes.FC_CODE;
  1406. if ([6, 40, 50, 89, 91, 163, 189, 191, 491].indexOf(routeId) > -1 && fc > 3) {
  1407. // US highway
  1408. fc = 3;
  1409. } else if (routeId <= 491 && fc > 4) {
  1410. // State highway
  1411. fc = 4;
  1412. }
  1413. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  1414. }
  1415. },
  1416. VT: {
  1417. baseUrl: 'https://maps.vtrans.vermont.gov/arcgis/rest/services/Master/General/FeatureServer/',
  1418. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  1419. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1420. fcMapLayers: [
  1421. {
  1422. layerID: 39, fcPropName: 'FUNCL', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCL', 'HWYSIGN'],
  1423. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  1424. }
  1425. ],
  1426. information: { Source: 'TxDOT', Permission: 'Visible to R4+ or R3-AM' },
  1427. getWhereClause: function (context) {
  1428. if (context.mapContext.zoom < 4) {
  1429. return context.layer.fcPropName + "<>7 AND " + context.layer.fcPropName + "<>0";
  1430. } else {
  1431. return null;
  1432. }
  1433. },
  1434. getFeatureRoadType: function (feature, layer) {
  1435. var roadID = feature.attributes.HWYSIGN;
  1436. var fc = feature.attributes[layer.fcPropName];
  1437. if (!(fc > 0)) { fc = 7; }
  1438. var isUS = RegExp(/^U/).test(roadID);
  1439. var isState = RegExp(/^V/).test(roadID);
  1440. var isUSBiz = RegExp(/^B/).test(roadID);
  1441. if (fc > 3 && isUS) {
  1442. fc = 3;
  1443. } else if (fc > 4 && (isUSBiz || isState)) {
  1444. fc = 4;
  1445. }
  1446. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  1447. }
  1448. },
  1449. VA: {
  1450. baseUrl: 'https://services.arcgis.com/p5v98VHDX9Atv3l7/arcgis/rest/services/FC_2014_FHWA_Submittal1/FeatureServer/',
  1451. defaultColors: { Fw: '#ff00c5', Ew: '#ff00c5', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  1452. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1453. fcMapLayers: [
  1454. { layerID: 0, fcPropName: 'FUNCTIONAL_CLASS_ID', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FUNCTIONAL_CLASS_ID', 'RTE_NM'], maxRecordCount: 2000, supportsPagination: true, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] } },
  1455. { layerID: 1, fcPropName: 'STATE_FUNCT_CLASS_ID', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'STATE_FUNCT_CLASS_ID', 'RTE_NM', 'ROUTE_NO'], maxRecordCount: 2000, supportsPagination: true, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] } },
  1456. { layerID: 3, fcPropName: 'TMPD_FC', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'TMPD_FC', 'RTE_NM'], maxRecordCount: 2000, supportsPagination: true, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] } }
  1457. ],
  1458. information: { Source: 'VDOT', Permission: 'Visible to R4+ or R3-AM' },
  1459. srExceptions: [217, 302, 303, 305, 308, 310, 313, 314, 315, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 339, 341, 342, 343, 344, 345, 346, 347, 348, 350, 353, 355, 357, 358, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 396, 397, 398, 399, 785, 895],
  1460. getWhereClause: function (context) {
  1461. if (context.mapContext.zoom < 4) {
  1462. return context.layer.fcPropName + '<>7';
  1463. } else {
  1464. //NOTE: As of 9/14/2016 there does not appear to be any US/SR/VA labeled routes with FC = 7.
  1465. return null;
  1466. }
  1467. },
  1468. getFeatureRoadType: function (feature, layer) {
  1469. if (layer.getFeatureRoadType) {
  1470. return layer.getFeatureRoadType(feature);
  1471. } else {
  1472. var fc = parseInt(feature.attributes[layer.fcPropName]);
  1473. var rtName = feature.attributes.RTE_NM;
  1474. var match = /^R-VA\s*(US|VA|SR)(\d{5})..(BUS)?/.exec(rtName);
  1475. var isBusiness = (match && (match !== null) && (match[3] === 'BUS'));
  1476. var isState = (match && (match !== null) && (match[1] === 'VA' || match[1] === 'SR'));
  1477. var rtNum = parseInt((layer.layerID === 1) ? feature.attributes.ROUTE_NO : (match ? match[2] : 99999));
  1478. var rtPrefix = match && match[1];
  1479. if (fc > 3 && rtPrefix === 'US') {
  1480. fc = isBusiness ? 4 : 3;
  1481. } else if (isState && fc > 4 && this.srExceptions.indexOf(rtNum) === -1 && rtNum < 600) {
  1482. fc = isBusiness ? 5 : 4;
  1483. }
  1484. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  1485. }
  1486. }
  1487. },
  1488. WA: {
  1489. baseUrl: 'https://data.wsdot.wa.gov/arcgis/rest/services/FunctionalClass/WSDOTFunctionalClassMap/MapServer/',
  1490. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  1491. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1492. fcMapLayers: [
  1493. {
  1494. layerID: 2, fcPropName: 'FederalFunctionalClassCode', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FederalFunctionalClassCode'],
  1495. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  1496. },
  1497. {
  1498. layerID: 1, fcPropName: 'FederalFunctionalClassCode', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FederalFunctionalClassCode'],
  1499. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  1500. },
  1501. {
  1502. layerID: 4, fcPropName: 'FederalFunctionalClassCode', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FederalFunctionalClassCode'],
  1503. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  1504. }
  1505. ],
  1506. information: { Source: 'WSDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  1507. getWhereClause: function (context) {
  1508. if (context.mapContext.zoom < 4) {
  1509. return context.layer.fcPropName + " <> 7";
  1510. } else {
  1511. return null;
  1512. }
  1513. },
  1514. getFeatureRoadType: function (feature, layer) {
  1515. if (layer.getFeatureRoadType) {
  1516. return layer.getFeatureRoadType(feature);
  1517. } else {
  1518. return _stateSettings.global.getFeatureRoadType(feature, layer);
  1519. }
  1520. }
  1521. },
  1522. WV: {
  1523. baseUrl: 'https://gis.transportation.wv.gov/arcgis/rest/services/Routes/MapServer/',
  1524. defaultColors: { Fw: '#ff00c5', Ew: '#ff00c5', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  1525. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1526. fcMapLayers: [
  1527. { layerID: 24, fcPropName: 'NAT_FUNCTIONAL_CLASS', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'NAT_FUNCTIONAL_CLASS', 'ROUTE_ID'], maxRecordCount: 1000, supportsPagination: true, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] } }
  1528. ],
  1529. information: { Source: 'WV DOT'},
  1530. isPermitted: function () { return true; },
  1531. getWhereClause: function (context) {
  1532. if (context.mapContext.zoom < 4) {
  1533. return context.layer.fcPropName + ' NOT IN (9,19)';
  1534. } else {
  1535. return null;
  1536. }
  1537. },
  1538. getFeatureRoadType: function (feature, layer) {
  1539. if (layer.getFeatureRoadType) {
  1540. return layer.getFeatureRoadType(feature);
  1541. } else {
  1542. var fcCode = feature.attributes[layer.fcPropName];
  1543. var fc = fcCode;
  1544. if (fcCode === 11) fc = 1;
  1545. else if (fcCode === 4 || fcCode === 12) fc = 2;
  1546. else if (fcCode === 2 || fcCode === 14) fc = 3;
  1547. else if (fcCode === 6 || fcCode === 16) fc = 4;
  1548. else if (fcCode === 7 || fcCode === 17 || fcCode === 8 || fcCode === 18) fc = 5;
  1549. else fc = 7;
  1550. var id = feature.attributes.ROUTE_ID;
  1551. var prefix = id.substr(2, 1);
  1552. var isInterstate = false;
  1553. var isUS = false;
  1554. var isState = false;
  1555. switch (prefix) {
  1556. case '1':
  1557. isInterstate = true;
  1558. break;
  1559. case '2':
  1560. isUS = true;
  1561. break;
  1562. case '3':
  1563. isState = true;
  1564. break;
  1565. }
  1566. if (fc > 1 && isInterstate) { fc = 1; }
  1567. else if (fc > 3 && isUS) { fc = 3; }
  1568. else if (fc > 4 && isState) { fc = 4; }
  1569. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  1570. }
  1571. }
  1572. },
  1573. WY: {
  1574. baseUrl: 'https://apps.wyoroad.info/arcgis/rest/services/ITSM/LAYERS/MapServer/',
  1575. defaultColors: { Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee' },
  1576. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1577. fcMapLayers: [
  1578. {
  1579. layerID: 21, fcPropName: 'CLASSIFICATION', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'CLASSIFICATION', 'COMMON_ROUTE_NAME'],
  1580. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  1581. },
  1582. {
  1583. layerID: 22, fcPropName: 'CLASSIFICATION', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'CLASSIFICATION', 'COMMON_ROUTE_NAME'],
  1584. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  1585. },
  1586. {
  1587. layerID: 23, fcPropName: 'CLASSIFICATION', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'CLASSIFICATION', 'COMMON_ROUTE_NAME'],
  1588. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  1589. },
  1590. {
  1591. layerID: 24, fcPropName: 'CLASSIFICATION', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'CLASSIFICATION', 'COMMON_ROUTE_NAME'],
  1592. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  1593. },
  1594. {
  1595. layerID: 25, fcPropName: 'CLASSIFICATION', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'CLASSIFICATION', 'COMMON_ROUTE_NAME'],
  1596. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  1597. },
  1598. {
  1599. layerID: 26, fcPropName: 'CLASSIFICATION', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'CLASSIFICATION', 'COMMON_ROUTE_NAME'],
  1600. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  1601. },
  1602. {
  1603. layerID: 27, fcPropName: 'CLASSIFICATION', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'CLASSIFICATION', 'COMMON_ROUTE_NAME'],
  1604. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] }, maxRecordCount: 1000, supportsPagination: false
  1605. }
  1606. ],
  1607. information: { Source: 'WYDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Minimum suggested FC.' },
  1608. getWhereClause: function (context) {
  1609. if (context.mapContext.zoom < 4) {
  1610. return context.layer.fcPropName + " <> 'Local'";
  1611. } else {
  1612. return null;
  1613. }
  1614. },
  1615. getFeatureRoadType: function (feature, layer) {
  1616. var attr = feature.attributes;
  1617. var fc = attr[layer.fcPropName];
  1618. var route = attr.COMMON_ROUTE_NAME;
  1619. switch (fc) {
  1620. case 'Interstate': fc = 1; break;
  1621. case 'Expressway': fc = 2; break;
  1622. case 'Principal Arterial': fc = 3; break;
  1623. case 'Minor Arterial': fc = 4; break;
  1624. case 'Major Collector': fc = 5; break
  1625. case 'Minor Collector': fc = 6; break;
  1626. default: fc = 7;
  1627. }
  1628. var isIntBiz = /I (25|80) BUS/.test(route);
  1629. var isUS = /US \d+/.test(route);
  1630. var isUSBiz = /US \d+ BUS/.test(route);
  1631. var isState = /WY \d+/.test(route);
  1632. var isStateBiz = /WY \d+ BUS/.test(route);
  1633. if (fc > 3 && (isUS || isIntBiz)) {
  1634. fc = isUSBiz ? 4 : 3;
  1635. } else if (fc > 4 && isState) {
  1636. fc = isStateBiz ? 5 : 4;
  1637. }
  1638. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  1639. }
  1640. }
  1641. };
  1642.  
  1643. function log(message, level) {
  1644. if (message && (!level || (level <= _debugLevel))) {
  1645. console.log('FC Layer: ', message);
  1646. }
  1647. }
  1648.  
  1649. function dynamicSort(property) {
  1650. var sortOrder = 1;
  1651. if (property[0] === "-") {
  1652. sortOrder = -1;
  1653. property = property.substr(1);
  1654. }
  1655. return function (a, b) {
  1656. var props = property.split('.');
  1657. props.forEach(function (prop) {
  1658. a = a[prop];
  1659. b = b[prop];
  1660. });
  1661. var result = (a < b) ? -1 : (a > b) ? 1 : 0;
  1662. return result * sortOrder;
  1663. };
  1664. }
  1665.  
  1666. function dynamicSortMultiple() {
  1667. /*
  1668. * save the arguments object as it will be overwritten
  1669. * note that arguments object is an array-like object
  1670. * consisting of the names of the properties to sort by
  1671. */
  1672. var props = arguments;
  1673. if (arguments[0] && Array.isArray(arguments[0])) {
  1674. props = arguments[0];
  1675. }
  1676. return function (obj1, obj2) {
  1677. var i = 0, result = 0, numberOfProperties = props.length;
  1678. /* try getting a different result from 0 (equal)
  1679. * as long as we have extra properties to compare
  1680. */
  1681. while (result === 0 && i < numberOfProperties) {
  1682. result = dynamicSort(props[i])(obj1, obj2);
  1683. i++;
  1684. }
  1685. return result;
  1686. };
  1687. }
  1688.  
  1689. function generateUUID() {
  1690. var d = new Date().getTime();
  1691. var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
  1692. var r = (d + Math.random() * 16) % 16 | 0;
  1693. d = Math.floor(d / 16);
  1694. return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
  1695. });
  1696. return uuid;
  1697. }
  1698.  
  1699. function loadSettingsFromStorage() {
  1700. var loadedSettings = $.parseJSON(localStorage.getItem(_settingsStoreName));
  1701. var defaultSettings = {
  1702. lastVersion: null,
  1703. layerVisible: true,
  1704. activeStateAbbr: 'ALL',
  1705. hideStreet: false
  1706. };
  1707. _settings = loadedSettings ? loadedSettings : defaultSettings;
  1708. for (var prop in defaultSettings) {
  1709. if (!_settings.hasOwnProperty(prop)) {
  1710. _settings[prop] = defaultSettings[prop];
  1711. }
  1712. }
  1713. }
  1714.  
  1715. function saveSettingsToStorage() {
  1716. if (localStorage) {
  1717. _settings.lastVersion = _scriptVersion;
  1718. _settings.layerVisible = _mapLayer.visibility;
  1719. localStorage.setItem(_settingsStoreName, JSON.stringify(_settings));
  1720. log('Settings saved', 1);
  1721. }
  1722. }
  1723.  
  1724. function getLineWidth() {
  1725. return 12 * Math.pow(1.15, (W.map.getZoom() - 1));
  1726. }
  1727.  
  1728. function sortArray(array) {
  1729. array.sort(function (a, b) { if (a < b) return -1; if (a > b) return 1; else return 0; });
  1730. }
  1731.  
  1732. function getVisibleStateAbbrs() {
  1733. var visibleStates = [];
  1734. W.model.states.getObjectArray().forEach(function (state) {
  1735. var stateAbbr = _statesHash[state.name];
  1736. var activeStateAbbr = _settings.activeStateAbbr;
  1737. if (_stateSettings[stateAbbr] && _stateSettings.global.isPermitted(stateAbbr) && (!activeStateAbbr || activeStateAbbr === 'ALL' || activeStateAbbr === stateAbbr)) {
  1738. visibleStates.push(stateAbbr);
  1739. }
  1740. });
  1741. return visibleStates;
  1742. }
  1743.  
  1744. function getAsync(url, context) {
  1745. return new Promise(function (resolve, reject) {
  1746. GM_xmlhttpRequest({
  1747. context: context, method: "GET", url: url,
  1748. onload: function (res) {
  1749. if (res.status.toString() === '200') {
  1750. resolve({ responseText: res.responseText, context: context });
  1751. } else {
  1752. reject({ responseText: res.responseText, context: context });
  1753. }
  1754. },
  1755. onerror: function () {
  1756. reject(Error("Network Error"));
  1757. }
  1758. });
  1759. });
  1760. }
  1761. function wait(ms) {
  1762. var start = new Date().getTime();
  1763. var end = start;
  1764. while (end < start + ms) {
  1765. end = new Date().getTime();
  1766. }
  1767. }
  1768. function getUrl(context, queryType, queryParams) {
  1769. var extent = context.mapContext.extent,
  1770. zoom = context.mapContext.zoom,
  1771. layer = context.layer,
  1772. state = context.state;
  1773.  
  1774. var whereParts = [];
  1775. var geometry = { xmin: extent.left, ymin: extent.bottom, xmax: extent.right, ymax: extent.top, spatialReference: { wkid: 102100, latestWkid: 3857 } };
  1776. var geometryStr = JSON.stringify(geometry);
  1777. var stateWhereClause = state.getWhereClause(context);
  1778. var layerPath = layer.layerPath || '';
  1779. var url = state.baseUrl + layerPath + layer.layerID + '/query?geometry=' + encodeURIComponent(geometryStr);
  1780.  
  1781. if (queryType === 'countOnly') {
  1782. url += '&returnCountOnly=true';
  1783. } else if (queryType === 'idsOnly') {
  1784. url += '&returnIdsOnly=true';
  1785. } else if (queryType === 'paged') {
  1786. // TODO
  1787. } else {
  1788. url += '&returnGeometry=true&maxAllowableOffset=' + state.zoomSettings.maxOffset[zoom];
  1789. url += '&outFields=' + encodeURIComponent(layer.outFields.join(','));
  1790. if (queryType === 'idRange') {
  1791. var idPropName = context.layer.idPropName;
  1792. whereParts.push('(' + queryParams.idFieldName + '>=' + queryParams.range[0] + ' AND ' + queryParams.idFieldName + '<=' + queryParams.range[1] + ')');
  1793. }
  1794. }
  1795. if (stateWhereClause) whereParts.push(stateWhereClause);
  1796. if (whereParts.length > 0) url += '&where=' + encodeURIComponent(whereParts.join(' AND '));
  1797. url += '&spatialRel=esriSpatialRelIntersects&geometryType=esriGeometryEnvelope&inSR=102100&outSR=3857&f=json';
  1798. //wait(500); // I don't know why this was in the code. Leaving it commented here just in case it was a hack to solve some issue.
  1799. return url;
  1800. }
  1801.  
  1802. function convertFcToRoadTypeVectors(feature, state, stateAbbr, layer, zoom) {
  1803. var roadType = state.getFeatureRoadType(feature, layer);
  1804. log(feature, 3);
  1805. var zIndex = _stateSettings.global.roadTypes.indexOf(roadType) * 100;
  1806. var vectors = [];
  1807. var lineFeatures = [];
  1808. var attr = {
  1809. //fcFeatureUniqueId: stateAbbr + '-' + layer.layerID + '-' + feature.attributes[layer.idPropName],
  1810. //fcFeatureId: feature.attributes[layer.idPropName],
  1811. state: stateAbbr,
  1812. layerID: layer.layerID,
  1813. roadType: roadType,
  1814. dotAttributes: $.extend({}, feature.attributes),
  1815. color: state.defaultColors[roadType],
  1816. strokeWidth: getLineWidth,
  1817. zIndex: zIndex
  1818. };
  1819.  
  1820. feature.geometry.paths.forEach(function (path) {
  1821. var pointList = [];
  1822. var newPoint = null;
  1823. var lastPoint = null;
  1824. path.forEach(function (point) {
  1825. pointList.push(new OL.Geometry.Point(point[0], point[1]));
  1826. });
  1827. var vectorFeature = new OL.Feature.Vector(new OL.Geometry.LineString(pointList), attr);
  1828. vectors.push(vectorFeature);
  1829. });
  1830.  
  1831. return vectors;
  1832. }
  1833.  
  1834. function fetchLayerFC(context) {
  1835. var url = getUrl(context, 'idsOnly');
  1836. log(url, 2);
  1837. if (!context.parentContext.cancel) {
  1838. return getAsync(url, context).bind(context).then(function (res) {
  1839. var ids = $.parseJSON(res.responseText);
  1840. if (!ids.objectIds) ids.objectIds = [];
  1841. sortArray(ids.objectIds);
  1842. log(ids, 2);
  1843. return ids;
  1844. }).then(function (res) {
  1845. var context = this;
  1846. var idRanges = [];
  1847. if (res.objectIds) {
  1848. var len = res.objectIds ? res.objectIds.length : 0;
  1849. var currentIndex = 0;
  1850. var offset = Math.min(this.layer.maxRecordCount, 1000);
  1851. while (currentIndex < len) {
  1852. var nextIndex = currentIndex + offset;
  1853. if (nextIndex >= len) nextIndex = len - 1;
  1854. idRanges.push({ range: [res.objectIds[currentIndex], res.objectIds[nextIndex]], idFieldName: res.objectIdFieldName });
  1855. currentIndex = nextIndex + 1;
  1856. }
  1857. log(context.layer.layerID, 2);
  1858. log(idRanges, 2);
  1859. }
  1860. return idRanges;
  1861. }).map(function (idRange) {
  1862. var context = this;
  1863. if (!context.parentContext.cancel) {
  1864. var url = getUrl(this, 'idRange', idRange);
  1865. log(url, 2);
  1866. return getAsync(url, context).then(function (res) {
  1867. var context = res.context;
  1868. if (!context.parentContext.cancel) {
  1869. var features = $.parseJSON(res.responseText).features;
  1870. // if (context.parentContext.callCount === 0 ) {
  1871. // _mapLayer.removeAllFeatures();
  1872. // }
  1873. context.parentContext.callCount++;
  1874. log('Feature Count=' + (features ? features.length : 0), 2);
  1875. features = features ? features : [];
  1876. var vectors = [];
  1877. features.forEach(function (feature) {
  1878. if (!res.context.parentContext.cancel) {
  1879. var vector = convertFcToRoadTypeVectors(feature, context.state, context.stateAbbr, context.layer, context.mapContext.zoom);
  1880. //var fcFeatureUniqueId = vector[0].attributes.fcFeatureUniqueId;
  1881. //context.parentContext.addedFcFeatureUniqueIds.push(fcFeatureUniqueId);
  1882. if (/*!context.parentContext.existingFcFeatureUniqueIds[fcFeatureUniqueId] &&*/ !(vector[0].attributes.roadType === 'St' && _settings.hideStreet)) {
  1883. vectors.push(vector);
  1884. }
  1885. }
  1886. });
  1887. return vectors;
  1888. }
  1889. });
  1890. } else {
  1891. log('Async call cancelled', 1);
  1892. }
  1893. });
  1894. }
  1895. }
  1896.  
  1897. function fetchStateFC(context) {
  1898. var state = _stateSettings[context.stateAbbr];
  1899. var contexts = [];
  1900. state.fcMapLayers.forEach(function (layer) {
  1901. contexts.push({ parentContext: context.parentContext, layer: layer, state: state, stateAbbr: context.stateAbbr, mapContext: context.mapContext });
  1902. });
  1903. return Promise.map(contexts, function (context) {
  1904. return fetchLayerFC(context);
  1905. });
  1906. }
  1907.  
  1908. var _lastPromise = null;
  1909. var _lastContext = null;
  1910. var _fcCallCount = 0;
  1911. function fetchAllFC() {
  1912. if (!_mapLayer.visibility) return;
  1913.  
  1914. if (_lastPromise) { _lastPromise.cancel(); }
  1915. $('#fc-loading-indicator').text('Loading FC...');
  1916.  
  1917. var mapContext = { zoom: W.map.getZoom(), extent: W.map.getExtent() };
  1918. var contexts = [];
  1919. var parentContext = { callCount: 0,/*existingFcFeatureUniqueIds:{}, addedFcFeatureUniqueIds:[],*/ startTime: Date.now() };
  1920. // _mapLayer.features.forEach(function(vectorFeature) {
  1921. // var fcFeatureUniqueId = vectorFeature.attributes.fcFeatureUniqueId;
  1922. // var existingFcFeatureUniqueIdArray = parentContext.existingFcFeatureUniqueIds[fcFeatureUniqueId];
  1923. // if (!existingFcFeatureUniqueIdArray) {
  1924. // existingFcFeatureUniqueIdArray = [];
  1925. // parentContext.existingFcFeatureUniqueIds[fcFeatureUniqueId] = existingFcFeatureUniqueIdArray;
  1926. // }
  1927. // existingFcFeatureUniqueIdArray.push(vectorFeature);
  1928. // });
  1929. if (_lastContext) _lastContext.cancel = true;
  1930. _lastContext = parentContext;
  1931. getVisibleStateAbbrs().forEach(function (stateAbbr) {
  1932. contexts.push({ parentContext: parentContext, stateAbbr: stateAbbr, mapContext: mapContext });
  1933. });
  1934. var map = Promise.map(contexts, function (context) {
  1935. return fetchStateFC(context);
  1936. }).bind(parentContext).then(function (statesVectorArrays) {
  1937. if (!this.cancel) {
  1938. _mapLayer.removeAllFeatures();
  1939. statesVectorArrays.forEach(function (vectorsArray) {
  1940. vectorsArray.forEach(function (vectors) {
  1941. vectors.forEach(function (vector) {
  1942. vector.forEach(function (vectorFeature) {
  1943. _mapLayer.addFeatures(vectorFeature);
  1944. });
  1945. });
  1946. });
  1947. });
  1948. //buildTable();
  1949. // for(var fcFeatureUniqueId in this.existingFcFeatureUniqueIds) {
  1950. // if(this.addedFcFeatureUniqueIds.indexOf(fcFeatureUniqueId) === -1) {
  1951. // if (!this.cancel) _mapLayer.removeFeatures(this.existingFcFeatureUniqueIds[fcFeatureUniqueId]);
  1952. // }
  1953. // }
  1954. log('TOTAL RETRIEVAL TIME = ' + (Date.now() - parentContext.startTime), 1);
  1955. log(statesVectorArrays, 1);
  1956. }
  1957. return statesVectorArrays;
  1958. }).catch(function (e) {
  1959. $('#fc-loading-indicator').text('FC Error! (check console for details)');
  1960. log(e, 0);
  1961. }).finally(function () {
  1962. _fcCallCount -= 1;
  1963. if (_fcCallCount === 0) {
  1964. $('#fc-loading-indicator').text('');
  1965. }
  1966. });
  1967.  
  1968. _fcCallCount += 1;
  1969. _lastPromise = map;
  1970. }
  1971.  
  1972. function onLayerCheckboxChanged(checked) {
  1973. _mapLayer.setVisibility(checked);
  1974. }
  1975.  
  1976. function onLayerVisibilityChanged(evt) {
  1977. _settings.layerVisible = _mapLayer.visibility;
  1978. saveSettingsToStorage();
  1979. if (_mapLayer.visibility) {
  1980. fetchAllFC();
  1981. }
  1982. }
  1983.  
  1984. function onModeChanged(model, modeId, context) {
  1985. if (!modeId || modeId === 1) {
  1986. initUserPanel();
  1987. }
  1988. }
  1989.  
  1990. function showScriptInfoAlert() {
  1991. /* Check version and alert on update */
  1992. if (_alertUpdate && _scriptVersion !== _settings.lastVersion) {
  1993. alert(_scriptVersionChanges);
  1994. }
  1995. }
  1996.  
  1997. function initLayer() {
  1998. var _drawingContext = {
  1999. getZIndex: function (feature) {
  2000. return feature.attributes.zIndex;
  2001. },
  2002. getStrokeWidth: function () { return getLineWidth(); }
  2003. };
  2004. var defaultStyle = new OL.Style({
  2005. strokeColor: '${color}', //'#00aaff',
  2006. strokeDashstyle: "solid",
  2007. strokeOpacity: 1.0,
  2008. strokeWidth: '${strokeWidth}',
  2009. graphicZIndex: '${zIndex}'
  2010. });
  2011.  
  2012. var selectStyle = new OL.Style({
  2013. //strokeOpacity: 1.0,
  2014. strokeColor: '#000000'
  2015. });
  2016.  
  2017. _mapLayer = new OL.Layer.Vector("FC Layer", {
  2018. uniqueName: "__FCLayer",
  2019. displayInLayerSwitcher: false,
  2020. rendererOptions: { zIndexing: true },
  2021. styleMap: new OL.StyleMap({
  2022. 'default': defaultStyle,
  2023. 'select': selectStyle
  2024. })
  2025. });
  2026.  
  2027. _mapLayer.setOpacity(0.5);
  2028.  
  2029. I18n.translations[I18n.locale].layers.name.__FCLayer = "FC Layer";
  2030.  
  2031. _mapLayer.displayInLayerSwitcher = true;
  2032. _mapLayer.events.register('visibilitychanged', null, onLayerVisibilityChanged);
  2033. _mapLayer.setVisibility(_settings.layerVisible);
  2034.  
  2035. W.map.addLayer(_mapLayer);
  2036. _mapLayer.setZIndex(_mapLayerZIndex);
  2037. WazeWrap.Interface.AddLayerCheckbox('Display', 'FC Layer', _settings.layerVisible, onLayerCheckboxChanged);
  2038. // Hack to fix layer zIndex. Some other code is changing it sometimes but I have not been able to figure out why.
  2039. // It may be that the FC layer is added to the map before some Waze code loads the base layers and forces other layers higher. (?)
  2040.  
  2041. var checkLayerZIndex = function () {
  2042. if (_mapLayer.getZIndex() != _mapLayerZIndex) {
  2043. log("ADJUSTED FC LAYER Z-INDEX " + _mapLayerZIndex + ', ' + _mapLayer.getZIndex(), 1);
  2044. _mapLayer.setZIndex(_mapLayerZIndex);
  2045. }
  2046. };
  2047.  
  2048. setInterval(function () { checkLayerZIndex(); }, 200);
  2049.  
  2050. W.map.events.register("moveend", W.map, function (e) {
  2051. fetchAllFC();
  2052. return true;
  2053. }, true);
  2054. }
  2055.  
  2056. function initUserPanel() {
  2057. var $tab = $('<li>').append($('<a>', { 'data-toggle': 'tab', href: '#sidepanel-fc-layer' }).text('FC'));
  2058. var $panel = $('<div>', { class: 'tab-pane', id: 'sidepanel-fc-layer' });
  2059. var $stateSelect = $('<select>', { id: 'fcl-state-select', class: 'form-control disabled', style: 'disabled' }).append($('<option>', { value: 'ALL' }).text('All'));
  2060. // $stateSelect.change(function(evt) {
  2061. // _settings.activeStateAbbr = evt.target.value;
  2062. // saveSettingsToStorage();
  2063. // _mapLayer.removeAllFeatures();
  2064. // fetchAllFC();
  2065. // });
  2066. for (var stateAbbr in _stateSettings) {
  2067. if (stateAbbr !== 'global') {
  2068. $stateSelect.append($('<option>', { value: stateAbbr }).text(reverseStatesHash(stateAbbr)));
  2069. }
  2070. }
  2071.  
  2072. var $hideStreet = $('<div>', { id: 'fcl-hide-street-container', class: 'controls-container' })
  2073. .append($('<input>', { type: 'checkbox', name: 'fcl-hide-street', id: 'fcl-hide-street' }).prop('checked', _settings.hideStreet).click(function () {
  2074. _settings.hideStreet = $(this).is(':checked');
  2075. saveSettingsToStorage();
  2076. _mapLayer.removeAllFeatures();
  2077. fetchAllFC();
  2078. }))
  2079. .append($('<label>', { for: 'fcl-hide-street' }).text('Hide local street highlights'));
  2080.  
  2081. $stateSelect.val(_settings.activeStateAbbr ? _settings.activeStateAbbr : 'ALL');
  2082.  
  2083. $panel.append(
  2084. $('<div>', { class: 'form-group' }).append(
  2085. $('<label>', { class: 'control-label' }).text('Select a state')
  2086. ).append(
  2087. $('<div>', { class: 'controls', id: 'fcl-state-select-container' }).append(
  2088. $('<div>').append($stateSelect)
  2089. )
  2090. ),
  2091. $hideStreet,
  2092. $('<div>', { id: 'fcl-table-container' })
  2093. );
  2094.  
  2095. $panel.append($('<div>', { id: 'fcl-state-info' }));
  2096.  
  2097. $panel.append(
  2098. $('<div>', { style: 'margin-top:10px;font-size:10px;color:#999999;' })
  2099. .append($('<div>').text('version ' + _scriptVersion))
  2100. .append(
  2101. $('<div>').append(
  2102. $('<a>', { href: '#' /*, target:'__blank'*/ }).text('Discussion Forum (currently n/a)')
  2103. )
  2104. )
  2105. );
  2106.  
  2107. $('#user-tabs > .nav-tabs').append($tab);
  2108. $('#user-info > .flex-parent > .tab-content').append($panel);
  2109. $('#fcl-state-select').change(function () {
  2110. _settings.activeStateAbbr = this.value;
  2111. saveSettingsToStorage();
  2112. loadStateFCInfo();
  2113. fetchAllFC();
  2114. });
  2115. loadStateFCInfo();
  2116. }
  2117.  
  2118. function loadStateFCInfo() {
  2119. $('#fcl-state-info').empty();
  2120. if (_stateSettings[_settings.activeStateAbbr]) {
  2121. var stateInfo = _stateSettings[_settings.activeStateAbbr].information;
  2122. var $panelStateInfo = $('<dl>');
  2123. for (var propertyName in stateInfo) {
  2124. $panelStateInfo.append($('<dt>', { style: 'margin-top:1em;color:#777777' }).text(propertyName))
  2125. .append($('<dd>').text(stateInfo[propertyName]))
  2126. }
  2127. $('#fcl-state-info').append($panelStateInfo);
  2128. }
  2129. }
  2130.  
  2131. function addLoadingIndicator() {
  2132. $('.loading-indicator').after($('<div class="loading-indicator" style="margin-right:10px" id="fc-loading-indicator">'));
  2133. }
  2134.  
  2135. function initGui() {
  2136. addLoadingIndicator();
  2137. initLayer();
  2138. initUserPanel();
  2139. showScriptInfoAlert();
  2140. }
  2141.  
  2142. function processText(text) {
  2143. return new Promise(function (resolve, reject) {
  2144. var newText = text.replace(/(e)/, 'E');
  2145. resolve(newText);
  2146. });
  2147. }
  2148.  
  2149. function init() {
  2150. if (_debugLevel > 0 && Promise.config) {
  2151. Promise.config({
  2152. warnings: true,
  2153. longStackTraces: true,
  2154. cancellation: true,
  2155. monitoring: false
  2156. });
  2157. } else {
  2158. Promise.config({
  2159. warnings: false,
  2160. longStackTraces: false,
  2161. cancellation: true,
  2162. monitoring: false
  2163. });
  2164. }
  2165.  
  2166. var u = W.loginManager.user;
  2167. _uid = u.id;
  2168. _r = u.rank + 1;
  2169. _isAM = u.isAreaManager;
  2170. loadSettingsFromStorage();
  2171. String.prototype.replaceAll = function (search, replacement) {
  2172. var target = this;
  2173. return target.replace(new RegExp(search, 'g'), replacement);
  2174. };
  2175. initGui();
  2176. W.app.modeController.model.bind('change:mode', onModeChanged);
  2177. W.prefs.on("change:isImperial", function () { initUserPanel(); loadSettingsFromStorage(); });
  2178. fetchAllFC();
  2179. log('Initialized.', 0);
  2180. }
  2181.  
  2182. function bootstrap() {
  2183. if (W && W.loginManager &&
  2184. W.loginManager.events &&
  2185. W.loginManager.events.register &&
  2186. W.model && W.model.states && W.model.states.getObjectArray().length &&
  2187. W.map && W.loginManager.user &&
  2188. WazeWrap.Ready) {
  2189. log('Initializing...', 0);
  2190.  
  2191. init();
  2192. } else {
  2193. log('Bootstrap failed. Trying again...', 0);
  2194. unsafeWindow.setTimeout(function () {
  2195. bootstrap();
  2196. }, 1000);
  2197. }
  2198. }
  2199.  
  2200. log('Bootstrap...', 0);
  2201. bootstrap();
  2202. })();