WME FC Layer

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

当前为 2024-10-05 提交的版本,查看 最新版本

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