WME FC Layer

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

当前为 2024-02-24 提交的版本,查看 最新版本

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