WME FC Layer

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

当前为 2023-03-22 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name WME FC Layer
  3. // @namespace https://greasyfork.org/users/45389
  4. // @version 2023.03.22.001
  5. // @description Adds a Functional Class layer for states that publish ArcGIS FC data.
  6. // @author MapOMatic
  7. // @match *://*.waze.com/*editor*
  8. // @exclude *://*.waze.com/user/editor*
  9. // @license GNU GPLv3
  10. // @contributionURL https://github.com/WazeDev/Thank-The-Authors
  11. // @require https://greasyfork.org/scripts/39002-bluebird/code/Bluebird.js?version=255146
  12. // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
  13. // @grant GM_xmlhttpRequest
  14. // @connect arcgis.com
  15. // @connect arkansas.gov
  16. // @connect azdot.gov
  17. // @connect ca.gov
  18. // @connect coloradodot.info
  19. // @connect delaware.gov
  20. // @connect dc.gov
  21. // @connect ga.gov
  22. // @connect uga.edu
  23. // @connect hawaii.gov
  24. // @connect idaho.gov
  25. // @connect in.gov
  26. // @connect iowadot.gov
  27. // @connect illinois.gov
  28. // @connect ksdot.org
  29. // @connect ky.gov
  30. // @connect la.gov
  31. // @connect maine.gov
  32. // @connect md.gov
  33. // @connect ma.us
  34. // @connect mn.us
  35. // @connect nv.gov
  36. // @connect state.mi.us
  37. // @connect modot.org
  38. // @connect mt.gov
  39. // @connect unh.edu
  40. // @connect ny.gov
  41. // @connect ncdot.gov
  42. // @connect nd.gov
  43. // @connect oh.us
  44. // @connect or.us
  45. // @connect penndot.gov
  46. // @connect sd.gov
  47. // @connect shelbycountytn.gov
  48. // @connect utah.gov
  49. // @connect vermont.gov
  50. // @connect wa.gov
  51. // @connect wv.gov
  52. // @connect wyoroad.info
  53. // ==/UserScript==
  54.  
  55. /* global W */
  56. /* global OpenLayers */
  57. /* global I18n */
  58. /* global WazeWrap */
  59.  
  60. (function main() {
  61. 'use strict';
  62.  
  63. const SETTINGS_STORE_NAME = 'wme_fc_layer';
  64. const DEBUG = false;
  65. const SCRIPT_VERSION = GM_info.script.version;
  66. let _mapLayer = null;
  67. let _isAM = false;
  68. let _uid;
  69. let _uName;
  70. let _settings = {};
  71. let _r;
  72. const MAP_LAYER_Z_INDEX = 335;
  73. const BETA_IDS = [103400892];
  74. const MIN_ZOOM_LEVEL = 11;
  75. const STATES_HASH = {
  76. Alabama: 'AL',
  77. Alaska: 'AK',
  78. 'American Samoa': 'AS',
  79. Arizona: 'AZ',
  80. Arkansas: 'AR',
  81. California: 'CA',
  82. Colorado: 'CO',
  83. Connecticut: 'CT',
  84. Delaware: 'DE',
  85. 'District of Columbia': 'DC',
  86. 'Federated States Of Micronesia': 'FM',
  87. Florida: 'FL',
  88. Georgia: 'GA',
  89. Guam: 'GU',
  90. Hawaii: 'HI',
  91. Idaho: 'ID',
  92. Illinois: 'IL',
  93. Indiana: 'IN',
  94. Iowa: 'IA',
  95. Kansas: 'KS',
  96. Kentucky: 'KY',
  97. Louisiana: 'LA',
  98. Maine: 'ME',
  99. 'Marshall Islands': 'MH',
  100. Maryland: 'MD',
  101. Massachusetts: 'MA',
  102. Michigan: 'MI',
  103. Minnesota: 'MN',
  104. Mississippi: 'MS',
  105. Missouri: 'MO',
  106. Montana: 'MT',
  107. Nebraska: 'NE',
  108. Nevada: 'NV',
  109. 'New Hampshire': 'NH',
  110. 'New Jersey': 'NJ',
  111. 'New Mexico': 'NM',
  112. 'New York': 'NY',
  113. 'North Carolina': 'NC',
  114. 'North Dakota': 'ND',
  115. 'Northern Mariana Islands': 'MP',
  116. Ohio: 'OH',
  117. Oklahoma: 'OK',
  118. Oregon: 'OR',
  119. Palau: 'PW',
  120. Pennsylvania: 'PA',
  121. 'Puerto Rico': 'PR',
  122. 'Rhode Island': 'RI',
  123. 'South Carolina': 'SC',
  124. 'South Dakota': 'SD',
  125. Tennessee: 'TN',
  126. Texas: 'TX',
  127. Utah: 'UT',
  128. Vermont: 'VT',
  129. 'Virgin Islands': 'VI',
  130. Virginia: 'VA',
  131. Washington: 'WA',
  132. 'West Virginia': 'WV',
  133. Wisconsin: 'WI',
  134. Wyoming: 'WY'
  135. };
  136.  
  137. function reverseStatesHash(stateAbbr) {
  138. // eslint-disable-next-line no-restricted-syntax
  139. for (const stateName in STATES_HASH) {
  140. if (STATES_HASH[stateName] === stateAbbr) return stateName;
  141. }
  142. throw new Error(`FC Layer: reverseStatesHash function did not return a value for ${stateAbbr}.`);
  143. }
  144.  
  145. const STATE_SETTINGS = {
  146. global: {
  147. 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.
  148. getFeatureRoadType(feature, layer) {
  149. const fc = feature.attributes[layer.fcPropName];
  150. return this.getRoadTypeFromFC(fc, layer);
  151. },
  152. getRoadTypeFromFC(fc, layer) {
  153. return Object.keys(layer.roadTypeMap).find(rt => layer.roadTypeMap[rt].indexOf(fc) !== -1);
  154. },
  155. isPermitted(stateAbbr) {
  156. if (BETA_IDS.indexOf(_uid) !== -1) {
  157. return true;
  158. }
  159. const state = STATE_SETTINGS[stateAbbr];
  160. if (state.isPermitted) return state.isPermitted();
  161. return (_r >= 3 && _isAM) || (_r >= 4);
  162. },
  163. getMapLayer(stateAbbr, layerID) {
  164. let returnValue;
  165. STATE_SETTINGS[stateAbbr].fcMapLayers.forEach(layer => {
  166. if (layer.layerID === layerID) {
  167. returnValue = layer;
  168. }
  169. });
  170. return returnValue;
  171. }
  172. },
  173. AL: {
  174. baseUrl: 'https://services.arcgis.com/LZzQi3xDiclG6XvQ/arcgis/rest/services/HPMS_Year2017_F_System_Data/FeatureServer/',
  175. defaultColors: {
  176. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  177. },
  178. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  179. fcMapLayers: [
  180. {
  181. layerID: 0,
  182. fcPropName: 'F_SYSTEM_V',
  183. idPropName: 'OBJECTID',
  184. outFields: ['FID', 'F_SYSTEM_V', 'State_Sys'],
  185. roadTypeMap: {
  186. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  187. },
  188. maxRecordCount: 1000,
  189. supportsPagination: false
  190. }
  191. ],
  192. isPermitted() { return _r >= 3; },
  193. information: { Source: 'ALDOT', Permission: 'Visible to R3+', Description: 'Federal and State highways set to a minimum of mH.' },
  194. getWhereClause(context) {
  195. if (context.mapContext.zoom < 16) {
  196. return `${context.layer.fcPropName} <> 7`;
  197. }
  198. return null;
  199. },
  200. getFeatureRoadType(feature, layer) {
  201. let fc = parseInt(feature.attributes[layer.fcPropName], 10);
  202. if (fc > 4 && feature.attributes.State_Sys === 'YES') { fc = 4; }
  203. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  204. }
  205. },
  206. AK: {
  207. baseUrl: 'https://services.arcgis.com/r4A0V7UzH9fcLVvv/ArcGIS/rest/services/AKDOTPF_Route_Data/FeatureServer/',
  208. defaultColors: {
  209. Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  210. },
  211. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  212. fcMapLayers: [
  213. {
  214. layerID: 13,
  215. fcPropName: 'Functional_Class',
  216. idPropName: 'OBJECTID',
  217. outFields: ['OBJECTID', 'Functional_Class'],
  218. roadTypeMap: {
  219. Ew: [1, 2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  220. },
  221. maxRecordCount: 1000,
  222. supportsPagination: false
  223. }
  224. ],
  225. information: { Source: 'Alaska DOT&PF', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  226. getWhereClause(context) {
  227. if (context.mapContext.zoom < 16) {
  228. return `${context.layer.fcPropName} <> 7`;
  229. }
  230. return null;
  231. },
  232. getFeatureRoadType(feature, layer) {
  233. if (layer.getFeatureRoadType) {
  234. return layer.getFeatureRoadType(feature);
  235. }
  236. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  237. }
  238. },
  239. AZ: {
  240. baseUrl: 'https://services1.arcgis.com/XAiBIVuto7zeZj1B/arcgis/rest/services/ATIS_prod_gdb_1/FeatureServer/',
  241. defaultColors: {
  242. Fw: '#ff00c5', Ew: '#ff00c5', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  243. },
  244. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  245. fcMapLayers: [
  246. {
  247. layerID: 38,
  248. fcPropName: 'FunctionalClass',
  249. idPropName: 'OBJECTID',
  250. outFields: ['OBJECTID', 'FunctionalClass', 'RouteId'],
  251. roadTypeMap: {
  252. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  253. },
  254. maxRecordCount: 1000,
  255. supportsPagination: false
  256. }
  257. ],
  258. information: { Source: 'ADOT', Permission: 'Visible to R4+ or R3-AM' },
  259. getWhereClause() {
  260. return null;
  261. },
  262. getFeatureRoadType(feature, layer) {
  263. const attr = feature.attributes;
  264. const roadID = attr.RouteId.trim().replace(/ +/g, ' ');
  265. const roadNum = parseInt(roadID.substring(2, 5), 10);
  266. let fc = attr[layer.fcPropName];
  267. switch (fc) {
  268. case 'Rural Principal Arterial - Interstate':
  269. case 'Urban Principal Arterial - Interstate': fc = 1; break;
  270. case 'Rural Principal Arterial - Other Fwys & Expwys':
  271. case 'Urban Principal Arterial - Other Fwys & Expwys': fc = 2; break;
  272. case 'Rural Principal Arterial - Other':
  273. case 'Urban Principal Arterial - Other': fc = 3; break;
  274. case 'Rural Minor Arterial':
  275. case 'Urban Minor Arterial': fc = 4; break;
  276. case 'Rural Major Collector':
  277. case 'Urban Major Collector': fc = 5; break;
  278. case 'Rural Minor Collector':
  279. case 'Urban Minor Collector': fc = 6; break;
  280. default: fc = 7;
  281. }
  282. const azIH = [8, 10, 11, 17, 19, 40]; // Interstate hwys in AZ
  283. const isUS = /^U\D\d{3}\b/.test(roadID);
  284. const isState = /^S\D\d{3}\b/.test(roadID);
  285. const isBiz = /^SB\d{3}\b/.test(roadID);
  286. if (fc > 4 && isState && azIH.includes(roadNum) && isBiz) fc = 4;
  287. else if (fc > 4 && isUS) fc = isBiz ? 6 : 4;
  288. else if (fc > 6 && isState) fc = isBiz ? 7 : 6;
  289. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  290. }
  291. },
  292. AR: {
  293. baseUrl: 'https://gis.arkansas.gov/arcgis/rest/services/FEATURESERVICES/Transportation/FeatureServer/',
  294. defaultColors: {
  295. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  296. },
  297. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  298. fcMapLayers: [
  299. {
  300. layerID: 8,
  301. fcPropName: 'functionalclass',
  302. idPropName: 'objectid',
  303. outFields: ['objectid', 'functionalclass', 'ah_route', 'ah_section'],
  304. roadTypeMap: {
  305. Fw: [1, 2], Ew: [], MH: [3], mH: [4], PS: [5, 6], St: [7]
  306. },
  307. maxRecordCount: 1000,
  308. supportsPagination: false
  309. }
  310. ],
  311. information: { Source: 'ARDOT', Permission: 'Visible to R4+ or R3-AM' },
  312. getWhereClause() {
  313. return null;
  314. },
  315. getFeatureRoadType(feature, layer) {
  316. const attr = feature.attributes;
  317. let fc = parseInt(attr[layer.fcPropName], 10);
  318. const roadID = parseInt(attr.ah_route, 10);
  319. const usHwys = [49, 59, 61, 62, 63, 64, 65, 67, 70, 71, 79, 82, 165, 167, 270, 271, 278, 371, 412, 425];
  320. const isUS = usHwys.includes(roadID);
  321. const isState = roadID < 613;
  322. const isBiz = attr.ah_section[attr.ah_section.length - 1] === 'B';
  323. if (fc > 3 && isUS) fc = isBiz ? 4 : 3;
  324. else if (fc > 4 && isState) fc = isBiz ? 5 : 4;
  325. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  326. }
  327. },
  328. CA: {
  329. baseUrl: 'https://caltrans-gis.dot.ca.gov/arcgis/rest/services/CHhighway/CRS_Functional_Classification/FeatureServer/',
  330. defaultColors: {
  331. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  332. },
  333. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  334. fcMapLayers: [
  335. {
  336. layerID: 0,
  337. fcPropName: 'F_System',
  338. idPropName: 'OBJECTID',
  339. outFields: ['OBJECTID', 'F_System'],
  340. roadTypeMap: {
  341. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  342. },
  343. maxRecordCount: 1000,
  344. supportsPagination: false
  345. }
  346. ],
  347. isPermitted() { return ['mapomatic', 'turbomkt', 'tonestertm', 'ottonomy', 'jemay'].includes(_uName.toLowerCase()); },
  348. information: { Source: 'Caltrans', Permission: 'Visible to ?', Description: '' },
  349. getWhereClause(context) {
  350. if (context.mapContext.zoom < 16) {
  351. return `${context.layer.fcPropName} <> 7`;
  352. }
  353. return null;
  354. },
  355. getFeatureRoadType(feature, layer) {
  356. const fc = parseInt(feature.attributes[layer.fcPropName], 10);
  357. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  358. }
  359. },
  360. CO: {
  361. baseUrl: 'https://dtdapps.coloradodot.info/arcgis/rest/services/CPLAN/open_data_sde/FeatureServer/',
  362. defaultColors: {
  363. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  364. },
  365. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  366. fcMapLayers: [
  367. {
  368. layerID: 14,
  369. fcPropName: 'FUNCCLASS',
  370. idPropName: 'OBJECTID',
  371. outFields: ['OBJECTID', 'FUNCCLASS', 'ROUTE', 'REFPT'],
  372. roadTypeMap: {
  373. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  374. },
  375. maxRecordCount: 1000,
  376. supportsPagination: false
  377. },
  378. {
  379. layerID: 17,
  380. fcPropName: 'FUNCCLASSID',
  381. idPropName: 'OBJECTID',
  382. outFields: ['OBJECTID', 'FUNCCLASSID', 'ROUTE', 'FIPSCOUNTY'],
  383. roadTypeMap: {
  384. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  385. },
  386. maxRecordCount: 1000,
  387. supportsPagination: false
  388. },
  389. {
  390. layerID: 21,
  391. fcPropName: 'FUNCCLASSID',
  392. idPropName: 'OBJECTID',
  393. outFields: ['OBJECTID', 'FUNCCLASSID', 'ROUTE'],
  394. roadTypeMap: {
  395. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  396. },
  397. maxRecordCount: 1000,
  398. supportsPagination: false
  399. }
  400. ],
  401. isPermitted() { return _r >= 3; },
  402. information: {
  403. Source: 'CDOT',
  404. Permission: 'Visible to R3+',
  405. Description: 'Please consult with a state manager before making any changes to road types based on the data presented.'
  406. },
  407. getWhereClause(context) {
  408. if (context.mapContext.zoom < 16) {
  409. return `${context.layer.fcPropName} <> '7'`;
  410. }
  411. return null;
  412. },
  413. getFeatureRoadType(feature, layer) {
  414. const attr = feature.attributes;
  415. let fc = parseInt(attr[layer.fcPropName], 10);
  416. const route = attr.ROUTE.replace(/ +/g, ' ');
  417. if (layer.layerID === 7) {
  418. const rtnum = parseInt(route.slice(0, 3), 10);
  419. const refpt = attr.REFPT;
  420. const hwys = [6, 24, 25, 34, 36, 40, 50, 70, 84, 85, 87, 138, 160, 285, 287, 350, 385, 400, 491, 550];
  421. // Exceptions first, then normal classification
  422. const doNothing = ['024D', '040G'];
  423. const notNothing = ['070K', '070L', '070O', '070Q', '070R'];
  424. const doMin = ['024E', '050D', '070O', '085F', '160D'];
  425. if (doNothing.includes(route) || (rtnum === 70 && route !== '070K' && !notNothing.includes(route))) {
  426. // do nothing
  427. } else if (doMin.includes(route)
  428. || (rtnum === 40 && refpt > 320 && refpt < 385)
  429. || (rtnum === 36 && refpt > 79 && refpt < 100.99)
  430. || (route === '034D' && refpt > 11)) {
  431. fc = 4;
  432. } else if (hwys.includes(rtnum)) {
  433. fc = Math.min(fc, 3);
  434. } else {
  435. fc = Math.min(fc, 4);
  436. }
  437. } else {
  438. // All exceptions
  439. const fips = parseInt(attr.FIPSCOUNTY, 10);
  440. if ((fips === 19 && route === 'COLORADO BD') || (fips === 37 && (route === 'GRAND AV' || route === 'S H6'))) {
  441. fc = 3;
  442. } else if (fips === 67 && route === 'BAYFIELDPAY') {
  443. fc = 4;
  444. }
  445. }
  446. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  447. }
  448. },
  449. CT: {
  450. baseUrl: 'https://services1.arcgis.com/FCaUeJ5SOVtImake/ArcGIS/rest/services/CTDOT_Roadway_Classification_and_Characteristic_Data/FeatureServer/',
  451. defaultColors: {
  452. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  453. },
  454. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  455. fcMapLayers: [
  456. {
  457. layerID: 3,
  458. fcPropName: 'FC_FC_CODE',
  459. idPropName: 'OBJECTID',
  460. outFields: ['OBJECTID', 'FC_FC_CODE'],
  461. roadTypeMap: {
  462. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  463. },
  464. maxRecordCount: 1000,
  465. supportsPagination: false
  466. }
  467. ],
  468. isPermitted() { return _r >= 3; },
  469. information: { Source: 'CTDOT', Permission: 'Visible to R3+', Description: 'Federal and State highways set to a minimum of mH.' },
  470. getWhereClause(context) {
  471. if (context.mapContext.zoom < 16) {
  472. return `${context.layer.fcPropName} <> 7`;
  473. }
  474. return null;
  475. },
  476. getFeatureRoadType(feature, layer) {
  477. let fc = parseInt(feature.attributes[layer.fcPropName], 10);
  478. if (fc > 4 && feature.attributes.State_Sys === 'YES') {
  479. fc = 4;
  480. }
  481. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  482. }
  483. },
  484. DE: {
  485. baseUrl: 'https://enterprise.firstmap.delaware.gov/arcgis/rest/services/Transportation/DE_Roadways_Main/FeatureServer/',
  486. defaultColors: {
  487. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  488. },
  489. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  490. fcMapLayers: [
  491. {
  492. layerID: 16,
  493. fcPropName: 'VALUE_TEXT',
  494. idPropName: 'OBJECTID',
  495. outFields: ['OBJECTID', 'VALUE_TEXT'],
  496. maxRecordCount: 1000,
  497. supportsPagination: false,
  498. roadTypeMap: {
  499. Fw: ['Interstate'], Ew: ['Other Expressways & Freeway'], MH: ['Other Principal Arterials'], mH: ['Minor Arterial'], PS: ['Major Collector', 'Minor Collector'], St: ['Local']
  500. }
  501. }
  502. ],
  503. information: { Source: 'Delaware FirstMap', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  504. getWhereClause(context) {
  505. if (context.mapContext.zoom < 16) {
  506. return `${context.layer.fcPropName} <> 'Local'`;
  507. }
  508. return null;
  509. },
  510. getFeatureRoadType(feature, layer) {
  511. if (layer.getFeatureRoadType) {
  512. return layer.getFeatureRoadType(feature);
  513. }
  514. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  515. }
  516. },
  517. DC: {
  518. baseUrl: 'https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Transportation_WebMercator/MapServer/',
  519. supportsPagination: false,
  520. defaultColors: {
  521. Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  522. },
  523. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  524. fetchAllFC: false,
  525. fcMapLayers: [
  526. {
  527. layerID: 48,
  528. fcPropName: 'FHWAFUNCTIONALCLASS',
  529. idPropName: 'OBJECTID',
  530. outFields: ['OBJECTID', 'FHWAFUNCTIONALCLASS'],
  531. maxRecordCount: 1000,
  532. supportsPagination: false,
  533. roadTypeMap: {
  534. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  535. }
  536. }
  537. ],
  538. information: { Source: 'DDOT', Permission: 'Visible to R4+ or R3-AM' },
  539. getWhereClause() {
  540. return null;
  541. },
  542. getFeatureRoadType(feature, layer) {
  543. if (layer.getFeatureRoadType) {
  544. return layer.getFeatureRoadType(feature);
  545. }
  546. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  547. }
  548. },
  549. FL: {
  550. baseUrl: 'https://services1.arcgis.com/O1JpcwDW8sjYuddV/ArcGIS/rest/services/Functional_Classification_TDA/FeatureServer/',
  551. supportsPagination: false,
  552. defaultColors: {
  553. Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  554. },
  555. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  556. fetchAllFC: false,
  557. fcMapLayers: [
  558. {
  559. layerID: 0,
  560. fcPropName: 'FUNCLASS',
  561. idPropName: 'FID',
  562. outFields: ['FID', 'FUNCLASS'],
  563. maxRecordCount: 1000,
  564. supportsPagination: false,
  565. roadTypeMap: {
  566. Fw: ['01', '11'], Ew: ['02', '12'], MH: ['04', '14'], mH: ['06', '16'], PS: ['07', '08', '17', '18']
  567. }
  568. }
  569. ],
  570. information: { Source: 'FDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  571. getWhereClause() {
  572. return null;
  573. },
  574. getFeatureRoadType(feature, layer) {
  575. if (layer.getFeatureRoadType) {
  576. return layer.getFeatureRoadType(feature);
  577. }
  578. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  579. }
  580. },
  581. GA: {
  582. baseUrl: 'https://maps.itos.uga.edu/arcgis/rest/services/GDOT/GDOT_FunctionalClass/mapserver/',
  583. supportsPagination: true,
  584. defaultColors: {
  585. Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  586. },
  587. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  588. fetchAllFC: false,
  589. /* eslint-disable object-curly-newline */
  590. fcMapLayers: [
  591. { 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] } },
  592. { 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] } },
  593. { 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] } },
  594. { 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] } },
  595. { 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] } },
  596. { 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] } },
  597. { 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] } }
  598. ],
  599. /* eslint-enable object-curly-newline */
  600. information: { Source: 'GDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Federal and State highways set to a minimum of mH.' },
  601. getWhereClause() {
  602. return null;
  603. },
  604. getFeatureRoadType(feature, layer) {
  605. if (layer.getFeatureRoadType) {
  606. return layer.getFeatureRoadType(feature);
  607. }
  608. const attr = feature.attributes;
  609. const fc = attr.FUNCTIONAL_CLASS;
  610. if (attr.SYSTEM_CODE === '1' && fc > 4) {
  611. return STATE_SETTINGS.global.getRoadTypeFromFC(4, layer);
  612. }
  613. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  614. }
  615. },
  616. HI: {
  617. baseUrl: 'http://geodata.hawaii.gov/arcgis/rest/services/Transportation/MapServer/',
  618. defaultColors: {
  619. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  620. },
  621. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  622. fcMapLayers: [
  623. {
  624. layerID: 12,
  625. fcPropName: 'funsystem',
  626. idPropName: 'OBJECTID',
  627. outFields: ['OBJECTID', 'funsystem'],
  628. roadTypeMap: {
  629. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  630. },
  631. maxRecordCount: 1000,
  632. supportsPagination: false
  633. }
  634. ],
  635. information: { Source: 'HDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  636. getWhereClause(context) {
  637. if (context.mapContext.zoom < 16) {
  638. return `${context.layer.fcPropName} <> 7`;
  639. }
  640. return null;
  641. },
  642. getFeatureRoadType(feature, layer) {
  643. if (layer.getFeatureRoadType) {
  644. return layer.getFeatureRoadType(feature);
  645. }
  646. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  647. }
  648. },
  649. ID: {
  650. baseUrl: 'https://gis.itd.idaho.gov/arcgisprod/rest/services/IPLAN/Functional_Classification/MapServer/',
  651. supportsPagination: false,
  652. defaultColors: {
  653. Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  654. },
  655. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  656. fetchAllFC: true,
  657. /* eslint-disable object-curly-newline */
  658. fcMapLayers: [
  659. { layerID: 0, fcPropName: 'FunctionalClass', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FunctionalClass'], maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  660. { layerID: 1, fcPropName: 'FunctionalClass', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FunctionalClass'], maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  661. { layerID: 2, fcPropName: 'FunctionalClass', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FunctionalClass'], maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  662. { layerID: 3, fcPropName: 'FunctionalClass', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FunctionalClass'], maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  663. { layerID: 4, fcPropName: 'FunctionalClass', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FunctionalClass'], maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } },
  664. { layerID: 5, fcPropName: 'FunctionalClass', idPropName: 'OBJECTID', outFields: ['OBJECTID', 'FunctionalClass'], maxRecordCount: 1000, supportsPagination: false, roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] } }
  665. ],
  666. /* eslint-enable object-curly-newline */
  667. information: { Source: 'ITD', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  668. getWhereClause() {
  669. return null;
  670. },
  671. getFeatureRoadType(feature, layer) {
  672. if (layer.getFeatureRoadType) {
  673. return layer.getFeatureRoadType(feature);
  674. }
  675. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  676. }
  677. },
  678. IL: {
  679. baseUrl: 'https://gis1.dot.illinois.gov/arcgis/rest/services/AdministrativeData/FunctionalClass/MapServer/',
  680. supportsPagination: false,
  681. defaultColors: {
  682. Fw: '#ff00c5', Ew: '#ff00c5', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  683. },
  684. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  685. fcMapLayers: [
  686. {
  687. layerID: 0,
  688. idPropName: 'OBJECTID',
  689. fcPropName: 'FC',
  690. outFields: ['FC'],
  691. roadTypeMap: {
  692. Fw: ['1'], Ew: ['2'], MH: ['3'], mH: ['4'], PS: ['5', '6'], St: ['7']
  693. },
  694. maxRecordCount: 1000,
  695. supportsPagination: false
  696. }
  697. ],
  698. isPermitted() { return _r >= 4; },
  699. information: { Source: 'IDOT', Permission: 'Visible to R4+', Description: 'Raw unmodified FC data.' },
  700. getWhereClause(context) {
  701. return context.mapContext.zoom < 16 ? 'FC<>7' : null;
  702. },
  703. getFeatureRoadType(feature, layer) {
  704. if (layer.getFeatureRoadType) {
  705. return layer.getFeatureRoadType(feature);
  706. }
  707. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  708. }
  709. },
  710. IN: {
  711. baseUrl: 'https://gis.in.gov/arcgis/rest/services/DOT/INDOT_LTAP/FeatureServer/',
  712. supportsPagination: false,
  713. overrideUrl: '1Sbwc7e6BfHpZWSTfU3_1otXGSxHrdDYcbn7fOf1VjpA',
  714. defaultColors: {
  715. Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  716. },
  717. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []], hideRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  718. fcMapLayers: [
  719. {
  720. layerID: 10,
  721. idPropName: 'OBJECTID',
  722. fcPropName: 'FUNCTIONAL_CLASS',
  723. outFields: ['FUNCTIONAL_CLASS', 'OBJECTID', 'TO_DATE'],
  724. roadTypeMap: {
  725. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  726. },
  727. maxRecordCount: 100000,
  728. supportsPagination: false
  729. }
  730. ],
  731. isPermitted() { return true; },
  732. information: { Source: 'INDOT', Description: 'Raw unmodified FC data.' },
  733. getWhereClause(context) {
  734. let whereParts = ['TO_DATE IS NULL'];
  735. if (context.mapContext.zoom < 16) {
  736. whereParts += ` AND ${context.layer.fcPropName} <> 7`;
  737. }
  738. return whereParts;
  739. },
  740. getFeatureRoadType(feature, layer) {
  741. if (layer.getFeatureRoadType) {
  742. return layer.getFeatureRoadType(feature);
  743. }
  744. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  745. }
  746. },
  747. IA: {
  748. baseUrl: 'https://gis.iowadot.gov/agshost/rest/services/RAMS/Road_Network/FeatureServer/',
  749. defaultColors: {
  750. Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee', PSGr: '#cc6533', StGr: '#e99cb6'
  751. },
  752. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  753. fcMapLayers: [
  754. {
  755. layerID: 0,
  756. fcPropName: 'FED_FUNCTIONAL_CLASS',
  757. idPropName: 'OBJECTID',
  758. outFields: ['OBJECTID', 'FED_FUNCTIONAL_CLASS', 'STATE_ROUTE_NAME_1', 'ACCESS_CONTROL', 'SURFACE_TYPE'],
  759. roadTypeMap: {
  760. Fw: [1], MH: [2, 3], mH: [4], PS: [5, 6], St: [7]
  761. },
  762. maxRecordCount: 1000,
  763. supportsPagination: false
  764. }
  765. ],
  766. information: { Source: 'Iowa DOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Additional colors denote unpaved PS and LS segements.' },
  767. getWhereClause(context) {
  768. let theWhereClause = "FACILITY_TYPE<>'7'"; // Removed proposed roads
  769. if (context.mapContext.zoom < 16) {
  770. theWhereClause += ` AND ${context.layer.fcPropName}<>'7'`;
  771. }
  772. return theWhereClause;
  773. },
  774. getFeatureRoadType(feature, layer) {
  775. const attr = feature.attributes;
  776. let fc = parseInt(attr[layer.fcPropName], 10);
  777. const isFw = attr.ACCESS_CONTROL === 1;
  778. const isUS = '^STATE OF IOWA, US'.test(attr.STATE_ROUTE_NAME_1);
  779. const isState = '^STATE OF IOWA, IA'.test(attr.STATE_ROUTE_NAME_1);
  780. if (isFw) fc = 1;
  781. else if (fc > 3 && isUS) fc = 3;
  782. else if (fc > 4 && isState) fc = 4;
  783. if (fc > 4 && attr.SURFACE_TYPE === 20) {
  784. return fc < 7 ? 'PSGr' : 'StGr';
  785. }
  786. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  787. }
  788. },
  789. KS: {
  790. baseUrl: 'http://wfs.ksdot.org/arcgis_web_adaptor/rest/services/Transportation/',
  791. defaultColors: {
  792. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  793. },
  794. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  795. fcMapLayers: [
  796. {
  797. layerID: 0,
  798. layerPath: 'Non_State_System/MapServer/',
  799. idPropName: 'ID2',
  800. fcPropName: 'FUNCLASS',
  801. outFields: ['FUNCLASS', 'ID2', 'ROUTE_ID'],
  802. roadTypeMap: {
  803. Fw: [1], MH: [2, 3], mH: [4], PS: [5, 6], St: [7]
  804. },
  805. maxRecordCount: 1000,
  806. supportsPagination: false
  807. },
  808. {
  809. layerID: 0,
  810. layerPath: 'State_System/MapServer/',
  811. idPropName: 'OBJECTID',
  812. fcPropName: 'FUN_CLASS_CD',
  813. outFields: ['FUN_CLASS_CD', 'OBJECTID', 'PREFIX', 'ACCESS_CONTROL'],
  814. roadTypeMap: {
  815. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  816. },
  817. maxRecordCount: 1000,
  818. supportsPagination: false
  819. }
  820. ],
  821. isPermitted() { return _r >= 3 || _isAM; },
  822. information: { Source: 'KDOT', Permission: 'Visible to area managers' },
  823. getWhereClause(context) {
  824. if (context.mapContext.zoom < 16) {
  825. return `${context.layer.fcPropName}<>'7'`;
  826. }
  827. return null;
  828. },
  829. getFeatureRoadType(feature, layer) {
  830. const attr = feature.attributes;
  831. let fc = parseInt(attr[layer.fcPropName], 10);
  832. const roadPrefix = attr.PREFIX;
  833. const isUS = roadPrefix === 'U';
  834. const isState = roadPrefix === 'K';
  835. if ((fc > 3 && isUS) || (fc === 2 && parseInt(attr.ACCESS_CONTROL, 10) !== 1)) fc = 3;
  836. else if (fc > 4 && isState) fc = 4;
  837. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  838. }
  839. },
  840. KY: {
  841. baseUrl: 'https://maps.kytc.ky.gov/arcgis/rest/services/BaseMap/System/MapServer/',
  842. supportsPagination: false,
  843. defaultColors: {
  844. Fw: '#ff00c5', Ew: '#ff00c5', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  845. },
  846. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  847. fcMapLayers: [
  848. {
  849. layerID: 0,
  850. idPropName: 'OBJECTID',
  851. fcPropName: 'FC',
  852. outFields: ['FC', 'OBJECTID', 'RT_PREFIX', 'RT_SUFFIX'],
  853. roadTypeMap: {
  854. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  855. },
  856. maxRecordCount: 1000,
  857. supportsPagination: false
  858. }
  859. ],
  860. isPermitted() { return true; },
  861. information: { Source: 'KYTC' },
  862. getWhereClause(context) {
  863. if (context.mapContext.zoom < 16) {
  864. return `${context.layer.fcPropName}<>'7'`;
  865. }
  866. return null;
  867. },
  868. getFeatureRoadType(feature, layer) {
  869. const attr = feature.attributes;
  870. let fc = parseInt(attr[layer.fcPropName], 10);
  871. if (fc > 3 && attr.RT_PREFIX === 'US') {
  872. const suffix = attr.RT_SUFFIX;
  873. fc = (suffix && suffix.indexOf('X') > -1) ? 4 : 3;
  874. }
  875. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  876. }
  877. },
  878. LA: {
  879. baseUrl: 'https://giswebnew.dotd.la.gov/arcgis/rest/services/Transportation/LA_RoadwayFunctionalClassification/FeatureServer/',
  880. supportsPagination: false,
  881. defaultColors: {
  882. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  883. },
  884. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  885. /* eslint-disable object-curly-newline */
  886. fcMapLayers: [
  887. { 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 },
  888. { 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 },
  889. { 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 },
  890. { 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 },
  891. { 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 },
  892. { 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 },
  893. { 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 }
  894. ],
  895. /* eslint-enable object-curly-newline */
  896. information: { Source: 'LaDOTD', Permission: 'Visible to R4+ or R3-AM' },
  897. getWhereClause(context) {
  898. if (context.mapContext.zoom < 16) {
  899. return `${context.layer.fcPropName}<>'7'`; // OR State_Route LIKE 'US%' OR State_Route LIKE 'LA%'";
  900. }
  901. return null;
  902. },
  903. getFeatureRoadType(feature, layer) {
  904. let fc = feature.attributes[layer.fcPropName];
  905. if (fc === '2a' || fc === '2b') { fc = 2; }
  906. fc = parseInt(fc, 10);
  907. const route = feature.attributes.RouteID.split('_')[1].trim();
  908. const isUS = /^US \d/.test(route);
  909. const isState = /^LA \d/.test(route);
  910. const isBiz = / BUS$/.test(route);
  911. if (fc > 3 && isUS) fc = isBiz ? 4 : 3;
  912. else if (fc > 4 && isState) fc = isBiz ? 5 : 4;
  913. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  914. }
  915. },
  916. ME: {
  917. baseUrl: 'https://arcgisserver.maine.gov/arcgis/rest/services/mdot/MaineDOT_Dynamic/MapServer/',
  918. defaultColors: {
  919. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  920. },
  921. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  922. fcMapLayers: [
  923. {
  924. layerID: 1015,
  925. fcPropName: 'fedfunccls',
  926. idPropName: 'objectid',
  927. outFields: ['objectid', 'fedfunccls', 'prirtename'],
  928. roadTypeMap: {
  929. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  930. },
  931. maxRecordCount: 1000,
  932. supportsPagination: false
  933. }
  934. ],
  935. information: { Source: 'MaineDOT', Permission: 'Visible to R4+ or R3-AM' },
  936. isPermitted() { return _r >= 4 || (_r === 3 && _isAM); },
  937. getWhereClause(context) {
  938. if (context.mapContext.zoom < 16) {
  939. return `${context.layer.fcPropName}<>'Local'`;
  940. }
  941. return null;
  942. },
  943. getFeatureRoadType(feature, layer) {
  944. const attr = feature.attributes;
  945. let fc = attr[layer.fcPropName];
  946. switch (fc) {
  947. case 'Princ art interstate': fc = 1; break;
  948. case 'Princ art other f&e': fc = 2; break;
  949. case 'Other princ arterial': fc = 3; break;
  950. case 'Minor arterial': fc = 4; break;
  951. case 'Major/urb collector':
  952. case 'Minor collector': fc = 5; break;
  953. default: fc = 7;
  954. }
  955. const route = attr.prirtename;
  956. const isUS = /^US \d/.test(route);
  957. const isState = /^ST RTE \d/.test(route);
  958. const isBiz = (isUS && /(1B|1BS)$/.test(route)) || (isState && /(15B|24B|25B|137B)$/.test(route));
  959. if (fc > 3 && isUS) fc = isBiz ? 4 : 3;
  960. else if (fc > 4 && isState) fc = isBiz ? 5 : 4;
  961. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  962. }
  963. },
  964. MD: {
  965. baseUrl: 'https://services.arcgis.com/njFNhDsUCentVYJW/arcgis/rest/services/MDOT_SHA_Roadway_Functional_Classification/FeatureServer/',
  966. defaultColors: {
  967. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#ffff00', St: '#eeeeee'
  968. },
  969. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  970. fcMapLayers: [
  971. {
  972. layerID: 0,
  973. fcPropName: 'FUNCTIONAL_CLASS',
  974. idPropName: 'OBJECTID',
  975. outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'ID_PREFIX', 'MP_SUFFIX'],
  976. roadTypeMap: {
  977. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  978. },
  979. maxRecordCount: 1000,
  980. supportsPagination: false
  981. }
  982. ],
  983. information: { Source: 'MDOT', Permission: 'Visible to R4+ or R3-AM' },
  984. getWhereClause(context) {
  985. if (context.mapContext.zoom < 16) {
  986. return "(FUNCTIONAL_CLASS < 7 OR ID_PREFIX IN('MD'))";
  987. }
  988. return null;
  989. },
  990. getFeatureRoadType(feature, layer) {
  991. const attr = feature.attributes;
  992. let fc = parseInt(attr.FUNCTIONAL_CLASS, 10);
  993. const isUS = attr.ID_PREFIX === 'US';
  994. const isState = attr.ID_PREFIX === 'MD';
  995. const isBiz = attr.MP_SUFFIX === 'BU';
  996. if (fc > 3 && isUS) fc = isBiz ? 4 : 3;
  997. else if (fc > 4 && isState) fc = isBiz ? 5 : 4;
  998. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  999. }
  1000. },
  1001. MA: {
  1002. baseUrl: 'https://gis.massdot.state.ma.us/arcgis/rest/services/Roads/RoadInventory/MapServer/',
  1003. defaultColors: {
  1004. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1005. },
  1006. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1007. fcMapLayers: [
  1008. {
  1009. layerID: 0,
  1010. fcPropName: 'F_F_Class',
  1011. idPropName: 'OBJECTID',
  1012. outFields: ['OBJECTID', 'F_F_Class', 'Route_ID'],
  1013. roadTypeMap: {
  1014. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  1015. },
  1016. maxRecordCount: 1000,
  1017. supportsPagination: false
  1018. }
  1019. ],
  1020. information: { Source: 'MDOT', Permission: 'Visible to R2+' },
  1021. isPermitted() { return _r >= 2; },
  1022. getWhereClause(context) {
  1023. if (context.mapContext.zoom < 16) {
  1024. return `${context.layer.fcPropName}<>'7'`;
  1025. }
  1026. return null;
  1027. },
  1028. getFeatureRoadType(feature, layer) {
  1029. const attr = feature.attributes;
  1030. let fc = parseInt(attr[layer.fcPropName], 10);
  1031. const route = attr.Route_ID;
  1032. const isUS = /^US\d/.test(route);
  1033. const isState = /^SR\d/.test(route);
  1034. if (fc > 3 && isUS) fc = 3;
  1035. else if (fc > 4 && isState) fc = 4;
  1036. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1037. }
  1038. },
  1039. MI: {
  1040. baseUrl: 'https://gisp.mcgi.state.mi.us/arcgis/rest/services/MDOT/NFC/MapServer/',
  1041. defaultColors: {
  1042. Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1043. },
  1044. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1045. fcMapLayers: [
  1046. {
  1047. layerID: 2,
  1048. idPropName: 'OBJECTID',
  1049. fcPropName: 'NFC',
  1050. outFields: ['NFC'],
  1051. roadTypeMap: {
  1052. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  1053. },
  1054. maxRecordCount: 1000,
  1055. supportsPagination: false
  1056. }
  1057. ],
  1058. isPermitted() { return true; },
  1059. information: { Source: 'MDOT', Description: 'Raw unmodified FC data.' },
  1060. getWhereClause(context) {
  1061. if (context.mapContext.zoom < 16) {
  1062. return `${context.layer.fcPropName}<>7`;
  1063. }
  1064. return null;
  1065. },
  1066. getFeatureRoadType(feature, layer) {
  1067. if (layer.getFeatureRoadType) {
  1068. return layer.getFeatureRoadType(feature);
  1069. }
  1070. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  1071. }
  1072. },
  1073. MN: {
  1074. baseUrl: 'https://dotapp9.dot.state.mn.us/lrs/rest/services/emma/emma_op/MapServer/',
  1075. defaultColors: {
  1076. Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1077. },
  1078. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1079. fcMapLayers: [
  1080. {
  1081. layerID: 13,
  1082. idPropName: 'OBJECTID',
  1083. fcPropName: 'FUNCTIONAL_CLASS',
  1084. outFields: ['FUNCTIONAL_CLASS', 'ROUTE_ID'],
  1085. roadTypeMap: {
  1086. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  1087. },
  1088. maxRecordCount: 1000,
  1089. supportsPagination: false
  1090. }
  1091. ],
  1092. isPermitted() { return true; },
  1093. information: { Source: 'MnDOT', Description: 'Raw unmodified FC data.' },
  1094. getWhereClause(context) {
  1095. if (context.mapContext.zoom < 16) {
  1096. return `${context.layer.fcPropName}<>7`;
  1097. }
  1098. return null;
  1099. },
  1100. getFeatureRoadType(feature, layer) {
  1101. if (layer.getFeatureRoadType) {
  1102. return layer.getFeatureRoadType(feature);
  1103. }
  1104. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  1105. }
  1106. },
  1107. MO: {
  1108. baseUrl: 'http://mapping.modot.org/external/rest/services/BaseMap/TmsUtility/MapServer/',
  1109. defaultColors: {
  1110. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1111. },
  1112. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1113. fcMapLayers: [
  1114. {
  1115. layerID: 5,
  1116. fcPropName: 'FUNC_CLASS_NAME',
  1117. idPropName: 'SS_PAVEMENT_ID',
  1118. outFields: ['SS_PAVEMENT_ID', 'FUNC_CLASS_NAME', 'TRAVELWAY_DESG', 'TRAVELWAY_NAME', 'ACCESS_CAT_NAME'],
  1119. roadTypeMap: {
  1120. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  1121. },
  1122. maxRecordCount: 1000,
  1123. supportsPagination: false
  1124. }
  1125. ],
  1126. isPermitted() { return _r >= 3 || (_r >= 2 && _isAM); },
  1127. information: { Source: 'MoDOT', Permission: 'Visible to R3+ or R2-AM' },
  1128. getWhereClause(context) {
  1129. if (context.mapContext.zoom < 13) {
  1130. return '1=0'; // WME very laggy at zoom 0
  1131. }
  1132. // Remove duplicate rows, but suss out interstate business loops
  1133. return "FUNC_CLASS_NAME <> ' ' AND (TRAVELWAY_ID = CNTL_TW_ID OR (TRAVELWAY_ID <> CNTL_TW_ID AND TRAVELWAY_DESG = 'LP'))";
  1134. },
  1135. getFeatureRoadType(feature, layer) {
  1136. const attr = feature.attributes;
  1137. let fc = attr[layer.fcPropName];
  1138. const rtType = attr.TRAVELWAY_DESG;
  1139. const route = attr.TRAVELWAY_NAME;
  1140. switch (fc) {
  1141. case 'INTERSTATE': fc = 1; break;
  1142. case 'FREEWAY': fc = 2; break;
  1143. case 'PRINCIPAL ARTERIAL': fc = 3; break;
  1144. case 'MINOR ARTERIAL': fc = 4; break;
  1145. case 'MAJOR COLLECTOR': fc = 5; break;
  1146. case 'MINOR COLLECTOR': fc = 6; break;
  1147. default: fc = 8; // not a typo
  1148. }
  1149. const usHwys = ['24', '36', '40', '50', '54', '56', '59', '60', '61', '62', '63', '65', '67', '69', '71', '136', '159', '160', '166', '169', '275', '400', '412'];
  1150. const isUS = ['US', 'LP'].includes(rtType); // is US or interstate biz
  1151. const isState = ['MO', 'AL'].includes(rtType);
  1152. const isSup = rtType === 'RT';
  1153. const isBiz = ['BU', 'SP'].includes(rtType) || /BUSINESS .+ \d/.test(route);
  1154. const isUSBiz = isBiz && usHwys.includes(route);
  1155. if ((fc === 2 && attr.ACCESS_CAT_NAME !== 'FULL') || (fc > 3 && isUS)) fc = 3;
  1156. else if (fc > 4 && (isState || isUSBiz)) fc = 4;
  1157. else if (fc > 6 && (isSup || isBiz)) fc = 6;
  1158. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1159. }
  1160. },
  1161. MT: {
  1162. baseUrl: 'https://app.mdt.mt.gov/arcgis/rest/services/Standard/FUNCTIONAL_CLASS/MapServer/',
  1163. defaultColors: {
  1164. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1165. },
  1166. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1167. fcMapLayers: [
  1168. {
  1169. layerID: 0,
  1170. fcPropName: 'FUNC_CLASS',
  1171. idPropName: 'OBJECTID',
  1172. outFields: ['OBJECTID', 'FUNC_CLASS', 'SIGN_ROUTE', 'ROUTE_NAME'],
  1173. roadTypeMap: {
  1174. Fw: ['1-Interstate']
  1175. },
  1176. maxRecordCount: 1000,
  1177. supportsPagination: false
  1178. },
  1179. {
  1180. layerID: 1,
  1181. fcPropName: 'FUNC_CLASS',
  1182. idPropName: 'OBJECTID',
  1183. outFields: ['OBJECTID', 'FUNC_CLASS', 'SIGN_ROUTE', 'ROUTE_NAME'],
  1184. roadTypeMap: {
  1185. MH: ['3-Principal Arterial - Other']
  1186. },
  1187. maxRecordCount: 1000,
  1188. supportsPagination: false
  1189. },
  1190. {
  1191. layerID: 2,
  1192. fcPropName: 'FUNC_CLASS',
  1193. idPropName: 'OBJECTID',
  1194. outFields: ['OBJECTID', 'FUNC_CLASS', 'SIGN_ROUTE', 'ROUTE_NAME'],
  1195. roadTypeMap: {
  1196. mH: ['4-Minor Arterial']
  1197. },
  1198. maxRecordCount: 1000,
  1199. supportsPagination: false
  1200. },
  1201. {
  1202. layerID: 3,
  1203. fcPropName: 'FUNC_CLASS',
  1204. idPropName: 'OBJECTID',
  1205. outFields: ['OBJECTID', 'FUNC_CLASS', 'SIGN_ROUTE', 'ROUTE_NAME'],
  1206. roadTypeMap: {
  1207. PS: ['5-Major Collector']
  1208. },
  1209. maxRecordCount: 1000,
  1210. supportsPagination: false
  1211. },
  1212. {
  1213. layerID: 4,
  1214. fcPropName: 'FUNC_CLASS',
  1215. idPropName: 'OBJECTID',
  1216. outFields: ['OBJECTID', 'FUNC_CLASS', 'SIGN_ROUTE', 'ROUTE_NAME'],
  1217. roadTypeMap: {
  1218. PS: ['6-Minor Collector']
  1219. },
  1220. maxRecordCount: 1000,
  1221. supportsPagination: false
  1222. },
  1223. {
  1224. layerID: 5,
  1225. fcPropName: 'FUNC_CLASS',
  1226. idPropName: 'OBJECTID',
  1227. outFields: ['OBJECTID', 'FUNC_CLASS', 'SIGN_ROUTE', 'ROUTE_NAME'],
  1228. roadTypeMap: {
  1229. St: ['7-Local']
  1230. },
  1231. maxRecordCount: 1000,
  1232. supportsPagination: false
  1233. }
  1234. ],
  1235. isPermitted() { /* return _r >= 3; */ return ['mapomatic', 'bobc455'].includes(_uName.toLowerCase()); },
  1236. information: { Source: 'MDT', Permission: 'Visible to R3+' },
  1237. getWhereClause(context) {
  1238. if (context.mapContext.zoom < 16) {
  1239. return `${context.layer.fcPropName}<>'LOCAL'`;
  1240. }
  1241. return null;
  1242. },
  1243. getFeatureRoadType(feature, layer) {
  1244. let rt = STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  1245. const roadID = feature.attributes.SIGN_ROUTE || feature.attributes.ROUTE_NAME;
  1246. const isUS = /^US[ -]?\d+/.test(roadID);
  1247. const isState = /^MONTANA \d+|ROUTE \d+|S-\d{3}\b/.test(roadID);
  1248. if (isUS && ['St', 'PS', 'mH'].includes(rt)) {
  1249. log(`FC UPGRADE: ${roadID} from ${rt} to MH`); // TODO - remove this when testing is finished (9/10/2022)
  1250. rt = 'MH';
  1251. } else if (isState && ['St', 'PS'].includes(rt)) {
  1252. log(`FC UPGRADE: ${roadID} from ${rt} to mH`); // TODO - remove this when testing is finished (9/10/2022)
  1253. rt = 'mH';
  1254. }
  1255. return rt;
  1256. }
  1257. },
  1258. NV: {
  1259. baseUrl: 'https://gis.dot.nv.gov/rhgis/rest/services/GeoHub/FSystem/MapServer/',
  1260. defaultColors: {
  1261. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1262. },
  1263. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  1264. fcMapLayers: [
  1265. {
  1266. layerID: 0,
  1267. fcPropName: 'FSystem',
  1268. idPropName: 'OBJECTID',
  1269. outFields: ['OBJECTID', 'FSystem'],
  1270. roadTypeMap: {
  1271. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  1272. },
  1273. maxRecordCount: 1000,
  1274. supportsPagination: false
  1275. }
  1276. ],
  1277. isPermitted() { return ['mapomatic', 'turbomkt', 'tonestertm', 'geopgeop'].includes(_uName.toLowerCase()); },
  1278. information: { Source: 'NDOT', Permission: '?' },
  1279. getWhereClause(context) {
  1280. if (context.mapContext.zoom < 16) {
  1281. return `${context.layer.fcPropName}<>7`;
  1282. }
  1283. return null;
  1284. },
  1285. getFeatureRoadType(feature, layer) {
  1286. const fc = parseInt(feature.attributes[layer.fcPropName], 10);
  1287. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1288. }
  1289. },
  1290. NH: {
  1291. baseUrl: 'https://nhgeodata.unh.edu/nhgeodata/rest/services/TN/RoadsForDOTViewer/MapServer/',
  1292. defaultColors: {
  1293. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1294. },
  1295. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  1296. fcMapLayers: [
  1297. {
  1298. layerID: 0,
  1299. fcPropName: 'FUNCT_SYSTEM',
  1300. idPropName: 'OBJECTID',
  1301. outFields: ['OBJECTID', 'FUNCT_SYSTEM', 'STREET_ALIASES', 'TIER'],
  1302. roadTypeMap: {
  1303. Fw: [1], Ew: [2], MH: [2, 3], mH: [4], PS: [5, 6], St: [7, 0]
  1304. },
  1305. maxRecordCount: 1000,
  1306. supportsPagination: false
  1307. }
  1308. ],
  1309. isPermitted() { return _r >= 2; },
  1310. information: { Source: 'NH GRANIT', Permission: 'Visible to R2+' },
  1311. getWhereClause(context) {
  1312. if (context.mapContext.zoom < 16) {
  1313. return `${context.layer.fcPropName}<>7 AND ${context.layer.fcPropName}<>0`;
  1314. }
  1315. return null;
  1316. },
  1317. getFeatureRoadType(feature, layer) {
  1318. let fc = parseInt(feature.attributes[layer.fcPropName], 10);
  1319. if (!(fc > 0)) { fc = 7; }
  1320. const route = feature.attributes.STREET_ALIASES;
  1321. const isUS = /US /.test(route);
  1322. const isState = /NH /.test(route);
  1323. if (fc === 2) fc = feature.attributes.TIER === 1 ? 1 : 3;
  1324. else if (fc > 3 && isUS) fc = /US 3B/.test(route) ? 4 : 3;
  1325. else if (fc > 4 && isState) fc = 4;
  1326. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1327. }
  1328. },
  1329. NM: {
  1330. baseUrl: 'https://services.arcgis.com/hOpd7wfnKm16p9D9/ArcGIS/rest/services/NMDOT_Functional_Class/FeatureServer/',
  1331. defaultColors: {
  1332. Fw: '#ff00c5', Ew: '#ff00c5', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1333. },
  1334. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  1335. fcMapLayers: [
  1336. {
  1337. layerID: 0,
  1338. fcPropName: 'Func_Class',
  1339. idPropName: 'OBJECTID_1',
  1340. maxRecordCount: 1000,
  1341. supportsPagination: false,
  1342. outFields: ['OBJECTID_1', 'Func_Class', 'D_RT_ROUTE'],
  1343. roadTypeMap: {
  1344. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  1345. }
  1346. }
  1347. ],
  1348. isPermitted() { return true; },
  1349. information: { Source: 'NMDOT' },
  1350. getWhereClause() {
  1351. return null;
  1352. },
  1353. getFeatureRoadType(feature, layer) {
  1354. let fc = parseInt(feature.attributes[layer.fcPropName], 10);
  1355. const roadType = feature.attributes.D_RT_ROUTE.split('-', 1).shift();
  1356. const isBiz = roadType === 'BL'; // Interstate Business Loop
  1357. const isUS = roadType === 'US';
  1358. const isState = roadType === 'NM';
  1359. if (roadType === 'IX') fc = 0;
  1360. else if (fc > 3 && (isBiz || isUS)) fc = 3;
  1361. else if (fc > 4 && isState) fc = 4;
  1362. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1363. }
  1364. },
  1365. NY: { // https://gis.dot.ny.gov/hostingny/rest/services/Basemap/MapServer/21
  1366. baseUrl: 'https://gis.dot.ny.gov/hostingny/rest/services',
  1367. defaultColors: {
  1368. Fw: '#ff00c5', Ew: '#5f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1369. },
  1370. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  1371. fcMapLayers: [
  1372. {
  1373. layerID: '/Geocortex/FC/MapServer/1',
  1374. fcPropName: 'FUNC_CLASS',
  1375. idPropName: 'OBJECTID',
  1376. outFields: ['OBJECTID', 'FUNC_CLASS', 'SEGMENT_NAME', 'ROUTE_NO'],
  1377. roadTypeMap: {
  1378. Fw: [1, 11], Ew: [2, 12], MH: [4, 14], mH: [6, 16], PS: [7, 8, 17, 18]
  1379. },
  1380. maxRecordCount: 1000,
  1381. supportsPagination: false
  1382. },
  1383. {
  1384. layerID: 'Basemap/MapServer/21',
  1385. idPropName: 'OBJECTID',
  1386. outFields: ['OBJECTID', 'SHIELD'],
  1387. maxRecordCount: 1000,
  1388. supportsPagination: false
  1389. }
  1390. ],
  1391. information: { Source: 'NYSDOT', Permission: 'Visible to R4+ or R3-AM' },
  1392. getWhereClause(context) {
  1393. if (context.layer.layerID === 'Basemap/MapServer/21') {
  1394. return ("SHIELD IN ('C','CT')");
  1395. }
  1396. return null;
  1397. },
  1398. getFeatureRoadType(feature, layer) {
  1399. let roadType;
  1400. if (layer.layerID === 'Basemap/MapServer/21') {
  1401. roadType = 'PS';
  1402. } else {
  1403. roadType = STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  1404. const routeNo = feature.attributes.ROUTE_NO;
  1405. if (/^NY.*/.test(routeNo)) {
  1406. if (roadType === 'PS') roadType = 'mH';
  1407. } else if (/^US.*/.test(routeNo)) {
  1408. if (roadType === 'PS' || roadType === 'mH') roadType = 'MH';
  1409. }
  1410. }
  1411. return roadType;
  1412. }
  1413. },
  1414. NC: {
  1415. baseUrl: 'https://gis11.services.ncdot.gov/arcgis/rest/services/NCDOT_FunctionalClassQtr/MapServer/',
  1416. defaultColors: {
  1417. Fw: '#ff00c5', Rmp: '#999999', Ew: '#5f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1418. },
  1419. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1420. fcMapLayers: [
  1421. {
  1422. layerID: 0,
  1423. fcPropName: 'FuncClass',
  1424. idPropName: 'OBJECTID',
  1425. outFields: ['OBJECTID', 'FuncClass', 'RouteClass', 'RouteQualifier'],
  1426. roadTypeMap: {
  1427. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  1428. },
  1429. zoomLevels: [3, 4, 5, 6, 7, 8, 9, 10],
  1430. maxRecordCount: 1000,
  1431. supportsPagination: false
  1432. }
  1433. ],
  1434. isPermitted() { return _r >= 3; },
  1435. information: { Source: 'NCDOT', Permission: 'Visible to R3+' },
  1436. getWhereClause(context) {
  1437. if (context.mapContext.zoom < 16) {
  1438. const clause = `(${context.layer.fcPropName} < 7 OR RouteClass IN ('I','FED','NC','RMP','US'))`;
  1439. return clause;
  1440. }
  1441. return null;
  1442. },
  1443. getFeatureRoadType(feature, layer) {
  1444. const fc = feature.attributes[layer.fcPropName];
  1445. let roadType;
  1446. switch (this.getHwySys(feature)) {
  1447. case 'interstate':
  1448. if (fc <= 2 || !this.isBusinessRoute(feature)) roadType = 'Fw';
  1449. else roadType = 'MH';
  1450. break;
  1451. case 'us':
  1452. if (fc <= 2) roadType = 'Ew';
  1453. else if (fc === 3 || !this.isBusinessRoute(feature)) roadType = 'MH';
  1454. else roadType = 'mH';
  1455. break;
  1456. case 'state':
  1457. if (fc <= 2) roadType = 'Ew';
  1458. else if (fc === 3) roadType = 'MH';
  1459. else if (fc === 4 || !this.isBusinessRoute(feature)) roadType = 'mH';
  1460. else roadType = 'PS';
  1461. break;
  1462. case 'ramp':
  1463. roadType = 'Rmp';
  1464. break;
  1465. default:
  1466. if (fc === 2) roadType = 'Ew';
  1467. else if (fc === 3) roadType = 'MH';
  1468. else if (fc === 4) roadType = 'mH';
  1469. else if (fc <= 6) roadType = 'PS';
  1470. else roadType = 'St';
  1471. // roadType = fc === 2 ? 'Ew' : (fc === 3 ? 'MH' : (fc === 4 ? 'mH' : (fc <= 6 ? 'PS' : 'St')));
  1472. }
  1473. return roadType;
  1474. },
  1475. getHwySys(feature) {
  1476. let hwySys;
  1477. switch (feature.attributes.RouteClass.toString()) {
  1478. case '1':
  1479. hwySys = 'interstate';
  1480. break;
  1481. case '2':
  1482. hwySys = 'us';
  1483. break;
  1484. case '3':
  1485. hwySys = 'state';
  1486. break;
  1487. case '80':
  1488. hwySys = 'ramp';
  1489. break;
  1490. default:
  1491. hwySys = 'local';
  1492. }
  1493. return hwySys;
  1494. },
  1495. isBusinessRoute(feature) {
  1496. const qual = feature.attributes.RouteQualifier.toString();
  1497. return qual === '9';
  1498. }
  1499. },
  1500. ND: {
  1501. baseUrl: 'https://gis.dot.nd.gov/arcgis/rest/services/external/transinfo/MapServer/',
  1502. defaultColors: {
  1503. Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1504. },
  1505. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1506. fcMapLayers: [
  1507. {
  1508. layerID: 10,
  1509. fcPropName: 'FUNCTION_CLASS',
  1510. idPropName: 'OBJECTID',
  1511. outFields: ['OBJECTID', 'FUNCTION_CLASS'],
  1512. roadTypeMap: {
  1513. Fw: ['Interstate'], MH: ['Principal Arterial'], mH: ['Minor Arterial'], PS: ['Major Collector', 'Collector'], St: ['Local']
  1514. },
  1515. maxRecordCount: 1000,
  1516. supportsPagination: false
  1517. },
  1518. {
  1519. layerID: 11,
  1520. fcPropName: 'FUNCTION_CLASS',
  1521. idPropName: 'OBJECTID',
  1522. outFields: ['OBJECTID', 'FUNCTION_CLASS'],
  1523. roadTypeMap: {
  1524. Fw: ['Interstate'], MH: ['Principal Arterial'], mH: ['Minor Arterial'], PS: ['Major Collector', 'Collector'], St: ['Local']
  1525. },
  1526. maxRecordCount: 1000,
  1527. supportsPagination: false
  1528. },
  1529. {
  1530. layerID: 12,
  1531. fcPropName: 'FUNCTION_CLASS',
  1532. idPropName: 'OBJECTID',
  1533. outFields: ['OBJECTID', 'FUNCTION_CLASS'],
  1534. roadTypeMap: { PS: ['Major Collector', 'Collector'] },
  1535. maxRecordCount: 1000,
  1536. supportsPagination: false
  1537. },
  1538. {
  1539. layerID: 16,
  1540. fcPropName: 'SYSTEM_CD',
  1541. idPropName: 'OBJECTID',
  1542. outFields: ['OBJECTID', 'SYSTEM_CD', 'SYSTEM_DESC', 'HIGHWAY', 'HWY_SUFFIX'],
  1543. roadTypeMap: { Fw: [1, 11], MH: [2, 14], mH: [6, 7, 16, 19] },
  1544. maxRecordCount: 1000,
  1545. supportsPagination: false
  1546. }
  1547. ],
  1548. information: { Source: 'NDDOT', Permission: 'Visible to R4+ or R3-AM' },
  1549. getWhereClause(context) {
  1550. if (context.mapContext.zoom < 16) {
  1551. if (context.layer.layerID !== 16) return `${context.layer.fcPropName}<>'Local'`;
  1552. }
  1553. return null;
  1554. },
  1555. getFeatureRoadType(feature, layer) {
  1556. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  1557. }
  1558. },
  1559. OH: {
  1560. baseUrl: 'https://gis.dot.state.oh.us/arcgis/rest/services/TIMS/Roadway_Information/MapServer/',
  1561. defaultColors: {
  1562. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1563. },
  1564. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1565.  
  1566. fcMapLayers: [
  1567. {
  1568. layerID: 8,
  1569. fcPropName: 'FUNCTION_CLASS_CD',
  1570. idPropName: 'ObjectID',
  1571. outFields: ['FUNCTION_CLASS_CD', 'ROUTE_TYPE', 'ROUTE_NBR', 'ObjectID'],
  1572. maxRecordCount: 1000,
  1573. supportsPagination: false,
  1574. roadTypeMap: {
  1575. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  1576. }
  1577. }
  1578. ],
  1579. isPermitted() { return true; },
  1580. information: { Source: 'ODOT' },
  1581. getWhereClause(context) {
  1582. if (context.mapContext.zoom < 16) {
  1583. const clause = `(${context.layer.fcPropName} < 7 OR ROUTE_TYPE IN ('CR','SR','US'))`;
  1584. return clause;
  1585. }
  1586. return null;
  1587. },
  1588. getFeatureRoadType(feature, layer) {
  1589. let fc = feature.attributes[layer.fcPropName];
  1590. const prefix = feature.attributes.ROUTE_TYPE;
  1591. const isUS = prefix === 'US';
  1592. const isState = prefix === 'SR';
  1593. const isCounty = prefix === 'CR';
  1594. if (isUS && fc > 3) { fc = 3; }
  1595. if (isState && fc > 4) { fc = 4; }
  1596. if (isCounty && fc > 6) { fc = 6; }
  1597. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1598. }
  1599. },
  1600. OK: {
  1601. baseUrl: 'https://services6.arcgis.com/RBtoEUQ2lmN0K3GY/arcgis/rest/services/Roadways/FeatureServer/',
  1602. defaultColors: {
  1603. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1604. },
  1605. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1606. fcMapLayers: [
  1607. {
  1608. layerID: 0,
  1609. fcPropName: 'FUNCTIONALCLASS',
  1610. idPropName: 'OBJECTID',
  1611. outFields: ['OBJECTID', 'FUNCTIONALCLASS', 'FHWAPRIMARYROUTE', 'ODOTROUTECLASS', 'ACCESSCONTROL'],
  1612. maxRecordCount: 1000,
  1613. supportsPagination: false,
  1614. roadTypeMap: {
  1615. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  1616. }
  1617. }
  1618. ],
  1619. information: { Source: 'ODOT', Permission: 'Visible to R4+ or R3-AM' },
  1620. getWhereClause(context) {
  1621. if (context.mapContext.zoom < 16) {
  1622. return `${context.layer.fcPropName} < 7 OR ODOTROUTECLASS IN ('U','S','I')`;
  1623. }
  1624. return null;
  1625. },
  1626. getFeatureRoadType(feature, layer) {
  1627. let fc = feature.attributes[layer.fcPropName];
  1628. const route = (feature.attributes.FHWAPRIMARYROUTE || '').trim();
  1629. const isBusinessOrSpur = /BUS$|SPR$/i.test(route);
  1630. const prefix = isBusinessOrSpur ? route.substring(0, 1) : feature.attributes.ODOTROUTECLASS;
  1631. const isFw = parseInt(feature.attributes.ACCESSCONTROL, 10) === 1;
  1632. const isInterstate = prefix === 'I';
  1633. const isUS = prefix === 'U';
  1634. const isState = prefix === 'S';
  1635. if (isFw) fc = 1;
  1636. else if (fc > 3 && ((isUS && !isBusinessOrSpur) || (isInterstate && isBusinessOrSpur))) fc = 3;
  1637. else if (fc > 4 && ((isUS && isBusinessOrSpur) || (isState && !isBusinessOrSpur))) fc = 4;
  1638. else if (fc > 5 && isState && isBusinessOrSpur) fc = 5;
  1639. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1640. }
  1641. },
  1642. OR: {
  1643. baseUrl: 'https://gis.odot.state.or.us/arcgis/rest/services/transgis/data_catalog_display/Mapserver/',
  1644. defaultColors: {
  1645. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1646. },
  1647. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1648. fcMapLayers: [
  1649. {
  1650. layerID: 78,
  1651. fcPropName: 'NEW_FC_CD',
  1652. idPropName: 'OBJECTID',
  1653. outFields: ['OBJECTID', 'NEW_FC_CD'],
  1654. roadTypeMap: {
  1655. Fw: ['1'], Ew: ['2'], MH: ['3'], mH: ['4'], PS: ['5', '6'], St: ['7']
  1656. },
  1657. maxRecordCount: 1000,
  1658. supportsPagination: false
  1659. },
  1660. {
  1661. layerID: 80,
  1662. fcPropName: 'NEW_FC_CD',
  1663. idPropName: 'OBJECTID',
  1664. outFields: ['OBJECTID', 'NEW_FC_CD'],
  1665. roadTypeMap: {
  1666. Fw: ['1'], Ew: ['2'], MH: ['3'], mH: ['4'], PS: ['5', '6'], St: ['7']
  1667. },
  1668. maxRecordCount: 1000,
  1669. supportsPagination: false
  1670. }
  1671. ],
  1672. information: { Source: 'ODOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  1673. getWhereClause(context) {
  1674. if (context.mapContext.zoom < 16) {
  1675. return `${context.layer.fcPropName} <> '7'`;
  1676. }
  1677. return null;
  1678. },
  1679. getFeatureRoadType(feature, layer) {
  1680. if (layer.getFeatureRoadType) {
  1681. return layer.getFeatureRoadType(feature);
  1682. }
  1683. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  1684. }
  1685. },
  1686. PA: {
  1687. baseUrl: 'https://gis.penndot.gov/arcgis/rest/services/opendata/roadwayadmin/MapServer/',
  1688. supportsPagination: false,
  1689. defaultColors: {
  1690. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1691. },
  1692. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1693. fcMapLayers: [
  1694. {
  1695. layerID: 0,
  1696. features: new Map(),
  1697. fcPropName: 'FUNC_CLS',
  1698. idPropName: 'MSLINK',
  1699. outFields: ['MSLINK', 'FUNC_CLS'],
  1700. maxRecordCount: 1000,
  1701. supportsPagination: false,
  1702. roadTypeMap: {
  1703. Fw: ['01', '11'], Ew: ['12'], MH: ['02', '14'], mH: ['06', '16'], PS: ['07', '08', '17'], St: ['09', '19']
  1704. }
  1705. }
  1706. ],
  1707. isPermitted() { return _r >= 4; },
  1708. information: { Source: 'PennDOT', Permission: 'Visible to R4+', Description: 'Raw unmodified FC data.' },
  1709. getWhereClause(context) {
  1710. return (context.mapContext.zoom < 16) ? `${context.layer.fcPropName} NOT IN ('09','19')` : null;
  1711. },
  1712. getFeatureRoadType(feature, layer) {
  1713. if (layer.getFeatureRoadType) {
  1714. return layer.getFeatureRoadType(feature);
  1715. }
  1716. const fc = feature.attributes[layer.fcPropName];
  1717. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1718. }
  1719. },
  1720. RI: {
  1721. baseUrl: 'https://services2.arcgis.com/S8zZg9pg23JUEexQ/arcgis/rest/services/RIDOT_Roads_2016/FeatureServer/',
  1722. defaultColors: {
  1723. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1724. },
  1725. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  1726. fcMapLayers: [
  1727. {
  1728. layerID: 0,
  1729. fcPropName: 'F_SYSTEM',
  1730. idPropName: 'OBJECTID',
  1731. outFields: ['OBJECTID', 'F_SYSTEM', 'ROADTYPE', 'RTNO'],
  1732. roadTypeMap: {
  1733. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7, 0]
  1734. },
  1735. maxRecordCount: 1000,
  1736. supportsPagination: false
  1737. }
  1738. ],
  1739. isPermitted() { return _r >= 2; },
  1740. information: { Source: 'RIDOT', Permission: 'Visible to R2+' },
  1741. getWhereClause(context) {
  1742. return (context.mapContext.zoom < 16) ? `${context.layer.fcPropName} NOT IN (7,0)` : null;
  1743. },
  1744. getFeatureRoadType(feature, layer) {
  1745. let fc = parseInt(feature.attributes[layer.fcPropName], 10);
  1746. const type = feature.attributes.ROADTYPE;
  1747. const rtnum = feature.attributes.RTNO;
  1748. if (fc === 2 && ['10', '24', '37', '78', '99', '138', '403'].includes(rtnum)) fc = 1; // isFW
  1749. else if ((fc > 3 && type === 'US') || rtnum === '1') fc = 3; // isUS
  1750. else if (fc > 4 && rtnum.trim() !== '') fc = 4; // isState
  1751. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1752. }
  1753. },
  1754. SC: {
  1755. baseUrl: 'https://services1.arcgis.com/VaY7cY9pvUYUP1Lf/arcgis/rest/services/Functional_Class/FeatureServer/',
  1756. defaultColors: {
  1757. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1758. },
  1759. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1760. fcMapLayers: [
  1761. {
  1762. layerID: 0,
  1763. fcPropName: 'FC_GIS',
  1764. idPropName: 'FID',
  1765. outFields: ['FID', 'FC_GIS', 'ROUTE_LRS'],
  1766. maxRecordCount: 1000,
  1767. supportsPagination: false,
  1768. roadTypeMap: {
  1769. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  1770. }
  1771. }
  1772. ],
  1773. isPermitted() { return _r >= 4; },
  1774. information: { Source: 'SCDOT', Permission: 'Visible to R4+' },
  1775. getWhereClause() {
  1776. return null;
  1777. },
  1778. getFeatureRoadType(feature, layer) {
  1779. const roadID = feature.attributes.ROUTE_LRS;
  1780. const roadType = parseInt(roadID.slice(3, 4), 10);
  1781. const isFw = roadType === 1;
  1782. const isUS = roadType === 2;
  1783. const isState = roadType === 4;
  1784. const isBiz = parseInt(roadID.slice(-2, -1), 10) === 7;
  1785. let fc = 7;
  1786. switch (feature.attributes[layer.fcPropName]) {
  1787. case 'INT': fc = 1; break;
  1788. case 'EXP': fc = 2; break;
  1789. case 'PRA': fc = 3; break;
  1790. case 'MIA': fc = 4; break;
  1791. case 'MAC':
  1792. case 'MIC': fc = 5; break;
  1793. default: throw new Error(`FC Layer: unexpected fc value: ${fc}`);
  1794. }
  1795. if (fc > 1 && isFw) fc = 1;
  1796. else if (fc > 3 && isUS) fc = isBiz ? 4 : 3;
  1797. else if (fc > 4 && isState) fc = (isBiz ? 5 : 4);
  1798. if (layer.getFeatureRoadType) {
  1799. return layer.getFeatureRoadType(feature);
  1800. }
  1801. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1802. }
  1803. },
  1804. SD: {
  1805. baseUrl: 'https://arcgis.sd.gov/arcgis/rest/services/DOT/LocalRoads/MapServer/',
  1806. defaultColors: {
  1807. Fw: '#ff00c5', Ew: '#149ece', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee', PSGr: '#cc6533', StGr: '#e99cb6'
  1808. },
  1809. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1810. fcMapLayers: [{
  1811. layerID: 1,
  1812. fcPropName: 'FUNC_CLASS',
  1813. idPropName: 'OBJECTID',
  1814. maxRecordCount: 1000,
  1815. supportsPagination: false,
  1816. outFields: ['OBJECTID', 'FUNC_CLASS', 'SURFACE_TYPE', 'ROADNAME'],
  1817. roadTypeMap: {
  1818. Fw: [1, 11], Ew: [2, 12], MH: [4, 14], mH: [6, 16], PS: [7, 8, 17], St: [9, 19]
  1819. }
  1820. }],
  1821. information: { Source: 'SDDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Additional colors denote unpaved PS and LS segements.' },
  1822. getWhereClause(context) {
  1823. if (context.mapContext.zoom < 16) {
  1824. return `${context.layer.fcPropName} NOT IN (9,19)`;
  1825. }
  1826. return null;
  1827. },
  1828. getFeatureRoadType(feature, layer) {
  1829. const attr = feature.attributes;
  1830. let fc = parseInt(attr[layer.fcPropName], 10) % 10;
  1831. const isFw = attr.ACCESS_CONTROL === 1;
  1832. const isUS = /^US HWY /i.test(attr.ROADNAME);
  1833. const isState = /^SD HWY /i.test(attr.ROADNAME);
  1834. const isBiz = /^(US|SD) HWY .* (E|W)?(B|L)$/i.test(attr.ROADNAME);
  1835. const isPaved = parseInt(attr.SURFACE_TYPE, 10) > 5;
  1836. if (isFw) fc = 1;
  1837. else if (fc > 4 && isUS) fc = (isBiz ? 6 : 4);
  1838. else if (fc > 6 && isState) fc = (isBiz ? 7 : 6);
  1839. if (fc > 6 && !isPaved) {
  1840. return fc < 9 ? 'PSGr' : 'StGr';
  1841. }
  1842. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1843. }
  1844. },
  1845. TN: {
  1846. baseUrl: 'https://',
  1847. defaultColors: {
  1848. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', PS2: '#cfae0e', St: '#eeeeee'
  1849. },
  1850. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  1851. fcMapLayers: [
  1852. {
  1853. layerPath: 'comgis4.memphistn.gov/arcgis/rest/services/AGO_DPD/Memphis_MPO/FeatureServer/',
  1854. maxRecordCount: 1000,
  1855. supportsPagination: false,
  1856. layerID: 4,
  1857. fcPropName: 'Functional_Classification',
  1858. idPropName: 'OBJECTID',
  1859. outFields: ['OBJECTID', 'Functional_Classification'],
  1860. getWhereClause(context) {
  1861. if (context.mapContext.zoom < 16) {
  1862. return `${context.layer.fcPropName} NOT LIKE '%Local'`;
  1863. }
  1864. return null;
  1865. },
  1866. roadTypeMap: {
  1867. Fw: ['(Urban) Interstate', '(Rural) Interstate'],
  1868. Ew: ['(Urban) Other Freeway or Expressway', '(Rural) Other Freeway or Expressway'],
  1869. MH: ['(Urban) Other Principal Arterial', '(Rural) Other Principal Arterial'],
  1870. mH: ['(Urban) Minor Arterial', '(Rural) Minor Arterial'],
  1871. PS: ['(Urban) Major Collector', '(Rural) Major Collector'],
  1872. PS2: ['(Urban) Minor Collector', '(Rural) Minor Collector'],
  1873. St: ['(Urban) Local', '(Rural) Local']
  1874. }
  1875. },
  1876. {
  1877. layerPath: 'services3.arcgis.com/pXGyp7DHTIE4RXOJ/ArcGIS/rest/services/Functional_Classification/FeatureServer/',
  1878. maxRecordCount: 1000,
  1879. supportsPagination: false,
  1880. layerID: 0,
  1881. fcPropName: 'FC_MPO',
  1882. idPropName: 'FID',
  1883. outFields: ['FID', 'FC_MPO'],
  1884. getWhereClause(context) {
  1885. if (context.mapContext.zoom < 16) {
  1886. return `${context.layer.fcPropName} NOT IN (0,7,9,19)`;
  1887. }
  1888. return `${context.layer.fcPropName} <> 0`;
  1889. },
  1890. roadTypeMap: {
  1891. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  1892. }
  1893. }
  1894. ],
  1895. information: {
  1896. Source: 'Memphis, Nashville Area MPO',
  1897. Permission: 'Visible to R4+ or R3-AM',
  1898. Description: 'Raw unmodified FC data for the Memphis and Nashville regions only.'
  1899. },
  1900. getWhereClause(context) {
  1901. if (context.layer.getWhereClause) {
  1902. return context.layer.getWhereClause(context);
  1903. }
  1904. return null;
  1905. },
  1906. getFeatureRoadType(feature, layer) {
  1907. if (layer.getFeatureRoadType) {
  1908. return layer.getFeatureRoadType(feature);
  1909. }
  1910. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  1911. }
  1912. },
  1913. TX: {
  1914. baseUrl: 'https://services.arcgis.com/KTcxiTD9dsQw4r7Z/ArcGIS/rest/services/TxDOT_Functional_Classification/FeatureServer/',
  1915. defaultColors: {
  1916. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1917. },
  1918. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  1919. fcMapLayers: [
  1920. {
  1921. layerID: 0,
  1922. fcPropName: 'F_SYSTEM',
  1923. idPropName: 'OBJECTID',
  1924. outFields: ['OBJECTID', 'F_SYSTEM', 'RTE_PRFX'],
  1925. maxRecordCount: 1000,
  1926. supportsPagination: false,
  1927. roadTypeMap: {
  1928. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  1929. }
  1930. }
  1931. ],
  1932. isPermitted() { return _r >= 2; },
  1933. information: { Source: 'TxDOT', Permission: 'Visible to R2+' },
  1934. getWhereClause(context) {
  1935. let where = ' F_SYSTEM IS NOT NULL AND RTE_PRFX IS NOT NULL';
  1936. if (context.mapContext.zoom < 16) {
  1937. where += ` AND ${context.layer.fcPropName} <> 7`;
  1938. }
  1939. return where;
  1940. },
  1941. getFeatureRoadType(feature, layer) {
  1942. // On-System:
  1943. // IH=Interstate BF=Business FM
  1944. // US=US Highway FM=Farm to Mkt
  1945. // UA=US Alt. RM=Ranch to Mkt
  1946. // UP=US Spur RR=Ranch Road
  1947. // SH=State Highway PR=Park Road
  1948. // SA=State Alt. RE=Rec Road
  1949. // SL=State Loop RP=Rec Rd Spur
  1950. // SS=State Spur FS=FM Spur
  1951. // BI=Business IH RS=RM Spur
  1952. // BU=Business US RU=RR Spur
  1953. // BS=Business State PA=Principal Arterial
  1954. // Off-System:
  1955. // TL=Off-System Tollroad CR=County Road
  1956. // FC=Func. Classified St. LS=Local Street
  1957. if (layer.getFeatureRoadType) {
  1958. return layer.getFeatureRoadType(feature);
  1959. }
  1960. let fc = feature.attributes[layer.fcPropName];
  1961. const type = feature.attributes.RTE_PRFX.substring(0, 2).toUpperCase();
  1962. if (type === 'IH' && fc > 1) {
  1963. fc = 1;
  1964. } else if ((type === 'US' || type === 'BI' || type === 'UA') && fc > 3) {
  1965. fc = 3;
  1966. } else if ((type === 'UP' || type === 'BU' || type === 'SH' || type === 'SA') && fc > 4) {
  1967. fc = 4;
  1968. } else if ((type === 'SL' || type === 'SS' || type === 'BS') && fc > 6) {
  1969. fc = 6;
  1970. }
  1971. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1972. }
  1973. },
  1974. UT: {
  1975. baseUrl: 'https://maps.udot.utah.gov/randh/rest/services/ALRS_DT/Functional_Class/MapServer/',
  1976. defaultColors: {
  1977. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  1978. },
  1979. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1980. fcMapLayers: [
  1981. {
  1982. layerID: 0,
  1983. fcPropName: 'FUNCTIONAL_CLASS',
  1984. idPropName: 'OBJECTID',
  1985. outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'ROUTE_ID'],
  1986. roadTypeMap: {
  1987. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  1988. },
  1989. maxRecordCount: 1000,
  1990. supportsPagination: false
  1991. }
  1992. ],
  1993. information: { Source: 'UDOT', Permission: 'Visible to R4+ or R3-AM' },
  1994. getWhereClause(context) {
  1995. return `${context.layer.fcPropName} NOT LIKE 'Proposed%'`;
  1996. },
  1997. getFeatureRoadType(feature, layer) {
  1998. const attr = feature.attributes;
  1999. let fc = attr[layer.fcPropName];
  2000. const routeID = attr.ROUTE_ID;
  2001. const roadNum = parseInt(routeID.substring(0, 4), 10);
  2002. switch (fc) {
  2003. case 'Interstate': fc = 1; break;
  2004. case 'Other Freeways and Expressways': fc = 2; break;
  2005. case 'Other Principal Arterial': fc = 3; break;
  2006. case 'Minor Arterial': fc = 4; break;
  2007. case 'Major Collector': fc = 5; break;
  2008. case 'Minor Collector': fc = 6; break;
  2009. default: fc = 7;
  2010. }
  2011. const re = /^(6|40|50|89|91|163|189|191|491)$/;
  2012. if (re.test(roadNum) && fc > 3) {
  2013. // US highway
  2014. fc = 3;
  2015. } else if (roadNum <= 491 && fc > 4) {
  2016. // State highway
  2017. fc = 4;
  2018. }
  2019. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  2020. }
  2021. },
  2022. VT: {
  2023. baseUrl: 'https://maps.vtrans.vermont.gov/arcgis/rest/services/Master/General/FeatureServer/',
  2024. defaultColors: {
  2025. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  2026. },
  2027. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2028. fcMapLayers: [
  2029. {
  2030. layerID: 39,
  2031. fcPropName: 'FUNCL',
  2032. idPropName: 'OBJECTID',
  2033. outFields: ['OBJECTID', 'FUNCL', 'HWYSIGN'],
  2034. roadTypeMap: {
  2035. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  2036. },
  2037. maxRecordCount: 1000,
  2038. supportsPagination: false
  2039. }
  2040. ],
  2041. information: { Source: 'VTrans', Permission: 'Visible to R2+' },
  2042. isPermitted() { return _r >= 2; },
  2043. getWhereClause(context) {
  2044. if (context.mapContext.zoom < 16) {
  2045. return `${context.layer.fcPropName}<>7 AND ${context.layer.fcPropName}<>0`;
  2046. }
  2047. return null;
  2048. },
  2049. getFeatureRoadType(feature, layer) {
  2050. const roadID = feature.attributes.HWYSIGN;
  2051. let fc = feature.attributes[layer.fcPropName];
  2052. if (!(fc > 0)) { fc = 7; }
  2053. const isUS = /^U/.test(roadID);
  2054. const isState = /^V/.test(roadID);
  2055. const isUSBiz = /^B/.test(roadID);
  2056. if (fc > 3 && isUS) fc = 3;
  2057. else if (fc > 4 && (isUSBiz || isState)) fc = 4;
  2058. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  2059. }
  2060. },
  2061. VA: {
  2062. baseUrl: 'https://services.arcgis.com/p5v98VHDX9Atv3l7/arcgis/rest/services/FC_2014_FHWA_Submittal1/FeatureServer/',
  2063. defaultColors: {
  2064. Fw: '#ff00c5', Ew: '#ff00c5', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  2065. },
  2066. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2067. fcMapLayers: [
  2068. {
  2069. layerID: 0,
  2070. fcPropName: 'STATE_FUNCT_CLASS_ID',
  2071. idPropName: 'OBJECTID',
  2072. outFields: ['OBJECTID', 'STATE_FUNCT_CLASS_ID', 'RTE_NM'],
  2073. maxRecordCount: 2000,
  2074. supportsPagination: true,
  2075. roadTypeMap: {
  2076. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  2077. }
  2078. }, {
  2079. layerID: 1,
  2080. fcPropName: 'STATE_FUNCT_CLASS_ID',
  2081. idPropName: 'OBJECTID',
  2082. outFields: ['OBJECTID', 'STATE_FUNCT_CLASS_ID', 'RTE_NM', 'ROUTE_NO'],
  2083. maxRecordCount: 2000,
  2084. supportsPagination: true,
  2085. roadTypeMap: {
  2086. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  2087. }
  2088. }, {
  2089. layerID: 3,
  2090. fcPropName: 'TMPD_FC',
  2091. idPropName: 'OBJECTID',
  2092. outFields: ['OBJECTID', 'TMPD_FC', 'RTE_NM'],
  2093. maxRecordCount: 2000,
  2094. supportsPagination: true,
  2095. roadTypeMap: {
  2096. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  2097. }
  2098. }
  2099. ],
  2100. information: { Source: 'VDOT', Permission: 'Visible to R4+ or R3-AM' },
  2101. srExceptions: [217, 302, 303, 305, 308, 310, 313, 314, 315, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328,
  2102. 329, 330, 331, 332, 333, 334, 335, 336, 339, 341, 342, 343, 344, 345, 346, 347, 348, 350, 353, 355, 357, 358, 361,
  2103. 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 382, 383, 384, 385, 386,
  2104. 387, 388, 389, 390, 391, 392, 393, 394, 396, 397, 398, 399, 785, 895],
  2105. getWhereClause(context) {
  2106. if (context.mapContext.zoom < 16) {
  2107. return `${context.layer.fcPropName}<>7`;
  2108. }
  2109. // NOTE: As of 9/14/2016 there does not appear to be any US/SR/VA labeled routes with FC = 7.
  2110. return null;
  2111. },
  2112. getFeatureRoadType(feature, layer) {
  2113. if (layer.getFeatureRoadType) {
  2114. return layer.getFeatureRoadType(feature);
  2115. }
  2116. let fc = parseInt(feature.attributes[layer.fcPropName], 10);
  2117. const rtName = feature.attributes.RTE_NM;
  2118. const match = /^R-VA\s*(US|VA|SR)(\d{5})..(BUS)?/.exec(rtName);
  2119. const isBusiness = (match && (match !== null) && (match[3] === 'BUS'));
  2120. const isState = (match && (match !== null) && (match[1] === 'VA' || match[1] === 'SR'));
  2121. let rtNumText;
  2122. if (layer.layerID === 1) {
  2123. rtNumText = feature.attributes.ROUTE_NO;
  2124. } else if (match) {
  2125. // eslint-disable-next-line prefer-destructuring
  2126. rtNumText = match[2];
  2127. } else {
  2128. rtNumText = '99999';
  2129. }
  2130. const rtNum = parseInt(rtNumText, 10);
  2131. const rtPrefix = match && match[1];
  2132. if (fc > 3 && rtPrefix === 'US') {
  2133. fc = isBusiness ? 4 : 3;
  2134. } else if (isState && fc > 4 && this.srExceptions.indexOf(rtNum) === -1 && rtNum < 600) {
  2135. fc = isBusiness ? 5 : 4;
  2136. }
  2137. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  2138. }
  2139. },
  2140. WA: {
  2141. baseUrl: 'https://data.wsdot.wa.gov/arcgis/rest/services/FunctionalClass/WSDOTFunctionalClassMap/MapServer/',
  2142. defaultColors: {
  2143. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  2144. },
  2145. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2146. fcMapLayers: [
  2147. {
  2148. layerID: 2,
  2149. fcPropName: 'FederalFunctionalClassCode',
  2150. idPropName: 'OBJECTID',
  2151. outFields: ['OBJECTID', 'FederalFunctionalClassCode'],
  2152. roadTypeMap: {
  2153. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  2154. },
  2155. maxRecordCount: 1000,
  2156. supportsPagination: false
  2157. }, {
  2158. layerID: 1,
  2159. fcPropName: 'FederalFunctionalClassCode',
  2160. idPropName: 'OBJECTID',
  2161. outFields: ['OBJECTID', 'FederalFunctionalClassCode'],
  2162. roadTypeMap: {
  2163. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  2164. },
  2165. maxRecordCount: 1000,
  2166. supportsPagination: false
  2167. }, {
  2168. layerID: 4,
  2169. fcPropName: 'FederalFunctionalClassCode',
  2170. idPropName: 'OBJECTID',
  2171. outFields: ['OBJECTID', 'FederalFunctionalClassCode'],
  2172. roadTypeMap: {
  2173. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  2174. },
  2175. maxRecordCount: 1000,
  2176. supportsPagination: false
  2177. }
  2178. ],
  2179. information: { Source: 'WSDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  2180. getWhereClause(context) {
  2181. if (context.mapContext.zoom < 16) {
  2182. return `${context.layer.fcPropName} <> 7`;
  2183. }
  2184. return null;
  2185. },
  2186. getFeatureRoadType(feature, layer) {
  2187. if (layer.getFeatureRoadType) {
  2188. return layer.getFeatureRoadType(feature);
  2189. }
  2190. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  2191. }
  2192. },
  2193. WV: {
  2194. baseUrl: 'https://gis.transportation.wv.gov/arcgis/rest/services/Routes/MapServer/',
  2195. defaultColors: {
  2196. Fw: '#ff00c5', Ew: '#ff00c5', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  2197. },
  2198. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2199. fcMapLayers: [
  2200. {
  2201. layerID: 1,
  2202. fcPropName: 'F_System',
  2203. idPropName: 'OBJECTID',
  2204. outFields: ['OBJECTID', 'F_System', 'RouteID'],
  2205. maxRecordCount: 1000,
  2206. supportsPagination: true,
  2207. roadTypeMap: {
  2208. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  2209. }
  2210. }
  2211. ],
  2212. information: { Source: 'WV DOT' },
  2213. isPermitted() { return true; },
  2214. getWhereClause(context) {
  2215. if (context.mapContext.zoom < 16) {
  2216. return `${context.layer.fcPropName} NOT IN (9,19)`;
  2217. }
  2218. return null;
  2219. },
  2220. getFeatureRoadType(feature, layer) {
  2221. if (layer.getFeatureRoadType) {
  2222. return layer.getFeatureRoadType(feature);
  2223. }
  2224. const fcCode = feature.attributes[layer.fcPropName];
  2225. let fc = fcCode;
  2226. if (fcCode === 11) fc = 1;
  2227. else if (fcCode === 4 || fcCode === 12) fc = 2;
  2228. else if (fcCode === 2 || fcCode === 14) fc = 3;
  2229. else if (fcCode === 6 || fcCode === 16) fc = 4;
  2230. else if (fcCode === 7 || fcCode === 17 || fcCode === 8 || fcCode === 18) fc = 5;
  2231. else fc = 7;
  2232. const id = feature.attributes.RouteID;
  2233. const prefix = id.substr(2, 1);
  2234. const isInterstate = prefix === '1';
  2235. const isUS = prefix === '2';
  2236. const isState = prefix === '3';
  2237. if (fc > 1 && isInterstate) fc = 1;
  2238. else if (fc > 3 && isUS) fc = 3;
  2239. else if (fc > 4 && isState) fc = 4;
  2240. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  2241. }
  2242. },
  2243. WY: {
  2244. baseUrl: 'https://gisservices.wyoroad.info/arcgis/rest/services/ITSM/LAYERS/MapServer/',
  2245. defaultColors: {
  2246. Fw: '#ff00c5', Ew: '#4f33df', MH: '#149ece', mH: '#4ce600', PS: '#cfae0e', St: '#eeeeee'
  2247. },
  2248. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2249. fcMapLayers: [
  2250. {
  2251. layerID: 20,
  2252. fcPropName: 'CLASSIFICATION',
  2253. idPropName: 'OBJECTID',
  2254. outFields: ['OBJECTID', 'CLASSIFICATION', 'COMMON_ROUTE_NAME'],
  2255. roadTypeMap: {
  2256. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  2257. },
  2258. maxRecordCount: 1000,
  2259. supportsPagination: false
  2260. }, {
  2261. layerID: 21,
  2262. fcPropName: 'CLASSIFICATION',
  2263. idPropName: 'OBJECTID',
  2264. outFields: ['OBJECTID', 'CLASSIFICATION', 'COMMON_ROUTE_NAME'],
  2265. roadTypeMap: {
  2266. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  2267. },
  2268. maxRecordCount: 1000,
  2269. supportsPagination: false
  2270. }, {
  2271. layerID: 22,
  2272. fcPropName: 'CLASSIFICATION',
  2273. idPropName: 'OBJECTID',
  2274. outFields: ['OBJECTID', 'CLASSIFICATION', 'COMMON_ROUTE_NAME'],
  2275. roadTypeMap: {
  2276. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  2277. },
  2278. maxRecordCount: 1000,
  2279. supportsPagination: false
  2280. }, {
  2281. layerID: 23,
  2282. fcPropName: 'CLASSIFICATION',
  2283. idPropName: 'OBJECTID',
  2284. outFields: ['OBJECTID', 'CLASSIFICATION', 'COMMON_ROUTE_NAME'],
  2285. roadTypeMap: {
  2286. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  2287. },
  2288. maxRecordCount: 1000,
  2289. supportsPagination: false
  2290. }, {
  2291. layerID: 24,
  2292. fcPropName: 'CLASSIFICATION',
  2293. idPropName: 'OBJECTID',
  2294. outFields: ['OBJECTID', 'CLASSIFICATION', 'COMMON_ROUTE_NAME'],
  2295. roadTypeMap: {
  2296. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  2297. },
  2298. maxRecordCount: 1000,
  2299. supportsPagination: false
  2300. }, {
  2301. layerID: 25,
  2302. fcPropName: 'CLASSIFICATION',
  2303. idPropName: 'OBJECTID',
  2304. outFields: ['OBJECTID', 'CLASSIFICATION', 'COMMON_ROUTE_NAME'],
  2305. roadTypeMap: {
  2306. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  2307. },
  2308. maxRecordCount: 1000,
  2309. supportsPagination: false
  2310. }, {
  2311. layerID: 26,
  2312. fcPropName: 'CLASSIFICATION',
  2313. idPropName: 'OBJECTID',
  2314. outFields: ['OBJECTID', 'CLASSIFICATION', 'COMMON_ROUTE_NAME'],
  2315. roadTypeMap: {
  2316. Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7]
  2317. },
  2318. maxRecordCount: 1000,
  2319. supportsPagination: false
  2320. }
  2321. ],
  2322. information: { Source: 'WYDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Minimum suggested FC.' },
  2323. getWhereClause(context) {
  2324. if (context.mapContext.zoom < 16) {
  2325. return `${context.layer.fcPropName} <> 'Local'`;
  2326. }
  2327. return null;
  2328. },
  2329. getFeatureRoadType(feature, layer) {
  2330. const attr = feature.attributes;
  2331. let fc = attr[layer.fcPropName];
  2332. const route = attr.COMMON_ROUTE_NAME;
  2333. switch (fc) {
  2334. case 'Interstate': fc = 1; break;
  2335. case 'Expressway': fc = 2; break;
  2336. case 'Principal Arterial': fc = 3; break;
  2337. case 'Minor Arterial': fc = 4; break;
  2338. case 'Major Collector': fc = 5; break;
  2339. case 'Minor Collector': fc = 6; break;
  2340. default: fc = 7;
  2341. }
  2342. const isIntBiz = /I (25|80) BUS/.test(route);
  2343. const isUS = /US \d+/.test(route);
  2344. const isUSBiz = /US \d+ BUS/.test(route);
  2345. const isState = /WY \d+/.test(route);
  2346. const isStateBiz = /WY \d+ BUS/.test(route);
  2347. if (fc > 3 && (isUS || isIntBiz)) fc = isUSBiz ? 4 : 3;
  2348. else if (fc > 4 && isState) fc = isStateBiz ? 5 : 4;
  2349. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  2350. }
  2351. }
  2352. };
  2353.  
  2354. function log(message) {
  2355. console.log('FC Layer: ', message);
  2356. }
  2357. function debugLog(message) {
  2358. console.debug('FC Layer: ', message);
  2359. }
  2360. function errorLog(message) {
  2361. console.error('FC Layer: ', message);
  2362. }
  2363.  
  2364. function loadSettingsFromStorage() {
  2365. const loadedSettings = $.parseJSON(localStorage.getItem(SETTINGS_STORE_NAME));
  2366. const defaultSettings = {
  2367. lastVersion: null,
  2368. layerVisible: true,
  2369. activeStateAbbr: 'ALL',
  2370. hideStreet: false
  2371. };
  2372. _settings = loadedSettings || defaultSettings;
  2373. Object.keys(defaultSettings).filter(prop => !_settings.hasOwnProperty(prop)).forEach(prop => {
  2374. _settings[prop] = defaultSettings[prop];
  2375. });
  2376. }
  2377.  
  2378. function saveSettingsToStorage() {
  2379. if (localStorage) {
  2380. _settings.lastVersion = SCRIPT_VERSION;
  2381. _settings.layerVisible = _mapLayer.visibility;
  2382. localStorage.setItem(SETTINGS_STORE_NAME, JSON.stringify(_settings));
  2383. // debugLog('Settings saved');
  2384. }
  2385. }
  2386.  
  2387. function getLineWidth() {
  2388. return 12 * (1.15 ** (W.map.getZoom() - 13));
  2389. }
  2390.  
  2391. function sortArray(array) {
  2392. array.sort((a, b) => { if (a < b) return -1; if (a > b) return 1; return 0; });
  2393. }
  2394.  
  2395. function getVisibleStateAbbrs() {
  2396. const visibleStates = [];
  2397. W.model.states.getObjectArray().forEach(state => {
  2398. const stateAbbr = STATES_HASH[state.name];
  2399. const { activeStateAbbr } = _settings;
  2400. if (STATE_SETTINGS[stateAbbr] && STATE_SETTINGS.global.isPermitted(stateAbbr) && (!activeStateAbbr || activeStateAbbr === 'ALL' || activeStateAbbr === stateAbbr)) {
  2401. visibleStates.push(stateAbbr);
  2402. }
  2403. });
  2404. return visibleStates;
  2405. }
  2406.  
  2407. function getAsync(url, context) {
  2408. return new Promise((resolve, reject) => {
  2409. GM_xmlhttpRequest({
  2410. context,
  2411. method: 'GET',
  2412. url,
  2413. onload(res) {
  2414. if (res.status.toString() === '200') {
  2415. resolve({ responseText: res.responseText, context });
  2416. } else {
  2417. reject(new Error({ responseText: res.responseText, context }));
  2418. }
  2419. },
  2420. onerror() {
  2421. reject(Error('Network Error'));
  2422. }
  2423. });
  2424. });
  2425. }
  2426.  
  2427. function getUrl(context, queryType, queryParams) {
  2428. const { extent } = context.mapContext;
  2429. const { zoom } = context.mapContext;
  2430. const { layer } = context;
  2431. const { state } = context;
  2432.  
  2433. const whereParts = [];
  2434. const geometry = {
  2435. xmin: extent.left, ymin: extent.bottom, xmax: extent.right, ymax: extent.top, spatialReference: { wkid: 102100, latestWkid: 3857 }
  2436. };
  2437. const geometryStr = JSON.stringify(geometry);
  2438. const stateWhereClause = state.getWhereClause(context);
  2439. const layerPath = layer.layerPath || '';
  2440. let url = `${state.baseUrl + layerPath + layer.layerID}/query?geometry=${encodeURIComponent(geometryStr)}`;
  2441.  
  2442. if (queryType === 'countOnly') {
  2443. url += '&returnCountOnly=true';
  2444. } else if (queryType === 'idsOnly') {
  2445. url += '&returnIdsOnly=true';
  2446. } else if (queryType === 'paged') {
  2447. // TODO
  2448. } else {
  2449. url += `&returnGeometry=true&maxAllowableOffset=${state.zoomSettings.maxOffset[zoom - 12]}`;
  2450. url += `&outFields=${encodeURIComponent(layer.outFields.join(','))}`;
  2451. if (queryType === 'idRange') {
  2452. whereParts.push(`(${queryParams.idFieldName}>=${queryParams.range[0]} AND ${queryParams.idFieldName}<=${queryParams.range[1]})`);
  2453. }
  2454. }
  2455. if (stateWhereClause) whereParts.push(stateWhereClause);
  2456. if (whereParts.length > 0) url += `&where=${encodeURIComponent(whereParts.join(' AND '))}`;
  2457. url += '&spatialRel=esriSpatialRelIntersects&geometryType=esriGeometryEnvelope&inSR=102100&outSR=3857&f=json';
  2458. return url;
  2459. }
  2460.  
  2461. function convertFcToRoadTypeVectors(feature, context) {
  2462. const { state, stateAbbr, layer } = context;
  2463. const roadType = state.getFeatureRoadType(feature, layer);
  2464. // debugLog(feature);
  2465. const zIndex = STATE_SETTINGS.global.roadTypes.indexOf(roadType) * 100;
  2466. const attr = {
  2467. state: stateAbbr,
  2468. layerID: layer.layerID,
  2469. roadType,
  2470. dotAttributes: $.extend({}, feature.attributes),
  2471. color: state.defaultColors[roadType],
  2472. strokeWidth: getLineWidth,
  2473. zIndex
  2474. };
  2475. const vectors = feature.geometry.paths.map(path => {
  2476. const pointList = path.map(pt => new OpenLayers.Geometry.Point(pt[0], pt[1]));
  2477. return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString(pointList), attr);
  2478. });
  2479.  
  2480. return vectors;
  2481. }
  2482.  
  2483. function fetchLayerFC(context) {
  2484. const url = getUrl(context, 'idsOnly');
  2485. debugLog(url);
  2486. if (!context.parentContext.cancel) {
  2487. return getAsync(url, context).bind(context).then(res => {
  2488. const ids = $.parseJSON(res.responseText);
  2489. if (!ids.objectIds) ids.objectIds = [];
  2490. sortArray(ids.objectIds);
  2491. // debugLog(ids);
  2492. return ids;
  2493. }).then(res => {
  2494. const idRanges = [];
  2495. if (res.objectIds) {
  2496. const len = res.objectIds ? res.objectIds.length : 0;
  2497. let currentIndex = 0;
  2498. const offset = Math.min(context.layer.maxRecordCount, 1000);
  2499. while (currentIndex < len) {
  2500. let nextIndex = currentIndex + offset;
  2501. if (nextIndex >= len) nextIndex = len - 1;
  2502. idRanges.push({ range: [res.objectIds[currentIndex], res.objectIds[nextIndex]], idFieldName: res.objectIdFieldName });
  2503. currentIndex = nextIndex + 1;
  2504. }
  2505. // debugLog(context.layer.layerID);
  2506. // debugLog(idRanges);
  2507. }
  2508. return idRanges;
  2509. }).map(idRange => {
  2510. if (!context.parentContext.cancel) {
  2511. const newUrl = getUrl(context, 'idRange', idRange);
  2512. debugLog(url);
  2513. return getAsync(newUrl, context).then(res => {
  2514. if (!context.parentContext.cancel) {
  2515. let { features } = $.parseJSON(res.responseText);
  2516. context.parentContext.callCount++;
  2517. // debugLog('Feature Count=' + (features ? features.length : 0));
  2518. features = features || [];
  2519. return features.map(feature => convertFcToRoadTypeVectors(feature, context))
  2520. .filter(vector => !(vector[0].attributes.roadType === 'St' && _settings.hideStreet));
  2521. }
  2522. return null;
  2523. });
  2524. }
  2525. // debugLog('Async call cancelled');
  2526. return null;
  2527. });
  2528. }
  2529. return null;
  2530. }
  2531.  
  2532. function fetchStateFC(context) {
  2533. const state = STATE_SETTINGS[context.stateAbbr];
  2534. const contexts = state.fcMapLayers.map(layer => ({
  2535. parentContext: context.parentContext, layer, state, stateAbbr: context.stateAbbr, mapContext: context.mapContext
  2536. }));
  2537.  
  2538. return Promise.map(contexts, ctx => fetchLayerFC(ctx));
  2539. }
  2540.  
  2541. let _lastPromise = null;
  2542. let _lastContext = null;
  2543. let _fcCallCount = 0;
  2544. function fetchAllFC() {
  2545. if (!_mapLayer.visibility) return;
  2546.  
  2547. if (_lastPromise) { _lastPromise.cancel(); }
  2548. $('#fc-loading-indicator').text('Loading FC...');
  2549.  
  2550. const mapContext = { zoom: W.map.getZoom(), extent: W.map.getExtent() };
  2551. if (mapContext.zoom > MIN_ZOOM_LEVEL) {
  2552. const parentContext = { callCount: 0, startTime: Date.now() };
  2553.  
  2554. if (_lastContext) _lastContext.cancel = true;
  2555. _lastContext = parentContext;
  2556. const contexts = getVisibleStateAbbrs().map(stateAbbr => ({ parentContext, stateAbbr, mapContext }));
  2557. const map = Promise.map(contexts, ctx => fetchStateFC(ctx)).then(statesVectorArrays => {
  2558. if (!parentContext.cancel) {
  2559. _mapLayer.removeAllFeatures();
  2560. statesVectorArrays.forEach(vectorsArray => {
  2561. vectorsArray.forEach(vectors => {
  2562. vectors.forEach(vector => {
  2563. vector.forEach(vectorFeature => {
  2564. _mapLayer.addFeatures(vectorFeature);
  2565. });
  2566. });
  2567. });
  2568. });
  2569. }
  2570. return statesVectorArrays;
  2571. }).catch(e => {
  2572. $('#fc-loading-indicator').text('FC Error! (check console for details)');
  2573. errorLog(e);
  2574. }).finally(() => {
  2575. _fcCallCount -= 1;
  2576. if (_fcCallCount === 0) {
  2577. $('#fc-loading-indicator').text('');
  2578. }
  2579. });
  2580.  
  2581. _fcCallCount += 1;
  2582. _lastPromise = map;
  2583. } else {
  2584. // if zoomed out too far, clear the layer
  2585. _mapLayer.removeAllFeatures();
  2586. }
  2587. }
  2588.  
  2589. function onLayerCheckboxChanged(checked) {
  2590. setEnabled(checked);
  2591. }
  2592.  
  2593. function onLayerVisibilityChanged() {
  2594. setEnabled(_mapLayer.visibility);
  2595. // _settings.layerVisible = _mapLayer.visibility;
  2596. // saveSettingsToStorage();
  2597. // if (_mapLayer.visibility) {
  2598. // fetchAllFC();
  2599. // }
  2600. }
  2601.  
  2602. function checkLayerZIndex() {
  2603. if (_mapLayer.getZIndex() !== MAP_LAYER_Z_INDEX) {
  2604. // ("ADJUSTED FC LAYER Z-INDEX " + _mapLayerZIndex + ', ' + _mapLayer.getZIndex());
  2605. _mapLayer.setZIndex(MAP_LAYER_Z_INDEX);
  2606. }
  2607. }
  2608.  
  2609. function initLayer() {
  2610. const defaultStyle = new OpenLayers.Style({
  2611. strokeColor: '${color}', // '#00aaff',
  2612. strokeDashstyle: 'solid',
  2613. strokeOpacity: 1.0,
  2614. strokeWidth: '${strokeWidth}',
  2615. graphicZIndex: '${zIndex}'
  2616. });
  2617.  
  2618. const selectStyle = new OpenLayers.Style({
  2619. // strokeOpacity: 1.0,
  2620. strokeColor: '#000000'
  2621. });
  2622.  
  2623. _mapLayer = new OpenLayers.Layer.Vector('FC Layer', {
  2624. uniqueName: '__FCLayer',
  2625. displayInLayerSwitcher: false,
  2626. rendererOptions: { zIndexing: true },
  2627. styleMap: new OpenLayers.StyleMap({
  2628. default: defaultStyle,
  2629. select: selectStyle
  2630. })
  2631. });
  2632.  
  2633. _mapLayer.setOpacity(0.5);
  2634.  
  2635. I18n.translations[I18n.locale].layers.name.__FCLayer = 'FC Layer';
  2636.  
  2637. _mapLayer.displayInLayerSwitcher = true;
  2638. _mapLayer.events.register('visibilitychanged', null, onLayerVisibilityChanged);
  2639. _mapLayer.setVisibility(_settings.layerVisible);
  2640.  
  2641. W.map.addLayer(_mapLayer);
  2642. _mapLayer.setZIndex(MAP_LAYER_Z_INDEX);
  2643. WazeWrap.Interface.AddLayerCheckbox('Display', 'FC Layer', _settings.layerVisible, onLayerCheckboxChanged);
  2644. // Hack to fix layer zIndex. Some other code is changing it sometimes but I have not been able to figure out why.
  2645. // 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. (?)
  2646.  
  2647. setInterval(checkLayerZIndex, 200);
  2648.  
  2649. W.map.events.register('moveend', W.map, () => {
  2650. fetchAllFC();
  2651. return true;
  2652. }, true);
  2653. }
  2654.  
  2655. function onHideStreetsClicked() {
  2656. _settings.hideStreet = $(this).is(':checked');
  2657. saveSettingsToStorage();
  2658. _mapLayer.removeAllFeatures();
  2659. fetchAllFC();
  2660. }
  2661.  
  2662. function onStateSelectionChanged() {
  2663. _settings.activeStateAbbr = this.value;
  2664. saveSettingsToStorage();
  2665. loadStateFCInfo();
  2666. fetchAllFC();
  2667. }
  2668.  
  2669. function setEnabled(value) {
  2670. _settings.layerVisible = value;
  2671. saveSettingsToStorage();
  2672. _mapLayer.setVisibility(value);
  2673. const color = value ? '#00bd00' : '#ccc';
  2674. $('span#fc-layer-power-btn').css({ color });
  2675. if (value) fetchAllFC();
  2676. $('#layer-switcher-item_fc_layer').prop('checked', value);
  2677. }
  2678.  
  2679. function initUserPanel() {
  2680. const $tab = $('<li>').append($('<a>', { 'data-toggle': 'tab', href: '#sidepanel-fc-layer' }).text('FC'));
  2681. const $panel = $('<div>', { class: 'tab-pane', id: 'sidepanel-fc-layer' });
  2682. const $stateSelect = $('<select>', { id: 'fcl-state-select', class: 'form-control disabled', style: 'disabled' }).append($('<option>', { value: 'ALL' }).text('All'));
  2683. // $stateSelect.change(function(evt) {
  2684. // _settings.activeStateAbbr = evt.target.value;
  2685. // saveSettingsToStorage();
  2686. // _mapLayer.removeAllFeatures();
  2687. // fetchAllFC();
  2688. // });
  2689. Object.keys(STATE_SETTINGS).forEach(stateAbbr => {
  2690. if (stateAbbr !== 'global') {
  2691. $stateSelect.append($('<option>', { value: stateAbbr }).text(reverseStatesHash(stateAbbr)));
  2692. }
  2693. });
  2694.  
  2695. const $hideStreet = $('<div>', { id: 'fcl-hide-street-container', class: 'controls-container' })
  2696. .append($('<input>', { type: 'checkbox', name: 'fcl-hide-street', id: 'fcl-hide-street' }).prop('checked', _settings.hideStreet).click(onHideStreetsClicked))
  2697. .append($('<label>', { for: 'fcl-hide-street' }).text('Hide local street highlights'));
  2698.  
  2699. $stateSelect.val(_settings.activeStateAbbr ? _settings.activeStateAbbr : 'ALL');
  2700.  
  2701. $panel.append(
  2702. $('<div>', { class: 'form-group' }).append(
  2703. $('<label>', { class: 'control-label' }).text('Select a state')
  2704. ).append(
  2705. $('<div>', { class: 'controls', id: 'fcl-state-select-container' }).append(
  2706. $('<div>').append($stateSelect)
  2707. )
  2708. ),
  2709. $hideStreet,
  2710. $('<div>', { id: 'fcl-table-container' })
  2711. );
  2712.  
  2713. $panel.append($('<div>', { id: 'fcl-state-info' }));
  2714.  
  2715. $panel.append(
  2716. $('<div>', { style: 'margin-top:10px;font-size:10px;color:#999999;' })
  2717. .append($('<div>').text(`version ${SCRIPT_VERSION}`))
  2718. .append(
  2719. $('<div>').append(
  2720. $('<a>', { href: '#' /* , target:'__blank' */ }).text('Discussion Forum (currently n/a)')
  2721. )
  2722. )
  2723. );
  2724.  
  2725. $('#user-tabs > .nav-tabs').append($tab);
  2726.  
  2727. // append the power button
  2728. if (!$('#fc-layer-power-btn').length) {
  2729. const color = _settings.layerVisible ? '#00bd00' : '#ccc';
  2730. $('a[href="#sidepanel-fc-layer"]').prepend(
  2731. $('<span>', {
  2732. class: 'fa fa-power-off',
  2733. id: 'fc-layer-power-btn',
  2734. style: `margin-right: 5px;cursor: pointer;color: ${color};font-size: 13px;`,
  2735. title: 'Toggle FC Layer'
  2736. }).click(evt => {
  2737. evt.stopPropagation();
  2738. setEnabled(!_settings.layerVisible);
  2739. })
  2740. );
  2741. }
  2742.  
  2743. $('#user-info > .flex-parent > .tab-content').append($panel);
  2744. $('#fcl-state-select').change(onStateSelectionChanged);
  2745. loadStateFCInfo();
  2746. }
  2747.  
  2748. function loadStateFCInfo() {
  2749. $('#fcl-state-info').empty();
  2750. if (STATE_SETTINGS[_settings.activeStateAbbr]) {
  2751. const stateInfo = STATE_SETTINGS[_settings.activeStateAbbr].information;
  2752. const $panelStateInfo = $('<dl>');
  2753. Object.keys(stateInfo).forEach(propertyName => {
  2754. $panelStateInfo.append($('<dt>', { style: 'margin-top:1em;color:#777777' }).text(propertyName))
  2755. .append($('<dd>').text(stateInfo[propertyName]));
  2756. });
  2757. $('#fcl-state-info').append($panelStateInfo);
  2758. }
  2759. }
  2760.  
  2761. function addLoadingIndicator() {
  2762. $('.loading-indicator').after($('<div class="loading-indicator" style="margin-right:10px" id="fc-loading-indicator">'));
  2763. }
  2764.  
  2765. function initGui() {
  2766. addLoadingIndicator();
  2767. initLayer();
  2768. initUserPanel();
  2769. }
  2770.  
  2771. function init() {
  2772. if (DEBUG && Promise.config) {
  2773. Promise.config({
  2774. warnings: true,
  2775. longStackTraces: true,
  2776. cancellation: true,
  2777. monitoring: false
  2778. });
  2779. } else {
  2780. Promise.config({
  2781. warnings: false,
  2782. longStackTraces: false,
  2783. cancellation: true,
  2784. monitoring: false
  2785. });
  2786. }
  2787.  
  2788. const u = W.loginManager.user;
  2789. _uid = u.id;
  2790. _r = u.rank + 1;
  2791. _isAM = u.isAreaManager;
  2792. _uName = u.userName;
  2793. loadSettingsFromStorage();
  2794. initGui();
  2795. fetchAllFC();
  2796. log('Initialized.');
  2797. }
  2798.  
  2799. function bootstrap() {
  2800. if (WazeWrap.Ready) {
  2801. log('Initializing...');
  2802. init();
  2803. } else {
  2804. log('Bootstrap failed. Trying again...');
  2805. unsafeWindow.setTimeout(bootstrap, 1000);
  2806. }
  2807. }
  2808.  
  2809. log('Bootstrap...');
  2810. bootstrap();
  2811. })();