WME FC Layer

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

当前为 2019-02-01 提交的版本,查看 最新版本

  1. /* global W */
  2. /* global Promise */
  3. /* global OL */
  4. /* global I18n */
  5. /* global unsafeWindow */
  6. /* global GM_info */
  7. /* global WazeWrap */
  8.  
  9. // // ==UserScript==
  10. // @name WME FC Layer
  11. // @namespace https://greasyfork.org/users/45389
  12. // @version 2019.01.29.001
  13. // @description Adds a Functional Class layer for states that publish ArcGIS FC data.
  14. // @author MapOMatic
  15. // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
  16. // @license GNU GPLv3
  17. // @contributionURL https://github.com/WazeDev/Thank-The-Authors
  18. // @require https://greasyfork.org/scripts/39002-bluebird/code/Bluebird.js?version=255146
  19. // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
  20. // @grant GM_xmlhttpRequest
  21. // @connect md.gov
  22. // @connect in.gov
  23. // @connect arcgis.com
  24. // @connect ncdot.gov
  25. // @connect state.mi.us
  26. // @connect dc.gov
  27. // @connect la.gov
  28. // @connect nd.gov
  29. // @connect pa.gov
  30. // @connect oh.us
  31. // @connect iowadot.gov
  32. // @connect ksdot.org
  33. // @connect ky.gov
  34. // @connect shelbycountytn.gov
  35. // @connect illinois.gov
  36. // @connect ny.gov
  37. // @connect utah.gov
  38. // @connect idaho.gov
  39. // @connect wv.gov
  40. // @connect ga.gov
  41. // @connect uga.edu
  42. // @connect nevadadot.com
  43. // @connect mn.us
  44. // @connect sd.gov
  45. // ==/UserScript==
  46.  
  47. (function() {
  48. 'use strict';
  49.  
  50. var _settingsStoreName = 'wme_fc_layer';
  51. var _alertUpdate = false;
  52. var _debugLevel = 0;
  53. var _scriptVersion = GM_info.script.version;
  54. var _scriptVersionChanges = [
  55. GM_info.script.name,
  56. 'v' + _scriptVersion,
  57. '',
  58. 'What\'s New',
  59. '------------------------------',
  60. '' // Add important stuff here when _alertUpdate = true.
  61. ].join('\n');
  62. var _mapLayer = null;
  63. var _isAM = false;
  64. var _uid;
  65. var _settings = {};
  66. var _r;
  67. var _mapLayerZIndex = 334;
  68. var _betaIDs = [103400892];
  69. var _statesHash = {
  70. 'Alabama':'AL','Alaska':'AK','American Samoa':'AS','Arizona':'AZ','Arkansas':'AR','California':'CA','Colorado':'CO','Connecticut':'CT','Delaware':'DE','District of Columbia':'DC',
  71. 'Federated States Of Micronesia':'FM','Florida':'FL','Georgia':'GA','Guam':'GU','Hawaii':'HI','Idaho':'ID','Illinois':'IL','Indiana':'IN','Iowa':'IA','Kansas':'KS',
  72. 'Kentucky':'KY','Louisiana':'LA','Maine':'ME','Marshall Islands':'MH','Maryland':'MD','Massachusetts':'MA','Michigan':'MI','Minnesota':'MN','Mississippi':'MS','Missouri':'MO',
  73. 'Montana':'MT','Nebraska':'NE','Nevada':'NV','New Hampshire':'NH','New Jersey':'NJ','New Mexico':'NM','New York':'NY','North Carolina':'NC','North Dakota':'ND',
  74. 'Northern Mariana Islands':'MP','Ohio':'OH','Oklahoma':'OK','Oregon':'OR','Palau':'PW','Pennsylvania':'PA','Puerto Rico':'PR','Rhode Island':'RI','South Carolina':'SC',
  75. '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'
  76. };
  77.  
  78. function reverseStatesHash(stateAbbr) {
  79. for (var stateName in _statesHash) {
  80. if (_statesHash[stateName] === stateAbbr) return stateName;
  81. }
  82. }
  83. var _stateSettings = {
  84. global: {
  85. 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.
  86. getFeatureRoadType: function(feature, layer) {
  87. var fc = feature.attributes[layer.fcPropName];
  88. return this.getRoadTypeFromFC(fc, layer);
  89. },
  90. getRoadTypeFromFC: function(fc, layer) {
  91. for (var roadType in layer.roadTypeMap) {
  92. if (layer.roadTypeMap[roadType].indexOf(fc) !== -1) {
  93. return roadType;
  94. }
  95. }
  96. return null;
  97. },
  98. isPermitted: function(stateAbbr) {if(_betaIDs.indexOf(_uid)!==-1)return true;var state=_stateSettings[stateAbbr];if(state.isPermitted){return state.isPermitted();}else{return(_r>=2&&_isAM)||(_r>=3);}},
  99. getMapLayer: function(stateAbbr, layerID) {
  100. var returnValue;
  101. _stateSettings[stateAbbr].fcMapLayers.forEach(function(layer) {
  102. if (layer.layerID === layerID) {
  103. returnValue = layer;
  104. }
  105. });
  106. return returnValue;
  107. }
  108. },
  109. DC: {
  110. baseUrl: 'https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Transportation_WebMercator/MapServer/',
  111. supportsPagination: false,
  112. defaultColors: {Fw:'#ff00c5',Ew:'#149ece',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  113. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [[],[],[],[],[],[],[],[],[],[],[]] },
  114. fetchAllFC: false,
  115. fcMapLayers: [
  116. { layerID:48, fcPropName:'FUNCTIONALCLASS', idPropName:'OBJECTID', outFields:['OBJECTID', 'FUNCTIONALCLASS'], maxRecordCount:1000, supportsPagination:false,
  117. roadTypeMap:{Fw:['Interstate'],Ew:['Other Freeway and Expressway'],MH:['Principal Arterial'],mH:['Minor Arterial'],PS:['Collector']} }
  118. ],
  119. getFeatureRoadType: function(feature, layer) {
  120. if (layer.getFeatureRoadType) {
  121. return layer.getFeatureRoadType(feature);
  122. } else {
  123. return _stateSettings.global.getFeatureRoadType(feature, layer);
  124. }
  125. },
  126. getWhereClause: function(context) {
  127. return null;
  128. }
  129. },
  130. FL: {
  131. baseUrl: 'https://services1.arcgis.com/O1JpcwDW8sjYuddV/ArcGIS/rest/services/Functional_Classification_TDA/FeatureServer/',
  132. supportsPagination: false,
  133. defaultColors: {Fw:'#ff00c5',Ew:'#149ece',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  134. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [[],[],[],[],[],[],[],[],[],[],[]] },
  135. fetchAllFC: false,
  136. fcMapLayers: [
  137. { layerID:0, fcPropName:'FUNCLASS', idPropName:'OBJECTID', outFields:['OBJECTID', 'FUNCLASS'], maxRecordCount:1000, supportsPagination:false,
  138. roadTypeMap:{Fw:['01','11'],Ew:['02','12'],MH:['04','14'],mH:['06','16'],PS:['07','08','17','18']} }
  139. ],
  140. getFeatureRoadType: function(feature, layer) {
  141. if (layer.getFeatureRoadType) {
  142. return layer.getFeatureRoadType(feature);
  143. } else {
  144. return _stateSettings.global.getFeatureRoadType(feature, layer);
  145. }
  146. },
  147. getWhereClause: function(context) {
  148. return null;
  149. }
  150. },
  151. GA: {
  152. baseUrl: 'https://maps.itos.uga.edu/arcgis/rest/services/GDOT/GDOT_FunctionalClass/mapserver/',
  153. supportsPagination: true,
  154. defaultColors: {Fw:'#ff00c5',Ew:'#149ece',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  155. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [[],[],[],[],[],[],[],[],[],[],[]] },
  156. fetchAllFC: false,
  157. fcMapLayers: [
  158. { 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]} },
  159. { 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]} },
  160. { 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]} },
  161. { 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]} },
  162. { 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]} },
  163. { 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]} },
  164. { 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]} }
  165. ],
  166. getFeatureRoadType: function(feature, layer) {
  167. if (layer.getFeatureRoadType) {
  168. return layer.getFeatureRoadType(feature);
  169. } else {
  170. var attr = feature.attributes;
  171. var fc = attr.FUNCTIONAL_CLASS;
  172. if (attr.SYSTEM_CODE === '1' && fc > 4) {
  173. return _stateSettings.global.getRoadTypeFromFC(4, layer);
  174. } else {
  175. return _stateSettings.global.getFeatureRoadType(feature, layer);
  176. }
  177. }
  178. },
  179. getWhereClause: function(context) {
  180. return null;
  181. }
  182. },
  183. ID: {
  184. baseUrl: 'https://gis.itd.idaho.gov/arcgisprod/rest/services/IPLAN/Functional_Classification/MapServer/',
  185. supportsPagination: false,
  186. defaultColors: {Fw:'#ff00c5',Ew:'#149ece',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  187. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [[],[],[],[],[],[],[],[],[],[],[]] },
  188. fetchAllFC: true,
  189. fcMapLayers: [
  190. { 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]} },
  191. { 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]} },
  192. { 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]} },
  193. { 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]} },
  194. { 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]} },
  195. { 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]} }
  196. ],
  197. getFeatureRoadType: function(feature, layer) {
  198. if (layer.getFeatureRoadType) {
  199. return layer.getFeatureRoadType(feature);
  200. } else {
  201. return _stateSettings.global.getFeatureRoadType(feature, layer);
  202. }
  203. },
  204. getWhereClause: function(context) {
  205. return null;
  206. }
  207. },
  208. IL: {
  209. baseUrl: 'https://ags10s1.dot.illinois.gov/ArcGIS/rest/services/AdministrativeData/Roads/MapServer/',
  210. supportsPagination: false,
  211. defaultColors: {Fw:'#ff00c5',Ew:'#ff00c5',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee',CH:'#ff5e0e'},
  212. zoomSettings: { maxOffset:[30,15,8,4,2,1,1,1,1,1] },
  213. fcMapLayers: [
  214. { layerID:0, idPropName:'OBJECTID', fcPropName:'FC', outFields:['FC','OBJECTID','MARKED_RT','MARKED_RT2','MARKED_RT3','MARKED_RT4','CH'],
  215. roadTypeMap:{Fw:['1'],Ew:['2'],MH:['3'],mH:['4'],PS:['5','6'],St:['7'],CH:['8']}, maxRecordCount:1000, supportsPagination:false },
  216. { layerID:1, idPropName:'OBJECTID', fcPropName:'FC', outFields:['FC','OBJECTID','MARKED_RT','MARKED_RT2','MARKED_RT3','MARKED_RT4','CH'],
  217. roadTypeMap:{Fw:['1'],Ew:['2'],MH:['3'],mH:['4'],PS:['5','6'],St:['7'],CH:['8']}, maxRecordCount:1000, supportsPagination:false },
  218. { layerID:2, idPropName:'OBJECTID', fcPropName:'FC', outFields:['FC','OBJECTID','MARKED_RT','MARKED_RT2','MARKED_RT3','MARKED_RT4','CH'],
  219. roadTypeMap:{Fw:['1'],Ew:['2'],MH:['3'],mH:['4'],PS:['5','6'],St:['7'],CH:['8']}, maxRecordCount:1000, supportsPagination:false },
  220. { layerID:3, idPropName:'OBJECTID', fcPropName:'FC', outFields:['FC','OBJECTID','MARKED_RT','MARKED_RT2','MARKED_RT3','MARKED_RT4','CH'],
  221. roadTypeMap:{Fw:['1'],Ew:['2'],MH:['3'],mH:['4'],PS:['5','6'],St:['7'],CH:['8']}, maxRecordCount:1000, supportsPagination:false },
  222. { layerID:4, idPropName:'OBJECTID', fcPropName:'FC', outFields:['FC','OBJECTID','MARKED_RT','MARKED_RT2','MARKED_RT3','MARKED_RT4','CH'],
  223. roadTypeMap:{Fw:['1'],Ew:['2'],MH:['3'],mH:['4'],PS:['5','6'],St:['7'],CH:['8']}, maxRecordCount:1000, supportsPagination:false },
  224. { layerID:5, idPropName:'OBJECTID', fcPropName:'FC', outFields:['FC','OBJECTID','MARKED_RT','MARKED_RT2','MARKED_RT3','MARKED_RT4','CH'],
  225. roadTypeMap:{Fw:['1'],Ew:['2'],MH:['3'],mH:['4'],PS:['5','6'],St:['7'],CH:['8']}, maxRecordCount:1000, supportsPagination:false },
  226. { layerID:6, idPropName:'OBJECTID', fcPropName:'FC', outFields:['FC','OBJECTID','MARKED_RT','MARKED_RT2','MARKED_RT3','MARKED_RT4','CH'],
  227. roadTypeMap:{Fw:['1'],Ew:['2'],MH:['3'],mH:['4'],PS:['5','6'],St:['7'],CH:['8']}, maxRecordCount:1000, supportsPagination:false },
  228. { layerID:7, idPropName:'OBJECTID', fcPropName:'FC', outFields:['FC','OBJECTID','MARKED_RT','MARKED_RT2','MARKED_RT3','MARKED_RT4','CH'],
  229. roadTypeMap:{Fw:['1'],Ew:['2'],MH:['3'],mH:['4'],PS:['5','6'],St:['7'],CH:['8']}, maxRecordCount:1000, supportsPagination:false },
  230. { layerID:8, idPropName:'OBJECTID', fcPropName:'FC', outFields:['FC','OBJECTID','MARKED_RT','MARKED_RT2','MARKED_RT3','MARKED_RT4','CH'],
  231. roadTypeMap:{Fw:['1'],Ew:['2'],MH:['3'],mH:['4'],PS:['5','6'],St:['7'],CH:['8']}, maxRecordCount:1000, supportsPagination:false }
  232. ],
  233. isPermitted: function() { return _r >= 3; },
  234. getWhereClause: function(context) {
  235. if(context.mapContext.zoom < 4) {
  236. return "FC<>'7' OR (FC='7' AND CH<>'0000')";
  237. } else {
  238. return null;
  239. }
  240. },
  241. getFeatureRoadType: function(feature, layer) {
  242. var attr = feature.attributes;
  243. var isUS = false;
  244. var isState = false;
  245. var isCounty = attr.CH !== '0000';
  246. [attr.MARKED_RT, attr.MARKED_RT2, attr.MARKED_RT3, attr.MARKED_RT4].forEach(function(rt) {
  247. if (!isUS) {
  248. isUS = /U\d+/.test(rt);
  249. if (!isUS && !isState) {
  250. isState = /S\d+/.test(rt);
  251. }
  252. }
  253. });
  254. var fc = attr.FC;
  255. fc = (fc > 3 && isUS) ? '3' : (fc > 4 && isState) ? '4' : (fc === '7' && isCounty) ? '8' : fc;
  256. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  257. }
  258. },
  259. IN: {
  260. baseUrl: 'https://gis.in.gov/arcgis/rest/services/DOT/INDOT_LTAP/FeatureServer/',
  261. supportsPagination: false,
  262. overrideUrl: '1Sbwc7e6BfHpZWSTfU3_1otXGSxHrdDYcbn7fOf1VjpA',
  263. defaultColors: {Fw:'#ff00c5',Ew:'#149ece',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  264. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [['St'],['St'],['St'],['St'],[],[],[],[],[],[],[]], hideRoadTypes: [['St'],['St'],['St'],['St'],[],[],[],[],[],[],[]] },
  265. fcMapLayers: [
  266. { layerID:10, idPropName:'OBJECTID', fcPropName:'FUNCTIONAL_CLASS', outFields:['FUNCTIONAL_CLASS','OBJECTID','TO_DATE'],
  267. roadTypeMap:{Fw:[1],Ew:[2],MH:[3],mH:[4],PS:[5,6],St:[7]}, maxRecordCount:100000, supportsPagination:false }
  268. ],
  269. isPermitted: function() { return true; },
  270. getWhereClauses: function(context) {
  271. },
  272. getWhereClause: function(context) {
  273. var whereParts = [];
  274. if(context.mapContext.zoom < 4) {
  275. whereParts.push(context.layer.fcPropName + '<>7');
  276. }
  277. whereParts.push('TO_DATE IS NULL');
  278. return whereParts.join(' AND ');
  279. },
  280. getFeatureRoadType: function(feature, layer) {
  281. if (layer.getFeatureRoadType) {
  282. return layer.getFeatureRoadType(feature);
  283. } else {
  284. return _stateSettings.global.getFeatureRoadType(feature, layer);
  285. }
  286. }
  287. },
  288. IA: {
  289. baseUrl: 'https://gis.iowadot.gov/public/rest/services/RAMS/Road_Network/MapServer/',
  290. supportsPagination: false,
  291. defaultColors: {Fw:'#ff00c5',Ew:'#149ece',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee',PSGr:'#8f5f00',StGr:'#837870'},
  292. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [[],[],[],[],[],[],[],[],[],[],[]] },
  293. fetchAllFC: false,
  294. fcMapLayers: [
  295. { layerID:0, fcPropName:'FED_FUNCTIONAL_CLASS', idPropName:'OBJECTID', outFields:['OBJECTID', 'FED_FUNCTIONAL_CLASS', 'STATE_ROUTE_NAME_1', 'ACCESS_CONTROL', 'SURFACE_TYPE'], maxRecordCount:1000, supportsPagination:false,
  296. roadTypeMap:{Fw:["1"],MH:["2","3"],mH:["4"],PS:["5","6"],St:["7"]} }
  297. ],
  298. getWhereClause: function(context) {
  299. var theclause = "FACILITY_TYPE<>'7'";
  300. if(context.mapContext.zoom < 4) { theclause += " AND " + context.layer.fcPropName + "<>'7'"; }
  301. return theclause;
  302. },
  303. getFeatureRoadType: function(feature, layer) {
  304. var attr = feature.attributes;
  305. var fcName = layer.fcPropName;
  306. var fc = parseInt(attr[fcName]);
  307. var isFw = attr.ACCESS_CONTROL === 1;
  308. var isUS = RegExp('STATE OF IOWA, US').test(attr.STATE_ROUTE_NAME_1);
  309. var isState = RegExp('STATE OF IOWA, IA').test(attr.STATE_ROUTE_NAME_1);
  310. fc = isFw ? 1 : ((fc > 3 && isUS) ? Math.min(fc,3) : ((fc > 4 && isState) ? Math.min(fc,4) : fc));
  311. var roadType = fc === 1 ? 'Fw' : (fc === 2 ? 'MH' : (fc === 3 ? 'MH' : (fc === 4 ? 'mH' : (fc <= 6 ? 'PS' : 'St'))));
  312. if (fc > 4 && attr.SURFACE_TYPE === 20) { roadType = roadType === 'PS' ? 'PSGr' : 'StGr' ; }
  313. return roadType;
  314. }
  315. },
  316. KS: {
  317. baseUrl: 'http://wfs.ksdot.org/arcgis_web_adaptor/rest/services/Transportation/',
  318. supportsPagination: false,
  319. defaultColors: {Fw:'#ff00c5',Ew:'#149ece',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  320. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1] },
  321. fcMapLayers: [
  322. { layerID:0, layerPath:'Non_State_System/MapServer/', idPropName:'ID2', fcPropName:'FUNCLASS', outFields:['FUNCLASS','ID2','ROUTE_ID'],
  323. roadTypeMap:{Fw:[1],MH:[2,3],mH:[4],PS:[5,6],St:[7]}, maxRecordCount:1000, supportsPagination:false },
  324. { layerID:1, layerPath:'National_Highway_System/MapServer/', idPropName:'OBJECTID', fcPropName:'FUN_CLASS_CD', outFields:['FUN_CLASS_CD','OBJECTID', 'PREFIX', 'ACCESS_CONTROL'],
  325. roadTypeMap:{Fw:["1"],MH:["2","3"],mH:["4"],PS:["5","6"],St:["7"]}, maxRecordCount:1000, supportsPagination:false },
  326. { layerID:0, layerPath:'State_System/MapServer/', idPropName:'OBJECTID', fcPropName:'FUN_CLASS_CD', outFields:['FUN_CLASS_CD','OBJECTID', 'PREFIX', 'ACCESS_CONTROL'],
  327. roadTypeMap:{Fw:["1"],MH:["2","3"],mH:["4"],PS:["5","6"],St:["7"]}, maxRecordCount:1000, supportsPagination:false }
  328. ],
  329. getWhereClause: function(context) {
  330. if(context.mapContext.zoom < 4) {
  331. return context.layer.fcPropName + "<>'7'";
  332. } else {
  333. return null;
  334. }
  335. },
  336. getFeatureRoadType: function(feature, layer) {
  337. var attr = feature.attributes;
  338. var fcName = layer.fcPropName;
  339. var fc = parseInt(attr[fcName]);
  340. var roadPrefix = attr.PREFIX;
  341. var isLocal = attr[fcName] === 'FUNCLASS';
  342. var isFw = false
  343. var isUS = false;
  344. var isState = false;
  345. var isBusiness = false;
  346. if (!isLocal) {
  347. isFw = attr.ACCESS_CONTROL === '1';
  348. isUS = roadPrefix === 'U';
  349. isState = roadPrefix === 'K';
  350. }
  351. if (fc > 4 && isState) {
  352. fc = (isBusiness ? Math.min(fc,5) : 4);
  353. } else if (fc > 3 && isUS) {
  354. fc = (isBusiness ? Math.min(fc, 4) : 3 );
  355. } else if (isFw) {
  356. fc = (isBusiness ? Math.min(fc, 3) : 1 );
  357. }
  358. var roadType = fc === 1 ? 'Fw' : (fc === 2 ? 'MH' : (fc === 3 ? 'MH' : (fc === 4 ? 'mH' : (fc <= 6 ? 'PS' : 'St'))));
  359. return roadType;
  360. },
  361. },
  362. KY: {
  363. baseUrl: 'https://maps.kytc.ky.gov/arcgis/rest/services/BaseMap/System/MapServer/',
  364. supportsPagination: false,
  365. defaultColors: {Fw:'#ff00c5',Ew:'#ff00c5',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  366. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1] },
  367. fcMapLayers: [
  368. { layerID:0, idPropName:'OBJECTID', fcPropName:'FC', outFields:['FC','OBJECTID','RT_PREFIX', 'RT_SUFFIX'],
  369. roadTypeMap:{Fw:['1'],Ew:['2'],MH:['3'],mH:['4'],PS:['5','6'],St:['7']}, maxRecordCount:1000, supportsPagination:false }
  370. ],
  371. isPermitted: function() { return true; },
  372. getWhereClause: function(context) {
  373. if(context.mapContext.zoom < 4) {
  374. return context.layer.fcPropName + "<>'7'";
  375. } else {
  376. return null;
  377. }
  378. },
  379. getFeatureRoadType: function(feature, layer) {
  380. if (feature.attributes.RT_PREFIX === 'US') {
  381. var suffix = feature.attributes.RT_SUFFIX;
  382. var type = 'MH';
  383. if (suffix && suffix.indexOf('X') > -1) type = 'mH';
  384. return type;
  385. } else {
  386. return _stateSettings.global.getFeatureRoadType(feature, layer);
  387. }
  388. }
  389. },
  390. LA: {
  391. baseUrl: 'https://giswebnew.dotd.la.gov/arcgis/rest/services/Transportation/LA_RoadwayFunctionalClassification/FeatureServer/',
  392. supportsPagination: false,
  393. defaultColors: {Fw:'#4094ff',Ew:'#ffbf40',MH:'#fb674d',mH:'#6abe40',PS1:'#bf40ec',PS2:'#ffff40',St:'#a2a2a2'},
  394. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [['St'],['St'],['St'],['St'],[],[],[],[],[],[],[]] },
  395. fcMapLayers: [
  396. { layerID:0, fcPropName:'FunctionalSystem', idPropName:'OBJECTID', outFields:['OBJECTID','FunctionalSystem'], roadTypeMap:{Fw:[1],Ew:['2','2a','2b'],MH:[3],mH:[4],PS1:[5],PS2:[6],St:[7]}, maxRecordCount:1000, supportsPagination:false },
  397. { layerID:1, fcPropName:'FunctionalSystem', idPropName:'OBJECTID', outFields:['OBJECTID','FunctionalSystem'], roadTypeMap:{Fw:[1],Ew:['2','2a','2b'],MH:[3],mH:[4],PS1:[5],PS2:[6],St:[7]}, maxRecordCount:1000, supportsPagination:false },
  398. { layerID:2, fcPropName:'FunctionalSystem', idPropName:'OBJECTID', outFields:['OBJECTID','FunctionalSystem'], roadTypeMap:{Fw:[1],Ew:['2','2a','2b'],MH:[3],mH:[4],PS1:[5],PS2:[6],St:[7]}, maxRecordCount:1000, supportsPagination:false },
  399. { layerID:3, fcPropName:'FunctionalSystem', idPropName:'OBJECTID', outFields:['OBJECTID','FunctionalSystem'], roadTypeMap:{Fw:[1],Ew:['2','2a','2b'],MH:[3],mH:[4],PS1:[5],PS2:[6],St:[7]}, maxRecordCount:1000, supportsPagination:false },
  400. { layerID:4, fcPropName:'FunctionalSystem', idPropName:'OBJECTID', outFields:['OBJECTID','FunctionalSystem'], roadTypeMap:{Fw:[1],Ew:['2','2a','2b'],MH:[3],mH:[4],PS1:[5],PS2:[6],St:[7]}, maxRecordCount:1000, supportsPagination:false },
  401. { layerID:5, fcPropName:'FunctionalSystem', idPropName:'OBJECTID', outFields:['OBJECTID','FunctionalSystem'], roadTypeMap:{Fw:[1],Ew:['2','2a','2b'],MH:[3],mH:[4],PS1:[5],PS2:[6],St:[7]}, maxRecordCount:1000, supportsPagination:false },
  402. { layerID:6, fcPropName:'FunctionalSystem', idPropName:'OBJECTID', outFields:['OBJECTID','FunctionalSystem'], roadTypeMap:{Fw:[1],Ew:['2','2a','2b'],MH:[3],mH:[4],PS1:[5],PS2:[6],St:[7]}, maxRecordCount:1000, supportsPagination:false }
  403. ],
  404. getWhereClause: function(context) {
  405. if(context.mapContext.zoom < 4) {
  406. return context.layer.fcPropName + "<>'7'"; // OR State_Route LIKE 'US%' OR State_Route LIKE 'LA%'";
  407. } else {
  408. return null;
  409. }
  410. },
  411. getFeatureRoadType: function(feature, layer) {
  412. var fc = feature.attributes[layer.fcPropName];
  413. if (fc === '2a' || fc === '2b') { fc = 2; }
  414. fc = parseInt(fc);
  415. // var stateRoute = feature.attributes.State_Route;
  416. // var isBusiness = /BUS$/.test(stateRoute);
  417. // if (fc > 3 && /^US\s/.test(stateRoute) && !isBusiness) {
  418. // fc = 3;
  419. // } else if (fc > 4 && /^LA\s/.test(stateRoute) && !isBusiness) {
  420. // fc = 4;
  421. // }
  422. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  423. }
  424. },
  425. MD: {
  426. baseUrl: 'https://geodata.md.gov/imap/rest/services/Transportation/MD_HighwayPerformanceMonitoringSystem/MapServer/',
  427. defaultColors: {Fw:'#ff00c5',Ew:'#4f33df',MH:'#149ece',mH:'#4ce600',PS:'#ffff00',St:'#eeeeee'},
  428. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [['St'],['St'],['St'],['St'],[],[],[],[],[],[],[]] },
  429. fcMapLayers: [
  430. { 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 }
  431. ],
  432. getWhereClause: function(context) {
  433. if(context.mapContext.zoom < 4) {
  434. return "(FUNCTIONAL_CLASS < 7 OR ID_PREFIX IN('MD'))";
  435. } else {
  436. return null;
  437. }
  438. },
  439. getFeatureRoadType: function(feature,layer) {
  440. var attr = feature.attributes;
  441. var fc = parseInt(attr.FUNCTIONAL_CLASS);
  442. var isState = attr.ID_PREFIX === 'MD';
  443. var isUS = attr.ID_PREFIX === 'US';
  444. var isBusiness = attr.MP_SUFFIX === 'BU';
  445. if (fc > 4 && isState) { fc = (isBusiness ? Math.min(fc,5) : 4); }
  446. else if (fc > 3 && isUS) { fc = (isBusiness ? Math.min(fc, 4) : 3 );}
  447. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  448. }
  449. },
  450. MI: {
  451. baseUrl: 'https://gisp.mcgi.state.mi.us/arcgis/rest/services/MDOT/NFC/MapServer/',
  452. defaultColors: {Fw:'#ff00c5',Ew:'#149ece',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  453. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [['St'],['St'],['St'],['St'],[],[],[],[],[],[],[]] },
  454. fcMapLayers: [
  455. { 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 }
  456. ],
  457. isPermitted: function() { return true; },
  458. getWhereClause: function(context) {
  459. if(context.mapContext.zoom < 4) {
  460. return context.layer.fcPropName + '<>7';
  461. } else {
  462. return null;
  463. }
  464. },
  465. getFeatureRoadType: function(feature, layer) {
  466. if (layer.getFeatureRoadType) {
  467. return layer.getFeatureRoadType(feature);
  468. } else {
  469. return _stateSettings.global.getFeatureRoadType(feature, layer);
  470. }
  471. }
  472. },
  473. MN: {
  474. baseUrl: 'https://dotapp9.dot.state.mn.us/egis12/rest/services/BASEMAP/BASEMAP_OPERATIONAL_LAYERS/MapServer/',
  475. supportsPagination: false, fetchAllFC: false,
  476. defaultColors: {Fw:'#ff00c5',Ew:'#149ece',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  477. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [[],[],[],[],[],[],[],[],[],[],[]] },
  478. fcMapLayers: [{ layerID:4, fcPropName:'FUNCTIONAL_CLASSIFICATION', idPropName:'OBJECTID', maxRecordCount:1000, supportsPagination:false,
  479. outFields:['OBJECTID', 'FUNCTIONAL_CLASSIFICATION'],
  480. roadTypeMap:{Fw:["Rural Principal arterial - Interstate","Urban Principal arterial - Interstate","Urban Principal arterial - Other freeways or expressways"],
  481. Ew:[],
  482. MH:["Rural Principal arterial - Other","Urban Other Principal Arterials"],
  483. mH:["Rural Minor arterial", "Urban Minor arterial"],
  484. PS:["Rural Major collector","Rural Minor collector","Urban Collector"],
  485. St:["Rural Local ","Urban Local"]} }
  486. ],
  487. getWhereClause: function(context) {
  488. if(context.mapContext.zoom < 4) {
  489. return context.layer.fcPropName + "<>'Urban Local' AND " + context.layer.fcPropName + "<>'Rural Local'";
  490. } else {
  491. return null;
  492. }
  493. },
  494. getFeatureRoadType: function(feature, layer) {
  495. if (layer.getFeatureRoadType) {
  496. return layer.getFeatureRoadType(feature);
  497. } else {
  498. return _stateSettings.global.getFeatureRoadType(feature, layer);
  499. }
  500. }
  501. },
  502. NV: {
  503. baseUrl: 'https://gis.nevadadot.com/arcgis/rest/services/ArcGISOnline/PublicMaintenanceMap/MapServer/',
  504. defaultColors: {Fw:'#ff00c5',Ew:'#149ece',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  505. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [['St'],['St'],['St'],['St'],[],[],[],[],[],[],[]] },
  506. fcMapLayers: [
  507. { layerID:3, fcPropName:'FUNC_CODE', idPropName:'OBJECTID', outFields:['OBJECTID','FUNC_CODE'], roadTypeMap:{Fw:[1],Ew:[2],MH:[3],mH:[4],PS:[5,6],St:[7]}, maxRecordCount:1000, supportsPagination:false }
  508. ],
  509. getWhereClause: function(context) {
  510. return null;
  511. },
  512. getFeatureRoadType: function(feature, layer) {
  513. return _stateSettings.global.getFeatureRoadType(feature, layer);
  514. }
  515. },
  516. NY: {//https://gis3.dot.ny.gov/arcgis/rest/services/Basemap/MapServer/21
  517. baseUrl: 'https://gis3.dot.ny.gov/arcgis/rest/services/',
  518. defaultColors: {Fw:'#ff00c5',Ew:'#5f33df',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  519. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1] },
  520. fcMapLayers: [
  521. { layerID:'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]},
  522. maxRecordCount:1000, supportsPagination:false },
  523. { layerID:'Basemap/MapServer/21', idPropName:'OBJECTID', outFields:['OBJECTID','SHIELD'], maxRecordCount:1000, supportsPagination:false }
  524. ],
  525. getWhereClause: function(context) {
  526. if (context.layer.layerID === 'Basemap/MapServer/21') {
  527. return ("SHIELD IN ('C','CT')");
  528. } else {
  529. return null;
  530. }
  531. },
  532. getFeatureRoadType: function(feature, layer) {
  533. var roadType;
  534. if (layer.layerID === 'Basemap/MapServer/21') {
  535. roadType = 'PS';
  536. } else {
  537. roadType = _stateSettings.global.getFeatureRoadType(feature, layer);
  538. var routeNo = feature.attributes.ROUTE_NO;
  539. if (/^NY.*/.test(routeNo)) {
  540. if (roadType === 'PS') roadType = 'mH';
  541. } else if (/^US.*/.test(routeNo)) {
  542. if (roadType === 'PS' || roadType === 'mH') roadType = 'MH';
  543. }
  544. }
  545. return roadType;
  546. }
  547. },
  548. NC: {
  549. baseUrl: 'https://gis11.services.ncdot.gov/arcgis/rest/services/NCDOT_FunctionalClass/MapServer/',
  550. defaultColors: {Fw:'#ff00c5',Rmp:'#999999',Ew:'#5f33df',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  551. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [['St'],['St'],['St'],['St'],[],[],[],[],[],[],[]] },
  552. fcMapLayers: [
  553. { layerID:0, 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:[3,4,5,6,7,8,9,10], maxRecordCount:1000, supportsPagination:false }
  554. //{ 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 },
  555. //{ 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 },
  556. //{ 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 },
  557. //{ 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 },
  558. //{ 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 }
  559. ],
  560. isPermitted: function() { return _r > 1; },
  561. getWhereClause: function(context) {
  562. if(context.mapContext.zoom < 4) {
  563. var clause = '(' + context.layer.fcPropName + " < 7 OR RTE_1_CLSS_CD IN ('I','FED','NC','RMP','US'))";
  564. return clause;
  565. } else {
  566. return null;
  567. }
  568. },
  569. getFeatureRoadType: function(feature, layer) {
  570. var fc = feature.attributes[layer.fcPropName];
  571. var roadType;
  572. switch (this.getHwySys(feature)) {
  573. case 'interstate':
  574. roadType = 'Fw';
  575. break;
  576. case 'us':
  577. roadType = fc <= 2 ? 'Ew' : 'MH';
  578. break;
  579. case 'state':
  580. roadType = fc === 2 ? 'Ew' : (fc === 3 ? 'MH' : 'mH');
  581. break;
  582. case 'ramp':
  583. roadType = 'Rmp';
  584. break;
  585. default:
  586. roadType = fc === 2 ? 'Ew' : (fc === 3 ? 'MH' : (fc === 4 ? 'mH' : (fc <= 6 ? 'PS' : 'St')));
  587. }
  588. return roadType;
  589. },
  590. getHwySys: function(feature) {
  591. var hwySys;
  592. switch (feature.attributes.RTE_1_CLSS_CD) {
  593. case 'I':
  594. hwySys = 'interstate';
  595. break;
  596. case 'FED':
  597. case 'US':
  598. hwySys = 'us';
  599. break;
  600. case 'NC':
  601. hwySys = 'state';
  602. break;
  603. case 'RMP':
  604. hwySys = 'ramp';
  605. break;
  606. default:
  607. hwySys = 'local';
  608. }
  609. return hwySys;
  610. }
  611. },
  612. ND: {
  613. baseUrl: 'https://gis.dot.nd.gov/arcgis/rest/services/external/transinfo/MapServer/',
  614. defaultColors: {Fw:'#ff00c5',Ew:'#149ece',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  615. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [['St'],['St'],['St'],['St'],[],[],[],[],[],[],[]] },
  616. fcMapLayers: [
  617. { 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']},
  618. maxRecordCount:1000, supportsPagination:false},
  619. { 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']},
  620. maxRecordCount:1000, supportsPagination:false},
  621. { layerID:12, fcPropName:'FUNCTION_CLASS', idPropName:'OBJECTID', outFields:['OBJECTID','FUNCTION_CLASS'], roadTypeMap:{PS:['Major Collector','Collector']},
  622. maxRecordCount:1000, supportsPagination:false},
  623. { layerID:16, fcPropName:'SYSTEM_CD', idPropName:'OBJECTID', outFields:['OBJECTID','SYSTEM_CD','SYSTEM_DESC','HIGHWAY'], roadTypeMap:{Fw:[1,11],MH:[2,14],mH:[6,7,16,19]},
  624. maxRecordCount:1000, supportsPagination:false}
  625. ],
  626. getWhereClause: function(context) {
  627. if(context.mapContext.zoom < 4) {
  628. if (context.layer.layerID !== 16) return context.layer.fcPropName + "<>'Local'";
  629. } else {
  630. return null;
  631. }
  632. },
  633. getFeatureRoadType: function(feature, layer) {
  634. return _stateSettings.global.getFeatureRoadType(feature, layer);
  635. }
  636. },
  637. OH: {
  638. baseUrl: 'https://gis.dot.state.oh.us/arcgis/rest/services/TIMS/Roadway_Information/MapServer/',
  639. defaultColors: {Fw:'#ff00c5',Ew:'#4f33df',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  640. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [['St'],['St'],['St'],['St'],[],[],[],[],[],[],[]] },
  641.  
  642. fcMapLayers: [
  643. { layerID:8, fcPropName:'FUNCTION_CLASS', idPropName:'ObjectID', outFields:['FUNCTION_CLASS','ROUTE_TYPE','ROUTE_NBR','ObjectID'],
  644. maxRecordCount:1000, supportsPagination:false, roadTypeMap:{Fw:[1],Ew:[2],MH:[3],mH:[4],PS:[5,6],St:[7]} }
  645. ],
  646. isPermitted: function() { return true; },
  647. getWhereClause: function(context) {
  648. if(context.mapContext.zoom < 4) {
  649. var clause = '(' + context.layer.fcPropName + " < 7 OR ROUTE_TYPE IN ('CR','SR','US'))";
  650. return clause;
  651. } else {
  652. return null;
  653. }
  654. },
  655. getFeatureRoadType: function(feature, layer) {
  656. var fc = feature.attributes[layer.fcPropName];
  657. var prefix = feature.attributes.ROUTE_TYPE;
  658. var isUS = prefix === 'US';
  659. var isState = prefix === 'SR';
  660. var isCounty = prefix === 'CR';
  661. if (isUS && fc > 3) { fc = 3; }
  662. if (isState && fc > 4) { fc = 4; }
  663. if (isCounty && fc > 6) { fc = 6; }
  664. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  665. }
  666. },
  667. OK: {
  668. baseUrl: 'https://services6.arcgis.com/RBtoEUQ2lmN0K3GY/arcgis/rest/services/Roadways/FeatureServer/',
  669. defaultColors: {Fw:'#ff00c5',Ew:'#4f33df',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  670. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [['St'],['St'],['St'],['St'],[],[],[],[],[],[],[]] },
  671. fcMapLayers: [
  672. { layerID:0, fcPropName:'NFC', idPropName:'OBJECTID', outFields:['F_PRIMARY_','NFC','OBJECTID','ROUTE_CLAS'],
  673. maxRecordCount:1000, supportsPagination:false, roadTypeMap:{Fw:[1],Ew:[2],MH:[3],mH:[4],PS:[5,6],St:[7]} }
  674. ],
  675. getWhereClause: function(context) {
  676. if(context.mapContext.zoom < 4) {
  677. var clause = '(' + context.layer.fcPropName + " < 7 OR ROUTE_CLAS IN ('U','S','I'))";
  678. return clause;
  679. } else {
  680. return null;
  681. }
  682. },
  683. getFeatureRoadType: function(feature, layer) {
  684. var fc = feature.attributes[layer.fcPropName];
  685. var route = (feature.attributes.F_PRIMARY_ || '').trim();
  686. var isBusinessOrSpur = /BUS$|SPR$/i.test(route);
  687. var prefix = isBusinessOrSpur ? route.substring(0,1) : feature.attributes.ROUTE_CLAS;
  688. var isInterstate = prefix === 'I';
  689. var isUS = prefix === 'U';
  690. var isState = prefix === 'S';
  691. if (((isUS && !isBusinessOrSpur) || (isInterstate && isBusinessOrSpur)) && fc > 3) { fc = 3; }
  692. if (((isUS && isBusinessOrSpur) || (isState && !isBusinessOrSpur)) && fc > 4) { fc = 4; }
  693. if (isState && isBusinessOrSpur && fc > 5) { fc = 5; }
  694. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  695. }
  696. },
  697. PA: {
  698. baseUrl: 'https://www.pdarcgissvr.pa.gov/penndotgis/rest/services/PennShare/PennShare/MapServer/',
  699. supportsPagination: false,
  700. defaultColors: {Fw:'#00ffff',Ew:'#732500',MH:'#ff0000',mH:'#00ff00',PS:'#b724ff',PS2:'#f3f300',St:'#ff9700'},
  701. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [['St'],['St'],['St'],['St'],[],[],[],[],[],[],[]] },
  702. isPermitted: function() { return _r >= 3; },
  703. fcMapLayers: [
  704. { layerID:3, features:new Map(), fcPropName:'FUNC_CLS', idPropName:'MSLINK', outFields:['MSLINK','FUNC_CLS'],
  705. maxRecordCount:1000, supportsPagination:false, roadTypeMap:{Fw:['01','11'],Ew:['12'],MH:['02','14'],mH:['06','16'],PS:['07','08','17'],St:['09','19']} }
  706. ],
  707. getWhereClause: function(context) {
  708. return null;
  709. },
  710. getFeatureRoadType: function(feature, layer) {
  711. if (layer.getFeatureRoadType) {
  712. return layer.getFeatureRoadType(feature);
  713. } else {
  714. var fc = feature.attributes[layer.fcPropName];
  715. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  716. }
  717. }
  718. },
  719. SD: {
  720. baseUrl: 'https://arcgis.sd.gov/arcgis/rest/services/DOT/LocalRoads/MapServer/',
  721. defaultColors: {Fw:'#ff00c5',Ew:'#149ece',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee',StGr:'#837870'},
  722. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [['St'],['St'],['St'],['St'],[],[],[],[],[],[],[]] },
  723. fcMapLayers: [{ layerID:1, fcPropName:'FUNC_CLASS', idPropName:'OBJECTID', maxRecordCount:1000, supportsPagination:false,
  724. outFields:['OBJECTID', 'FUNC_CLASS', 'SURFACE_TYPE', 'ROADNAME'],
  725. roadTypeMap:{Fw:[1,11], Ew:[2,12], MH:[4,14], mH:[6,16], PS:[7,8,17], St:[9,19]} }
  726. ],
  727. getWhereClause: function(context) {
  728. if(context.mapContext.zoom < 4) {
  729. return context.layer.fcPropName + "<>19 AND " + context.layer.fcPropName + "<>9";
  730. } else {
  731. return null;
  732. }
  733. },
  734. getFeatureRoadType: function(feature, layer) {
  735. var attr = feature.attributes;
  736. var fc = parseInt(attr[layer.fcPropName]) % 10;
  737. var isFw = attr.ACCESS_CONTROL === 1;
  738. var isUS = RegExp('^US HWY ','i').test(attr.ROADNAME);
  739. var isState = RegExp('^SD HWY ','i').test(attr.ROADNAME);
  740. var isBus = RegExp('^(US|SD) HWY .* (E|W)?(B|L)$','i').test(attr.ROADNAME);
  741. var isPaved = parseInt(attr.SURFACE_TYPE) > 5;
  742. if (fc > 1 && isFw) { fc = 1; }
  743. else if (fc > 4 && isUS) { fc = (isBus ? Math.min(fc, 6) : 4 ); }
  744. else if (fc > 6 && isState) { fc = (isBus ? Math.min(fc,7) : 6); }
  745. if (fc > 6 && !isPaved) {
  746. return 'StGr';
  747. } else {
  748. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  749. }
  750. }
  751. },
  752. TN: {
  753. // NOTE: DUE TO ERRORS FROM THE SHELBY COUNTY SERVER, FC IS NOT WORKING PROPERLY HERE YET (9/23/2016)
  754. baseUrl: 'https://testuasiportal.shelbycountytn.gov/arcgis/rest/services/MPO/Webmap_2015_04_20_TMPO/MapServer/',
  755.  
  756. // TODO: UPDATE COLORS TO MATCH ORIGINAL TN FC MAP COLORS.
  757. defaultColors: {Fw:'#ff00c5',Ew:'#4f33df',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',PS2:'#cfae0e',St:'#eeeeee'},
  758. zoomSettings: { maxOffset:[30,15,8,4,2,1,1,1,1,1] },
  759. fcMapLayers: [
  760. { layerID:17, fcPropName:'FuncClass', idPropName:'OBJECTID', outFields:['OBJECTID','FuncClass'], maxRecordCount:1000, supportsPagination:false, roadTypeMap:{Fw:[1,11],Ew:[2,12],MH:[4,14],mH:[6,16],PS:[7,17],PS2:[8,18],St:[9,19]} }
  761. ],
  762. getWhereClause: function(context) {
  763. if(context.mapContext.zoom < 4) {
  764. return context.layer.fcPropName + ' NOT IN (9,19)';
  765. } else {
  766. return null;
  767. }
  768. },
  769. getFeatureRoadType: function(feature, layer) {
  770. if (layer.getFeatureRoadType) {
  771. return layer.getFeatureRoadType(feature);
  772. } else {
  773. var fc = feature.attributes[layer.fcPropName];
  774. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  775. }
  776. }
  777. },
  778. TX: {
  779. baseUrl: 'https://services.arcgis.com/KTcxiTD9dsQw4r7Z/ArcGIS/rest/services/TxDOT_Functional_Classification/FeatureServer/',
  780. defaultColors: {Fw:'#ff00c5',Ew:'#4f33df',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  781. zoomSettings: { maxOffset:[30,15,8,4,2,1,1,1,1,1] },
  782. fcMapLayers: [
  783. { 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]} }
  784. ],
  785. getWhereClause: function(context) {
  786. var where = " F_SYSTEM IS NOT NULL AND RTE_PRFX IS NOT NULL";
  787. if(context.mapContext.zoom < 4) {
  788. where += ' AND ' + context.layer.fcPropName + " <> 7";
  789. }
  790. return where;
  791. },
  792. getFeatureRoadType: function(feature, layer) {
  793. // On-System:
  794. // IH=Interstate BF=Business FM
  795. // US=US Highway FM=Farm to Mkt
  796. // UA=US Alt. RM=Ranch to Mkt
  797. // UP=US Spur RR=Ranch Road
  798. // SH=State Highway PR=Park Road
  799. // SA=State Alt. RE=Rec Road
  800. // SL=State Loop RP=Rec Rd Spur
  801. // SS=State Spur FS=FM Spur
  802. // BI=Business IH RS=RM Spur
  803. // BU=Business US RU=RR Spur
  804. // BS=Business State PA=Principal Arterial
  805. // Off-System:
  806. // TL=Off-System Tollroad CR=County Road
  807. // FC=Func. Classified St. LS=Local Street
  808. if (layer.getFeatureRoadType) {
  809. return layer.getFeatureRoadType(feature);
  810. } else {
  811. var fc = feature.attributes[layer.fcPropName];
  812. var type = feature.attributes.RTE_PRFX.substring(0,2).toUpperCase();
  813. if (type === 'IH' && fc > 1) {
  814. fc = 1;
  815. } else if ((type === 'US' || type === 'BI' || type === 'UA') && fc > 3) {
  816. fc = 3;
  817. } else if ((type === 'UP' || type === 'BU' || type === 'SH' || type === 'SA') && fc > 4) {
  818. fc = 4;
  819. } else if ((type === 'SL' || type === 'SS' || type === 'BS') && fc > 6) {
  820. fc = 6;
  821. }
  822. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  823. }
  824. },
  825. isPermitted: function() { return _r >= 1; }
  826. },
  827. UT: {
  828. baseUrl: 'https://maps.udot.utah.gov/arcgis/rest/services/Functional_Class/MapServer/',
  829. defaultColors: {Fw:'#ff00c5',Ew:'#4f33df',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  830. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [['St'],['St'],['St'],['St'],[],[],[],[],[],[],[]] },
  831. fcMapLayers: [
  832. { 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]},
  833. maxRecordCount:1000, supportsPagination:false }
  834. ],
  835. getWhereClause: function(context) {
  836. var clause = context.layer.fcPropName + '<=7';
  837. if(context.mapContext.zoom < 4) {
  838. clause += ' OR ' + context.layer.fcPropName + '<7';
  839. }
  840. return clause;
  841. },
  842. getFeatureRoadType: function(feature, layer) {
  843. var routeId = feature.attributes.ROUTE_ID;
  844. var fc = feature.attributes.FC_CODE;
  845. if ([6,40,50,89,91,163,189,191,491].indexOf(routeId) > -1 && fc > 3) {
  846. // US highway
  847. fc = 3;
  848. } else if (routeId <= 491 && fc > 4) {
  849. // State highway
  850. fc = 4;
  851. }
  852. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  853. }
  854. },
  855. VA: {
  856. baseUrl: 'https://services.arcgis.com/p5v98VHDX9Atv3l7/arcgis/rest/services/FC_2014_FHWA_Submittal1/FeatureServer/',
  857. defaultColors: {Fw:'#ff00c5',Ew:'#ff00c5',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  858. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [['St'],['St'],['St'],['St'],[],[],[],[],[],[],[]] },
  859. fcMapLayers: [
  860. { 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]} },
  861. { 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]} },
  862. { 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]} }
  863. ],
  864. 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],
  865. getWhereClause: function(context) {
  866. if(context.mapContext.zoom < 4) {
  867. return context.layer.fcPropName + '<>7';
  868. } else {
  869. //NOTE: As of 9/14/2016 there does not appear to be any US/SR/VA labeled routes with FC = 7.
  870. return null;
  871. }
  872. },
  873. getFeatureRoadType: function(feature, layer) {
  874. if (layer.getFeatureRoadType) {
  875. return layer.getFeatureRoadType(feature);
  876. } else {
  877. var fc = parseInt(feature.attributes[layer.fcPropName]);
  878. var rtName = feature.attributes.RTE_NM;
  879. var match = /^R-VA\s*(US|VA|SR)(\d{5})..(BUS)?/.exec(rtName);
  880. var isBusiness = (match && (match !== null) && (match[3] === 'BUS'));
  881. var isState = (match && (match !== null) && (match[1] === 'VA' || match[1] === 'SR'));
  882. var rtNum = parseInt((layer.layerID === 1) ? feature.attributes.ROUTE_NO : (match ? match[2] : 99999));
  883. var rtPrefix = match && match[1];
  884. if (fc > 3 && rtPrefix === 'US') {
  885. fc = isBusiness ? 4 : 3;
  886. } else if (isState && fc > 4 && this.srExceptions.indexOf(rtNum) === -1 && rtNum < 600) {
  887. fc = isBusiness ? 5 : 4;
  888. }
  889. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  890. }
  891. }
  892. },
  893. WV: {
  894. baseUrl: 'https://gis.transportation.wv.gov/arcgis/rest/services/Roads_And_Highways/Publication_LRS/MapServer/',
  895. defaultColors: {Fw:'#ff00c5',Ew:'#ff00c5',MH:'#149ece',mH:'#4ce600',PS:'#cfae0e',St:'#eeeeee'},
  896. zoomSettings: { maxOffset: [30,15,8,4,2,1,1,1,1,1], excludeRoadTypes: [['St'],['St'],['St'],['St'],[],[],[],[],[],[],[]] },
  897. fcMapLayers: [
  898. { layerID:35, fcPropName:'NAT_FUNCTIONAL_CLASS', idPropName:'OBJECTID', outFields:['OBJECTID','NAT_FUNCTIONAL_CLASS','ROUTE_ID'], maxRecordCount:1000, supportsPagination:true, roadTypeMap:{Fw:[1],Ew:[2],MH:[3],mH:[4],PS:[5,6],St:[7]} }
  899. ],
  900. getWhereClause: function(context) {
  901. if(context.mapContext.zoom < 4) {
  902. return context.layer.fcPropName + ' NOT IN(9,19)';
  903. } else {
  904. return null;
  905. }
  906. },
  907. getFeatureRoadType: function(feature, layer) {
  908. if (layer.getFeatureRoadType) {
  909. return layer.getFeatureRoadType(feature);
  910. } else {
  911. var fcCode = feature.attributes[layer.fcPropName];
  912. var fc = fcCode;
  913. if (fcCode===11) fc = 1;
  914. else if (fcCode===4 || fcCode===12) fc = 2;
  915. else if (fcCode===2 || fcCode===14) fc = 3;
  916. else if (fcCode===6 || fcCode===16) fc = 4;
  917. else if (fcCode===7 || fcCode===17 || fcCode===8 || fcCode===18) fc = 5;
  918. else fc = 7;
  919. var id = feature.attributes.ROUTE_ID;
  920. var prefix = id.substr(2,1);
  921. var isInterstate = false;
  922. var isUS = false;
  923. var isState = false;
  924. switch (prefix) {
  925. case '1':
  926. isInterstate = true;
  927. break;
  928. case '2':
  929. isUS = true;
  930. break;
  931. case '3':
  932. isState = true;
  933. break;
  934. }
  935. if (fc > 1 && isInterstate)
  936. fc = 1;
  937. else if (fc > 3 && isUS)
  938. fc = 3;
  939. else if (fc > 4 && isState)
  940. fc = 4;
  941. return _stateSettings.global.getRoadTypeFromFC(fc, layer);
  942. }
  943. }
  944. }
  945. };
  946.  
  947. function log(message, level) {
  948. if (message && (!level || (level <= _debugLevel))) {
  949. console.log('FC Layer: ', message);
  950. }
  951. }
  952.  
  953. function dynamicSort(property) {
  954. var sortOrder = 1;
  955. if(property[0] === "-") {
  956. sortOrder = -1;
  957. property = property.substr(1);
  958. }
  959. return function (a,b) {
  960. var props = property.split('.');
  961. props.forEach(function(prop) {
  962. a = a[prop];
  963. b = b[prop];
  964. });
  965. var result = (a < b) ? -1 : (a > b) ? 1 : 0;
  966. return result * sortOrder;
  967. };
  968. }
  969.  
  970. function dynamicSortMultiple() {
  971. /*
  972. * save the arguments object as it will be overwritten
  973. * note that arguments object is an array-like object
  974. * consisting of the names of the properties to sort by
  975. */
  976. var props = arguments;
  977. if (arguments[0] && Array.isArray(arguments[0])) {
  978. props = arguments[0];
  979. }
  980. return function (obj1, obj2) {
  981. var i = 0, result = 0, numberOfProperties = props.length;
  982. /* try getting a different result from 0 (equal)
  983. * as long as we have extra properties to compare
  984. */
  985. while(result === 0 && i < numberOfProperties) {
  986. result = dynamicSort(props[i])(obj1, obj2);
  987. i++;
  988. }
  989. return result;
  990. };
  991. }
  992.  
  993. function generateUUID() {
  994. var d = new Date().getTime();
  995. var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
  996. var r = (d + Math.random()*16)%16 | 0;
  997. d = Math.floor(d/16);
  998. return (c==='x' ? r : (r&0x3|0x8)).toString(16);
  999. });
  1000. return uuid;
  1001. }
  1002.  
  1003. function loadSettingsFromStorage() {
  1004. var loadedSettings = $.parseJSON(localStorage.getItem(_settingsStoreName));
  1005. var defaultSettings = {
  1006. lastVersion:null,
  1007. layerVisible:true,
  1008. activeStateAbbr:'ALL',
  1009. hideStreet:false
  1010. };
  1011. _settings = loadedSettings ? loadedSettings : defaultSettings;
  1012. for (var prop in defaultSettings) {
  1013. if (!_settings.hasOwnProperty(prop)) {
  1014. _settings[prop] = defaultSettings[prop];
  1015. }
  1016. }
  1017. }
  1018.  
  1019. function saveSettingsToStorage() {
  1020. if (localStorage) {
  1021. _settings.lastVersion = _scriptVersion;
  1022. _settings.layerVisible = _mapLayer.visibility;
  1023. localStorage.setItem(_settingsStoreName, JSON.stringify(_settings));
  1024. log('Settings saved', 1);
  1025. }
  1026. }
  1027.  
  1028. function getLineWidth() {
  1029. return 12 * Math.pow(1.15, (W.map.getZoom()-1));
  1030. }
  1031.  
  1032. function sortArray(array) {
  1033. array.sort(function(a, b){if (a < b)return -1;if (a > b)return 1;else return 0;});
  1034. }
  1035.  
  1036. function getVisibleStateAbbrs() {
  1037. var visibleStates = [];
  1038. W.model.states.additionalInfo.forEach(function(state) {
  1039. var stateAbbr = _statesHash[state.name];
  1040. var activeStateAbbr = _settings.activeStateAbbr;
  1041. if(_stateSettings[stateAbbr] && _stateSettings.global.isPermitted(stateAbbr) && (!activeStateAbbr || activeStateAbbr === 'ALL' || activeStateAbbr === stateAbbr)) {
  1042. visibleStates.push(stateAbbr);
  1043. }
  1044. });
  1045. return visibleStates;
  1046. }
  1047.  
  1048. function getAsync(url, context) {
  1049. return new Promise(function(resolve, reject) {
  1050. GM_xmlhttpRequest({
  1051. context:context, method:"GET", url:url,
  1052. onload:function(res) {
  1053. if (res.status.toString() === '200') {
  1054. resolve({responseText: res.responseText, context:context});
  1055. } else {
  1056. reject({responseText: res.responseText, context:context});
  1057. }
  1058. },
  1059. onerror: function() {
  1060. reject(Error("Network Error"));
  1061. }
  1062. });
  1063. });
  1064. }
  1065. function wait(ms){
  1066. var start = new Date().getTime();
  1067. var end = start;
  1068. while(end < start + ms) {
  1069. end = new Date().getTime();
  1070. }
  1071. }
  1072. function getUrl(context, queryType, queryParams) {
  1073. var extent = context.mapContext.extent,
  1074. zoom = context.mapContext.zoom,
  1075. layer = context.layer,
  1076. state = context.state;
  1077.  
  1078. var whereParts = [];
  1079. var geometry = { xmin:extent.left, ymin:extent.bottom, xmax:extent.right, ymax:extent.top, spatialReference: {wkid: 102100, latestWkid: 3857} };
  1080. var geometryStr = JSON.stringify(geometry);
  1081. var stateWhereClause = state.getWhereClause(context);
  1082. var layerPath = layer.layerPath || '';
  1083. var url = state.baseUrl + layerPath + layer.layerID + '/query?geometry=' + encodeURIComponent(geometryStr);
  1084.  
  1085. if (queryType === 'countOnly') {
  1086. url += '&returnCountOnly=true';
  1087. } else if (queryType === 'idsOnly') {
  1088. url += '&returnIdsOnly=true';
  1089. } else if (queryType === 'paged') {
  1090. // TODO
  1091. } else {
  1092. url += '&returnGeometry=true&maxAllowableOffset=' + state.zoomSettings.maxOffset[zoom];
  1093. url += '&outFields=' + encodeURIComponent(layer.outFields.join(','));
  1094. if (queryType === 'idRange') {
  1095. var idPropName = context.layer.idPropName;
  1096. whereParts.push('(' + queryParams.idFieldName + '>=' + queryParams.range[0] + ' AND ' + queryParams.idFieldName + '<=' + queryParams.range[1] + ')');
  1097. }
  1098. }
  1099. if (stateWhereClause) whereParts.push(stateWhereClause);
  1100. if (whereParts.length > 0 ) url += '&where=' + encodeURIComponent(whereParts.join(' AND '));
  1101. url += '&spatialRel=esriSpatialRelIntersects&geometryType=esriGeometryEnvelope&inSR=102100&outSR=3857&f=json';
  1102. //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.
  1103. return url;
  1104. }
  1105.  
  1106. function convertFcToRoadTypeVectors(feature, state, stateAbbr, layer, zoom) {
  1107. var roadType = state.getFeatureRoadType(feature, layer);
  1108. log(feature,3);
  1109. var zIndex = _stateSettings.global.roadTypes.indexOf(roadType) * 100;
  1110. var vectors = [];
  1111. var lineFeatures = [];
  1112. var attr = {
  1113. //fcFeatureUniqueId: stateAbbr + '-' + layer.layerID + '-' + feature.attributes[layer.idPropName],
  1114. //fcFeatureId: feature.attributes[layer.idPropName],
  1115. state: stateAbbr,
  1116. layerID: layer.layerID,
  1117. roadType: roadType,
  1118. dotAttributes: $.extend({}, feature.attributes),
  1119. color: state.defaultColors[roadType],
  1120. strokeWidth: getLineWidth,
  1121. zIndex: zIndex
  1122. };
  1123.  
  1124. feature.geometry.paths.forEach(function(path){
  1125. var pointList = [];
  1126. var newPoint = null;
  1127. var lastPoint = null;
  1128. path.forEach(function(point){
  1129. pointList.push(new OL.Geometry.Point(point[0],point[1]));
  1130. });
  1131. var vectorFeature = new OL.Feature.Vector(new OL.Geometry.LineString(pointList),attr);
  1132. vectors.push(vectorFeature);
  1133. });
  1134.  
  1135. return vectors;
  1136. }
  1137.  
  1138. function fetchLayerFC(context) {
  1139. var url = getUrl(context, 'idsOnly');
  1140. log(url,2);
  1141. if (!context.parentContext.cancel) {
  1142. return getAsync(url, context).bind(context).then(function(res) {
  1143. var ids = $.parseJSON(res.responseText);
  1144. if(!ids.objectIds) ids.objectIds = [];
  1145. sortArray(ids.objectIds);
  1146. log(ids,2);
  1147. return ids;
  1148. }).then(function(res) {
  1149. var context = this;
  1150. var idRanges = [];
  1151. if (res.objectIds) {
  1152. var len = res.objectIds ? res.objectIds.length : 0;
  1153. var currentIndex = 0;
  1154. var offset = Math.min(this.layer.maxRecordCount,1000);
  1155. while (currentIndex < len) {
  1156. var nextIndex = currentIndex + offset;
  1157. if (nextIndex >= len) nextIndex = len - 1;
  1158. idRanges.push({range:[res.objectIds[currentIndex], res.objectIds[nextIndex]], idFieldName:res.objectIdFieldName});
  1159. currentIndex = nextIndex + 1;
  1160. }
  1161. log(context.layer.layerID, 2);
  1162. log(idRanges,2);
  1163. }
  1164. return idRanges;
  1165. }).map(function(idRange) {
  1166. var context = this;
  1167. if(!context.parentContext.cancel) {
  1168. var url = getUrl(this, 'idRange', idRange);
  1169. log(url,2);
  1170. return getAsync(url, context).then(function(res) {
  1171. var context = res.context;
  1172. if(!context.parentContext.cancel) {
  1173. var features = $.parseJSON(res.responseText).features;
  1174. // if (context.parentContext.callCount === 0 ) {
  1175. // _mapLayer.removeAllFeatures();
  1176. // }
  1177. context.parentContext.callCount++;
  1178. log('Feature Count=' + (features ? features.length : 0),2);
  1179. features = features ? features : [];
  1180. var vectors = [];
  1181. features.forEach(function(feature) {
  1182. if(!res.context.parentContext.cancel) {
  1183. var vector = convertFcToRoadTypeVectors(feature, context.state, context.stateAbbr, context.layer, context.mapContext.zoom);
  1184. //var fcFeatureUniqueId = vector[0].attributes.fcFeatureUniqueId;
  1185. //context.parentContext.addedFcFeatureUniqueIds.push(fcFeatureUniqueId);
  1186. if (/*!context.parentContext.existingFcFeatureUniqueIds[fcFeatureUniqueId] &&*/ !(vector[0].attributes.roadType === 'St' && _settings.hideStreet)) {
  1187. vectors.push(vector);
  1188. }
  1189. }
  1190. });
  1191. return vectors;
  1192. }
  1193. });
  1194. } else {
  1195. log('Async call cancelled',1);
  1196. }
  1197. });
  1198. }
  1199. }
  1200.  
  1201. function fetchStateFC(context) {
  1202. var state = _stateSettings[context.stateAbbr];
  1203. var contexts = [];
  1204. state.fcMapLayers.forEach(function(layer) {
  1205. contexts.push({parentContext:context.parentContext, layer:layer, state:state, stateAbbr:context.stateAbbr, mapContext:context.mapContext});
  1206. });
  1207. return Promise.map(contexts, function(context) {
  1208. return fetchLayerFC(context);
  1209. });
  1210. }
  1211.  
  1212. var _lastPromise = null;
  1213. var _lastContext = null;
  1214. var _fcCallCount = 0;
  1215. function fetchAllFC() {
  1216. if (!_mapLayer.visibility) return;
  1217.  
  1218. if (_lastPromise) { _lastPromise.cancel(); }
  1219. $('#fc-loading-indicator').text('Loading FC...');
  1220.  
  1221. var mapContext = { zoom:W.map.getZoom(), extent:W.map.getExtent() };
  1222. var contexts = [];
  1223. var parentContext = {callCount:0,/*existingFcFeatureUniqueIds:{}, addedFcFeatureUniqueIds:[],*/ startTime:Date.now()};
  1224. // _mapLayer.features.forEach(function(vectorFeature) {
  1225. // var fcFeatureUniqueId = vectorFeature.attributes.fcFeatureUniqueId;
  1226. // var existingFcFeatureUniqueIdArray = parentContext.existingFcFeatureUniqueIds[fcFeatureUniqueId];
  1227. // if (!existingFcFeatureUniqueIdArray) {
  1228. // existingFcFeatureUniqueIdArray = [];
  1229. // parentContext.existingFcFeatureUniqueIds[fcFeatureUniqueId] = existingFcFeatureUniqueIdArray;
  1230. // }
  1231. // existingFcFeatureUniqueIdArray.push(vectorFeature);
  1232. // });
  1233. if (_lastContext) _lastContext.cancel = true;
  1234. _lastContext = parentContext;
  1235. getVisibleStateAbbrs().forEach(function(stateAbbr) {
  1236. contexts.push({ parentContext:parentContext, stateAbbr:stateAbbr, mapContext:mapContext});
  1237. });
  1238. var map = Promise.map(contexts, function(context) {
  1239. return fetchStateFC(context);
  1240. }).bind(parentContext).then(function(statesVectorArrays) {
  1241. if (!this.cancel) {
  1242. _mapLayer.removeAllFeatures();
  1243. statesVectorArrays.forEach(function(vectorsArray) {
  1244. vectorsArray.forEach(function(vectors) {
  1245. vectors.forEach(function(vector) {
  1246. vector.forEach(function(vectorFeature) {
  1247. _mapLayer.addFeatures(vectorFeature);
  1248. });
  1249. });
  1250. });
  1251. });
  1252. //buildTable();
  1253. // for(var fcFeatureUniqueId in this.existingFcFeatureUniqueIds) {
  1254. // if(this.addedFcFeatureUniqueIds.indexOf(fcFeatureUniqueId) === -1) {
  1255. // if (!this.cancel) _mapLayer.removeFeatures(this.existingFcFeatureUniqueIds[fcFeatureUniqueId]);
  1256. // }
  1257. // }
  1258. log('TOTAL RETRIEVAL TIME = ' + (Date.now() - parentContext.startTime),1);
  1259. log(statesVectorArrays,1);
  1260. }
  1261. return statesVectorArrays;
  1262. }).catch(function(e) {
  1263. $('#fc-loading-indicator').text('FC Error! (check console for details)');
  1264. log(e,0);
  1265. }).finally(function() {
  1266. _fcCallCount -= 1;
  1267. if (_fcCallCount === 0) {
  1268. $('#fc-loading-indicator').text('');
  1269. }
  1270. });
  1271.  
  1272. _fcCallCount += 1;
  1273. _lastPromise = map;
  1274. }
  1275.  
  1276. function onLayerCheckboxChanged(checked) {
  1277. _mapLayer.setVisibility(checked);
  1278. }
  1279.  
  1280. function onLayerVisibilityChanged(evt) {
  1281. _settings.layerVisible = _mapLayer.visibility;
  1282. saveSettingsToStorage();
  1283. if (_mapLayer.visibility) {
  1284. fetchAllFC();
  1285. }
  1286. }
  1287.  
  1288. function onModeChanged(model, modeId, context) {
  1289. if(!modeId || modeId === 1) {
  1290. initUserPanel();
  1291. }
  1292. }
  1293.  
  1294. function showScriptInfoAlert() {
  1295. /* Check version and alert on update */
  1296. if (_alertUpdate && _scriptVersion !== _settings.lastVersion) {
  1297. alert(_scriptVersionChanges);
  1298. }
  1299. }
  1300.  
  1301. function initLayer(){
  1302. var _drawingContext = {
  1303. getZIndex: function(feature) {
  1304. return feature.attributes.zIndex;
  1305. },
  1306. getStrokeWidth: function() { return getLineWidth(); }
  1307. };
  1308. var defaultStyle = new OL.Style({
  1309. strokeColor: '${color}', //'#00aaff',
  1310. strokeDashstyle: "solid",
  1311. strokeOpacity: 1.0,
  1312. strokeWidth: '${strokeWidth}',
  1313. graphicZIndex: '${zIndex}'
  1314. });
  1315.  
  1316. var selectStyle = new OL.Style({
  1317. //strokeOpacity: 1.0,
  1318. strokeColor: '#000000'
  1319. });
  1320.  
  1321. _mapLayer = new OL.Layer.Vector("FC Layer", {
  1322. uniqueName: "__FCLayer",
  1323. displayInLayerSwitcher: false,
  1324. rendererOptions: { zIndexing: true },
  1325. styleMap: new OL.StyleMap({
  1326. 'default': defaultStyle,
  1327. 'select': selectStyle
  1328. })
  1329. });
  1330.  
  1331. _mapLayer.setOpacity(0.5);
  1332.  
  1333. I18n.translations[I18n.locale].layers.name.__FCLayer = "FC Layer";
  1334.  
  1335. _mapLayer.displayInLayerSwitcher = true;
  1336. _mapLayer.events.register('visibilitychanged',null,onLayerVisibilityChanged);
  1337. _mapLayer.setVisibility(_settings.layerVisible);
  1338.  
  1339. W.map.addLayer(_mapLayer);
  1340. _mapLayer.setZIndex(_mapLayerZIndex);
  1341. WazeWrap.Interface.AddLayerCheckbox('Display', 'FC Layer', _settings.layerVisible, onLayerCheckboxChanged);
  1342. // Hack to fix layer zIndex. Some other code is changing it sometimes but I have not been able to figure out why.
  1343. // 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. (?)
  1344.  
  1345. var checkLayerZIndex = function() {
  1346. if (_mapLayer.getZIndex() != _mapLayerZIndex) {
  1347. log("ADJUSTED FC LAYER Z-INDEX " + _mapLayerZIndex + ', ' + _mapLayer.getZIndex(),1);
  1348. _mapLayer.setZIndex(_mapLayerZIndex);
  1349. }
  1350. };
  1351.  
  1352. setInterval(function(){checkLayerZIndex();}, 200);
  1353.  
  1354. W.map.events.register("moveend",W.map,function(e){
  1355. fetchAllFC();
  1356. return true;
  1357. },true);
  1358. }
  1359.  
  1360. function initUserPanel() {
  1361. var $tab = $('<li>').append($('<a>', {'data-toggle':'tab', href:'#sidepanel-fc-layer'}).text('FC'));
  1362. var $panel = $('<div>', {class:'tab-pane', id:'sidepanel-fc-layer'});
  1363. var $stateSelect = $('<select>', {id:'fcl-state-select',class:'form-control disabled',style:'disabled'}).append($('<option>', {value:'ALL'}).text('All'));
  1364. // $stateSelect.change(function(evt) {
  1365. // _settings.activeStateAbbr = evt.target.value;
  1366. // saveSettingsToStorage();
  1367. // _mapLayer.removeAllFeatures();
  1368. // fetchAllFC();
  1369. // });
  1370. for (var stateAbbr in _stateSettings) {
  1371. if (stateAbbr !== 'global') {
  1372. $stateSelect.append($('<option>', {value:stateAbbr}).text(reverseStatesHash(stateAbbr)));
  1373. }
  1374. }
  1375.  
  1376. var $hideStreet = $('<div>',{id: 'fcl-hide-street-container', class:'controls-container'})
  1377. .append($('<input>', {type:'checkbox',name:'fcl-hide-street',id:'fcl-hide-street'}).prop('checked', _settings.hideStreet).click(function() {
  1378. _settings.hideStreet = $(this).is(':checked');
  1379. saveSettingsToStorage();
  1380. _mapLayer.removeAllFeatures();
  1381. fetchAllFC();
  1382. }))
  1383. .append($('<label>', {for:'fcl-hide-street'}).text('Hide street highlights'));
  1384.  
  1385. $stateSelect.val(_settings.activeStateAbbr ? _settings.activeStateAbbr : 'ALL');
  1386.  
  1387. $panel.append(
  1388. $('<div>', {class:'form-group'}).append(
  1389. $('<label>', {class:'control-label'}).text('Select a state')
  1390. ).append(
  1391. $('<div>', {class:'controls', id:'fcl-state-select-container'}).append(
  1392. $('<div>').append($stateSelect)
  1393. )
  1394. ),
  1395. $hideStreet ,
  1396. $('<div>', {id:'fcl-table-container'})
  1397. );
  1398.  
  1399. $panel.append(
  1400. $('<div>',{style:'margin-top:10px;font-size:10px;color:#999999;'})
  1401. .append($('<div>').text('version ' + _scriptVersion))
  1402. .append(
  1403. $('<div>').append(
  1404. $('<a>',{href:'#' /*, target:'__blank'*/}).text('Discussion Forum (currently n/a)')
  1405. )
  1406. )
  1407. );
  1408.  
  1409. $('#user-tabs > .nav-tabs').append($tab);
  1410. $('#user-info > .flex-parent > .tab-content').append($panel);
  1411. $('#fcl-state-select').change(function () {
  1412. _settings.activeStateAbbr = this.value;
  1413. saveSettingsToStorage();
  1414. fetchAllFC();
  1415. });
  1416. }
  1417.  
  1418. function addLoadingIndicator() {
  1419. $('.loading-indicator').after($('<div class="loading-indicator" style="margin-right:10px" id="fc-loading-indicator">'));
  1420. }
  1421.  
  1422. function initGui() {
  1423. addLoadingIndicator();
  1424. initLayer();
  1425. initUserPanel();
  1426. showScriptInfoAlert();
  1427. }
  1428.  
  1429. function processText(text) {
  1430. return new Promise(function(resolve, reject) {
  1431. var newText = text.replace(/(e)/,'E');
  1432. resolve(newText);
  1433. });
  1434. }
  1435.  
  1436. function init() {
  1437. if (_debugLevel > 0 && Promise.config) {
  1438. Promise.config({
  1439. warnings: true,
  1440. longStackTraces: true,
  1441. cancellation: true,
  1442. monitoring: false
  1443. });
  1444. } else {
  1445. Promise.config({
  1446. warnings: false,
  1447. longStackTraces: false,
  1448. cancellation: true,
  1449. monitoring: false
  1450. });
  1451. }
  1452.  
  1453. var u = W.loginManager.user;
  1454. _uid = u.id;
  1455. _r = u.rank;
  1456. _isAM = u.isAreaManager;
  1457. loadSettingsFromStorage();
  1458. String.prototype.replaceAll = function(search, replacement) {
  1459. var target = this;
  1460. return target.replace(new RegExp(search, 'g'), replacement);
  1461. };
  1462. initGui();
  1463. W.app.modeController.model.bind('change:mode', onModeChanged);
  1464. W.prefs.on("change:isImperial", function() {initUserPanel();loadSettingsFromStorage();});
  1465. fetchAllFC();
  1466. log('Initialized.', 0);
  1467. }
  1468.  
  1469. function bootstrap() {
  1470. if (W && W.loginManager &&
  1471. W.loginManager.events &&
  1472. W.loginManager.events.register &&
  1473. W.model && W.model.states && W.model.states.additionalInfo &&
  1474. W.map && W.loginManager.user &&
  1475. WazeWrap.Version) {
  1476. log('Initializing...', 0);
  1477.  
  1478. init();
  1479. } else {
  1480. log('Bootstrap failed. Trying again...', 0);
  1481. unsafeWindow.setTimeout(function () {
  1482. bootstrap();
  1483. }, 1000);
  1484. }
  1485. }
  1486.  
  1487. log('Bootstrap...', 0);
  1488. bootstrap();
  1489. })();