WME FC Layer

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

目前为 2020-11-22 提交的版本,查看 最新版本

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