WME FC Layer

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

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

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