WME FC Layer

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

  1. // ==UserScript==
  2. // @name WME FC Layer
  3. // @namespace https://greasyfork.org/users/45389
  4. // @version 2025.06.19.000
  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. // @exclude *://*.waze.com/editor/sdk/*
  10. // @license GNU GPLv3
  11. // @contributionURL https://github.com/WazeDev/Thank-The-Authors
  12. // @require https://greasyfork.org/scripts/39002-bluebird/code/Bluebird.js?version=255146
  13. // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
  14. // @require https://cdn.jsdelivr.net/npm/@turf/turf@7/turf.min.js
  15. // @require https://update.greasyfork.org/scripts/509664/WME%20Utils%20-%20Bootstrap.js
  16. // @connect greasyfork.org
  17. // @grant GM_xmlhttpRequest
  18. // @connect arcgis.com
  19. // @connect gis.ardot.gov
  20. // @connect azdot.gov
  21. // @connect ca.gov
  22. // @connect coloradodot.info
  23. // @connect delaware.gov
  24. // @connect dc.gov
  25. // @connect ga.gov
  26. // @connect uga.edu
  27. // @connect hawaii.gov
  28. // @connect idaho.gov
  29. // @connect in.gov
  30. // @connect iowadot.gov
  31. // @connect illinois.gov
  32. // @connect ksdot.org
  33. // @connect ky.gov
  34. // @connect la.gov
  35. // @connect maine.gov
  36. // @connect md.gov
  37. // @connect ma.us
  38. // @connect mn.us
  39. // @connect nv.gov
  40. // @connect memphistn.gov
  41. // @connect state.mi.us
  42. // @connect modot.org
  43. // @connect mt.gov
  44. // @connect unh.edu
  45. // @connect nh.gov
  46. // @connect ny.gov
  47. // @connect ncdot.gov
  48. // @connect nd.gov
  49. // @connect oh.us
  50. // @connect or.us
  51. // @connect penndot.gov
  52. // @connect sd.gov
  53. // @connect shelbycountytn.gov
  54. // @connect utah.gov
  55. // @connect vermont.gov
  56. // @connect wa.gov
  57. // @connect wv.gov
  58. // @connect wyoroad.info
  59. // ==/UserScript==
  60.  
  61. /* global turf */
  62. /* global bootstrap */
  63.  
  64. (async function main() {
  65. 'use strict';
  66.  
  67. const settingsStoreName = 'wme_fc_layer';
  68. const debug = false;
  69. const scriptVersion = GM_info.script.version;
  70. const downloadUrl = 'https://greasyfork.org/scripts/369633-wme-fc-layer/code/WME%20FC%20Layer.user.js';
  71. const sdk = await bootstrap({ scriptUpdateMonitor: { downloadUrl } });
  72. const layerName = 'FC Layer';
  73. let isAM = false;
  74. let userNameLC;
  75. let settings = {};
  76. let rank;
  77. let MAP_LAYER_Z_INDEX;
  78. const MIN_ZOOM_LEVEL = 11;
  79. const STATES_HASH = {
  80. Alabama: 'AL',
  81. Alaska: 'AK',
  82. 'American Samoa': 'AS',
  83. Arizona: 'AZ',
  84. Arkansas: 'AR',
  85. California: 'CA',
  86. Colorado: 'CO',
  87. Connecticut: 'CT',
  88. Delaware: 'DE',
  89. 'District of Columbia': 'DC',
  90. 'Federated States Of Micronesia': 'FM',
  91. Florida: 'FL',
  92. Georgia: 'GA',
  93. Guam: 'GU',
  94. Hawaii: 'HI',
  95. Idaho: 'ID',
  96. Illinois: 'IL',
  97. Indiana: 'IN',
  98. Iowa: 'IA',
  99. Kansas: 'KS',
  100. Kentucky: 'KY',
  101. Louisiana: 'LA',
  102. Maine: 'ME',
  103. 'Marshall Islands': 'MH',
  104. Maryland: 'MD',
  105. Massachusetts: 'MA',
  106. Michigan: 'MI',
  107. Minnesota: 'MN',
  108. Mississippi: 'MS',
  109. Missouri: 'MO',
  110. Montana: 'MT',
  111. Nebraska: 'NE',
  112. Nevada: 'NV',
  113. 'New Hampshire': 'NH',
  114. 'New Jersey': 'NJ',
  115. 'New Mexico': 'NM',
  116. 'New York': 'NY',
  117. 'North Carolina': 'NC',
  118. 'North Dakota': 'ND',
  119. 'Northern Mariana Islands': 'MP',
  120. Ohio: 'OH',
  121. Oklahoma: 'OK',
  122. Oregon: 'OR',
  123. Palau: 'PW',
  124. Pennsylvania: 'PA',
  125. 'Puerto Rico': 'PR',
  126. 'Rhode Island': 'RI',
  127. 'South Carolina': 'SC',
  128. 'South Dakota': 'SD',
  129. Tennessee: 'TN',
  130. Texas: 'TX',
  131. Utah: 'UT',
  132. Vermont: 'VT',
  133. 'Virgin Islands': 'VI',
  134. Virginia: 'VA',
  135. Washington: 'WA',
  136. 'West Virginia': 'WV',
  137. Wisconsin: 'WI',
  138. Wyoming: 'WY'
  139. };
  140.  
  141. function reverseStatesHash(stateAbbr) {
  142. // eslint-disable-next-line no-restricted-syntax
  143. for (const stateName in STATES_HASH) {
  144. if (STATES_HASH[stateName] === stateAbbr) return stateName;
  145. }
  146. throw new Error(`FC Layer: reverseStatesHash function did not return a value for ${stateAbbr}.`);
  147. }
  148.  
  149. const STATE_SETTINGS = {
  150. global: {
  151. 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.
  152. getFeatureRoadType(feature, layer) {
  153. const fc = feature.attributes[layer.fcPropName];
  154. return this.getRoadTypeFromFC(fc, layer);
  155. },
  156. getRoadTypeFromFC(fc, layer) {
  157. return Object.keys(layer.roadTypeMap).find(rt => layer.roadTypeMap[rt].indexOf(fc) !== -1);
  158. },
  159. isPermitted(stateAbbr) {
  160. const state = STATE_SETTINGS[stateAbbr];
  161. if (state.isPermitted) return state.isPermitted();
  162. return (rank >= 3 && isAM) || rank >= 4;
  163. },
  164. getMapLayer(stateAbbr, layerID) {
  165. let returnValue;
  166. STATE_SETTINGS[stateAbbr].fcMapLayers.forEach(layer => {
  167. if (layer.layerID === layerID) {
  168. returnValue = layer;
  169. }
  170. });
  171. return returnValue;
  172. }
  173. },
  174. AL: {
  175. baseUrl: 'https://services.arcgis.com/LZzQi3xDiclG6XvQ/arcgis/rest/services/HPMS_Year2017_F_System_Data/FeatureServer/',
  176. defaultColors: {
  177. Fw: '#ff00c5',
  178. Ew: '#4f33df',
  179. MH: '#149ece',
  180. mH: '#4ce600',
  181. PS: '#cfae0e',
  182. St: '#eeeeee'
  183. },
  184. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  185. fcMapLayers: [
  186. {
  187. layerID: 0,
  188. fcPropName: 'F_SYSTEM_V',
  189. idPropName: 'OBJECTID',
  190. outFields: ['FID', 'F_SYSTEM_V', 'State_Sys'],
  191. roadTypeMap: {
  192. Fw: [1],
  193. Ew: [2],
  194. MH: [3],
  195. mH: [4],
  196. PS: [5, 6],
  197. St: [7]
  198. },
  199. maxRecordCount: 1000,
  200. supportsPagination: false
  201. }
  202. ],
  203. isPermitted() {
  204. return rank >= 3;
  205. },
  206. information: { Source: 'ALDOT', Permission: 'Visible to R3+', Description: 'Federal and State highways set to a minimum of mH.' },
  207. getWhereClause(context) {
  208. if (context.mapContext.zoom < 16) {
  209. return `${context.layer.fcPropName} <> 7`;
  210. }
  211. return null;
  212. },
  213. getFeatureRoadType(feature, layer) {
  214. let fc = parseInt(feature.attributes[layer.fcPropName], 10);
  215. if (fc > 4 && feature.attributes.State_Sys === 'YES') {
  216. fc = 4;
  217. }
  218. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  219. }
  220. },
  221. AK: {
  222. baseUrl: 'https://services.arcgis.com/r4A0V7UzH9fcLVvv/ArcGIS/rest/services/AKDOTPF_Route_Data/FeatureServer/',
  223. defaultColors: {
  224. Ew: '#4f33df',
  225. MH: '#149ece',
  226. mH: '#4ce600',
  227. PS: '#cfae0e',
  228. St: '#eeeeee'
  229. },
  230. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  231. fcMapLayers: [
  232. {
  233. layerID: 13,
  234. fcPropName: 'Functional_Class',
  235. idPropName: 'OBJECTID',
  236. outFields: ['OBJECTID', 'Functional_Class'],
  237. roadTypeMap: {
  238. Ew: [1, 2],
  239. MH: [3],
  240. mH: [4],
  241. PS: [5, 6],
  242. St: [7]
  243. },
  244. maxRecordCount: 1000,
  245. supportsPagination: false
  246. }
  247. ],
  248. information: { Source: 'Alaska DOT&PF', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  249. getWhereClause(context) {
  250. if (context.mapContext.zoom < 16) {
  251. return `${context.layer.fcPropName} <> 7`;
  252. }
  253. return null;
  254. },
  255. getFeatureRoadType(feature, layer) {
  256. if (layer.getFeatureRoadType) {
  257. return layer.getFeatureRoadType(feature);
  258. }
  259. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  260. }
  261. },
  262. AZ: {
  263. baseUrl: 'https://services1.arcgis.com/XAiBIVuto7zeZj1B/arcgis/rest/services/ATIS_prod_gdb_1/FeatureServer/',
  264. defaultColors: {
  265. Fw: '#ff00c5',
  266. Ew: '#ff00c5',
  267. MH: '#149ece',
  268. mH: '#4ce600',
  269. PS: '#cfae0e',
  270. St: '#eeeeee'
  271. },
  272. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  273. fcMapLayers: [
  274. {
  275. layerID: 38,
  276. fcPropName: 'FunctionalClass',
  277. idPropName: 'OBJECTID',
  278. outFields: ['OBJECTID', 'FunctionalClass', 'RouteId'],
  279. roadTypeMap: {
  280. Fw: [1],
  281. Ew: [2],
  282. MH: [3],
  283. mH: [4],
  284. PS: [5, 6],
  285. St: [7]
  286. },
  287. maxRecordCount: 1000,
  288. supportsPagination: false
  289. }
  290. ],
  291. information: { Source: 'ADOT', Permission: 'Visible to R4+ or R3-AM' },
  292. getWhereClause() {
  293. return null;
  294. },
  295. getFeatureRoadType(feature, layer) {
  296. const attr = feature.attributes;
  297. const roadID = attr.RouteId.trim().replace(/ +/g, ' ');
  298. const roadNum = parseInt(roadID.substring(2, 5), 10);
  299. let fc = attr[layer.fcPropName];
  300. switch (fc) {
  301. case 'Rural Principal Arterial - Interstate':
  302. case 'Urban Principal Arterial - Interstate':
  303. fc = 1;
  304. break;
  305. case 'Rural Principal Arterial - Other Fwys & Expwys':
  306. case 'Urban Principal Arterial - Other Fwys & Expwys':
  307. fc = 2;
  308. break;
  309. case 'Rural Principal Arterial - Other':
  310. case 'Urban Principal Arterial - Other':
  311. fc = 3;
  312. break;
  313. case 'Rural Minor Arterial':
  314. case 'Urban Minor Arterial':
  315. fc = 4;
  316. break;
  317. case 'Rural Major Collector':
  318. case 'Urban Major Collector':
  319. fc = 5;
  320. break;
  321. case 'Rural Minor Collector':
  322. case 'Urban Minor Collector':
  323. fc = 6;
  324. break;
  325. default:
  326. fc = 7;
  327. }
  328. const azIH = [8, 10, 11, 17, 19, 40]; // Interstate hwys in AZ
  329. const isUS = /^U\D\d{3}\b/.test(roadID);
  330. const isState = /^S\D\d{3}\b/.test(roadID);
  331. const isBiz = /^SB\d{3}\b/.test(roadID);
  332. if (fc > 4 && isState && azIH.includes(roadNum) && isBiz) fc = 4;
  333. else if (fc > 4 && isUS) fc = isBiz ? 6 : 4;
  334. else if (fc > 6 && isState) fc = isBiz ? 7 : 6;
  335. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  336. }
  337. },
  338. AR: {
  339. baseUrl: 'https://gis.ardot.gov/hosting/rest/services/SIR_TIS/RoadInvDissolves/FeatureServer/',
  340. defaultColors: {
  341. Fw: '#ff00c5',
  342. Ew: '#4f33df',
  343. MH: '#149ece',
  344. mH: '#4ce600',
  345. PS: '#cfae0e',
  346. St: '#eeeeee'
  347. },
  348. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  349. fcMapLayers: [
  350. {
  351. layerID: 0,
  352. fcPropName: 'FunctionalClass',
  353. idPropName: 'OBJECTID',
  354. outFields: ['OBJECTID', 'FunctionalClass', 'AH_Route', 'AH_Section'],
  355. roadTypeMap: {
  356. Fw: [1, 2],
  357. Ew: [],
  358. MH: [3],
  359. mH: [4],
  360. PS: [5, 6],
  361. St: [7]
  362. },
  363. maxRecordCount: 1000,
  364. supportsPagination: false
  365. }
  366. ],
  367. information: { Source: 'ARDOT', Permission: 'Visible to R4+ or R3-AM' },
  368. getWhereClause() {
  369. return null;
  370. },
  371. getFeatureRoadType(feature, layer) {
  372. const attr = feature.attributes;
  373. let fc = parseInt(attr[layer.fcPropName], 10);
  374. const roadID = parseInt(attr.AH_Route, 10);
  375. const usHwys = [49, 59, 61, 62, 63, 64, 65, 67, 70, 71, 79, 82, 165, 167, 270, 271, 278, 371, 412, 425];
  376. const isUS = usHwys.includes(roadID);
  377. const isState = roadID < 613;
  378. const isBiz = attr.AH_Section[attr.AH_Section.length - 1] === 'B';
  379. if (fc > 3 && isUS) fc = isBiz ? 4 : 3;
  380. else if (fc > 4 && isState) fc = isBiz ? 5 : 4;
  381. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  382. }
  383. },
  384. CA: {
  385. baseUrl: 'https://caltrans-gis.dot.ca.gov/arcgis/rest/services/CHhighway/CRS_Functional_Classification/FeatureServer/',
  386. defaultColors: {
  387. Fw: '#ff00c5',
  388. Ew: '#4f33df',
  389. MH: '#149ece',
  390. mH: '#4ce600',
  391. PS: '#cfae0e',
  392. St: '#eeeeee'
  393. },
  394. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  395. fcMapLayers: [
  396. {
  397. layerID: 0,
  398. fcPropName: 'F_System',
  399. idPropName: 'OBJECTID',
  400. outFields: ['OBJECTID', 'F_System'],
  401. roadTypeMap: {
  402. Fw: [1],
  403. Ew: [2],
  404. MH: [3],
  405. mH: [4],
  406. PS: [5, 6],
  407. St: [7]
  408. },
  409. maxRecordCount: 1000,
  410. supportsPagination: false
  411. }
  412. ],
  413. isPermitted() {
  414. return ['mapomatic', 'turbomkt', 'tonestertm', 'ottonomy', 'jemay', 'ojlaw', 'js55ct'].includes(userNameLC);
  415. },
  416. information: { Source: 'Caltrans', Permission: 'Visible to ?', Description: '' },
  417. getWhereClause(context) {
  418. if (context.mapContext.zoom < 16) {
  419. return `${context.layer.fcPropName} <> 7`;
  420. }
  421. return null;
  422. },
  423. getFeatureRoadType(feature, layer) {
  424. const fc = parseInt(feature.attributes[layer.fcPropName], 10);
  425. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  426. }
  427. },
  428. CO: {
  429. baseUrl: 'https://dtdapps.coloradodot.info/arcgis/rest/services/CPLAN/open_data_sde/FeatureServer/',
  430. defaultColors: {
  431. Fw: '#ff00c5',
  432. Ew: '#4f33df',
  433. MH: '#149ece',
  434. mH: '#4ce600',
  435. PS: '#cfae0e',
  436. St: '#eeeeee'
  437. },
  438. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  439. fcMapLayers: [
  440. {
  441. layerID: 11,
  442. fcPropName: 'FUNCCLASS',
  443. idPropName: 'OBJECTID',
  444. outFields: ['OBJECTID', 'FUNCCLASS', 'ROUTE', 'REFPT'],
  445. roadTypeMap: {
  446. Fw: [1],
  447. Ew: [2],
  448. MH: [3],
  449. mH: [4],
  450. PS: [5, 6],
  451. St: [7]
  452. },
  453. maxRecordCount: 1000,
  454. supportsPagination: false
  455. },
  456. {
  457. layerID: 14,
  458. fcPropName: 'FUNCCLASSID',
  459. idPropName: 'OBJECTID',
  460. outFields: ['OBJECTID', 'FUNCCLASSID', 'ROUTE', 'FIPSCOUNTY'],
  461. roadTypeMap: {
  462. Fw: [1],
  463. Ew: [2],
  464. MH: [3],
  465. mH: [4],
  466. PS: [5, 6],
  467. St: [7]
  468. },
  469. maxRecordCount: 1000,
  470. supportsPagination: false
  471. },
  472. {
  473. layerID: 17,
  474. fcPropName: 'FUNCCLASSID',
  475. idPropName: 'OBJECTID',
  476. outFields: ['OBJECTID', 'FUNCCLASSID', 'ROUTE', 'FIPSCOUNTY'],
  477. roadTypeMap: {
  478. Fw: [1],
  479. Ew: [2],
  480. MH: [3],
  481. mH: [4],
  482. PS: [5, 6],
  483. St: [7]
  484. },
  485. maxRecordCount: 1000,
  486. supportsPagination: false
  487. }
  488. ],
  489. isPermitted() {
  490. return rank >= 3;
  491. },
  492. information: {
  493. Source: 'CDOT',
  494. Permission: 'Visible to R3+',
  495. Description: 'Please consult with a state manager before making any changes to road types based on the data presented.'
  496. },
  497. getWhereClause(context) {
  498. if (context.mapContext.zoom < 16) {
  499. return `${context.layer.fcPropName} <> '7'`;
  500. }
  501. return null;
  502. },
  503. getFeatureRoadType(feature, layer) {
  504. const attr = feature.attributes;
  505. let fc = parseInt(attr[layer.fcPropName], 10);
  506. const route = attr.ROUTE.replace(/ +/g, ' ');
  507. if (layer.layerID === 7) {
  508. const rtnum = parseInt(route.slice(0, 3), 10);
  509. const refpt = attr.REFPT;
  510. const hwys = [6, 24, 25, 34, 36, 40, 50, 70, 84, 85, 87, 138, 160, 285, 287, 350, 385, 400, 491, 550];
  511. // Exceptions first, then normal classification
  512. const doNothing = ['024D', '040G'];
  513. const notNothing = ['070K', '070L', '070O', '070Q', '070R'];
  514. const doMin = ['024E', '050D', '070O', '085F', '160D'];
  515. if (doNothing.includes(route) || (rtnum === 70 && route !== '070K' && !notNothing.includes(route))) {
  516. // do nothing
  517. } else if (doMin.includes(route) || (rtnum === 40 && refpt > 320 && refpt < 385) || (rtnum === 36 && refpt > 79 && refpt < 100.99) || (route === '034D' && refpt > 11)) {
  518. fc = 4;
  519. } else if (hwys.includes(rtnum)) {
  520. fc = Math.min(fc, 3);
  521. } else {
  522. fc = Math.min(fc, 4);
  523. }
  524. } else {
  525. // All exceptions
  526. const fips = parseInt(attr.FIPSCOUNTY, 10);
  527. if ((fips === 19 && route === 'COLORADO BD') || (fips === 37 && (route === 'GRAND AV' || route === 'S H6'))) {
  528. fc = 3;
  529. } else if (fips === 67 && route === 'BAYFIELDPAY') {
  530. fc = 4;
  531. }
  532. }
  533. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  534. }
  535. },
  536. CT: {
  537. baseUrl: 'https://services1.arcgis.com/FCaUeJ5SOVtImake/ArcGIS/rest/services/',
  538. defaultColors: {
  539. Fw: '#ff00c5',
  540. Ew: '#4f33df',
  541. MH: '#149ece',
  542. mH: '#4ce600',
  543. PS: '#cfae0e',
  544. St: '#eeeeee'
  545. },
  546. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  547. fcMapLayers: [
  548. {
  549. layerID: 3,
  550. layerPath: 'CTDOT_Roadway_Classification_and_Characteristic_Data/FeatureServer/',
  551. fcPropName: 'FC_FC_CODE',
  552. idPropName: 'OBJECTID',
  553. outFields: ['OBJECTID', 'FC_FC_CODE'],
  554. roadTypeMap: {
  555. Fw: [1],
  556. Ew: [2],
  557. MH: [3],
  558. mH: [4],
  559. PS: [5, 6],
  560. St: [7]
  561. },
  562. maxRecordCount: 1000,
  563. supportsPagination: false
  564. },
  565. {
  566. layerID: 0,
  567. layerPath: 'CTDOT_State_Routes_and_Local_Roads/FeatureServer/',
  568. fcPropName: 'ROUTE_PREFIX',
  569. idPropName: 'OBJECTID',
  570. outFields: ['OBJECTID', 'ROUTE_PREFIX'],
  571. roadTypeMap: {
  572. Fw: [1],
  573. Ew: [2],
  574. MH: [3],
  575. mH: [4],
  576. PS: [5, 6],
  577. St: [7]
  578. },
  579. maxRecordCount: 1000,
  580. supportsPagination: false
  581. }
  582. ],
  583. isPermitted() {
  584. return rank >= 3;
  585. },
  586. information: { Source: 'CTDOT', Permission: 'Visible to R3+', Description: 'Federal and State highways set to a minimum of mH.' },
  587. getWhereClause(context) {
  588. if (context.mapContext.zoom < 16) {
  589. if (context.layer.layerID === 3) {
  590. return `${context.layer.fcPropName} <> 7`;
  591. }
  592. if (context.layer.layerID === 'CTDOT_State_Routes_and_Local_Roads/FeatureServer/0') {
  593. return `${context.layer.fcPropName} IN ('I', 'CT', 'SR', 'US')`;
  594. }
  595. return null;
  596. }
  597. return null;
  598. },
  599. getFeatureRoadType(feature, layer) {
  600. let fc;
  601. let primaryFc;
  602. let secondaryFc;
  603.  
  604. if (layer.layerID === 3) {
  605. // Extract primary FC based on 'FC_FC_CODE'
  606. primaryFc = parseInt(feature.attributes[layer.fcPropName], 10);
  607. } else if (layer.layerID === 0) {
  608. // Determine secondary FC using 'ROUTE_PREFIX'
  609. const prefix = feature.attributes[layer.fcPropName];
  610.  
  611. if (prefix === 'I') {
  612. secondaryFc = 1;
  613. } else if (prefix === 'US') {
  614. secondaryFc = 3;
  615. } else if (prefix === 'CT' || prefix === 'SR') {
  616. secondaryFc = 4;
  617. }
  618. }
  619.  
  620. // Determine fc based on available values
  621. if (typeof primaryFc !== 'undefined' && typeof secondaryFc !== 'undefined') {
  622. fc = Math.min(primaryFc, secondaryFc);
  623. } else if (typeof primaryFc !== 'undefined') {
  624. fc = primaryFc;
  625. } else if (typeof secondaryFc !== 'undefined') {
  626. fc = secondaryFc;
  627. } else {
  628. fc = null; // Default if both are undefined, decide handling of null based on safeguards or other logic
  629. }
  630.  
  631. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  632. }
  633. },
  634. DE: {
  635. baseUrl: 'https://enterprise.firstmap.delaware.gov/arcgis/rest/services/Transportation/DE_Roadways_Main/FeatureServer/',
  636. defaultColors: {
  637. Fw: '#ff00c5',
  638. Ew: '#4f33df',
  639. MH: '#149ece',
  640. mH: '#4ce600',
  641. PS: '#cfae0e',
  642. St: '#eeeeee'
  643. },
  644. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  645. fcMapLayers: [
  646. {
  647. layerID: 16,
  648. fcPropName: 'VALUE_TEXT',
  649. idPropName: 'OBJECTID',
  650. outFields: ['OBJECTID', 'VALUE_TEXT'],
  651. maxRecordCount: 1000,
  652. supportsPagination: false,
  653. roadTypeMap: {
  654. Fw: ['Interstate'],
  655. Ew: ['Other Expressways & Freeway'],
  656. MH: ['Other Principal Arterials'],
  657. mH: ['Minor Arterial'],
  658. PS: ['Major Collector', 'Minor Collector'],
  659. St: ['Local']
  660. }
  661. }
  662. ],
  663. information: { Source: 'Delaware FirstMap', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  664. getWhereClause(context) {
  665. if (context.mapContext.zoom < 16) {
  666. return `${context.layer.fcPropName} <> 'Local'`;
  667. }
  668. return null;
  669. },
  670. getFeatureRoadType(feature, layer) {
  671. if (layer.getFeatureRoadType) {
  672. return layer.getFeatureRoadType(feature);
  673. }
  674. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  675. }
  676. },
  677. DC: {
  678. baseUrl: 'https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Transportation_WebMercator/MapServer/',
  679. supportsPagination: false,
  680. defaultColors: {
  681. Fw: '#ff00c5',
  682. Ew: '#149ece',
  683. MH: '#149ece',
  684. mH: '#4ce600',
  685. PS: '#cfae0e',
  686. St: '#eeeeee'
  687. },
  688. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  689. fetchAllFC: false,
  690. fcMapLayers: [
  691. {
  692. layerID: 48,
  693. fcPropName: 'FHWAFUNCTIONALCLASS',
  694. idPropName: 'OBJECTID',
  695. outFields: ['OBJECTID', 'FHWAFUNCTIONALCLASS'],
  696. maxRecordCount: 1000,
  697. supportsPagination: false,
  698. roadTypeMap: {
  699. Fw: [1],
  700. Ew: [2],
  701. MH: [3],
  702. mH: [4],
  703. PS: [5, 6],
  704. St: [7]
  705. }
  706. }
  707. ],
  708. information: { Source: 'DDOT', Permission: 'Visible to R4+ or R3-AM' },
  709. getWhereClause() {
  710. return null;
  711. },
  712. getFeatureRoadType(feature, layer) {
  713. if (layer.getFeatureRoadType) {
  714. return layer.getFeatureRoadType(feature);
  715. }
  716. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  717. }
  718. },
  719. FL: {
  720. baseUrl: 'https://services1.arcgis.com/O1JpcwDW8sjYuddV/ArcGIS/rest/services/Functional_Classification_TDA/FeatureServer/',
  721. supportsPagination: false,
  722. defaultColors: {
  723. Fw: '#ff00c5',
  724. Ew: '#149ece',
  725. MH: '#149ece',
  726. mH: '#4ce600',
  727. PS: '#cfae0e',
  728. St: '#eeeeee'
  729. },
  730. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  731. fetchAllFC: false,
  732. fcMapLayers: [
  733. {
  734. layerID: 0,
  735. fcPropName: 'FUNCLASS',
  736. idPropName: 'FID',
  737. outFields: ['FID', 'FUNCLASS'],
  738. maxRecordCount: 1000,
  739. supportsPagination: false,
  740. roadTypeMap: {
  741. Fw: ['01', '11'],
  742. Ew: ['02', '12'],
  743. MH: ['04', '14'],
  744. mH: ['06', '16'],
  745. PS: ['07', '08', '17', '18']
  746. }
  747. }
  748. ],
  749. information: { Source: 'FDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  750. getWhereClause() {
  751. return null;
  752. },
  753. getFeatureRoadType(feature, layer) {
  754. if (layer.getFeatureRoadType) {
  755. return layer.getFeatureRoadType(feature);
  756. }
  757. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  758. }
  759. },
  760. GA: {
  761. baseUrl: 'https://maps.itos.uga.edu/arcgis/rest/services/GDOT/GDOT_FunctionalClass/mapserver/',
  762. supportsPagination: true,
  763. defaultColors: {
  764. Fw: '#ff00c5',
  765. Ew: '#149ece',
  766. MH: '#149ece',
  767. mH: '#4ce600',
  768. PS: '#cfae0e',
  769. St: '#eeeeee'
  770. },
  771. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  772. fetchAllFC: false,
  773. /* eslint-disable object-curly-newline */
  774. fcMapLayers: [
  775. {
  776. layerID: 0,
  777. fcPropName: 'FUNCTIONAL_CLASS',
  778. idPropName: 'OBJECTID',
  779. outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'SYSTEM_CODE'],
  780. maxRecordCount: 1000,
  781. supportsPagination: true,
  782. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] }
  783. },
  784. {
  785. layerID: 1,
  786. fcPropName: 'FUNCTIONAL_CLASS',
  787. idPropName: 'OBJECTID',
  788. outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'SYSTEM_CODE'],
  789. maxRecordCount: 1000,
  790. supportsPagination: true,
  791. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] }
  792. },
  793. {
  794. layerID: 2,
  795. fcPropName: 'FUNCTIONAL_CLASS',
  796. idPropName: 'OBJECTID',
  797. outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'SYSTEM_CODE'],
  798. maxRecordCount: 1000,
  799. supportsPagination: true,
  800. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] }
  801. },
  802. {
  803. layerID: 3,
  804. fcPropName: 'FUNCTIONAL_CLASS',
  805. idPropName: 'OBJECTID',
  806. outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'SYSTEM_CODE'],
  807. maxRecordCount: 1000,
  808. supportsPagination: true,
  809. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] }
  810. },
  811. {
  812. layerID: 4,
  813. fcPropName: 'FUNCTIONAL_CLASS',
  814. idPropName: 'OBJECTID',
  815. outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'SYSTEM_CODE'],
  816. maxRecordCount: 1000,
  817. supportsPagination: true,
  818. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] }
  819. },
  820. {
  821. layerID: 5,
  822. fcPropName: 'FUNCTIONAL_CLASS',
  823. idPropName: 'OBJECTID',
  824. outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'SYSTEM_CODE'],
  825. maxRecordCount: 1000,
  826. supportsPagination: true,
  827. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] }
  828. },
  829. {
  830. layerID: 6,
  831. fcPropName: 'FUNCTIONAL_CLASS',
  832. idPropName: 'OBJECTID',
  833. outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'SYSTEM_CODE'],
  834. maxRecordCount: 1000,
  835. supportsPagination: true,
  836. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] }
  837. }
  838. ],
  839. /* eslint-enable object-curly-newline */
  840. information: { Source: 'GDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Federal and State highways set to a minimum of mH.' },
  841. getWhereClause() {
  842. return null;
  843. },
  844. getFeatureRoadType(feature, layer) {
  845. if (layer.getFeatureRoadType) {
  846. return layer.getFeatureRoadType(feature);
  847. }
  848. const attr = feature.attributes;
  849. const fc = attr.FUNCTIONAL_CLASS;
  850. if (attr.SYSTEM_CODE === '1' && fc > 4) {
  851. return STATE_SETTINGS.global.getRoadTypeFromFC(4, layer);
  852. }
  853. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  854. }
  855. },
  856. HI: {
  857. baseUrl: 'https://geodata.hawaii.gov/arcgis/rest/services/Transportation/MapServer/',
  858. defaultColors: {
  859. Fw: '#ff00c5',
  860. Ew: '#4f33df',
  861. MH: '#149ece',
  862. mH: '#4ce600',
  863. PS: '#cfae0e',
  864. St: '#eeeeee'
  865. },
  866. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  867. fcMapLayers: [
  868. {
  869. layerID: 12,
  870. fcPropName: 'f_system',
  871. idPropName: 'objectid',
  872. outFields: ['objectid', 'f_system'],
  873. roadTypeMap: {
  874. Fw: [1],
  875. Ew: [2],
  876. MH: [3],
  877. mH: [4],
  878. PS: [5, 6],
  879. St: [7]
  880. },
  881. maxRecordCount: 1000,
  882. supportsPagination: false
  883. }
  884. ],
  885. information: { Source: 'HDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  886. getWhereClause(context) {
  887. if (context.mapContext.zoom < 16) {
  888. return `${context.layer.fcPropName}<7`;
  889. }
  890. return null;
  891. },
  892. getFeatureRoadType(feature, layer) {
  893. if (layer.getFeatureRoadType) {
  894. return layer.getFeatureRoadType(feature);
  895. }
  896. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  897. }
  898. },
  899. ID: {
  900. baseUrl: 'https://gisportalp.itd.idaho.gov/xserver/rest/services/RH_GeneralService/MapServer/',
  901. supportsPagination: false,
  902. defaultColors: {
  903. Fw: '#ff00c5',
  904. Ew: '#149ece',
  905. MH: '#149ece',
  906. mH: '#4ce600',
  907. PS: '#cfae0e',
  908. St: '#eeeeee'
  909. },
  910. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  911. fetchAllFC: true,
  912. /* eslint-disable object-curly-newline */
  913. fcMapLayers: [
  914. {
  915. layerID: 67,
  916. fcPropName: 'FunctionalClass',
  917. idPropName: 'ObjectId',
  918. outFields: ['ObjectId', 'FunctionalClass'],
  919. maxRecordCount: 1000,
  920. supportsPagination: false,
  921. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6] }
  922. }
  923. ],
  924. /* eslint-enable object-curly-newline */
  925. information: { Source: 'ITD', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  926. getWhereClause() {
  927. return null;
  928. },
  929. getFeatureRoadType(feature, layer) {
  930. if (layer.getFeatureRoadType) {
  931. return layer.getFeatureRoadType(feature);
  932. }
  933. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  934. }
  935. },
  936. IL: {
  937. baseUrl: 'https://gis1.dot.illinois.gov/arcgis/rest/services/AdministrativeData/FunctionalClass/MapServer/',
  938. supportsPagination: false,
  939. defaultColors: {
  940. Fw: '#ff00c5',
  941. Ew: '#ff00c5',
  942. MH: '#149ece',
  943. mH: '#4ce600',
  944. PS: '#cfae0e',
  945. St: '#eeeeee'
  946. },
  947. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  948. fcMapLayers: [
  949. {
  950. layerID: 0,
  951. idPropName: 'OBJECTID',
  952. fcPropName: 'FC',
  953. outFields: ['FC'],
  954. roadTypeMap: {
  955. Fw: ['1'],
  956. Ew: ['2'],
  957. MH: ['3'],
  958. mH: ['4'],
  959. PS: ['5', '6'],
  960. St: ['7']
  961. },
  962. maxRecordCount: 1000,
  963. supportsPagination: false
  964. }
  965. ],
  966. isPermitted() {
  967. return rank >= 4;
  968. },
  969. information: { Source: 'IDOT', Permission: 'Visible to R4+', Description: 'Raw unmodified FC data.' },
  970. getWhereClause(context) {
  971. return context.mapContext.zoom < 16 ? 'FC<>7' : null;
  972. },
  973. getFeatureRoadType(feature, layer) {
  974. if (layer.getFeatureRoadType) {
  975. return layer.getFeatureRoadType(feature);
  976. }
  977. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  978. }
  979. },
  980. IN: {
  981. baseUrl: 'https://gis.indot.in.gov/ro/rest/services/DOT/INDOT_LTAP/MapServer/',
  982. supportsPagination: false,
  983. overrideUrl: '1Sbwc7e6BfHpZWSTfU3_1otXGSxHrdDYcbn7fOf1VjpA',
  984. defaultColors: {
  985. Fw: '#ff00c5',
  986. Ew: '#149ece',
  987. MH: '#149ece',
  988. mH: '#4ce600',
  989. PS: '#cfae0e',
  990. St: '#eeeeee'
  991. },
  992. zoomSettings: {
  993. maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1],
  994. excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []],
  995. hideRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []]
  996. },
  997. fcMapLayers: [
  998. {
  999. layerID: 10,
  1000. idPropName: 'OBJECTID',
  1001. fcPropName: 'FUNCTIONAL_CLASS',
  1002. outFields: ['FUNCTIONAL_CLASS', 'OBJECTID', 'TO_DATE'],
  1003. roadTypeMap: {
  1004. Fw: [1],
  1005. Ew: [2],
  1006. MH: [3],
  1007. mH: [4],
  1008. PS: [5, 6],
  1009. St: [7]
  1010. },
  1011. maxRecordCount: 100000,
  1012. supportsPagination: false
  1013. }
  1014. ],
  1015. isPermitted() {
  1016. return true;
  1017. },
  1018. information: { Source: 'INDOT', Description: 'Raw unmodified FC data.' },
  1019. getWhereClause(context) {
  1020. let whereParts = ['TO_DATE IS NULL'];
  1021. if (context.mapContext.zoom < 16) {
  1022. whereParts += ` AND ${context.layer.fcPropName} <> 7`;
  1023. }
  1024. return whereParts;
  1025. },
  1026. getFeatureRoadType(feature, layer) {
  1027. if (layer.getFeatureRoadType) {
  1028. return layer.getFeatureRoadType(feature);
  1029. }
  1030. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  1031. }
  1032. },
  1033. IA: {
  1034. baseUrl: 'https://gis.iowadot.gov/agshost/rest/services/RAMS/Road_Network/FeatureServer/',
  1035. defaultColors: {
  1036. Fw: '#ff00c5',
  1037. Ew: '#149ece',
  1038. MH: '#149ece',
  1039. mH: '#4ce600',
  1040. PS: '#cfae0e',
  1041. St: '#eeeeee',
  1042. PSGr: '#cc6533',
  1043. StGr: '#e99cb6'
  1044. },
  1045. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1046. fcMapLayers: [
  1047. {
  1048. layerID: 0,
  1049. fcPropName: 'FED_FUNCTIONAL_CLASS',
  1050. idPropName: 'OBJECTID',
  1051. outFields: ['OBJECTID', 'FED_FUNCTIONAL_CLASS', 'STATE_ROUTE_NAME_1', 'ACCESS_CONTROL', 'SURFACE_TYPE'],
  1052. roadTypeMap: {
  1053. Fw: [1],
  1054. MH: [2, 3],
  1055. mH: [4],
  1056. PS: [5, 6],
  1057. St: [7]
  1058. },
  1059. maxRecordCount: 1000,
  1060. supportsPagination: false
  1061. }
  1062. ],
  1063. information: { Source: 'Iowa DOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Additional colors denote unpaved PS and LS segements.' },
  1064. getWhereClause(context) {
  1065. let theWhereClause = "FACILITY_TYPE<>'7'"; // Removed proposed roads
  1066. if (context.mapContext.zoom < 16) {
  1067. theWhereClause += ` AND ${context.layer.fcPropName}<>'7'`;
  1068. }
  1069. return theWhereClause;
  1070. },
  1071. getFeatureRoadType(feature, layer) {
  1072. const attr = feature.attributes;
  1073. let fc = parseInt(attr[layer.fcPropName], 10);
  1074. const isFw = attr.ACCESS_CONTROL === 1;
  1075. const isUS = /^STATE OF IOWA, US/.test(attr.STATE_ROUTE_NAME_1);
  1076. const isState = /^STATE OF IOWA, IA/.test(attr.STATE_ROUTE_NAME_1);
  1077. if (isFw) fc = 1;
  1078. else if (fc > 3 && isUS) fc = 3;
  1079. else if (fc > 4 && isState) fc = 4;
  1080. if (fc > 4 && attr.SURFACE_TYPE === 20) {
  1081. return fc < 7 ? 'PSGr' : 'StGr';
  1082. }
  1083. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1084. }
  1085. },
  1086. KS: {
  1087. baseUrl: 'http://wfs.ksdot.org/arcgis_web_adaptor/rest/services/Transportation/',
  1088. defaultColors: {
  1089. Fw: '#ff00c5',
  1090. Ew: '#4f33df',
  1091. MH: '#149ece',
  1092. mH: '#4ce600',
  1093. PS: '#cfae0e',
  1094. St: '#eeeeee'
  1095. },
  1096. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1097. fcMapLayers: [
  1098. {
  1099. layerID: 3,
  1100. layerPath: 'Functional_Classification/MapServer/',
  1101. idPropName: 'Id',
  1102. fcPropName: 'FunctionalClassification',
  1103. outFields: ['FunctionalClassification', 'Id'],
  1104. roadTypeMap: {
  1105. Fw: [1],
  1106. MH: [2, 3],
  1107. mH: [4],
  1108. PS: [5, 6],
  1109. St: [7]
  1110. },
  1111. maxRecordCount: 1000,
  1112. supportsPagination: false
  1113. },
  1114.  
  1115. // 2024-03-20 (mapomatic) The "non-state system" layer was removed from the KS server,
  1116. // so we're forced to use the function_classification layer (above) which doesn't include
  1117. // any metadata for US/state road designations. I'm leaving the old layers commented below
  1118. // in case they're of use in the future.
  1119.  
  1120. // {
  1121. // layerID: 0,
  1122. // layerPath: 'Non_State_System/MapServer/',
  1123. // idPropName: 'ID2',
  1124. // fcPropName: 'FUNCLASS',
  1125. // outFields: ['FUNCLASS', 'ID2', 'ROUTE_ID'],
  1126. // roadTypeMap: {
  1127. // Fw: [1], MH: [2, 3], mH: [4], PS: [5, 6], St: [7]
  1128. // },
  1129. // maxRecordCount: 1000,
  1130. // supportsPagination: false
  1131. // },
  1132. {
  1133. layerID: 0,
  1134. layerPath: 'State_System/MapServer/',
  1135. idPropName: 'OBJECTID',
  1136. fcPropName: 'FUN_CLASS_CD',
  1137. outFields: ['FUN_CLASS_CD', 'OBJECTID', 'NHS'],
  1138. roadTypeMap: {
  1139. Fw: [1],
  1140. Ew: [2],
  1141. MH: [3],
  1142. mH: [4],
  1143. PS: [5, 6],
  1144. St: [7]
  1145. },
  1146. maxRecordCount: 1000,
  1147. supportsPagination: false
  1148. }
  1149. ],
  1150. isPermitted() {
  1151. return rank >= 3 || isAM;
  1152. },
  1153. information: { Source: 'KDOT', Permission: 'Visible to area managers', Description: 'Federal and State highways set to a minimum of mH.' },
  1154. getWhereClause(context) {
  1155. if (context.mapContext.zoom < 16) {
  1156. return `${context.layer.fcPropName}<>'7'`;
  1157. }
  1158. return null;
  1159. },
  1160. getFeatureRoadType(feature, layer) {
  1161. const attr = feature.attributes;
  1162. let fc = parseInt(attr[layer.fcPropName], 10);
  1163. const roadNHS = attr.NHS;
  1164. const isUS = roadNHS === 'YES';
  1165. const isState = roadNHS === 'NO';
  1166. if (fc > 3 && isUS) fc = 3;
  1167. else if (fc > 4 && isState) fc = 4;
  1168. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1169. }
  1170. },
  1171. KY: {
  1172. baseUrl: 'https://maps.kytc.ky.gov/arcgis/rest/services/BaseMap/System/MapServer/',
  1173. supportsPagination: false,
  1174. defaultColors: {
  1175. Fw: '#ffaac5',
  1176. Ew: '#ff00c5',
  1177. MH: '#149ece',
  1178. mH: '#4ce600',
  1179. PS: '#cfae0e',
  1180. St: '#eeeeee'
  1181. },
  1182. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  1183. /* eslint-disable object-curly-newline */
  1184. fcMapLayers: [
  1185. {
  1186. layerID: 0,
  1187. idPropName: 'OBJECTID',
  1188. fcPropName: 'FC',
  1189. outFields: ['FC', 'OBJECTID', 'RT_PREFIX', 'RT_SUFFIX'],
  1190. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] },
  1191. maxRecordCount: 1000,
  1192. supportsPagination: false
  1193. }
  1194. ],
  1195. isPermitted() {
  1196. return true;
  1197. },
  1198. information: { Source: 'KYTC', Permission: 'Visible to All', Description: 'Federal and State highways set to a minimum of mH.' },
  1199. getWhereClause(context) {
  1200. if (context.mapContext.zoom < 16) {
  1201. return `${context.layer.fcPropName}<7`;
  1202. }
  1203. return null;
  1204. },
  1205. getFeatureRoadType(feature, layer) {
  1206. const attr = feature.attributes;
  1207. let fc = parseInt(attr[layer.fcPropName], 10);
  1208. if (fc > 3 && attr.RT_PREFIX === 'US') {
  1209. const suffix = attr.RT_SUFFIX;
  1210. fc = suffix && suffix.indexOf('X') > -1 ? 4 : 3;
  1211. }
  1212. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1213. }
  1214. },
  1215. LA: {
  1216. baseUrl: 'https://maps.dotd.la.gov/road/rest/services/Roads_and_Highways_OpenData/FeatureServer/',
  1217. supportsPagination: false,
  1218. defaultColors: {
  1219. Fw: '#ff00c5',
  1220. Ew: '#4f33df',
  1221. MH: '#149ece',
  1222. mH: '#4ce600',
  1223. PS: '#cfae0e',
  1224. St: '#eeeeee'
  1225. },
  1226. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1227. /* eslint-disable object-curly-newline */
  1228. fcMapLayers: [
  1229. {
  1230. layerID: 84,
  1231. fcPropName: 'FunctionalSystem',
  1232. idPropName: 'OBJECTID',
  1233. outFields: ['OBJECTID', 'FunctionalSystem', 'RouteID'],
  1234. roadTypeMap: { Fw: [1], Ew: [2], MH: [3], mH: [4], PS: [5, 6], St: [7] },
  1235. maxRecordCount: 1000,
  1236. supportsPagination: false
  1237. }
  1238. ],
  1239. /* eslint-enable object-curly-newline */
  1240. information: { Source: 'LaDOTD', Permission: 'Visible to R4+ or R3-AM', Description: 'Federal and State highways set to a minimum of mH.' },
  1241. getWhereClause(context) {
  1242. if (context.mapContext.zoom < 16) {
  1243. return `${context.layer.fcPropName}<7`;
  1244. }
  1245. return null;
  1246. },
  1247. getFeatureRoadType(feature, layer) {
  1248. let fc = feature.attributes[layer.fcPropName];
  1249. if (fc === '2a' || fc === '2b') {
  1250. fc = 2;
  1251. }
  1252. fc = parseInt(fc, 10);
  1253. const route = feature.attributes.RouteID.split('_')[1].trim();
  1254. const isUS = /^US \d/.test(route);
  1255. const isState = /^LA \d/.test(route);
  1256. const isBiz = / BUS$/.test(route);
  1257. if (fc > 3 && isUS) fc = isBiz ? 4 : 3;
  1258. else if (fc > 4 && isState) fc = isBiz ? 5 : 4;
  1259. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1260. }
  1261. },
  1262. ME: {
  1263. baseUrl: 'https://arcgisserver.maine.gov/arcgis/rest/services/mdot/MaineDOT_Dynamic/MapServer/',
  1264. defaultColors: {
  1265. Fw: '#ff00c5',
  1266. Ew: '#4f33df',
  1267. MH: '#149ece',
  1268. mH: '#4ce600',
  1269. PS: '#cfae0e',
  1270. St: '#eeeeee'
  1271. },
  1272. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1273. fcMapLayers: [
  1274. {
  1275. layerID: 6,
  1276. fcPropName: 'fedfunccls',
  1277. idPropName: 'objectid',
  1278. outFields: ['objectid', 'fedfunccls'],
  1279. roadTypeMap: {
  1280. Fw: [1],
  1281. Ew: [2],
  1282. MH: [3],
  1283. mH: [4],
  1284. PS: [5, 6],
  1285. St: [7]
  1286. },
  1287. maxRecordCount: 1000,
  1288. supportsPagination: false
  1289. }
  1290. ],
  1291. information: { Source: 'MaineDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  1292. isPermitted() {
  1293. return rank >= 4 || (rank === 3 && isAM);
  1294. },
  1295. getWhereClause(context) {
  1296. if (context.mapContext.zoom < 16) {
  1297. return `${context.layer.fcPropName}<>'Local'`;
  1298. }
  1299. return null;
  1300. },
  1301. getFeatureRoadType(feature, layer) {
  1302. const attr = feature.attributes;
  1303. let fc = attr[layer.fcPropName];
  1304. switch (fc) {
  1305. case 'Interstate':
  1306. fc = 1;
  1307. break;
  1308. case 'Other Freeway or Expressway':
  1309. fc = 2;
  1310. break;
  1311. case 'Other Principal Arterial':
  1312. fc = 3;
  1313. break;
  1314. case 'Minor Arterial':
  1315. fc = 4;
  1316. break;
  1317. case 'Major Collector':
  1318. case 'Minor Collector':
  1319. fc = 5;
  1320. break;
  1321. default:
  1322. fc = 7;
  1323. }
  1324. // 2024-6-28 (mapomatic) MaineDOT removed the prirtename field so we can't "upgrade" FC anymore.
  1325. // const route = attr.prirtename;
  1326. // const isUS = /^US \d/.test(route);
  1327. // const isState = /^ST RTE \d/.test(route);
  1328. // const isBiz = (isUS && /(1B|1BS)$/.test(route)) || (isState && /(15B|24B|25B|137B)$/.test(route));
  1329. // if (fc > 3 && isUS) fc = isBiz ? 4 : 3;
  1330. // else if (fc > 4 && isState) fc = isBiz ? 5 : 4;
  1331. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1332. }
  1333. },
  1334. MD: {
  1335. baseUrl: 'https://services.arcgis.com/njFNhDsUCentVYJW/arcgis/rest/services/MDOT_SHA_Roadway_Functional_Classification/FeatureServer/',
  1336. defaultColors: {
  1337. Fw: '#ff00c5',
  1338. Ew: '#4f33df',
  1339. MH: '#149ece',
  1340. mH: '#4ce600',
  1341. PS: '#ffff00',
  1342. St: '#eeeeee'
  1343. },
  1344. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1345. fcMapLayers: [
  1346. {
  1347. layerID: 0,
  1348. fcPropName: 'FUNCTIONAL_CLASS',
  1349. idPropName: 'OBJECTID',
  1350. outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'ID_PREFIX', 'MP_SUFFIX'],
  1351. roadTypeMap: {
  1352. Fw: [1],
  1353. Ew: [2],
  1354. MH: [3],
  1355. mH: [4],
  1356. PS: [5, 6],
  1357. St: [7]
  1358. },
  1359. maxRecordCount: 1000,
  1360. supportsPagination: false
  1361. }
  1362. ],
  1363. information: { Source: 'MDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Federal and State highways set to a minimum of mH.' },
  1364. getWhereClause(context) {
  1365. if (context.mapContext.zoom < 16) {
  1366. return "(FUNCTIONAL_CLASS < 7 OR ID_PREFIX IN('MD'))";
  1367. }
  1368. return null;
  1369. },
  1370. getFeatureRoadType(feature, layer) {
  1371. const attr = feature.attributes;
  1372. let fc = parseInt(attr.FUNCTIONAL_CLASS, 10);
  1373. const isUS = attr.ID_PREFIX === 'US';
  1374. const isState = attr.ID_PREFIX === 'MD';
  1375. const isBiz = attr.MP_SUFFIX === 'BU';
  1376. if (fc > 3 && isUS) fc = isBiz ? 4 : 3;
  1377. else if (fc > 4 && isState) fc = isBiz ? 5 : 4;
  1378. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1379. }
  1380. },
  1381. MA: {
  1382. baseUrl: 'https://gis.massdot.state.ma.us/arcgis/rest/services/Roads/RoadInventory/MapServer/',
  1383. defaultColors: {
  1384. Fw: '#ff00c5',
  1385. Ew: '#4f33df',
  1386. MH: '#149ece',
  1387. mH: '#4ce600',
  1388. PS: '#cfae0e',
  1389. St: '#eeeeee'
  1390. },
  1391. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1392. fcMapLayers: [
  1393. {
  1394. layerID: 0,
  1395. fcPropName: 'F_F_Class',
  1396. idPropName: 'OBJECTID',
  1397. outFields: ['OBJECTID', 'F_F_Class', 'route_id'],
  1398. roadTypeMap: {
  1399. Fw: [1],
  1400. Ew: [2],
  1401. MH: [3],
  1402. mH: [4],
  1403. PS: [5, 6],
  1404. St: [7]
  1405. },
  1406. maxRecordCount: 1000,
  1407. supportsPagination: false
  1408. }
  1409. ],
  1410. information: { Source: 'MDOT', Permission: 'Visible to R2+', Description: 'Federal and State highways set to a minimum of mH.' },
  1411. isPermitted() {
  1412. return rank >= 2;
  1413. },
  1414. getWhereClause(context) {
  1415. if (context.mapContext.zoom < 16) {
  1416. return `${context.layer.fcPropName}<>7`;
  1417. }
  1418. return null;
  1419. },
  1420. getFeatureRoadType(feature, layer) {
  1421. const attr = feature.attributes;
  1422. let fc = parseInt(attr[layer.fcPropName], 10);
  1423. const route = attr.route_id;
  1424. const isUS = /^US\d/.test(route);
  1425. const isState = /^SR\d/.test(route);
  1426. if (fc > 3 && isUS) fc = 3;
  1427. else if (fc > 4 && isState) fc = 4;
  1428. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1429. }
  1430. },
  1431. MI: {
  1432. baseUrl: 'https://mdotgis.state.mi.us/arcgis/rest/services/DataAccess/NfcNhsPub/MapServer/',
  1433. defaultColors: {
  1434. Fw: '#ff00c5',
  1435. Ew: '#149ece',
  1436. MH: '#149ece',
  1437. mH: '#4ce600',
  1438. PS: '#cfae0e',
  1439. St: '#eeeeee'
  1440. },
  1441. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1442. fcMapLayers: [
  1443. {
  1444. layerID: 353,
  1445. idPropName: 'OBJECTID',
  1446. fcPropName: 'FunctionalSystem',
  1447. outFields: ['FunctionalSystem'],
  1448. roadTypeMap: {
  1449. Fw: [1],
  1450. Ew: [2],
  1451. MH: [3],
  1452. mH: [4],
  1453. PS: [5, 6],
  1454. St: [7]
  1455. },
  1456. maxRecordCount: 1000,
  1457. supportsPagination: false
  1458. }
  1459. ],
  1460. isPermitted() {
  1461. return true;
  1462. },
  1463. information: { Source: 'MDOT', Permission: 'Visible to All', Description: 'Raw unmodified FC data.' },
  1464. getWhereClause(context) {
  1465. if (context.mapContext.zoom < 16) {
  1466. return `${context.layer.fcPropName}<7`;
  1467. }
  1468. return null;
  1469. },
  1470. getFeatureRoadType(feature, layer) {
  1471. if (layer.getFeatureRoadType) {
  1472. return layer.getFeatureRoadType(feature);
  1473. }
  1474. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  1475. }
  1476. },
  1477. MN: {
  1478. baseUrl: 'https://dotapp9.dot.state.mn.us/lrs/rest/services/emma/emma_op/MapServer/',
  1479. defaultColors: {
  1480. Fw: '#ff00c5',
  1481. Ew: '#149ece',
  1482. MH: '#149ece',
  1483. mH: '#4ce600',
  1484. PS: '#cfae0e',
  1485. St: '#eeeeee'
  1486. },
  1487. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1488. fcMapLayers: [
  1489. {
  1490. layerID: 13,
  1491. idPropName: 'OBJECTID',
  1492. fcPropName: 'FUNCTIONAL_CLASS',
  1493. outFields: ['FUNCTIONAL_CLASS', 'ROUTE_ID'],
  1494. roadTypeMap: {
  1495. Fw: [1],
  1496. Ew: [2],
  1497. MH: [3],
  1498. mH: [4],
  1499. PS: [5, 6],
  1500. St: [7]
  1501. },
  1502. maxRecordCount: 1000,
  1503. supportsPagination: false
  1504. }
  1505. ],
  1506. isPermitted() {
  1507. return true;
  1508. },
  1509. information: { Source: 'MnDOT', Permission: 'Visible to All', Description: 'Raw unmodified FC data.' },
  1510. getWhereClause(context) {
  1511. if (context.mapContext.zoom < 16) {
  1512. return `${context.layer.fcPropName}<>7`;
  1513. }
  1514. return null;
  1515. },
  1516. getFeatureRoadType(feature, layer) {
  1517. if (layer.getFeatureRoadType) {
  1518. return layer.getFeatureRoadType(feature);
  1519. }
  1520. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  1521. }
  1522. },
  1523. MO: {
  1524. baseUrl: 'https://mapping.modot.org/arcgis/rest/services/BaseMap/TmsUtility/MapServer/',
  1525. defaultColors: {
  1526. Fw: '#ff00c5',
  1527. Ew: '#4f33df',
  1528. MH: '#149ece',
  1529. mH: '#4ce600',
  1530. PS: '#cfae0e',
  1531. St: '#eeeeee'
  1532. },
  1533. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1534. fcMapLayers: [
  1535. {
  1536. layerID: 5,
  1537. fcPropName: 'FUNC_CLASS_NAME',
  1538. idPropName: 'SS_PAVEMENT_ID',
  1539. outFields: ['SS_PAVEMENT_ID', 'FUNC_CLASS_NAME', 'TRAVELWAY_DESG', 'TRAVELWAY_NAME', 'ACCESS_CAT_NAME'],
  1540. roadTypeMap: {
  1541. Fw: [1],
  1542. Ew: [2],
  1543. MH: [3],
  1544. mH: [4],
  1545. PS: [5, 6],
  1546. St: [7]
  1547. },
  1548. maxRecordCount: 1000,
  1549. supportsPagination: false
  1550. }
  1551. ],
  1552. isPermitted() {
  1553. return rank >= 3 || (rank >= 2 && isAM);
  1554. },
  1555. information: { Source: 'MoDOT', Permission: 'Visible to R3+ or R2-AM', Description: 'Federal and State highways set to a minimum of mH.' },
  1556. getWhereClause(context) {
  1557. if (context.mapContext.zoom < 13) {
  1558. return '1=0'; // WME very laggy at zoom 0
  1559. }
  1560. // Remove duplicate rows, but suss out interstate business loops
  1561. return "FUNC_CLASS_NAME <> ' ' AND (TRAVELWAY_ID = CNTL_TW_ID OR (TRAVELWAY_ID <> CNTL_TW_ID AND TRAVELWAY_DESG = 'LP'))";
  1562. },
  1563. getFeatureRoadType(feature, layer) {
  1564. const attr = feature.attributes;
  1565. let fc = attr[layer.fcPropName];
  1566. const rtType = attr.TRAVELWAY_DESG;
  1567. const route = attr.TRAVELWAY_NAME;
  1568. switch (fc) {
  1569. case 'INTERSTATE':
  1570. fc = 1;
  1571. break;
  1572. case 'FREEWAY':
  1573. fc = 2;
  1574. break;
  1575. case 'PRINCIPAL ARTERIAL':
  1576. fc = 3;
  1577. break;
  1578. case 'MINOR ARTERIAL':
  1579. fc = 4;
  1580. break;
  1581. case 'MAJOR COLLECTOR':
  1582. fc = 5;
  1583. break;
  1584. case 'MINOR COLLECTOR':
  1585. fc = 6;
  1586. break;
  1587. default:
  1588. fc = 8; // not a typo
  1589. }
  1590. 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'];
  1591. const isUS = ['US', 'LP'].includes(rtType); // is US or interstate biz
  1592. const isState = ['MO', 'AL'].includes(rtType);
  1593. const isSup = rtType === 'RT';
  1594. const isBiz = ['BU', 'SP'].includes(rtType) || /BUSINESS .+ \d/.test(route);
  1595. const isUSBiz = isBiz && usHwys.includes(route);
  1596. if ((fc === 2 && attr.ACCESS_CAT_NAME !== 'FULL') || (fc > 3 && isUS)) fc = 3;
  1597. else if (fc > 4 && (isState || isUSBiz)) fc = 4;
  1598. else if (fc > 6 && (isSup || isBiz)) fc = 6;
  1599. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1600. }
  1601. },
  1602. MT: {
  1603. baseUrl: 'https://app.mdt.mt.gov/arcgis/rest/services/Standard/FUNCTIONAL_CLASS/MapServer/',
  1604. defaultColors: {
  1605. Fw: '#ff00c5',
  1606. Ew: '#4f33df',
  1607. MH: '#149ece',
  1608. mH: '#4ce600',
  1609. PS: '#cfae0e',
  1610. St: '#eeeeee'
  1611. },
  1612. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1613. fcMapLayers: [
  1614. {
  1615. layerID: 0,
  1616. fcPropName: 'FUNC_CLASS',
  1617. idPropName: 'OBJECTID',
  1618. outFields: ['OBJECTID', 'FUNC_CLASS', 'SIGN_ROUTE', 'ROUTE_NAME'],
  1619. roadTypeMap: {
  1620. Fw: ['1-Interstate']
  1621. },
  1622. maxRecordCount: 1000,
  1623. supportsPagination: false
  1624. },
  1625. {
  1626. layerID: 1,
  1627. fcPropName: 'FUNC_CLASS',
  1628. idPropName: 'OBJECTID',
  1629. outFields: ['OBJECTID', 'FUNC_CLASS', 'SIGN_ROUTE', 'ROUTE_NAME'],
  1630. roadTypeMap: {
  1631. MH: ['3-Principal Arterial - Other']
  1632. },
  1633. maxRecordCount: 1000,
  1634. supportsPagination: false
  1635. },
  1636. {
  1637. layerID: 2,
  1638. fcPropName: 'FUNC_CLASS',
  1639. idPropName: 'OBJECTID',
  1640. outFields: ['OBJECTID', 'FUNC_CLASS', 'SIGN_ROUTE', 'ROUTE_NAME'],
  1641. roadTypeMap: {
  1642. mH: ['4-Minor Arterial']
  1643. },
  1644. maxRecordCount: 1000,
  1645. supportsPagination: false
  1646. },
  1647. {
  1648. layerID: 3,
  1649. fcPropName: 'FUNC_CLASS',
  1650. idPropName: 'OBJECTID',
  1651. outFields: ['OBJECTID', 'FUNC_CLASS', 'SIGN_ROUTE', 'ROUTE_NAME'],
  1652. roadTypeMap: {
  1653. PS: ['5-Major Collector']
  1654. },
  1655. maxRecordCount: 1000,
  1656. supportsPagination: false
  1657. },
  1658. {
  1659. layerID: 4,
  1660. fcPropName: 'FUNC_CLASS',
  1661. idPropName: 'OBJECTID',
  1662. outFields: ['OBJECTID', 'FUNC_CLASS', 'SIGN_ROUTE', 'ROUTE_NAME'],
  1663. roadTypeMap: {
  1664. PS: ['6-Minor Collector']
  1665. },
  1666. maxRecordCount: 1000,
  1667. supportsPagination: false
  1668. },
  1669. {
  1670. layerID: 5,
  1671. fcPropName: 'FUNC_CLASS',
  1672. idPropName: 'OBJECTID',
  1673. outFields: ['OBJECTID', 'FUNC_CLASS', 'SIGN_ROUTE', 'ROUTE_NAME'],
  1674. roadTypeMap: {
  1675. St: ['7-Local']
  1676. },
  1677. maxRecordCount: 1000,
  1678. supportsPagination: false
  1679. }
  1680. ],
  1681. isPermitted() {
  1682. /* return _r >= 3; */ return ['mapomatic', 'bobc455', 'js55ct'].includes(userNameLC);
  1683. },
  1684. information: { Source: 'MDT', Permission: '?', Description: 'Federal and State highways set to a minimum of mH.' },
  1685. getWhereClause(context) {
  1686. if (context.mapContext.zoom < 16) {
  1687. return `${context.layer.fcPropName}<>'LOCAL'`;
  1688. }
  1689. return null;
  1690. },
  1691. getFeatureRoadType(feature, layer) {
  1692. let rt = STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  1693. const roadID = feature.attributes.SIGN_ROUTE || feature.attributes.ROUTE_NAME;
  1694. const isUS = /^US[ -]?\d+/.test(roadID);
  1695. const isState = /^MONTANA \d+|ROUTE \d+|S-\d{3}\b/.test(roadID);
  1696. if (isUS && ['St', 'PS', 'mH'].includes(rt)) {
  1697. rt = 'MH';
  1698. } else if (isState && ['St', 'PS'].includes(rt)) {
  1699. rt = 'mH';
  1700. }
  1701. return rt;
  1702. }
  1703. },
  1704. NV: {
  1705. baseUrl: 'https://gis.dot.nv.gov/rhgis/rest/services/GeoHub/FSystem/MapServer/',
  1706. defaultColors: {
  1707. Fw: '#ff00c5',
  1708. Ew: '#4f33df',
  1709. MH: '#149ece',
  1710. mH: '#4ce600',
  1711. PS: '#cfae0e',
  1712. St: '#eeeeee'
  1713. },
  1714. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  1715. fcMapLayers: [
  1716. {
  1717. layerID: 0,
  1718. fcPropName: 'FSystem',
  1719. idPropName: 'OBJECTID',
  1720. outFields: ['OBJECTID', 'FSystem'],
  1721. roadTypeMap: {
  1722. Fw: [1],
  1723. Ew: [2],
  1724. MH: [3],
  1725. mH: [4],
  1726. PS: [5, 6],
  1727. St: [7]
  1728. },
  1729. maxRecordCount: 1000,
  1730. supportsPagination: false
  1731. }
  1732. ],
  1733. isPermitted() {
  1734. return ['mapomatic', 'turbomkt', 'tonestertm', 'geopgeop', 'ojlaw', 'js55ct'].includes(userNameLC);
  1735. },
  1736. information: { Source: 'NDOT', Permission: '?', Description: 'Raw unmodified FC data.' },
  1737. getWhereClause(context) {
  1738. if (context.mapContext.zoom < 16) {
  1739. return `${context.layer.fcPropName}<7`;
  1740. }
  1741. return null;
  1742. },
  1743. getFeatureRoadType(feature, layer) {
  1744. const fc = parseInt(feature.attributes[layer.fcPropName], 10);
  1745. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1746. }
  1747. },
  1748. NH: {
  1749. baseUrl: 'https://maps.dot.nh.gov/arcgis_server/rest/services/Highways/NHDOT_HIGHWAYS_Functional_System/FeatureServer/',
  1750. defaultColors: {
  1751. Fw: '#ff00c5',
  1752. Ew: '#4f33df',
  1753. MH: '#149ece',
  1754. mH: '#4ce600',
  1755. PS: '#cfae0e',
  1756. St: '#eeeeee'
  1757. },
  1758. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  1759. fcMapLayers: [
  1760. {
  1761. layerID: 19, // 0
  1762. fcPropName: 'FUNCT_SYSTEM',
  1763. idPropName: 'OBJECTID',
  1764. outFields: ['OBJECTID', 'FUNCT_SYSTEM', 'STREET', 'TIER'],
  1765. roadTypeMap: {
  1766. Fw: [1],
  1767. Ew: [2],
  1768. MH: [2, 3],
  1769. mH: [4],
  1770. PS: [5, 6],
  1771. St: [7, 0]
  1772. },
  1773. maxRecordCount: 1000,
  1774. supportsPagination: false
  1775. }
  1776. ],
  1777. isPermitted() {
  1778. return rank >= 2;
  1779. },
  1780. information: { Source: 'NH GRANIT', Permission: 'Visible to R2+', Description: 'Federal and State highways set to a minimum of mH.' },
  1781. getWhereClause(context) {
  1782. if (context.mapContext.zoom < 16) {
  1783. return `${context.layer.fcPropName}>0 AND ${context.layer.fcPropName}<7`;
  1784. }
  1785. return null;
  1786. },
  1787. getFeatureRoadType(feature, layer) {
  1788. let fc = parseInt(feature.attributes[layer.fcPropName], 10);
  1789. if (!(fc > 0)) {
  1790. fc = 7;
  1791. }
  1792. const route = feature.attributes.STREET_ALIASES;
  1793. const isUS = /US /.test(route);
  1794. const isState = /NH /.test(route);
  1795. if (fc === 2) fc = feature.attributes.TIER === 1 ? 1 : 3;
  1796. else if (fc > 3 && isUS) fc = /US 3B/.test(route) ? 4 : 3;
  1797. else if (fc > 4 && isState) fc = 4;
  1798. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1799. }
  1800. },
  1801. NM: {
  1802. baseUrl: 'https://services.arcgis.com/hOpd7wfnKm16p9D9/ArcGIS/rest/services/NMDOT_Functional_Class/FeatureServer/',
  1803. defaultColors: {
  1804. Fw: '#ff00c5',
  1805. Ew: '#ff00c5',
  1806. MH: '#149ece',
  1807. mH: '#4ce600',
  1808. PS: '#cfae0e',
  1809. St: '#eeeeee'
  1810. },
  1811. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  1812. fcMapLayers: [
  1813. {
  1814. layerID: 0,
  1815. fcPropName: 'FSystem',
  1816. idPropName: 'OBJECTID',
  1817. maxRecordCount: 1000,
  1818. supportsPagination: false,
  1819. outFields: ['OBJECTID', 'FSystem', 'RouteID'],
  1820. roadTypeMap: {
  1821. Fw: [1],
  1822. Ew: [2],
  1823. MH: [3],
  1824. mH: [4],
  1825. PS: [5, 6],
  1826. St: [7, 0]
  1827. }
  1828. }
  1829. ],
  1830. isPermitted() {
  1831. return true;
  1832. },
  1833. information: { Source: 'NMDOT', Permission: 'Visible to All', Description: 'Federal and State highways set to a minimum of mH.' },
  1834. getWhereClause(context) {
  1835. if (context.mapContext.zoom < 16) {
  1836. return `${context.layer.fcPropName}>0 AND ${context.layer.fcPropName}<7`;
  1837. }
  1838. return null;
  1839. },
  1840. getFeatureRoadType(feature, layer) {
  1841. let fc = parseInt(feature.attributes[layer.fcPropName], 10);
  1842. const roadType = feature.attributes.RouteID.substring(0, 2); // Get first two characters
  1843. const isBiz = roadType === 'BL'; // Interstate Business Loop
  1844. const isUS = roadType === 'US';
  1845. const isState = roadType === 'NM';
  1846.  
  1847. if (roadType === 'IX') {
  1848. fc = 0;
  1849. } else if (fc > 3 && (isBiz || isUS)) {
  1850. fc = 3;
  1851. } else if (fc > 4 && isState) {
  1852. fc = 4;
  1853. }
  1854.  
  1855. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  1856. }
  1857. },
  1858. NY: {
  1859. // https://gis.dot.ny.gov/hostingny/rest/services/Basemap/MapServer/21
  1860. baseUrl: 'https://gis.dot.ny.gov/hostingny/rest/services/',
  1861. defaultColors: {
  1862. Fw: '#ff00c5',
  1863. Ew: '#5f33df',
  1864. MH: '#149ece',
  1865. mH: '#4ce600',
  1866. PS: '#cfae0e',
  1867. St: '#eeeeee'
  1868. },
  1869. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  1870. fcMapLayers: [
  1871. {
  1872. layerID: 1,
  1873. layerPath: 'Geocortex/FC/MapServer/',
  1874. fcPropName: 'FUNC_CLASS',
  1875. idPropName: 'OBJECTID',
  1876. outFields: ['OBJECTID', 'FUNC_CLASS', 'SEGMENT_NAME', 'ROUTE_NO'],
  1877. roadTypeMap: {
  1878. Fw: [1, 11],
  1879. Ew: [2, 12],
  1880. MH: [4, 14],
  1881. mH: [6, 16],
  1882. PS: [7, 8, 17, 18],
  1883. St: [9, 19]
  1884. },
  1885. maxRecordCount: 1000,
  1886. supportsPagination: false
  1887. },
  1888. {
  1889. layerID: 21,
  1890. layerPath: 'Basemap/MapServer/',
  1891. idPropName: 'OBJECTID',
  1892. outFields: ['OBJECTID', 'SHIELD'],
  1893. maxRecordCount: 1000,
  1894. supportsPagination: false
  1895. }
  1896. ],
  1897. information: { Source: 'NYSDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Federal and State highways set to a minimum of mH.' },
  1898. getWhereClause(context) {
  1899. if (context.layer.layerID === 21) {
  1900. return "SHIELD IN ('C','CT')";
  1901. }
  1902. return null;
  1903. },
  1904. getFeatureRoadType(feature, layer) {
  1905. let roadType;
  1906. if (layer.layerID === 21) {
  1907. roadType = 'PS';
  1908. } else {
  1909. roadType = STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  1910. const routeNo = feature.attributes.ROUTE_NO;
  1911. if (/^NY.*/.test(routeNo)) {
  1912. if (roadType === 'PS') roadType = 'mH';
  1913. } else if (/^US.*/.test(routeNo)) {
  1914. if (roadType === 'PS' || roadType === 'mH') roadType = 'MH';
  1915. }
  1916. }
  1917. return roadType;
  1918. }
  1919. },
  1920. NC: {
  1921. baseUrl: 'https://gis11.services.ncdot.gov/arcgis/rest/services/NCDOT_FunctionalClassQtr/MapServer/',
  1922. defaultColors: {
  1923. Fw: '#ff00c5',
  1924. Rmp: '#999999',
  1925. Ew: '#5f33df',
  1926. MH: '#149ece',
  1927. mH: '#4ce600',
  1928. PS: '#cfae0e',
  1929. St: '#eeeeee'
  1930. },
  1931. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  1932. fcMapLayers: [
  1933. {
  1934. layerID: 0,
  1935. fcPropName: 'FuncClass',
  1936. idPropName: 'OBJECTID',
  1937. outFields: ['OBJECTID', 'FuncClass', 'RouteClass', 'RouteQualifier'],
  1938. roadTypeMap: {
  1939. Fw: [1],
  1940. Ew: [2],
  1941. MH: [3],
  1942. mH: [4],
  1943. PS: [5, 6],
  1944. St: [7]
  1945. },
  1946. zoomLevels: [3, 4, 5, 6, 7, 8, 9, 10],
  1947. maxRecordCount: 1000,
  1948. supportsPagination: false
  1949. }
  1950. ],
  1951. isPermitted() {
  1952. return rank >= 3;
  1953. },
  1954. information: { Source: 'NCDOT', Permission: 'Visible to R3+', Description: 'Federal and State highways set to a minimum of mH.' },
  1955. getWhereClause(context) {
  1956. if (context.mapContext.zoom < 16) {
  1957. const clause = `(${context.layer.fcPropName} < 7 OR RouteClass IN ('I','FED','NC','RMP','US'))`;
  1958. return clause;
  1959. }
  1960. return null;
  1961. },
  1962. getFeatureRoadType(feature, layer) {
  1963. const fc = feature.attributes[layer.fcPropName];
  1964. let roadType;
  1965. switch (this.getHwySys(feature)) {
  1966. case 'interstate':
  1967. if (fc <= 2 || !this.isBusinessRoute(feature)) roadType = 'Fw';
  1968. else roadType = 'MH';
  1969. break;
  1970. case 'us':
  1971. if (fc <= 2) roadType = 'Ew';
  1972. else if (fc === 3 || !this.isBusinessRoute(feature)) roadType = 'MH';
  1973. else roadType = 'mH';
  1974. break;
  1975. case 'state':
  1976. if (fc <= 2) roadType = 'Ew';
  1977. else if (fc === 3) roadType = 'MH';
  1978. else if (fc === 4 || !this.isBusinessRoute(feature)) roadType = 'mH';
  1979. else roadType = 'PS';
  1980. break;
  1981. case 'ramp':
  1982. roadType = 'Rmp';
  1983. break;
  1984. default:
  1985. if (fc === 2) roadType = 'Ew';
  1986. else if (fc === 3) roadType = 'MH';
  1987. else if (fc === 4) roadType = 'mH';
  1988. else if (fc <= 6) roadType = 'PS';
  1989. else roadType = 'St';
  1990. // roadType = fc === 2 ? 'Ew' : (fc === 3 ? 'MH' : (fc === 4 ? 'mH' : (fc <= 6 ? 'PS' : 'St')));
  1991. }
  1992. return roadType;
  1993. },
  1994. getHwySys(feature) {
  1995. let hwySys;
  1996. switch (feature.attributes.RouteClass.toString()) {
  1997. case '1':
  1998. hwySys = 'interstate';
  1999. break;
  2000. case '2':
  2001. hwySys = 'us';
  2002. break;
  2003. case '3':
  2004. hwySys = 'state';
  2005. break;
  2006. case '80':
  2007. hwySys = 'ramp';
  2008. break;
  2009. default:
  2010. hwySys = 'local';
  2011. }
  2012. return hwySys;
  2013. },
  2014. isBusinessRoute(feature) {
  2015. const qual = feature.attributes.RouteQualifier.toString();
  2016. return qual === '9';
  2017. }
  2018. },
  2019. ND: {
  2020. baseUrl: 'https://ndgishub.nd.gov/arcgis/rest/services/Basemap_General/MapServer/',
  2021. defaultColors: {
  2022. Fw: '#ff00c5',
  2023. Ew: '#149ece',
  2024. MH: '#149ece',
  2025. mH: '#4ce600',
  2026. PS: '#cfae0e',
  2027. St: '#eeeeee'
  2028. },
  2029. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2030. fcMapLayers: [
  2031. {
  2032. layerID: 193,
  2033. fcPropName: 'FUNCTIONAL_CLASS',
  2034. idPropName: 'OBJECTID',
  2035. outFields: ['OBJECTID', 'FUNCTIONAL_CLASS'],
  2036. roadTypeMap: {
  2037. MH: [3],
  2038. mH: [4],
  2039. PS: [5, 6],
  2040. St: [7]
  2041. },
  2042. maxRecordCount: 1000,
  2043. supportsPagination: false
  2044. },
  2045. {
  2046. layerID: 192,
  2047. fcPropName: 'RTE_SIN',
  2048. idPropName: 'OBJECTID',
  2049. outFields: ['OBJECTID', 'RTE_SIN'],
  2050. roadTypeMap: {
  2051. Fw: ['I'],
  2052. MH: ['U'],
  2053. mH: ['S']
  2054. },
  2055. maxRecordCount: 1000,
  2056. supportsPagination: false
  2057. }
  2058. ],
  2059. information: { Source: 'NDDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Federal and State highways set to a minimum of mH.' },
  2060. getWhereClause(context) {
  2061. if (context.mapContext.zoom < 16) {
  2062. if (context.layer.layerID === 193) {
  2063. return `${context.layer.fcPropName} < 7`;
  2064. }
  2065. if (context.layer.layerID === 192) {
  2066. return "RTE_SIN IN ('I','U','S')";
  2067. }
  2068. }
  2069. return null;
  2070. },
  2071. getFeatureRoadType(feature, layer) {
  2072. if (layer.getFeatureRoadType) {
  2073. return layer.getFeatureRoadType(feature);
  2074. }
  2075. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  2076. }
  2077. },
  2078. OH: {
  2079. baseUrl: 'https://gis.dot.state.oh.us/arcgis/rest/services/TIMS/Roadway_Information/MapServer/',
  2080. defaultColors: {
  2081. Fw: '#ff00c5',
  2082. Ew: '#4f33df',
  2083. MH: '#149ece',
  2084. mH: '#4ce600',
  2085. PS: '#cfae0e',
  2086. St: '#eeeeee'
  2087. },
  2088. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2089.  
  2090. fcMapLayers: [
  2091. {
  2092. layerID: 8,
  2093. fcPropName: 'FUNCTION_CLASS_CD',
  2094. idPropName: 'ObjectID',
  2095. outFields: ['FUNCTION_CLASS_CD', 'ROUTE_TYPE', 'ROUTE_NBR', 'ObjectID'],
  2096. maxRecordCount: 1000,
  2097. supportsPagination: false,
  2098. roadTypeMap: {
  2099. Fw: [1],
  2100. Ew: [2],
  2101. MH: [3],
  2102. mH: [4],
  2103. PS: [5, 6],
  2104. St: [7]
  2105. }
  2106. }
  2107. ],
  2108. isPermitted() {
  2109. return true;
  2110. },
  2111. information: { Source: 'ODOT', Permission: 'Visible to All', Description: 'Federal and State highways set to a minimum of mH.' },
  2112. getWhereClause(context) {
  2113. if (context.mapContext.zoom < 16) {
  2114. const clause = `(${context.layer.fcPropName} < 7 OR ROUTE_TYPE IN ('CR','SR','US')) AND ${context.layer.fcPropName} IS NOT NULL`;
  2115. return clause;
  2116. }
  2117. return `${context.layer.fcPropName} IS NOT NULL`;
  2118. },
  2119. getFeatureRoadType(feature, layer) {
  2120. let fc = feature.attributes[layer.fcPropName];
  2121. const prefix = feature.attributes.ROUTE_TYPE;
  2122. const isUS = prefix === 'US';
  2123. const isState = prefix === 'SR';
  2124. const isCounty = prefix === 'CR';
  2125. if (isUS && fc > 3) {
  2126. fc = 3;
  2127. }
  2128. if (isState && fc > 4) {
  2129. fc = 4;
  2130. }
  2131. if (isCounty && fc > 6) {
  2132. fc = 6;
  2133. }
  2134. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  2135. }
  2136. },
  2137. OK: {
  2138. baseUrl: 'https://services6.arcgis.com/RBtoEUQ2lmN0K3GY/arcgis/rest/services/Roadways/FeatureServer/',
  2139. defaultColors: {
  2140. Fw: '#ff00c5',
  2141. Ew: '#4f33df',
  2142. MH: '#149ece',
  2143. mH: '#4ce600',
  2144. PS: '#cfae0e',
  2145. St: '#eeeeee'
  2146. },
  2147. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2148. fcMapLayers: [
  2149. {
  2150. layerID: 0,
  2151. fcPropName: 'FUNCTIONALCLASS',
  2152. idPropName: 'OBJECTID',
  2153. outFields: ['OBJECTID', 'FUNCTIONALCLASS', 'FHWAPRIMARYROUTE', 'ODOTROUTECLASS', 'ACCESSCONTROL'],
  2154. maxRecordCount: 1000,
  2155. supportsPagination: false,
  2156. roadTypeMap: {
  2157. Fw: [1],
  2158. Ew: [2],
  2159. MH: [3],
  2160. mH: [4],
  2161. PS: [5, 6],
  2162. St: [7]
  2163. }
  2164. }
  2165. ],
  2166. information: { Source: 'ODOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Federal and State highways set to a minimum of mH.' },
  2167. getWhereClause(context) {
  2168. if (context.mapContext.zoom < 16) {
  2169. return `${context.layer.fcPropName} < 7 OR ODOTROUTECLASS IN ('U','S','I')`;
  2170. }
  2171. return null;
  2172. },
  2173. getFeatureRoadType(feature, layer) {
  2174. let fc = feature.attributes[layer.fcPropName];
  2175. const route = (feature.attributes.FHWAPRIMARYROUTE || '').trim();
  2176. const isBusinessOrSpur = /BUS$|SPR$/i.test(route);
  2177. const prefix = isBusinessOrSpur ? route.substring(0, 1) : feature.attributes.ODOTROUTECLASS;
  2178. const isFw = parseInt(feature.attributes.ACCESSCONTROL, 10) === 1;
  2179. const isInterstate = prefix === 'I';
  2180. const isUS = prefix === 'U';
  2181. const isState = prefix === 'S';
  2182. if (isFw) fc = 1;
  2183. else if (fc > 3 && ((isUS && !isBusinessOrSpur) || (isInterstate && isBusinessOrSpur))) fc = 3;
  2184. else if (fc > 4 && ((isUS && isBusinessOrSpur) || (isState && !isBusinessOrSpur))) fc = 4;
  2185. else if (fc > 5 && isState && isBusinessOrSpur) fc = 5;
  2186. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  2187. }
  2188. },
  2189. OR: {
  2190. baseUrl: 'https://gis.odot.state.or.us/arcgis1006/rest/services/transgis/catalog/MapServer/',
  2191. defaultColors: {
  2192. Fw: '#ff00c5',
  2193. Ew: '#4f33df',
  2194. MH: '#149ece',
  2195. mH: '#4ce600',
  2196. PS: '#cfae0e',
  2197. St: '#eeeeee'
  2198. },
  2199. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2200. fcMapLayers: [
  2201. {
  2202. layerID: 171,
  2203. fcPropName: 'NEW_FC_CD',
  2204. idPropName: 'OBJECTID',
  2205. outFields: ['OBJECTID', 'NEW_FC_CD'],
  2206. roadTypeMap: {
  2207. Fw: ['1'],
  2208. Ew: ['2'],
  2209. MH: ['3'],
  2210. mH: ['4'],
  2211. PS: ['5', '6'],
  2212. St: ['7']
  2213. },
  2214. maxRecordCount: 1000,
  2215. supportsPagination: false
  2216. },
  2217. {
  2218. layerID: 173,
  2219. fcPropName: 'NEW_FC_CD',
  2220. idPropName: 'OBJECTID',
  2221. outFields: ['OBJECTID', 'NEW_FC_CD'],
  2222. roadTypeMap: {
  2223. Fw: ['1'],
  2224. Ew: ['2'],
  2225. MH: ['3'],
  2226. mH: ['4'],
  2227. PS: ['5', '6'],
  2228. St: ['7']
  2229. },
  2230. maxRecordCount: 1000,
  2231. supportsPagination: false
  2232. }
  2233. ],
  2234. information: { Source: 'ODOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  2235. getWhereClause(context) {
  2236. if (context.mapContext.zoom < 16) {
  2237. return `${context.layer.fcPropName} < 7`;
  2238. }
  2239. return null;
  2240. },
  2241. getFeatureRoadType(feature, layer) {
  2242. if (layer.getFeatureRoadType) {
  2243. return layer.getFeatureRoadType(feature);
  2244. }
  2245. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  2246. }
  2247. },
  2248. PA: {
  2249. baseUrl: 'https://gis.penndot.gov/arcgis/rest/services/opendata/roadwayadmin/MapServer/',
  2250. supportsPagination: false,
  2251. defaultColors: {
  2252. Fw: '#ff00c5',
  2253. Ew: '#4f33df',
  2254. MH: '#149ece',
  2255. mH: '#4ce600',
  2256. PS: '#cfae0e',
  2257. St: '#eeeeee'
  2258. },
  2259. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2260. fcMapLayers: [
  2261. {
  2262. layerID: 0,
  2263. features: new Map(),
  2264. fcPropName: 'FUNC_CLS',
  2265. idPropName: 'MSLINK',
  2266. outFields: ['MSLINK', 'FUNC_CLS'],
  2267. maxRecordCount: 1000,
  2268. supportsPagination: false,
  2269. roadTypeMap: {
  2270. Fw: ['01', '11'],
  2271. Ew: ['12'],
  2272. MH: ['02', '14'],
  2273. mH: ['06', '16'],
  2274. PS: ['07', '08', '17'],
  2275. St: ['09', '19']
  2276. }
  2277. }
  2278. ],
  2279. isPermitted() {
  2280. return rank >= 4;
  2281. },
  2282. information: { Source: 'PennDOT', Permission: 'Visible to R4+', Description: 'Raw unmodified FC data.' },
  2283. getWhereClause(context) {
  2284. return context.mapContext.zoom < 16 ? `${context.layer.fcPropName} NOT IN ('09','19')` : null;
  2285. },
  2286. getFeatureRoadType(feature, layer) {
  2287. if (layer.getFeatureRoadType) {
  2288. return layer.getFeatureRoadType(feature);
  2289. }
  2290. const fc = feature.attributes[layer.fcPropName];
  2291. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  2292. }
  2293. },
  2294. RI: {
  2295. baseUrl: 'https://services2.arcgis.com/S8zZg9pg23JUEexQ/arcgis/rest/services/RIDOT_Roads_2016/FeatureServer/',
  2296. defaultColors: {
  2297. Fw: '#ff00c5',
  2298. Ew: '#4f33df',
  2299. MH: '#149ece',
  2300. mH: '#4ce600',
  2301. PS: '#cfae0e',
  2302. St: '#eeeeee'
  2303. },
  2304. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [[], [], [], [], [], [], [], [], [], [], []] },
  2305. fcMapLayers: [
  2306. {
  2307. layerID: 0,
  2308. fcPropName: 'F_SYSTEM',
  2309. idPropName: 'OBJECTID',
  2310. outFields: ['OBJECTID', 'F_SYSTEM', 'ROADTYPE', 'RTNO'],
  2311. roadTypeMap: {
  2312. Fw: [1],
  2313. Ew: [2],
  2314. MH: [3],
  2315. mH: [4],
  2316. PS: [5, 6],
  2317. St: [7, 0]
  2318. },
  2319. maxRecordCount: 1000,
  2320. supportsPagination: false
  2321. }
  2322. ],
  2323. isPermitted() {
  2324. return rank >= 2;
  2325. },
  2326. information: { Source: 'RIDOT', Permission: 'Visible to R2+', Description: 'Federal and State highways set to a minimum of mH.' },
  2327. getWhereClause(context) {
  2328. return context.mapContext.zoom < 16 ? `${context.layer.fcPropName} NOT IN (7,0)` : null;
  2329. },
  2330. getFeatureRoadType(feature, layer) {
  2331. let fc = parseInt(feature.attributes[layer.fcPropName], 10);
  2332. const type = feature.attributes.ROADTYPE;
  2333. const rtnum = feature.attributes.RTNO;
  2334. if (fc === 2 && ['10', '24', '37', '78', '99', '138', '403'].includes(rtnum)) fc = 1; // isFW
  2335. else if ((fc > 3 && type === 'US') || rtnum === '1') fc = 3; // isUS
  2336. else if (fc > 4 && rtnum.trim() !== '') fc = 4; // isState
  2337. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  2338. }
  2339. },
  2340. SC: {
  2341. baseUrl: 'https://services1.arcgis.com/VaY7cY9pvUYUP1Lf/ArcGIS/rest/services/FunctionalClass/FeatureServer/',
  2342. defaultColors: {
  2343. Fw: '#ff00c5',
  2344. Ew: '#4f33df',
  2345. MH: '#149ece',
  2346. mH: '#4ce600',
  2347. PS: '#cfae0e',
  2348. St: '#eeeeee'
  2349. },
  2350. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2351. fcMapLayers: [
  2352. {
  2353. layerID: 0,
  2354. fcPropName: 'Functional',
  2355. idPropName: 'FID',
  2356. outFields: ['FID', 'Functional', 'RouteType'],
  2357. maxRecordCount: 1000,
  2358. supportsPagination: false,
  2359. roadTypeMap: {
  2360. Fw: [1, 11],
  2361. Ew: [6, 12],
  2362. MH: [2, 13],
  2363. mH: [3, 14],
  2364. PS: [4, 5, 15, 16],
  2365. St: []
  2366. }
  2367. }
  2368. ],
  2369. isPermitted() {
  2370. return rank >= 4;
  2371. },
  2372. information: { Source: 'SCDOT', Permission: 'Visible to R4+', Description: 'Federal and State highways set to a minimum of mH.' },
  2373. getWhereClause() {
  2374. return null;
  2375. },
  2376. getFeatureRoadType(feature, layer) {
  2377. const SCroadType = feature.attributes.RouteType;
  2378. const isFw = SCroadType === 1;
  2379. const isUS = SCroadType === 2;
  2380. const isState = SCroadType === 4;
  2381.  
  2382. let roadType = STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  2383. if (roadType === 'Fw' || roadType === 'Ew' || isFw) {
  2384. roadType = 'Fw';
  2385. } else if ((roadType === 'mH' || roadType === 'PS') && isUS) {
  2386. roadType = 'MH';
  2387. } else if (roadType === 'PS' && isState) {
  2388. roadType = 'mH';
  2389. }
  2390. return roadType;
  2391. }
  2392. },
  2393. SD: {
  2394. baseUrl: 'https://arcgis.sd.gov/arcgis/rest/services/SD_All/Transportation_Roads/MapServer/',
  2395. defaultColors: {
  2396. Fw: '#ff00c5',
  2397. Ew: '#149ece',
  2398. MH: '#149ece',
  2399. mH: '#4ce600',
  2400. PS: '#cfae0e',
  2401. St: '#eeeeee',
  2402. PSGr: '#cc6533',
  2403. StGr: '#e99cb6'
  2404. },
  2405. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2406. fcMapLayers: [
  2407. {
  2408. layerID: 1,
  2409. fcPropName: 'HighwayClass',
  2410. idPropName: 'GisHighwayCategoryID',
  2411. maxRecordCount: 1000,
  2412. supportsPagination: false,
  2413. outFields: ['GisHighwayCategoryID', 'HighwayClass'],
  2414. roadTypeMap: {
  2415. Fw: ['IN'],
  2416. MH: ['US'],
  2417. mH: ['SD']
  2418.  
  2419. }
  2420. },
  2421. {
  2422. layerID: 2,
  2423. fcPropName: 'FUNC_CLASS',
  2424. idPropName: 'OBJECTID',
  2425. maxRecordCount: 1000,
  2426. supportsPagination: false,
  2427. outFields: ['OBJECTID', 'FUNC_CLASS', 'SURFACE_TYPE', 'ROADNAME'],
  2428. roadTypeMap: {
  2429. Fw: [1, 11],
  2430. Ew: [2, 12],
  2431. MH: [4, 14],
  2432. mH: [6, 16],
  2433. PS: [7, 8, 17],
  2434. St: [9, 19]
  2435. }
  2436. }
  2437. ],
  2438. information: { Source: 'SDDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Additional colors denote unpaved PS and LS segements.' },
  2439. getWhereClause(context) {
  2440. if (context.mapContext.zoom < 16) {
  2441. if (context.layer.layerID === 2) {
  2442. return `${context.layer.fcPropName} NOT IN (9,19)`;
  2443. }
  2444. }
  2445. return null;
  2446. },
  2447. getFeatureRoadType(feature, layer) {
  2448. const attr = feature.attributes;
  2449. let fc = attr[layer.fcPropName];
  2450. const fc2 = parseInt(fc, 10) % 10;
  2451.  
  2452. const isUS = /^US HWY /i.test(attr.ROADNAME);
  2453. const isState = /^SD HWY /i.test(attr.ROADNAME);
  2454. const isBiz = /^(US|SD) HWY .* (E|W)?(B|L)$/i.test(attr.ROADNAME);
  2455. const isPaved = parseInt(attr.SURFACE_TYPE, 10) > 5;
  2456.  
  2457. if (fc2 > 4 && isUS) fc = isBiz ? 6 : 4;
  2458. else if (fc2 > 6 && isState) fc = isBiz ? 7 : 6;
  2459. if (fc2 > 6 && !isPaved) {
  2460. return fc < 9 ? 'PSGr' : 'StGr';
  2461. }
  2462. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  2463. }
  2464. },
  2465. TN: {
  2466. baseUrl: 'https://',
  2467. defaultColors: {
  2468. Fw: '#ff00c5',
  2469. Ew: '#4f33df',
  2470. MH: '#149ece',
  2471. mH: '#4ce600',
  2472. PS: '#cfae0e',
  2473. PS2: '#cfae0e',
  2474. St: '#eeeeee'
  2475. },
  2476. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  2477. fcMapLayers: [
  2478. {
  2479. layerPath: 'services2.arcgis.com/nf3p7v7Zy4fTOh6M/ArcGIS/rest/services/Road_Segment/FeatureServer/',
  2480. maxRecordCount: 1000,
  2481. supportsPagination: false,
  2482. layerID: 0,
  2483. fcPropName: 'FUNC_CLASS',
  2484. idPropName: 'OBJECTID',
  2485. outFields: ['OBJECTID', 'FUNC_CLASS', 'NBR_RTE', 'NBR_US_RTE'],
  2486. getWhereClause(context) {
  2487. if (context.mapContext.zoom < 16) {
  2488. return `${context.layer.fcPropName} NOT LIKE '%Local'`;
  2489. }
  2490. return null;
  2491. },
  2492. roadTypeMap: {
  2493. Fw: ['Urban Interstate', 'Rural Interstate'],
  2494. Ew: ['Urban Freeway or Expressway', 'Rural Freeway or Expressway'],
  2495. MH: ['Urban Other Principal Arterial', 'Rural Other Principal Arterial'],
  2496. mH: ['Urban Minor Arterial', 'Rural Minor Arterial'],
  2497. PS: ['Urban Major Collector', 'Rural Major Collector'],
  2498. PS2: ['Urban Minor Collector', 'Rural Minor Collector'],
  2499. St: ['Urban Local', 'Rural Local']
  2500. }
  2501. }
  2502. ],
  2503. information: {
  2504. Source: 'Memphis, Nashville Area MPO',
  2505. Permission: 'Visible to R4+ or R3-AM',
  2506. Description: 'Raw unmodified FC data for the Memphis and Nashville regions only.'
  2507. },
  2508. getWhereClause(context) {
  2509. if (context.layer.getWhereClause) {
  2510. return context.layer.getWhereClause(context);
  2511. }
  2512. return null;
  2513. },
  2514. getFeatureRoadType(feature, layer) {
  2515. if (layer.getFeatureRoadType) {
  2516. return layer.getFeatureRoadType(feature);
  2517. }
  2518. let fc = STATE_SETTINGS.global.getRoadTypeFromFC(feature.attributes.FUNC_CLASS, layer);
  2519. if ((fc === 'PS' || fc === 'mH') && feature.attributes.NBR_US_RTE != null) {
  2520. fc = feature.attributes.NBR_US_RTE.endsWith('BR') ? 'mH' : 'MH';
  2521. } else if (fc === 'PS' && (feature.attributes.NBR_RTE.startsWith('SR') || feature.attributes.NBR_RTE.startsWith('TN'))) {
  2522. fc = 'mH';
  2523. }
  2524. return fc;
  2525. }
  2526. },
  2527. TX: {
  2528. baseUrl: 'https://services.arcgis.com/KTcxiTD9dsQw4r7Z/ArcGIS/rest/services/TxDOT_Functional_Classification/FeatureServer/',
  2529. defaultColors: {
  2530. Fw: '#ff00c5',
  2531. Ew: '#4f33df',
  2532. MH: '#149ece',
  2533. mH: '#4ce600',
  2534. PS: '#cfae0e',
  2535. St: '#eeeeee'
  2536. },
  2537. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1] },
  2538. fcMapLayers: [
  2539. {
  2540. layerID: 0,
  2541. fcPropName: 'F_SYSTEM',
  2542. idPropName: 'OBJECTID',
  2543. outFields: ['OBJECTID', 'F_SYSTEM', 'RTE_PRFX'],
  2544. maxRecordCount: 1000,
  2545. supportsPagination: false,
  2546. roadTypeMap: {
  2547. Fw: [1],
  2548. Ew: [2],
  2549. MH: [3],
  2550. mH: [4],
  2551. PS: [5, 6],
  2552. St: [7]
  2553. }
  2554. }
  2555. ],
  2556. isPermitted() {
  2557. return rank >= 2;
  2558. },
  2559. information: { Source: 'TxDOT', Permission: 'Visible to R2+', Description: 'Federal and State highways set to a minimum of mH.' },
  2560. getWhereClause(context) {
  2561. let where = ' F_SYSTEM IS NOT NULL AND RTE_PRFX IS NOT NULL';
  2562. if (context.mapContext.zoom < 16) {
  2563. where += ` AND ${context.layer.fcPropName} <> 7`;
  2564. }
  2565. return where;
  2566. },
  2567. getFeatureRoadType(feature, layer) {
  2568. // On-System:
  2569. // IH=Interstate BF=Business FM
  2570. // US=US Highway FM=Farm to Mkt
  2571. // UA=US Alt. RM=Ranch to Mkt
  2572. // UP=US Spur RR=Ranch Road
  2573. // SH=State Highway PR=Park Road
  2574. // SA=State Alt. RE=Rec Road
  2575. // SL=State Loop RP=Rec Rd Spur
  2576. // SS=State Spur FS=FM Spur
  2577. // BI=Business IH RS=RM Spur
  2578. // BU=Business US RU=RR Spur
  2579. // BS=Business State PA=Principal Arterial
  2580. // Off-System:
  2581. // TL=Off-System Tollroad CR=County Road
  2582. // FC=Func. Classified St. LS=Local Street
  2583. if (layer.getFeatureRoadType) {
  2584. return layer.getFeatureRoadType(feature);
  2585. }
  2586. let fc = feature.attributes[layer.fcPropName];
  2587. const type = feature.attributes.RTE_PRFX.substring(0, 2).toUpperCase();
  2588. if (type === 'IH' && fc > 1) {
  2589. fc = 1;
  2590. } else if ((type === 'US' || type === 'BI' || type === 'UA') && fc > 3) {
  2591. fc = 3;
  2592. } else if ((type === 'UP' || type === 'BU' || type === 'SH' || type === 'SA') && fc > 4) {
  2593. fc = 4;
  2594. } else if ((type === 'SL' || type === 'SS' || type === 'BS') && fc > 6) {
  2595. fc = 6;
  2596. }
  2597. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  2598. }
  2599. },
  2600. UT: {
  2601. baseUrl: 'https://roads.udot.utah.gov/server/rest/services/Public/Functional_Class/MapServer/0',
  2602. defaultColors: {
  2603. Fw: '#ff00c5',
  2604. Ew: '#4f33df',
  2605. MH: '#149ece',
  2606. mH: '#4ce600',
  2607. PS: '#cfae0e',
  2608. St: '#eeeeee'
  2609. },
  2610. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2611. fcMapLayers: [
  2612. {
  2613. layerID: 0,
  2614. fcPropName: 'FUNCTIONAL_CLASS',
  2615. idPropName: 'OBJECTID',
  2616. outFields: ['OBJECTID', 'FUNCTIONAL_CLASS', 'route_id'],
  2617. roadTypeMap: {
  2618. Fw: [1],
  2619. Ew: [2],
  2620. MH: [3],
  2621. mH: [4],
  2622. PS: [5, 6],
  2623. St: [7]
  2624. },
  2625. maxRecordCount: 1000,
  2626. supportsPagination: false
  2627. }
  2628. ],
  2629. information: { Source: 'UDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Federal and State highways set to a minimum of mH.' },
  2630. getWhereClause(context) {
  2631. return `${context.layer.fcPropName} NOT LIKE 'Proposed%'`;
  2632. },
  2633. getFeatureRoadType(feature, layer) {
  2634. const attr = feature.attributes;
  2635. let fc = attr[layer.fcPropName];
  2636. const routeID = attr.route_id;
  2637. const roadNum = parseInt(routeID.substring(0, 4), 10);
  2638. switch (fc) {
  2639. case 'Interstate':
  2640. fc = 1;
  2641. break;
  2642. case 'Other Freeways and Expressways':
  2643. fc = 2;
  2644. break;
  2645. case 'Other Principal Arterial':
  2646. fc = 3;
  2647. break;
  2648. case 'Minor Arterial':
  2649. fc = 4;
  2650. break;
  2651. case 'Major Collector':
  2652. fc = 5;
  2653. break;
  2654. case 'Minor Collector':
  2655. fc = 6;
  2656. break;
  2657. default:
  2658. fc = 7;
  2659. }
  2660. const re = /^(6|40|50|89|91|163|189|191|491)$/;
  2661. if (re.test(roadNum) && fc > 3) {
  2662. // US highway
  2663. fc = 3;
  2664. } else if (roadNum <= 491 && fc > 4) {
  2665. // State highway
  2666. fc = 4;
  2667. }
  2668. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  2669. }
  2670. },
  2671. VT: {
  2672. baseUrl: 'https://maps.vtrans.vermont.gov/arcgis/rest/services/Master/General/FeatureServer/',
  2673. defaultColors: {
  2674. Fw: '#ff00c5',
  2675. Ew: '#4f33df',
  2676. MH: '#149ece',
  2677. mH: '#4ce600',
  2678. PS: '#cfae0e',
  2679. St: '#eeeeee'
  2680. },
  2681. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2682. fcMapLayers: [
  2683. {
  2684. layerID: 39,
  2685. fcPropName: 'FUNCL',
  2686. idPropName: 'OBJECTID',
  2687. outFields: ['OBJECTID', 'FUNCL', 'HWYSIGN'],
  2688. roadTypeMap: {
  2689. Fw: [1],
  2690. Ew: [2],
  2691. MH: [3],
  2692. mH: [4],
  2693. PS: [5, 6],
  2694. St: [7]
  2695. },
  2696. maxRecordCount: 1000,
  2697. supportsPagination: false
  2698. }
  2699. ],
  2700. information: { Source: 'VTrans', Permission: 'Visible to R2+' },
  2701. isPermitted() {
  2702. return rank >= 2;
  2703. },
  2704. getWhereClause(context) {
  2705. if (context.mapContext.zoom < 16) {
  2706. return `${context.layer.fcPropName}<>7 AND ${context.layer.fcPropName}<>0`;
  2707. }
  2708. return null;
  2709. },
  2710. getFeatureRoadType(feature, layer) {
  2711. const roadID = feature.attributes.HWYSIGN;
  2712. let fc = feature.attributes[layer.fcPropName];
  2713. if (!(fc > 0)) {
  2714. fc = 7;
  2715. }
  2716. const isUS = /^U/.test(roadID);
  2717. const isState = /^V/.test(roadID);
  2718. const isUSBiz = /^B/.test(roadID);
  2719. if (fc > 3 && isUS) fc = 3;
  2720. else if (fc > 4 && (isUSBiz || isState)) fc = 4;
  2721. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  2722. }
  2723. },
  2724. VA: {
  2725. baseUrl: 'https://services.arcgis.com/p5v98VHDX9Atv3l7/arcgis/rest/services/FC_2014_FHWA_Submittal1/FeatureServer/',
  2726. defaultColors: {
  2727. Fw: '#ff00c5',
  2728. Ew: '#ff00c5',
  2729. MH: '#149ece',
  2730. mH: '#4ce600',
  2731. PS: '#cfae0e',
  2732. St: '#eeeeee'
  2733. },
  2734. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2735. fcMapLayers: [
  2736. {
  2737. layerID: 0,
  2738. fcPropName: 'STATE_FUNCT_CLASS_ID',
  2739. idPropName: 'OBJECTID',
  2740. outFields: ['OBJECTID', 'STATE_FUNCT_CLASS_ID', 'RTE_NM'],
  2741. maxRecordCount: 2000,
  2742. supportsPagination: true,
  2743. roadTypeMap: {
  2744. Fw: [1],
  2745. Ew: [2],
  2746. MH: [3],
  2747. mH: [4],
  2748. PS: [5, 6],
  2749. St: [7]
  2750. }
  2751. },
  2752. {
  2753. layerID: 1,
  2754. fcPropName: 'STATE_FUNCT_CLASS_ID',
  2755. idPropName: 'OBJECTID',
  2756. outFields: ['OBJECTID', 'STATE_FUNCT_CLASS_ID', 'Opp_RTE_NM', 'ROUTE_NO'],
  2757. maxRecordCount: 2000,
  2758. supportsPagination: true,
  2759. roadTypeMap: {
  2760. Fw: [1],
  2761. Ew: [2],
  2762. MH: [3],
  2763. mH: [4],
  2764. PS: [5, 6],
  2765. St: [7]
  2766. }
  2767. },
  2768. {
  2769. layerID: 3,
  2770. fcPropName: 'TMPD_FC',
  2771. idPropName: 'OBJECTID',
  2772. outFields: ['OBJECTID', 'TMPD_FC', 'RTE_NM'],
  2773. maxRecordCount: 2000,
  2774. supportsPagination: true,
  2775. roadTypeMap: {
  2776. Fw: [1],
  2777. Ew: [2],
  2778. MH: [3],
  2779. mH: [4],
  2780. PS: [5, 6],
  2781. St: [7]
  2782. }
  2783. }
  2784. ],
  2785. information: { Source: 'VDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Federal and State highways set to a minimum of mH.' },
  2786. srExceptions: [
  2787. 217, 302, 303, 305, 308, 310, 313, 314, 315, 317, 318, 319, 320, 321, 322, 323, 324, 325,
  2788. 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 339, 341, 342, 343, 344, 345, 346,
  2789. 347, 348, 350, 353, 355, 357, 358, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371,
  2790. 372, 373, 374, 375, 376, 377, 378, 379, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391,
  2791. 392, 393, 394, 396, 397, 398, 399, 785, 895
  2792. ],
  2793. getWhereClause(context) {
  2794. if (context.mapContext.zoom < 16) {
  2795. return `${context.layer.fcPropName}<7`;
  2796. }
  2797. // NOTE: As of 9/14/2016 there does not appear to be any US/SR/VA labeled routes with FC = 7.
  2798. return null;
  2799. },
  2800. getFeatureRoadType(feature, layer) {
  2801. if (layer.getFeatureRoadType) {
  2802. return layer.getFeatureRoadType(feature);
  2803. }
  2804. let fc = parseInt(feature.attributes[layer.fcPropName], 10);
  2805. const rtName = feature.attributes.RTE_NM || feature.attributes.Opp_RTE_NM;
  2806. const match = /^R-VA\s*(US|VA|SR)(\d{5})..(BUS)?/.exec(rtName);
  2807. const isBusiness = match && match !== null && match[3] === 'BUS';
  2808. const isState = match && match !== null && (match[1] === 'VA' || match[1] === 'SR');
  2809. let rtNumText;
  2810. if (layer.layerID === 1) {
  2811. rtNumText = feature.attributes.ROUTE_NO;
  2812. } else if (match) {
  2813. // eslint-disable-next-line prefer-destructuring
  2814. rtNumText = match[2];
  2815. } else {
  2816. rtNumText = '99999';
  2817. }
  2818. const rtNum = parseInt(rtNumText, 10);
  2819. const rtPrefix = match && match[1];
  2820. if (fc > 3 && rtPrefix === 'US') {
  2821. fc = isBusiness ? 4 : 3;
  2822. } else if (isState && fc > 4 && this.srExceptions.indexOf(rtNum) === -1 && rtNum < 600) {
  2823. fc = isBusiness ? 5 : 4;
  2824. }
  2825. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  2826. }
  2827. },
  2828. WA: {
  2829. baseUrl: 'https://data.wsdot.wa.gov/arcgis/rest/services/FunctionalClass/WSDOTFunctionalClassMap/MapServer/',
  2830. defaultColors: {
  2831. Fw: '#ff00c5',
  2832. Ew: '#4f33df',
  2833. MH: '#149ece',
  2834. mH: '#4ce600',
  2835. PS: '#cfae0e',
  2836. St: '#eeeeee'
  2837. },
  2838. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2839. fcMapLayers: [
  2840. {
  2841. layerID: 2,
  2842. fcPropName: 'FederalFunctionalClassCode',
  2843. idPropName: 'OBJECTID',
  2844. outFields: ['OBJECTID', 'FederalFunctionalClassCode'],
  2845. roadTypeMap: {
  2846. Fw: [1],
  2847. Ew: [2],
  2848. MH: [3],
  2849. mH: [4],
  2850. PS: [5, 6],
  2851. St: [7]
  2852. },
  2853. maxRecordCount: 1000,
  2854. supportsPagination: false
  2855. },
  2856. {
  2857. layerID: 1,
  2858. fcPropName: 'FederalFunctionalClassCode',
  2859. idPropName: 'OBJECTID',
  2860. outFields: ['OBJECTID', 'FederalFunctionalClassCode'],
  2861. roadTypeMap: {
  2862. Fw: [1],
  2863. Ew: [2],
  2864. MH: [3],
  2865. mH: [4],
  2866. PS: [5, 6],
  2867. St: [7]
  2868. },
  2869. maxRecordCount: 1000,
  2870. supportsPagination: false
  2871. },
  2872. {
  2873. layerID: 4,
  2874. fcPropName: 'FederalFunctionalClassCode',
  2875. idPropName: 'OBJECTID',
  2876. outFields: ['OBJECTID', 'FederalFunctionalClassCode'],
  2877. roadTypeMap: {
  2878. Fw: [1],
  2879. Ew: [2],
  2880. MH: [3],
  2881. mH: [4],
  2882. PS: [5, 6],
  2883. St: [7]
  2884. },
  2885. maxRecordCount: 1000,
  2886. supportsPagination: false
  2887. }
  2888. ],
  2889. information: { Source: 'WSDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Raw unmodified FC data.' },
  2890. getWhereClause(context) {
  2891. if (context.mapContext.zoom < 16) {
  2892. return `${context.layer.fcPropName} <> 7`;
  2893. }
  2894. return null;
  2895. },
  2896. getFeatureRoadType(feature, layer) {
  2897. if (layer.getFeatureRoadType) {
  2898. return layer.getFeatureRoadType(feature);
  2899. }
  2900. return STATE_SETTINGS.global.getFeatureRoadType(feature, layer);
  2901. }
  2902. },
  2903. WV: {
  2904. baseUrl: 'https://gis.transportation.wv.gov/arcgis/rest/services/Routes/MapServer/',
  2905. defaultColors: {
  2906. Fw: '#ff00c5',
  2907. Ew: '#ff00c5',
  2908. MH: '#149ece',
  2909. mH: '#4ce600',
  2910. PS: '#cfae0e',
  2911. St: '#eeeeee'
  2912. },
  2913. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2914. fcMapLayers: [
  2915. {
  2916. layerID: 2,
  2917. fcPropName: 'NAT_FUNCTIONAL_CLASS',
  2918. idPropName: 'OBJECTID',
  2919. outFields: ['OBJECTID', 'NAT_FUNCTIONAL_CLASS', 'ROUTE_ID'],
  2920. maxRecordCount: 1000,
  2921. supportsPagination: true,
  2922. roadTypeMap: {
  2923. Fw: [1],
  2924. Ew: [2],
  2925. MH: [3],
  2926. mH: [4],
  2927. PS: [5, 6],
  2928. St: [7]
  2929. }
  2930. }
  2931. ],
  2932. information: { Source: 'WV DOT' },
  2933. isPermitted() {
  2934. return true;
  2935. },
  2936. getWhereClause(context) {
  2937. if (context.mapContext.zoom < 16) {
  2938. return `${context.layer.fcPropName} NOT IN (9,19)`;
  2939. }
  2940. return null;
  2941. },
  2942. getFeatureRoadType(feature, layer) {
  2943. if (layer.getFeatureRoadType) {
  2944. return layer.getFeatureRoadType(feature);
  2945. }
  2946. const fcCode = feature.attributes[layer.fcPropName];
  2947. let fc = fcCode;
  2948. if (fcCode === 11) fc = 1;
  2949. else if (fcCode === 4 || fcCode === 12) fc = 2;
  2950. else if (fcCode === 2 || fcCode === 14) fc = 3;
  2951. else if (fcCode === 6 || fcCode === 16) fc = 4;
  2952. else if (fcCode === 7 || fcCode === 17 || fcCode === 8 || fcCode === 18) fc = 5;
  2953. else fc = 7;
  2954. const id = feature.attributes.ROUTE_ID;
  2955. const prefix = id.substr(2, 1);
  2956. const isInterstate = prefix === '1';
  2957. const isUS = prefix === '2';
  2958. const isState = prefix === '3';
  2959. if (fc > 1 && isInterstate) fc = 1;
  2960. else if (fc > 3 && isUS) fc = 3;
  2961. else if (fc > 4 && isState) fc = 4;
  2962. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  2963. }
  2964. },
  2965. WY: {
  2966. baseUrl: 'https://gisservices.wyoroad.info/arcgis/rest/services/ITSM/ITSM_Data_Layers/MapServer/',
  2967. defaultColors: {
  2968. Fw: '#ff00c5',
  2969. Ew: '#4f33df',
  2970. MH: '#149ece',
  2971. mH: '#4ce600',
  2972. PS: '#cfae0e',
  2973. St: '#eeeeee'
  2974. },
  2975. zoomSettings: { maxOffset: [30, 15, 8, 4, 2, 1, 1, 1, 1, 1], excludeRoadTypes: [['St'], ['St'], ['St'], ['St'], [], [], [], [], [], [], []] },
  2976. fcMapLayers: [
  2977. {
  2978. layerID: 20,
  2979. fcPropName: 'classification',
  2980. idPropName: 'objectid',
  2981. outFields: ['objectid', 'classification', 'common_route_name'],
  2982. roadTypeMap: {
  2983. Fw: [1],
  2984. Ew: [2],
  2985. MH: [3],
  2986. mH: [4],
  2987. PS: [5, 6],
  2988. St: [7]
  2989. },
  2990. maxRecordCount: 1000,
  2991. supportsPagination: false
  2992. },
  2993. {
  2994. layerID: 21,
  2995. fcPropName: 'classification',
  2996. idPropName: 'objectid',
  2997. outFields: ['objectid', 'classification', 'common_route_name'],
  2998. roadTypeMap: {
  2999. Fw: [1],
  3000. Ew: [2],
  3001. MH: [3],
  3002. mH: [4],
  3003. PS: [5, 6],
  3004. St: [7]
  3005. },
  3006. maxRecordCount: 1000,
  3007. supportsPagination: false
  3008. },
  3009. {
  3010. layerID: 22,
  3011. fcPropName: 'classification',
  3012. idPropName: 'objectid',
  3013. outFields: ['objectid', 'classification', 'common_route_name'],
  3014. roadTypeMap: {
  3015. Fw: [1],
  3016. Ew: [2],
  3017. MH: [3],
  3018. mH: [4],
  3019. PS: [5, 6],
  3020. St: [7]
  3021. },
  3022. maxRecordCount: 1000,
  3023. supportsPagination: false
  3024. },
  3025. {
  3026. layerID: 23,
  3027. fcPropName: 'classification',
  3028. idPropName: 'objectid',
  3029. outFields: ['objectid', 'classification', 'common_route_name'],
  3030. roadTypeMap: {
  3031. Fw: [1],
  3032. Ew: [2],
  3033. MH: [3],
  3034. mH: [4],
  3035. PS: [5, 6],
  3036. St: [7]
  3037. },
  3038. maxRecordCount: 1000,
  3039. supportsPagination: false
  3040. },
  3041. {
  3042. layerID: 24,
  3043. fcPropName: 'classification',
  3044. idPropName: 'objectid',
  3045. outFields: ['objectid', 'classification', 'common_route_name'],
  3046. roadTypeMap: {
  3047. Fw: [1],
  3048. Ew: [2],
  3049. MH: [3],
  3050. mH: [4],
  3051. PS: [5, 6],
  3052. St: [7]
  3053. },
  3054. maxRecordCount: 1000,
  3055. supportsPagination: false
  3056. },
  3057. {
  3058. layerID: 25,
  3059. fcPropName: 'classification',
  3060. idPropName: 'objectid',
  3061. outFields: ['objectid', 'classification', 'common_route_name'],
  3062. roadTypeMap: {
  3063. Fw: [1],
  3064. Ew: [2],
  3065. MH: [3],
  3066. mH: [4],
  3067. PS: [5, 6],
  3068. St: [7]
  3069. },
  3070. maxRecordCount: 1000,
  3071. supportsPagination: false
  3072. },
  3073. {
  3074. layerID: 26,
  3075. fcPropName: 'classification',
  3076. idPropName: 'objectid',
  3077. outFields: ['objectid', 'classification', 'common_route_name'],
  3078. roadTypeMap: {
  3079. Fw: [1],
  3080. Ew: [2],
  3081. MH: [3],
  3082. mH: [4],
  3083. PS: [5, 6],
  3084. St: [7]
  3085. },
  3086. maxRecordCount: 1000,
  3087. supportsPagination: false
  3088. }
  3089. ],
  3090. information: { Source: 'WYDOT', Permission: 'Visible to R4+ or R3-AM', Description: 'Minimum suggested FC.' },
  3091. getWhereClause(context) {
  3092. if (context.mapContext.zoom < 16) {
  3093. return `${context.layer.fcPropName} <> 'Local'`;
  3094. }
  3095. return null;
  3096. },
  3097. getFeatureRoadType(feature, layer) {
  3098. const attr = feature.attributes;
  3099. let fc = parseInt(attr[layer.fcPropName], 10);
  3100. const route = attr.common_route_name;
  3101. const isIntBiz = /I (25|80) BUS/.test(route);
  3102. const isUS = /US \d+/.test(route);
  3103. const isUSBiz = /US \d+ BUS/.test(route);
  3104. const isState = /WY \d+/.test(route);
  3105. const isStateBiz = /WY \d+ BUS/.test(route);
  3106. if (fc > 3 && (isUS || isIntBiz)) fc = isUSBiz ? 4 : 3;
  3107. else if (fc > 4 && isState) fc = isStateBiz ? 5 : 4;
  3108. return STATE_SETTINGS.global.getRoadTypeFromFC(fc, layer);
  3109. }
  3110. }
  3111. };
  3112.  
  3113. function log(message, object) {
  3114. if (object !== undefined) {
  3115. console.log('FC Layer:', message, object);
  3116. } else {
  3117. console.log('FC Layer:', message);
  3118. }
  3119. }
  3120.  
  3121. function debugLog(message, object) {
  3122. if (debug) {
  3123. if (object !== undefined) {
  3124. console.debug('FC Layer:', message, object);
  3125. } else {
  3126. console.debug('FC Layer:', message);
  3127. }
  3128. }
  3129. }
  3130.  
  3131. function errorLog(message, object) {
  3132. if (object !== undefined) {
  3133. console.error('FC Layer:', message, object);
  3134. } else {
  3135. console.error('FC Layer:', message);
  3136. }
  3137. }
  3138.  
  3139. function loadSettingsFromStorage() {
  3140. const storedSettings = $.parseJSON(localStorage.getItem(settingsStoreName)) || {};
  3141. const defaultSettings = {
  3142. layerVisible: true,
  3143. activeStateAbbr: 'ALL',
  3144. hideStreet: false
  3145. };
  3146. settings = { ...defaultSettings, ...storedSettings };
  3147. }
  3148.  
  3149. function saveSettingsToStorage() {
  3150. if (localStorage) {
  3151. // In case the layer is turned off some other way...
  3152. settings.layerVisible = sdk.Map.isLayerVisible({ layerName });
  3153. localStorage.setItem(settingsStoreName, JSON.stringify(settings));
  3154. }
  3155. }
  3156.  
  3157. function sortArray(array) {
  3158. array.sort((a, b) => {
  3159. if (a < b) return -1;
  3160. if (a > b) return 1;
  3161. return 0;
  3162. });
  3163. }
  3164.  
  3165. function getVisibleStateAbbreviations() {
  3166. const { activeStateAbbr } = settings;
  3167. return sdk.DataModel.States.getAll()
  3168. .map(state => STATES_HASH[state.name])
  3169. .filter(stateAbbr => STATE_SETTINGS[stateAbbr] && STATE_SETTINGS.global.isPermitted(stateAbbr) && (!activeStateAbbr || activeStateAbbr === 'ALL' || activeStateAbbr === stateAbbr));
  3170. }
  3171.  
  3172. function getAsync(url, context) {
  3173. debugLog(
  3174. `Fetching data for ${context.stateAbbr} from ${context.state.baseUrl}
  3175. ${context.layer.layerPath ? context.layer.layerPath : ''}${context.layer.layerID ? context.layer.layerID : ''}`,
  3176. context
  3177. );
  3178.  
  3179. return new Promise((resolve, reject) => {
  3180. GM_xmlhttpRequest({
  3181. context,
  3182. method: 'GET',
  3183. url,
  3184. onload(res) {
  3185. if (res.status.toString() === '200') {
  3186. const parsedResponse = JSON.parse(res.responseText);
  3187. if (parsedResponse.error) {
  3188. reject(new Error(`API Error: ${parsedResponse.error.message}`));
  3189. } else {
  3190. resolve({ responseText: res.responseText, context });
  3191. }
  3192. } else {
  3193. reject(new Error(`HTTP ${res.status}: ${res.responseText}`));
  3194. }
  3195. },
  3196. onerror() {
  3197. reject(new Error('Network Error'));
  3198. }
  3199. });
  3200. });
  3201. }
  3202.  
  3203. function getUrl(context, queryType, queryParams) {
  3204. const { extent } = context.mapContext;
  3205. const { zoom } = context.mapContext;
  3206. const { layer } = context;
  3207. const { state } = context;
  3208.  
  3209. const whereParts = [];
  3210.  
  3211. const geometry = {
  3212. xmin: extent[0],
  3213. ymin: extent[1],
  3214. xmax: extent[2],
  3215. ymax: extent[3],
  3216. spatialReference: {
  3217. wkid: 4326
  3218. }
  3219. };
  3220. const geometryStr = JSON.stringify(geometry);
  3221. const stateWhereClause = state.getWhereClause(context);
  3222. const layerPath = layer.layerPath || '';
  3223. let url = `${state.baseUrl + layerPath + layer.layerID}/query?geometry=${encodeURIComponent(geometryStr)}`;
  3224.  
  3225. if (queryType === 'countOnly') {
  3226. url += '&returnCountOnly=true';
  3227. } else if (queryType === 'idsOnly') {
  3228. url += '&returnIdsOnly=true';
  3229. } else if (queryType === 'paged') {
  3230. // TODO
  3231. } else {
  3232. // Convert to degrees (4326) from the old Meters (3857)
  3233. url += `&returnGeometry=true&maxAllowableOffset=${state.zoomSettings.maxOffset[zoom - 12] / 111000}`;
  3234. url += `&outFields=${encodeURIComponent(layer.outFields.join(','))}`;
  3235. if (queryType === 'idRange') {
  3236. whereParts.push(`(${queryParams.idFieldName}>=${queryParams.range[0]} AND ${queryParams.idFieldName}<=${queryParams.range[1]})`);
  3237. }
  3238. }
  3239. if (stateWhereClause) whereParts.push(stateWhereClause);
  3240. if (whereParts.length > 0) url += `&where=${encodeURIComponent(whereParts.join(' AND '))}`;
  3241. url += '&spatialRel=esriSpatialRelIntersects&geometryType=esriGeometryEnvelope&inSR=4326&outSR=4326&f=json'; // &geometryPrecision=7 Not needed as it scales with maxAllowableOffset
  3242. // debugLog(`URL Fetch Type = ${queryType}`, url );
  3243. return url;
  3244. }
  3245.  
  3246. function convertFcToRoadTypeLineStrings(feature, context) {
  3247. const { state, stateAbbr, layer } = context;
  3248. const roadType = state.getFeatureRoadType(feature, layer);
  3249. const attr = {
  3250. state: stateAbbr,
  3251. layerID: layer.layerID,
  3252. roadType,
  3253. color: state.defaultColors[roadType]
  3254. };
  3255.  
  3256. const lineStrings = feature.geometry.paths.map(path => {
  3257. const line = turf.lineString(path, attr);
  3258. line.id = 0;
  3259. return line;
  3260. });
  3261.  
  3262. return lineStrings;
  3263. }
  3264.  
  3265. function fetchLayerFC(context) {
  3266. const url = getUrl(context, 'idsOnly');
  3267. context.idsOnlyURL = url;
  3268. if (!context.parentContext.cancel) {
  3269. return getAsync(url, context)
  3270. .bind(context)
  3271. .then(res => {
  3272. try {
  3273. const ids = JSON.parse(res.responseText);
  3274. if (!ids.objectIds) ids.objectIds = [];
  3275. if (ids.objectIds.length === 0) {
  3276. debugLog(`objectIds array is empty or undefined for State "${context.stateAbbr}" Layer "${
  3277. context.layer.layerID}" with context:`, context);
  3278. }
  3279. sortArray(ids.objectIds);
  3280. // debugLog(``,ids);
  3281. return ids;
  3282. } catch (err) {
  3283. errorLog(`Error parsing URL response JSON for State "${context.stateAbbr}" Layer "${context.layer.layerID}" with context:`, context);
  3284. throw err;
  3285. }
  3286. })
  3287. .then(res => {
  3288. const idRanges = [];
  3289. if (res.objectIds) {
  3290. const len = res.objectIds ? res.objectIds.length : 0;
  3291. let currentIndex = 0;
  3292. const offset = Math.min(context.layer.maxRecordCount, 1000);
  3293. while (currentIndex < len) {
  3294. let nextIndex = currentIndex + offset;
  3295. if (nextIndex >= len) nextIndex = len - 1;
  3296. idRanges.push({
  3297. range: [res.objectIds[currentIndex], res.objectIds[nextIndex]],
  3298. idFieldName: res.objectIdFieldName
  3299. });
  3300. currentIndex = nextIndex + 1;
  3301. }
  3302. }
  3303. return idRanges;
  3304. })
  3305. .map(idRange => {
  3306. if (!context.parentContext.cancel) {
  3307. const newUrl = getUrl(context, 'idRange', idRange);
  3308. context.idRangeURL = newUrl;
  3309. return getAsync(newUrl, context).then(res => {
  3310. if (!context.parentContext.cancel) {
  3311. let { features } = JSON.parse(res.responseText);
  3312. context.parentContext.callCount++;
  3313. features = features || [];
  3314. return features.map(feature => convertFcToRoadTypeLineStrings(feature, context)).filter(feature => !(feature[0].properties.roadType === 'St' && settings.hideStreet));
  3315. }
  3316. return null;
  3317. });
  3318. }
  3319. // debugLog('Async call cancelled');
  3320. return null;
  3321. });
  3322. }
  3323. return null;
  3324. }
  3325.  
  3326. function fetchStateFC(context) {
  3327. const state = STATE_SETTINGS[context.stateAbbr];
  3328. const contexts = state.fcMapLayers.map(layer => ({
  3329. parentContext: context.parentContext,
  3330. layer,
  3331. state,
  3332. stateAbbr: context.stateAbbr,
  3333. mapContext: context.mapContext
  3334. }));
  3335.  
  3336. return Promise.map(contexts, ctx => fetchLayerFC(ctx).catch(err => {
  3337. const errorMessage = `
  3338. | Failed to fetch layer:
  3339. | State: ${ctx.stateAbbr}
  3340. | layerID: ${ctx.layer.layerID}
  3341. | Base URL: ${ctx.state.baseUrl}.
  3342. | ${err.message}.
  3343. `;
  3344. return Promise.reject(new Error(errorMessage.trim()));
  3345. }));
  3346. }
  3347.  
  3348. let _lastPromise = null;
  3349. let _lastContext = null;
  3350. let _fcCallCount = 0;
  3351.  
  3352. function getArrayDepth(arr) {
  3353. if (Array.isArray(arr)) {
  3354. return 1 + Math.max(0, ...arr.map(getArrayDepth));
  3355. }
  3356. return 0;
  3357. }
  3358.  
  3359. function fetchAllFC() {
  3360. if (!sdk.Map.isLayerVisible({ layerName })) return;
  3361.  
  3362. if (_lastPromise) {
  3363. _lastPromise.cancel();
  3364. }
  3365. $('#fc-loading-indicator').css('color', 'green').html('<span>Loading FC Layers ...</span>');
  3366.  
  3367. const mapContext = { zoom: sdk.Map.getZoomLevel(), extent: sdk.Map.getMapExtent() };
  3368. if (mapContext.zoom > MIN_ZOOM_LEVEL) {
  3369. const parentContext = { callCount: 0, startTime: Date.now() };
  3370.  
  3371. if (_lastContext) _lastContext.cancel = true;
  3372. _lastContext = parentContext;
  3373.  
  3374. const contexts = getVisibleStateAbbreviations().map(stateAbbr => ({ parentContext, stateAbbr, mapContext }));
  3375. let errorOccurred = false; // Flag to track error state
  3376. let featureCount = 0;
  3377.  
  3378. const map = Promise.map(contexts, ctx => fetchStateFC(ctx))
  3379. .then(statesLineStringArrays => {
  3380. if (!parentContext.cancel) {
  3381. sdk.Map.removeAllFeaturesFromLayer({ layerName });
  3382.  
  3383. // Determine the depth of the nested array structure
  3384. const depth = getArrayDepth(statesLineStringArrays);
  3385. debugLog(`Detected array depth: ${depth}`);
  3386.  
  3387. // Flatten the array based on the detected depth
  3388. const flattenedFeatures = statesLineStringArrays.flat(depth);
  3389. featureCount = flattenedFeatures.length;
  3390.  
  3391. // Add all features to the layer at once
  3392. sdk.Map.dangerouslyAddFeaturesToLayerWithoutValidation({
  3393. layerName,
  3394. features: flattenedFeatures
  3395. });
  3396. }
  3397. return statesLineStringArrays;
  3398. })
  3399. .catch(e => {
  3400. const formattedMessage = e.message.replace(/\|/g, '<br>');
  3401. $('#fc-loading-indicator').css('color', 'red').html(`${formattedMessage}`);
  3402. errorOccurred = true;
  3403. errorLog(e.message);
  3404. })
  3405. .finally(() => {
  3406. _fcCallCount -= 1;
  3407. if (_fcCallCount === 0 && !errorOccurred) {
  3408. $('#fc-loading-indicator').html('<span></span>');
  3409. }
  3410.  
  3411. const endTime = Date.now();
  3412. const durationSeconds = (endTime - parentContext.startTime) / 1000;
  3413. debugLog(`Loaded ${featureCount} features in ${durationSeconds.toFixed(2)} seconds`);
  3414. });
  3415.  
  3416. _fcCallCount += 1;
  3417. _lastPromise = map;
  3418. } else {
  3419. // if zoomed out too far, clear the layer
  3420. sdk.Map.removeAllFeaturesFromLayer({ layerName });
  3421. }
  3422. }
  3423.  
  3424. function onLayerCheckboxChanged(args) {
  3425. setEnabled(args.checked);
  3426. }
  3427.  
  3428. function checkLayerZIndex() {
  3429. try {
  3430. if (sdk.Map.getLayerZIndex({ layerName }) !== MAP_LAYER_Z_INDEX) {
  3431. // ("ADJUSTED FC LAYER Z-INDEX " + mapLayerZIndex + ', ' + mapLayer.getZIndex());
  3432. sdk.Map.setLayerZIndex({ layerName, zIndex: MAP_LAYER_Z_INDEX });
  3433. }
  3434. } catch {
  3435. // ignore this hack if it crashes
  3436. }
  3437. }
  3438.  
  3439. function initLayer() {
  3440. const styleRules = [
  3441. {
  3442. style: {
  3443. strokeColor: 'black',
  3444. strokeDashstyle: 'solid',
  3445. strokeOpacity: 1.0,
  3446. strokeWidth: '15'
  3447. }
  3448. }
  3449. ];
  3450. for (let zoom = 12; zoom < 22; zoom++) {
  3451. styleRules.push({
  3452. // eslint-disable-next-line no-loop-func
  3453. predicate: () => sdk.Map.getZoomLevel() === zoom,
  3454. style: {
  3455. strokeWidth: 12 * 1.15 ** (zoom - 13)
  3456. }
  3457. });
  3458. }
  3459. Object.values(STATE_SETTINGS)
  3460. .filter(state => !!state.defaultColors)
  3461. .forEach(state => Object.values(state.defaultColors).forEach(color => {
  3462. if (!styleRules.some(rule => rule.style.strokeColor === color)) {
  3463. styleRules.push({
  3464. predicate: props => props.color === color,
  3465. style: { strokeColor: color }
  3466. });
  3467. }
  3468. }));
  3469.  
  3470. STATE_SETTINGS.global.roadTypes.forEach((roadType, index) => {
  3471. styleRules.push({
  3472. predicate: props => props.roadType === roadType,
  3473. style: { graphicZIndex: index * 100 }
  3474. });
  3475. });
  3476. sdk.Map.addLayer({
  3477. layerName,
  3478. styleRules,
  3479. zIndexing: true
  3480. });
  3481.  
  3482. sdk.Map.setLayerOpacity({ layerName, opacity: 0.5 });
  3483. sdk.Map.setLayerVisibility({ layerName, visibility: settings.layerVisible });
  3484. MAP_LAYER_Z_INDEX = sdk.Map.getLayerZIndex({ layerName: 'roads' }) - 3;
  3485. sdk.Map.setLayerZIndex({ layerName, zIndex: MAP_LAYER_Z_INDEX });
  3486.  
  3487. window.addEventListener('beforeunload', () => saveSettingsToStorage);
  3488.  
  3489. sdk.LayerSwitcher.addLayerCheckbox({ name: 'FC Layer' });
  3490. sdk.LayerSwitcher.setLayerCheckboxChecked({ name: 'FC Layer', isChecked: settings.layerVisible });
  3491. sdk.Events.on({ eventName: 'wme-layer-checkbox-toggled', eventHandler: onLayerCheckboxChanged });
  3492.  
  3493. // Hack to fix layer zIndex. Some other code is changing it sometimes but I have not been able to figure out why.
  3494. // 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. (?)
  3495. setInterval(checkLayerZIndex, 1000);
  3496.  
  3497. sdk.Events.on({ eventName: 'wme-map-move-end', eventHandler: fetchAllFC });
  3498. }
  3499.  
  3500. function onHideStreetsClicked() {
  3501. settings.hideStreet = $(this).is(':checked');
  3502. saveSettingsToStorage();
  3503. sdk.Map.removeAllFeaturesFromLayer({ layerName });
  3504. fetchAllFC();
  3505. }
  3506.  
  3507. function onStateSelectionChanged() {
  3508. settings.activeStateAbbr = this.value;
  3509. saveSettingsToStorage();
  3510. loadStateFCInfo();
  3511. fetchAllFC();
  3512. }
  3513.  
  3514. function setEnabled(value) {
  3515. sdk.Map.setLayerVisibility({ layerName, visibility: value });
  3516. settings.layerVisible = value;
  3517. saveSettingsToStorage();
  3518.  
  3519. const color = value ? '#00bd00' : '#ccc';
  3520. $('span#fc-layer-power-btn').css({ color });
  3521. if (value) fetchAllFC();
  3522. sdk.LayerSwitcher.setLayerCheckboxChecked({ name: 'FC Layer', isChecked: value });
  3523. }
  3524.  
  3525. async function initUserPanel() {
  3526. const $panel = $('<div>');
  3527. // Updated to play better with DarkMode
  3528. const $stateSelect = $('<select>', {
  3529. id: 'fcl-state-select',
  3530. class: 'form-control disabled',
  3531. style: `
  3532. border-radius: 4px;
  3533. padding: 6px 12px;
  3534. background-color: var(--background_default); /* Use dark mode background */
  3535. color: var(--content_default); /* Use dark mode content color */
  3536. border: 1px solid var(--separator_default); /* Dark mode border color */
  3537. transition: background-color 0.3s, color 0.3s, border-color 0.3s;
  3538. outline: none;
  3539. cursor: pointer;
  3540. font-weight: bold;
  3541. `
  3542. }).append($('<option>', { value: 'ALL' }).text('All'));
  3543.  
  3544. Object.keys(STATE_SETTINGS).forEach(stateAbbr => {
  3545. if (stateAbbr !== 'global') {
  3546. $stateSelect.append($('<option>', { value: stateAbbr }).text(reverseStatesHash(stateAbbr)));
  3547. }
  3548. });
  3549.  
  3550. $stateSelect.val(settings.activeStateAbbr ? settings.activeStateAbbr : 'ALL');
  3551.  
  3552. const $hideStreet = $('<div>', { id: 'fcl-hide-street-container', class: 'controls-container' })
  3553. .append($('<input>', { type: 'checkbox', name: 'fcl-hide-street', id: 'fcl-hide-street' }).prop('checked', settings.hideStreet).click(onHideStreetsClicked))
  3554. .append($('<label>', { for: 'fcl-hide-street' }).text('Hide local street highlights'));
  3555.  
  3556. $panel.append(
  3557. $('<div>', { class: 'form-group' })
  3558. .append($('<label>', { class: 'control-label' }).text('Select a state'))
  3559. .append($('<div>', { class: 'controls', id: 'fcl-state-select-container' }).append($('<div>').append($stateSelect))),
  3560. $hideStreet,
  3561. $('<div>', { id: 'fcl-table-container' })
  3562. );
  3563.  
  3564. $panel.append(
  3565. $('<div>', {
  3566. class: 'loading-indicator',
  3567. id: 'fc-loading-indicator',
  3568. style: 'margin-top:10px; margin-right:10px; font-weight:bold; color:green; font-size:0.9em;'
  3569. }).html('<span></span>')
  3570. );
  3571.  
  3572. $panel.append($('<div>', { id: 'fcl-state-info' }));
  3573.  
  3574. $panel.append(
  3575. $('<div>', { style: 'margin-top:10px;font-size:10px;color:#999999;' })
  3576. .append($('<div>').text(`version ${scriptVersion}`))
  3577. .append($('<div>').append($('<a>', { href: '#' /* , target:'__blank' */ }).text('Discussion Forum (currently n/a)')))
  3578. );
  3579.  
  3580. const { tabLabel, tabPane } = await sdk.Sidebar.registerScriptTab();
  3581. $(tabLabel).text('FC');
  3582. $(tabPane).append($panel);
  3583.  
  3584. // append the power button
  3585. if (!$('#fc-layer-power-btn').length) {
  3586. const color = settings.layerVisible ? '#00bd00' : '#ccc';
  3587. $(tabLabel).prepend(
  3588. $('<span>', {
  3589. class: 'fa fa-power-off',
  3590. id: 'fc-layer-power-btn',
  3591. style: `margin-right: 5px;cursor: pointer;color: ${color};font-size: 13px;`,
  3592. title: 'Toggle FC Layer'
  3593. }).click(evt => {
  3594. evt.stopPropagation();
  3595. setEnabled(!settings.layerVisible);
  3596. })
  3597. );
  3598. }
  3599.  
  3600. $('#fcl-state-select').change(onStateSelectionChanged);
  3601. loadStateFCInfo();
  3602. }
  3603.  
  3604. function loadStateFCInfo() {
  3605. $('#fcl-state-info').empty();
  3606. if (STATE_SETTINGS[settings.activeStateAbbr]) {
  3607. const stateInfo = STATE_SETTINGS[settings.activeStateAbbr].information;
  3608. const $panelStateInfo = $('<dl>');
  3609. Object.keys(stateInfo).forEach(propertyName => {
  3610. $panelStateInfo.append($('<dt>', { style: 'margin-top:1em;color:#777777' }).text(propertyName)).append($('<dd>').text(stateInfo[propertyName]));
  3611. });
  3612. $('#fcl-state-info').append($panelStateInfo);
  3613. }
  3614. }
  3615.  
  3616. async function initGui() {
  3617. initLayer();
  3618. await initUserPanel();
  3619. }
  3620.  
  3621. async function init() {
  3622. if (debug && Promise.config) {
  3623. Promise.config({
  3624. warnings: true,
  3625. longStackTraces: true,
  3626. cancellation: true,
  3627. monitoring: false
  3628. });
  3629. } else {
  3630. Promise.config({
  3631. warnings: false,
  3632. longStackTraces: false,
  3633. cancellation: true,
  3634. monitoring: false
  3635. });
  3636. }
  3637.  
  3638. const u = sdk.State.getUserInfo();
  3639. rank = u.rank + 1;
  3640. isAM = u.isAreaManager;
  3641. userNameLC = u.userName.toLowerCase();
  3642.  
  3643. loadSettingsFromStorage();
  3644. await initGui();
  3645. fetchAllFC();
  3646. log('Initialized.');
  3647. }
  3648.  
  3649. init();
  3650. })();