- // ==UserScript==
- // @name WME Cities Overlay
- // @namespace https://greasyfork.org/en/users/166843-wazedev
- // @version 2024.04.08.01
- // @description Adds a city overlay for selected states
- // @author WazeDev
- // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
- // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
- // @require https://greasyfork.org/scripts/369729-wme-cities-overlay-db/code/WME%20Cities%20Overlay%20DB.js
- // @license GNU GPLv3
- // @grant GM_xmlhttpRequest
- // @connect api.github.com
- // @connect raw.githubusercontent.com
- // @connect
- // @contributionURL https://github.com/WazeDev/Thank-The-Authors
- // ==/UserScript==
-
- /* global W */
- /* global OpenLayers */
- /* ecmaVersion 2017 */
- /* global $ */
- /* global idbKeyval */
- /* global WazeWrap */
- /* global I18n */
- /* eslint curly: ["warn", "multi-or-nest"] */
-
- (function() {
- 'use strict';
-
- var _color = '#E6E6E6';
- var _settingsStoreName = '_wme_cities';
- var _settings;
- var _features;
- var _kml;
- var _layerName = 'Cities Overlay';
- var _layer = null;
- var defaultFillOpacity = 0.3;
- var defaultStrokeOpacity = 0.6;
- var noFillStrokeOpacity = 0.9;
- var repoOwner = 'WazeDev';
-
- let currState = "";
- let currCity = "";
- let _US_States = {};
- let _MX_States = {};
- let kmlCache = {};
-
- let indexedDBSupport = false;
- let citiesDB;
-
- function isChecked(checkboxId) {
- return $('#' + checkboxId).is(':checked');
- }
-
- function setChecked(checkboxId, checked) {
- $('#' + checkboxId).prop('checked', checked);
- }
-
- function loadSettings() {
- _settings = $.parseJSON(localStorage.getItem(_settingsStoreName));
- let _defaultsettings = {
- layerVisible: true,
- ShowCityLabels: true,
- FillPolygons: true,
- HighlightFocusedCity: true,
- AutoUpdateKMLs: true
- //hiddenAreas: []
- };
- if(!_settings)
- _settings = _defaultsettings;
- for (var prop in _defaultsettings) {
- if (!_settings.hasOwnProperty(prop))
- _settings[prop] = _defaultsettings[prop];
- }
- }
-
- function saveSettings() {
- if (localStorage) {
- var settings = {
- layerVisible: _layer.visibility,
- ShowCityLabels: _settings.ShowCityLabels,
- FillPolygons: _settings.FillPolygons,
- HighlightFocusedCity: _settings.HighlightFocusedCity,
- AutoUpdateKMLs: _settings.AutoUpdateKMLs
- };
- localStorage.setItem(_settingsStoreName, JSON.stringify(settings));
- }
- }
-
- function GetFeaturesFromKMLString(strKML) {
- var format = new OpenLayers.Format.KML({
- 'internalProjection': W.map.getProjectionObject(),
- 'externalProjection': new OpenLayers.Projection("EPSG:4326")
- });
- return format.read(strKML);
- }
-
- function findCurrCity(){
- let newCity = "";
- var mapCenter = new OpenLayers.Geometry.Point(W.map.getCenter().lon,W.map.getCenter().lat);
- for (var i=0;i<_layer.features.length;i++){
- var feature = _layer.features[i];
- if(pointInFeature(feature.geometry, mapCenter)){
- newCity = feature.attributes.name;
- break;
- }
- }
- return newCity;
- }
-
- async function updateCitiesLayer(){
- let newCurrCity = findCurrCity();
- if(currCity != newCurrCity){
- currCity = newCurrCity;
- _layer.redraw();
- }
- await updateCityPolygons();
- updateDistrictNameDisplay();
- }
-
- function updateDistrictNameDisplay(){
- $('.wmecitiesoverlay-region').remove();
- if (_layer !== null) {
- if(_layer.features.length > 0){
- if(currCity != ""){
- let color = '#00ffff';
- var $div = $('<div>', {id:'wmecitiesoverlay', class:"wmecitiesoverlay-region", style:'float:left; margin-left:10px;'})//, title:'Click to toggle color on/off for this group'})
- .css({color:color, cursor:"pointer"});
- //.click(toggleAreaFill);
- var $span = $('<span>').css({display:'inline-block'});
- $span.text(currCity).appendTo($div);
- $('.location-info-region').after($div);
- }
- }
- }
- else
- _layer.destroyFeatures();
- }
-
- function pointInFeature(geometry, mapCenter){
- try{
- if(geometry.CLASS_NAME == "OpenLayers.Geometry.Collection" || geometry.CLASS_NAME == "OpenLayers.Geometry.Collection"){
- for(let i=0; i<geometry.components.length; i++){
- if(geometry.components[i].containsPoint(mapCenter))
- return true;
- }
- }
- else
- return geometry.containsPoint(mapCenter);
- }
- catch(err){
- console.log(err);
- }
- return false;
- }
-
- async function fetch(url){
- //return await $.get(url);
- return new Promise((resolve, reject) => {
- GM_xmlhttpRequest({
- url: url,
- method: 'GET',
- onload(res) {
- if (res.status < 400) {
- resolve(res.responseText);
- } else {
- reject(res);
- }
- },
- onerror(res) {
- reject(res);
- }
- });
- });
- }
-
- async function updateAllMaps(){
- let countryAbbr = W.model.getTopCountry().attributes.abbr;
- let keys = await idbKeyval.keys(`${countryAbbr}_states_cities`);
- let updatedCount = 0;
- let updatedStates = "";
- let countryAbbrObj;
-
- if(countryAbbr === "US")
- countryAbbrObj = _US_States;
- else if(countryAbbr === "MX")
- countryAbbrObj = _MX_States;
-
- let KMLinfoArr = await fetch(`https://api.github.com/repos/WazeDev/WME-Cities-Overlay/contents/KMLs/${countryAbbr}`);
- KMLinfoArr = $.parseJSON(KMLinfoArr);
- let state;
- for(let i=0; i<keys.length; i++){
- state = keys[i];
-
- for(let j=0; j<KMLinfoArr.length; j++){
- if(KMLinfoArr[j].name === `${state}_Cities.kml`){ //check the size in db against server - if different, update db
- let stateObj = await idbKeyval.get(`${countryAbbr}_states_cities`, state);
-
- if(stateObj.kmlsize !== KMLinfoArr[j].size){
- let kml = await fetch(`https://raw.githubusercontent.com/${repoOwner}/WME-Cities-Overlay/master/KMLs/${countryAbbr}/${state}_Cities.kml`);
-
- if(state === countryAbbrObj.getAbbreviation(currState))
- _kml = kml;
-
- await idbKeyval.set(`${countryAbbr}_states_cities`, {
- kml: kml,
- state: state,
- kmlsize: KMLinfoArr[j].size
- });
- if(kmlCache[state] != null)
- kmlCache[state] = _kml;
- if(updatedStates != "")
- updatedStates += `, ${state}`;
- else
- updatedStates += state;
- updatedCount+=1;
- }
- break;
- }
- }
- }
- if(updatedCount > 0)
- $('#WMECOupdateStatus').text(`${updatedCount} state file${updatedCount >1 ? "s" : ""} updated - ${updatedStates}`);
- else
- $('#WMECOupdateStatus').text("No updates available");
-
- updatePolygons();
- }
-
- /*function toggleAreaFill() {
- var text = $('#wmecitiesoverlay span').text();
- if (text) {
- var match = text.match(/WV-(\d+)/);
- if (match.length > 1) {
- var group = parseInt(match[1]);
- var f = _layer.features[group-1];
- var hide = f.attributes.fillOpacity !== 0;
- f.attributes.fillOpacity = hide ? 0 : defaultFillOpacity;
- var idx = _settings.hiddenAreas.indexOf(group);
- if (hide) {
- if (idx === -1) _settings.hiddenAreas.push(group);
- } else {
- if (idx > -1) {
- _settings.hiddenAreas.splice(idx,1);
- }
- }
- //saveSettingsToStorage();
- _layer.redraw();
- }
- }
- }*/
-
- function init() {
- _US_States = {
- Alabama:"AL", Alaska:"AK", Arizona:"AZ", Arkansas:"AR", California:"CA", Colorado:"CO", Connecticut:"CT",
- "District of Columbia":"DC", Delaware:"DE", Florida:"FL", Georgia:"GA", Hawaii:"HI", Idaho:"ID", Illinois:"IL", Indiana:"IN",
- Iowa:"IA", Kansas:"KS", Kentucky:"KY", Louisiana:"LA", Maine:"ME", Maryland:"MD", Massachusetts:"MA",
- Michigan:"MI", Minnesota:"MN", Mississippi:"MS", Missouri:"MO", Montana:"MT", Nebraska:"NE", Nevada:"NV", "New Hampshire":"NH",
- "New Jersey":"NJ", "New Mexico":"NM", "New York":"NY", "North Carolina":"NC", "North Dakota":"ND", Ohio:"OH", Oklahoma:"OK", Oregon:"OR", Pennsylvania:"PA",
- "Rhode Island":"RI", "South Carolina":"SC", "South Dakota":"SD", Tennessee:"TN", Texas:"TX", Utah:"UT",
- Vermont:"VT", Virginia:"VA", Washington:"WA", "West Virginia":"WV", Wisconsin:"WI", Wyoming:"WY",
- getAbbreviation: function(state) { return this[state];},
- getStateFromAbbr: function(abbr) { return Object.entries(_US_States).filter(x => {if(x[1] == abbr) return x})[0][0];},
- getStatesArray: function() { return Object.keys(_US_States).filter(x => {if(typeof _US_States[x] !== "function") return x;});},
- getStateAbbrArray: function() { return Object.values(_US_States).filter(x => {if(typeof x !== "function") return x;});}
- };
-
- _MX_States = {
- Aguascalientes:"AGS", "Baja California":"BC", "Baja California Sur":"BCS",Campeche:"CAM", "Coahuila de Zaragoza":"COAH", Colima:"COL",
- Chiapas:"CHIS", Durango:"DGO", "Ciudad de México":"CDMX", "Guanajuato":"GTO", Guerrero:"GRO", Hidalgo:"HGO", Jalisco:"JAL",
- "Estado de México":"EM", "Michoacán de Ocampo":"MICH", Morelos:"MOR", Nayarit:"NAY", "Nuevo León":"NL", Oaxaca:"OAX", Puebla:"PUE",
- "Quintana Roo":"QROO", "Querétaro":"QRO", "San Luis Potosí":"SLP", Sinaloa:"SIN", Sonora:"SON", Tabasco:"TAB", Tamaulipas:"TAM", Tlaxcala:"TLAX",
- "Veracruz Ignacio de la Llave":"VER", "Yucatán":"YUC", "Zacatecas":"ZAC",
- getAbbreviation: function(state) { return this[state];},
- getStateFromAbbr: function(abbr) { return Object.entries(_MX_States).filter(x => {if(x[1] == abbr) return x})[0][0];},
- getStatesArray: function() { return Object.keys(_MX_States).filter(x => {if(typeof _MX_States[x] !== "function") return x;});},
- getStateAbbrArray: function() { return Object.values(_MX_States).filter(x => {if(typeof x !== "function") return x;});}};
-
- loadSettings();
-
- var layerid = 'wme_cities_overlay';
- var layerStyle = new OpenLayers.StyleMap({
- strokeDashstyle: 'solid', strokeColor: _color,
- strokeOpacity: _settings.FillPolygons ? defaultStrokeOpacity : noFillStrokeOpacity,
- strokeWidth: 2,
- fillOpacity: _settings.FillPolygons ? defaultFillOpacity : 0,
- fillColor: _color,fontColor: '#ffffff',
- label : "${labelText}", labelOutlineColor: '#000000',
- labelOutlineWidth: 4, labelAlign: 'cm',
- fontSize: "16px"
- });
-
- _layer = new OpenLayers.Layer.Vector("Cities Overlay", {
- rendererOptions: { zIndexing: true },
- uniqueName: layerid,
- shortcutKey: "S+" + 0,
- layerGroup: 'cities_overlay',
- zIndex: -9999,
- displayInLayerSwitcher: true,
- visibility: _settings.layerVisible,
- styleMap: layerStyle
- });
- I18n.translations[I18n.locale].layers.name[layerid] = "Cities Overlay";
- W.map.addLayer(_layer);
- if(_settings.layerVisible) //"reusing" this setting - should have set it up to enable/disable the moveend handler from the start instead of just hiding the layer. Durp
- W.map.events.register("moveend", null, updateCitiesLayer);
-
- if(!_settings.ShowCityLabels)
- _layer.styleMap.styles.default.defaultStyle.label = "";
-
- updateCitiesLayer();
- // Add the layer checkbox to the Layers menu.
- WazeWrap.Interface.AddLayerCheckbox("display", "Cities Overlay", _settings.layerVisible, layerToggled);
-
- var $section = $("<div>", {style:"padding:8px 16px", id:"WMECitiesOverlaySettings"});
- $section.html([
- `<h4 style="margin-bottom:0px;"><i id="citiesPower" class="fa fa-power-off" aria-hidden="true" style="color:${_settings.layerVisible ? 'rgb(0,180,0)' : 'black'}; cursor:pointer;"></i> <b>WME Cities Overlay</b></h4>`,
- `<h6 style="margin-top:0px;">${GM_info.script.version}</h6>`,
- '<div id="divWMECOFillPolygons"><input type="checkbox" id="_cbCOFillPolygons" class="wmecoSettingsCheckbox" /><label for="_cbCOFillPolygons">Fill polygons</label></div>',
- '<div id="divWMECOShowCityLabels"><input type="checkbox" id="_cbCOShowCityLabels" class="wmecoSettingsCheckbox" /><label for="_cbCOShowCityLabels">Show city labels</label></div>',
- '<div id="divWMECOHighlightFocusedCity"><input type="checkbox" id="_cbCOHighlightFocusedCity" class="wmecoSettingsCheckbox" /><label for="_cbCOHighlightFocusedCity">Highlight focused city</label></div>',
- '<fieldset id="fieldUpdates" style="border: 1px solid silver; padding: 8px; border-radius: 4px;">',
- '<legend style="margin-bottom:0px; border-bottom-style:none;width:auto;"><h4>Update Settings</h4></legend>',
- '<div id="divWMECOUpdateMaps" title="Checks for new state files for the current country"><button id="WMECOupdateMaps" type="button">Update database</button></div>',
- '<div id="WMECOupdateStatus"></div>',
- '<div id="divWMECOAutoUpdateKMLs" title="Checks for updated state files for the current country when WME loads"><input type="checkbox" id="_cbCOAutoUpdateKMLs" class="wmecoSettingsCheckbox" /><label for="_cbCOAutoUpdateKMLs">Automatically update database</label></div>','</fieldset>',
- '</div>'
- ].join(' '));
-
- WazeWrap.Interface.Tab('Cities', $section.html(), init2, 'Cities');
- }
-
- function init2(){
- $('.wmecoSettingsCheckbox').change(function() {
- var settingName = $(this)[0].id.substr(5);
- _settings[settingName] = this.checked;
- saveSettings();
- });
-
- setChecked('_cbCOShowCityLabels', _settings.ShowCityLabels);
- setChecked('_cbCOFillPolygons', _settings.FillPolygons);
- setChecked('_cbCOHighlightFocusedCity', _settings.HighlightFocusedCity);
- setChecked('_cbCOAutoUpdateKMLs', _settings.AutoUpdateKMLs);
-
- $('#citiesPower').click(function(){
- _settings.layerVisible = !_settings.layerVisible;
- layerToggled(_settings.layerVisible);
- if(_settings.layerVisible)
- W.map.events.register("moveend", null, updateCitiesLayer);
- else
- W.map.events.unregister("moveend", null, updateCitiesLayer);
- });
-
- $('#WMECOupdateMaps').click(updateAllMaps);
-
- $('#_cbCOFillPolygons').change(function(){
- _layer.styleMap.styles.default.defaultStyle.fillOpacity = this.checked ? defaultFillOpacity : 0;
- _layer.styleMap.styles.default.defaultStyle.strokeOpacity = this.checked ? defaultStrokeOpacity : noFillStrokeOpacity;
- _layer.redraw();
- });
-
- $('#_cbCOShowCityLabels').change(function(){
- _layer.styleMap.styles.default.defaultStyle.label = this.checked ? "${labelText}" : "";
- _layer.redraw();
- });
-
- $('#_cbCOHighlightFocusedCity').change(function(){
- if(this.checked){
- insertHighlightingRules();
- }
- else{
- let index = _layer.styleMap.styles.default.rules.findIndex(function(e){ return e.name == "WMECOHighlightCurr";});
- if(index > -1)
- _layer.styleMap.styles.default.rules.splice(index, 1);
-
- index = _layer.styleMap.styles.default.rules.findIndex(function(e){ return e.name == "WMECONoHighlight";});
- if(index > -1)
- _layer.styleMap.styles.default.rules.splice(index, 1);
- _layer.redraw();
- }
- });
-
- currCity = findCurrCity();
-
- if(_settings.HighlightFocusedCity)
- insertHighlightingRules();
-
- if(_settings.layerVisible && _settings.AutoUpdateKMLs)
- updateAllMaps();
- }
-
- function insertHighlightingRules(){
- //********** Highlighting Rules ***********
- let myRule = new W.Rule({
- filter: new OpenLayers.Filter.Comparison({
- type: '==',
- evaluate: function(cityFeature) {
- return cityFeature.attributes.name === currCity;
- }
- }),
- symbolizer: {
- strokeColor: '#f7ad25',
- fillColor: '#f7ad25'
- },
- name: "WMECOHighlightCurr"
- });
- let myRule2 = new W.Rule({
- filter: new OpenLayers.Filter.Comparison({
- type: '!=',
- evaluate: function(cityFeature) {
- return cityFeature.attributes.name != currCity;
- }
- }),
- symbolizer: {
- strokeColor: _color,
- fillColor: _color
- },
- name: "WMECONoHighlight"
- });
- _layer.styleMap.styles['default'].rules.push(myRule);
- _layer.styleMap.styles['default'].rules.push(myRule2);
- _layer.redraw();
- }
-
- function layerToggled(visible) {
- _settings.layerVisible = visible;
- _layer.setVisibility(visible);
- if(visible){
- $('#citiesPower').css("color", "rgb(0,180,0)");
- W.map.events.register("moveend", null, updateCitiesLayer);
- }
- else{
- $('#citiesPower').css("color", "black");
- W.map.events.unregister("moveend", null, updateCitiesLayer);
- }
- saveSettings();
- }
-
- async function updateCityPolygons(){
- if(currState != W.model.getTopState().attributes.name)
- {
- _layer.destroyFeatures();
- currState = W.model.getTopState().attributes.name;
- let countryAbbr = W.model.getTopCountry().attributes.abbr;
- let stateAbbr;
-
- if(countryAbbr === "US")
- stateAbbr = _US_States.getAbbreviation(currState);
- else if(countryAbbr === "MX")
- stateAbbr = _MX_States.getAbbreviation(currState);
-
- if(typeof stateAbbr !== "undefined"){
- if(typeof kmlCache[stateAbbr] == 'undefined'){
- //get the current state info from the store.
- var request = await idbKeyval.get(`${countryAbbr}_states_cities`, stateAbbr);
-
- //if the store didn't have the state, look it up from github and enter it in the store
- if(!request){
- let kml = await fetch(`https://raw.githubusercontent.com/${repoOwner}/WME-Cities-Overlay/master/KMLs/${countryAbbr}/${stateAbbr}_Cities.kml`);
- _kml = kml;
- updatePolygons();
-
- await idbKeyval.set(`${countryAbbr}_states_cities`, {
- kml: kml,
- state: stateAbbr,
- kmlsize: 0
- });
- kmlCache[stateAbbr] = _kml; //keep a local cache so we don't have to hit the indexeddb repeatedly if the user crosses state lines multiple times
- }
- else{
- _kml = request.kml;
- kmlCache[stateAbbr] = _kml;//keep a local cache so we don't have to hit the indexeddb repeatedly if the user crosses state lines multiple times
- updatePolygons();
- }
- }
- else{
- _kml = kmlCache[stateAbbr];
- updatePolygons();
- }
- }
- }
- }
-
- function updatePolygons(){
- var _features = GetFeaturesFromKMLString(_kml);
- _layer.destroyFeatures();
- for(let i=0; i< _features.length; i++){
- _features[i].attributes.name = _features[i].attributes.name.replace('<at><openparen>', '').replace('<closeparen>','');
- _features[i].attributes.labelText = _features[i].attributes.name;
- }
-
- _layer.addFeatures(_features);
- }
-
- function bootstrap(tries = 1) {
- if (W && W.loginManager && W.loginManager.user && W.model.getTopState() && WazeWrap.Ready) {
- init();
- console.log('WME Cities Overlay:', 'Initialized');
- } else if(tries < 1000){
- console.log('WME Cities Overlay: ', 'Bootstrap failed. Trying again...');
- window.setTimeout(() => bootstrap(tries++), 100);
- }
- }
-
- bootstrap();
- })();