WME FC Layer

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

目前为 2022-09-10 提交的版本,查看 最新版本

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