WME SC Emergency Management Closures

Adds a road closure layer for Sourth Carolina Emergency Management

  1. // ==UserScript==
  2. // @name WME SC Emergency Management Closures
  3. // @namespace https://greatest.deepsurf.us/users/45389
  4. // @version 0.5
  5. // @description Adds a road closure layer for Sourth Carolina Emergency Management
  6. // @author MapOMatic
  7. // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
  8. // @license GNU GPLv3
  9. // @grant GM_xmlhttpRequest
  10. // @connect arcgis.com
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. var _settingsStoreName = 'wme_sc_emergency_management_closures';
  17. var _alertUpdate = false;
  18. var _debugLevel = 0;
  19. var _scriptVersion = GM_info.script.version;
  20. var _scriptVersionChanges = [
  21. GM_info.script.name + '\nv' + _scriptVersion + '\n\nWhat\'s New\n------------------------------\n',
  22. '\n- .',
  23. ].join('');
  24. var _tabDiv = {}; // stores the user tab div so it can be restored after switching back from Events mode to Default mode
  25. var _mapLayer = null;
  26. var _styles = {};
  27. var _settings = {};
  28. var _statesHash = {
  29. 'Alabama': 'AL',
  30. 'Alaska': 'AK',
  31. 'American Samoa': 'AS',
  32. 'Arizona': 'AZ',
  33. 'Arkansas': 'AR',
  34. 'California': 'CA',
  35. 'Colorado': 'CO',
  36. 'Connecticut': 'CT',
  37. 'Delaware': 'DE',
  38. 'District Of Columbia': 'DC',
  39. 'Federated States Of Micronesia': 'FM',
  40. 'Florida': 'FL',
  41. 'Georgia': 'GA',
  42. 'Guam': 'GU',
  43. 'Hawaii': 'HI',
  44. 'Idaho': 'ID',
  45. 'Illinois': 'IL',
  46. 'Indiana': 'IN',
  47. 'Iowa': 'IA',
  48. 'Kansas': 'KS',
  49. 'Kentucky': 'KY',
  50. 'Louisiana': 'LA',
  51. 'Maine': 'ME',
  52. 'Marshall Islands': 'MH',
  53. 'Maryland': 'MD',
  54. 'Massachusetts': 'MA',
  55. 'Michigan': 'MI',
  56. 'Minnesota': 'MN',
  57. 'Mississippi': 'MS',
  58. 'Missouri': 'MO',
  59. 'Montana': 'MT',
  60. 'Nebraska': 'NE',
  61. 'Nevada': 'NV',
  62. 'New Hampshire': 'NH',
  63. 'New Jersey': 'NJ',
  64. 'New Mexico': 'NM',
  65. 'New York': 'NY',
  66. 'North Carolina': 'NC',
  67. 'North Dakota': 'ND',
  68. 'Northern Mariana Islands': 'MP',
  69. 'Ohio': 'OH',
  70. 'Oklahoma': 'OK',
  71. 'Oregon': 'OR',
  72. 'Palau': 'PW',
  73. 'Pennsylvania': 'PA',
  74. 'Puerto Rico': 'PR',
  75. 'Rhode Island': 'RI',
  76. 'South Carolina': 'SC',
  77. 'South Dakota': 'SD',
  78. 'Tennessee': 'TN',
  79. 'Texas': 'TX',
  80. 'Utah': 'UT',
  81. 'Vermont': 'VT',
  82. 'Virgin Islands': 'VI',
  83. 'Virginia': 'VA',
  84. 'Washington': 'WA',
  85. 'West Virginia': 'WV',
  86. 'Wisconsin': 'WI',
  87. 'Wyoming': 'WY'
  88. };
  89. var _stateSettings = {
  90. SC: {
  91. baseUrl: '',
  92. mapLayers: [
  93. { layerID:'https://services1.arcgis.com/VaY7cY9pvUYUP1Lf/ArcGIS/rest/services/CLOSURES_06_OCT_LINES/FeatureServer/0', outFields:[''] },
  94. { layerID:'https://services1.arcgis.com/VaY7cY9pvUYUP1Lf/arcgis/rest/services/Hurricane_Matthew_Closures_Lines/FeatureServer/0', outFields:[''] },
  95. ],
  96. colors: {closure:'#ff0000'}
  97. }
  98. };
  99.  
  100. function log(message, level) {
  101. if (message && level <= _debugLevel) {
  102. console.log('WME SCEM: ' + message);
  103. }
  104. }
  105.  
  106. function loadSettingsFromStorage() {
  107. var settings = $.parseJSON(localStorage.getItem(_settingsStoreName));
  108. if(!settings) {
  109. settings = {
  110. lastVersion:null,
  111. layerVisible:true
  112. };
  113. } else {
  114. settings.layerVisible = true; //(settings.layerVisible === true);
  115. }
  116. _settings = settings;
  117. }
  118.  
  119. function saveSettingsToStorage() {
  120. if (localStorage) {
  121. var settings = {
  122. lastVersion: _scriptVersion,
  123. layerVisible: _mapLayer.visibility,
  124. };
  125. localStorage.setItem(_settingsStoreName, JSON.stringify(settings));
  126. log('Settings saved', 1);
  127. }
  128. }
  129.  
  130. function getLineWidth() {
  131. return 8 * Math.pow(1.15, (W.map.getZoom()-1));
  132. }
  133.  
  134. function draw(buckets) {
  135. _mapLayer.removeAllFeatures();
  136. for(var i=buckets.length-1; i>-1; i--) {
  137. _mapLayer.addFeatures(buckets[i]);
  138. }
  139. }
  140.  
  141. function processFeatures(data,context) {
  142. var layer = context.layer;
  143. var stateSettings = context.stateSettings;
  144. var styles = {};
  145. for (var prefix in stateSettings.colors) {
  146. var color = stateSettings.colors[prefix];
  147. styles[prefix] = {
  148. strokeColor: color,
  149. strokeDashstyle: "solid",
  150. strokeOpacity: 0.6,
  151. strokeWidth: getLineWidth()
  152. };
  153. }
  154.  
  155. data.features.forEach(function(feature) {
  156. var style = styles.closure;
  157. var lineFeatures = [];
  158. feature.geometry.paths.forEach(function(path){
  159. var pointList = [];
  160. var newPoint = null;
  161. path.forEach(function(point){
  162. pointList.push(new OpenLayers.Geometry.Point(point[0],point[1]));
  163. });
  164. context.buckets[0].push( new OpenLayers.Feature.Vector(
  165. new OpenLayers.Geometry.LineString(pointList),null,style
  166. ));
  167. });
  168. });
  169.  
  170. context.callCount.count -= 1;
  171. if (context.callCount.count === 0) {
  172. //console.log(context.buckets);
  173. draw(context.buckets);
  174. }
  175. if (data.exceededTransferLimit) {
  176. log('Exceeded server\'s feature transfer limit. Some features may not be drawn.',0);
  177. }
  178. }
  179.  
  180. function fetchFeatures() {
  181. var visibleStates = [];
  182. //debugger;
  183. console.log(W.model.states.additionalInfo);
  184. W.model.states.additionalInfo.forEach(function(state) {
  185. visibleStates.push(_statesHash[state.name]);
  186. });
  187.  
  188. var zoom = W.map.getZoom();
  189. var ext = W.map.getExtent();
  190.  
  191. var geometry = {
  192. xmin: ext.left,
  193. ymin: ext.bottom,
  194. xmax: ext.right,
  195. ymax: ext.top
  196. };
  197.  
  198. var geometryStr = JSON.stringify(geometry).replaceAll('{','%7B').replaceAll('}','%7D').replaceAll('"','%22').replaceAll(':','%3A').replaceAll(',','%2C');
  199. var callCount = 0;
  200. var buckets = [];
  201. var urls = [];
  202. var contexts = [];
  203.  
  204. visibleStates.forEach(function(stateAbbr) {
  205. var state = _stateSettings[stateAbbr];
  206. if (state) {
  207. for(var i=0; i<7; i++) {buckets.push([]);}
  208. state.mapLayers.forEach(function(layer) {
  209. if (!layer.zoomLevels || layer.zoomLevels.indexOf(zoom) !== -1) {
  210. var context = {
  211. stateSettings: state,
  212. layer: layer,
  213. buckets: buckets
  214. };
  215. var url = state.baseUrl + layer.layerID + '/query?';
  216. url += 'geometry=' + geometryStr;
  217. url += '&outFields=' + layer.outFields.join('%2C');
  218. url += '&returnGeometry=true&spatialRel=esriSpatialRelIntersects&geometryType=esriGeometryEnvelope&inSR=102100&outSR=3857&f=json';
  219. urls.push(url);
  220. console.log(url);
  221. contexts.push(context);
  222. callCount += 1;
  223.  
  224. }
  225. });
  226. }
  227. });
  228. var countObj = {count: callCount};
  229. for (var j=0; j<urls.length; j++) {
  230. contexts[j].callCount = countObj;
  231. console.log(urls[j]);
  232. GM_xmlhttpRequest({
  233. context: contexts[j],
  234. method: "GET",
  235. url: urls[j],
  236. onload: function(res) {
  237. processFeatures($.parseJSON(res.responseText), res.context);
  238. }
  239. });
  240. }
  241. }
  242.  
  243. function onLayerVisibilityChanged(evt) {
  244. saveSettingsToStorage();
  245. }
  246.  
  247. function restoreUserTab() {
  248. // $('#user-tabs > .nav-tabs').append(_tabDiv.tab);
  249. // $('#user-info > .flex-parent > .tab-content').append(_tabDiv.panel);
  250. // $('#fcl-state-select').change(function () {
  251. // _settings.activeStateAbbr = this.value;
  252. // saveSettingsToStorage();
  253. // fetchFeatures();
  254. // });
  255. }
  256.  
  257. function onModeChanged(model, modeId, context) {
  258. if(!modeId || modeId === 1) {
  259. restoreUserTab();
  260. }
  261. }
  262.  
  263. function showScriptInfoAlert() {
  264. /* Check version and alert on update */
  265. if (_alertUpdate && _scriptVersion !== _settings.lastVersion) {
  266. alert(_scriptVersionChanges);
  267. }
  268. }
  269.  
  270. function initLayer(){
  271. var mapLayerZIndex = 334;
  272. _mapLayer = new OpenLayers.Layer.Vector("SC Emergency Closures ", {
  273. uniqueName: "__SCECRoadsLayer",
  274. displayInLayerSwitcher: false
  275. });
  276. I18n.translations[W.location.locale].layers.name.__FCLayer = "SC Emergency Closures";
  277. W.map.addLayer(_mapLayer);
  278. _mapLayer.setZIndex(mapLayerZIndex);
  279.  
  280. _mapLayer.displayInLayerSwitcher = true;
  281. _mapLayer.events.register('visibilitychanged',null,onLayerVisibilityChanged);
  282. _mapLayer.setVisibility(_settings.layerVisible);
  283. // Hack to fix layer zIndex. Some other code is changing it sometimes but I have not been able to figure out why.
  284. // 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.
  285. var checkLayerZIndex = function(layerZIndex) {
  286. if (_mapLayer.getZIndex() != mapLayerZIndex) {
  287. //log("ADJUSTED LAYER Z-INDEX",1);
  288. _mapLayer.setZIndex(mapLayerZIndex);
  289. }
  290. };
  291.  
  292. setInterval(function(){checkLayerZIndex(mapLayerZIndex);}, 200);
  293.  
  294. W.map.events.register("moveend",W.map,function(e){
  295. fetchFeatures();
  296. return true;
  297. },true);
  298.  
  299. // for(var i=0; i<_activeState.defaultColors.length; i++) {
  300. // var color = _activeState.defaultColors[i];
  301. // var fc = i + 1;
  302. // _styles[fc] = {
  303. // strokeColor: color,
  304. // strokeDashstyle: "solid",
  305. // strokeOpacity: 0.5,
  306. // strokeWidth: getLineWidth()
  307. // };
  308. // }
  309. }
  310.  
  311. function initUserPanel() {
  312. // _tabDiv.tab = $('<li>').append(
  313. // $('<a>', {'data-toggle':'tab', href:'#sidepanel-fc-layer'}).text('FC Layer')
  314. // );
  315.  
  316. // _tabDiv.panel = $('<div>', {class:'tab-pane', id:'sidepanel-fc-layer'});/*.append(
  317. // $('<div>', {class:'side-panel-section>'}).append(
  318. // $('<div>', {class:'form-group'}).append(
  319. // $('<label>', {class:'control-label'}).text('Select a state')
  320. // ).append(
  321. // $('<div>', {class:'controls', id:'fcl-state-select-container'}).append(
  322. // $('<div>').append(
  323. // $('<select>', {id:'fcl-state-select',class:'form-control disabled',style:'disabled'})
  324. // .append($('<option>', {value:'IN'}).text('Indiana'))
  325. // //.append($('<option>', {value:'KY'}).text('Kentucky'))
  326. // .append($('<option>', {value:'MD'}).text('Maryland'))
  327. // .append($('<option>', {value:'MI'}).text('Michigan'))
  328. // .append($('<option>', {value:'NC'}).text('North Carolina'))
  329. // .append($('<option>', {value:'VA'}).text('Virginia'))
  330. // .val(_settings.activeStateAbbr)
  331. // // ).append(
  332. // // $('<div>',{class:'controls-container'})
  333. // // .append($('<input>', {type:'checkbox',class:'csSettingsCheckBox',name:'csDirectionButtonsCheckBox',id:'csDirectionButtonsCheckBox'}))
  334. // // .append($('<label>', {for:'csDirectionButtonsCheckBox'}).text('Add road direction buttons'))
  335. // // ).append(
  336. // // $('<input class="jscolor" value="ab2567">').change(function(evt) {console.log(evt);})
  337. // )
  338. // )
  339. // )
  340. // )
  341. // );*/
  342.  
  343. // restoreUserTab();
  344. }
  345.  
  346. function initGui() {
  347. initLayer();
  348. initUserPanel();
  349. showScriptInfoAlert();
  350. }
  351.  
  352. function init() {
  353. loadSettingsFromStorage();
  354. String.prototype.replaceAll = function(search, replacement) {
  355. var target = this;
  356. return target.replace(new RegExp(search, 'g'), replacement);
  357. };
  358. //_activeState = _stateSettings[_settings.activeStateAbbr];
  359. initGui();
  360. unsafeWindow.addEventListener('beforeunload', function saveOnClose() { saveSettingsToStorage(); }, false);
  361. Waze.app.modeController.model.bind('change:mode', onModeChanged);
  362. fetchFeatures();
  363. log('Initialized.', 0);
  364. }
  365.  
  366. function bootstrap() {
  367. if (W && W.loginManager &&
  368. W.loginManager.events.register && W.model && W.model.states && W.model.states.additionalInfo &&
  369. W.map && W.loginManager.isLoggedIn()) {
  370. log('Initializing...', 0);
  371. init();
  372. } else {
  373. log('Bootstrap failed. Trying again...', 0);
  374. setTimeout(function () {
  375. bootstrap();
  376. }, 1000);
  377. }
  378. }
  379.  
  380. log('Bootstrap...', 0);
  381. bootstrap();
  382. })();