WME FC Layer

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

目前为 2021-11-30 提交的版本。查看 最新版本

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