WME Geometries

Import geometry files into Waze Map Editor. Supports GeoJSON, GML, WKT, KML and GPX.

目前為 2023-11-22 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name                WME Geometries
// @version             1.8
// @description         Import geometry files into Waze Map Editor. Supports GeoJSON, GML, WKT, KML and GPX.
// @match               https://www.waze.com/*/editor*
// @match               https://www.waze.com/editor*
// @match               https://beta.waze.com/*
// @exclude             https://www.waze.com/*user/*editor/*
// @require             https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.4.4/lz-string.min.js
// @grant               none
// @author              Timbones
// @contributor         wlodek76
// @contributor         Twister-UK
// @namespace           https://greasyfork.org/users/3339
// @run-at              document-idle
// ==/UserScript==
/*

    Blah Blah Blah

*/

/* JSHint Directives */
/* globals OpenLayers: true */
/* globals LZString: true */
/* globals W: true */
/* globals $: true */
/* globals I18n: true */
/* jshint bitwise: false */
/* jshint evil: true */
/* jshint esversion: 6 */

var geometries = function() {
  // maximum number of features that will be shown with labels
  var maxlabels = 2500;

  // show labels using first attribute that starts or ends with 'name' (case insensitive regexp)
  var labelname = /^name|name$/;

  // each loaded file will be rendered with one of these colours in ascending order
  var colorlist = ["deepskyblue", "magenta", "limegreen", "orange", "teal", "grey"];

  // -------------------------------------------------------------
  var geolist;

  var formats;
  var EPSG_4326; // lat,lon
  var EPSG_4269; // NAD 83
  var EPSG_3857; // WGS 84

  var layerindex = 0;
  var storedLayers = [];

  // delayed initialisation
  setTimeout(bootstrap, 1654);

  function layerStoreObj(fileContent, color, fileext, filename) {
    this.fileContent = fileContent;
    this.color = color;
    this.fileext = fileext;
    this.filename = filename;
  }

  function loadLayers() {
    // Parse any locally stored layer objects
    if (localStorage.WMEGeoLayers != undefined) {
      storedLayers = JSON.parse(LZString.decompress(localStorage.WMEGeoLayers));
      for (layerindex = 0; layerindex < storedLayers.length; ++layerindex) {
        parseFile(storedLayers[layerindex]);
      }
    } else {
      storedLayers = [];
    }
  }

  function bootstrap() {
    if (W.userscripts?.state.isReady) {
        init();
    } else {
        document.addEventListener("wme-ready", init, {
            once: true,
        });
    }
  }

  // add interface to Settings tab
  function init() {
    var formathelp = 'GeoJSON, WKT';
    formats = { 'GEOJSON':new OpenLayers.Format.GeoJSON(),
                'WKT':new OpenLayers.Format.WKT() };
    patchOpenLayers(); // patch adds KML, GPX and TXT formats

    EPSG_4326 = new OpenLayers.Projection("EPSG:4326"); // lat,lon
    EPSG_4269 = new OpenLayers.Projection("EPSG:4269"); // NAD 83
    EPSG_3857 = new OpenLayers.Projection("EPSG:3857"); // WGS 84
    var geobox = document.createElement('div');
    geobox.style.paddingTop = '6px';

    console.group("WME Geometries: Initialising for Editor");
    $("#sidepanel-areas").append(geobox);

    var geotitle = document.createElement('h4');
    geotitle.innerHTML = 'Import Geometry File';
    geobox.appendChild(geotitle);

    geolist = document.createElement('ul');
    geobox.appendChild(geolist);

    var geoform = document.createElement('form');
    geobox.appendChild(geoform);

    var inputfile = document.createElement('input');
    inputfile.type = 'file';
    inputfile.id = 'GeometryFile';
    inputfile.title = '.geojson, .gml or .wkt';
    inputfile.addEventListener('change', addGeometryLayer, false);
    geoform.appendChild(inputfile);

    var notes = document.createElement('p');
    notes.style.marginTop = "12px";
    notes.innerHTML = `<b>Formats:</b> <span id="formathelp">${formathelp}</span><br> `
                    + '<b>Coords:</b> EPSG:4326, EPSG:4269, EPSG:3857';
    geoform.appendChild(notes);

    var inputstate = document.createElement('input');
    inputstate.type = 'button';
    inputstate.value = 'Draw State Boundary';
    inputstate.title = 'Draw the boundary for the topmost state';
    inputstate.onclick = drawStateBoundary;
    geoform.appendChild(inputstate);

    var inputclear = document.createElement('input');
    inputclear.type = 'button';
    inputclear.value = 'Clear All';
    inputclear.style.marginLeft = '8px';
    inputclear.onclick = removeGeometryLayers;
    geoform.appendChild(inputclear);

    loadLayers();

    console.groupEnd("WME Geometries: initialised");
  }

  function addFormat(format) {
    $('#formathelp')[0].innerText += ", " + format;
  }

  function drawStateBoundary() {
    if (!W.model.topState || !W.model.topState.attributes || !W.model.topState.attributes.geometry) {
      console.info("WME Geometries: no state or geometry available, sorry");
      return;
    }

    var layerName = `(${W.model.topState.attributes.name})`;
    var layers = W.map.getLayersBy("layerGroup", "wme_geometry");
    for (var i = 0; i < layers.length; i++) {
      if (layers[i].name == "Geometry: " + layerName) {
        console.info("WME Geometries: current state already loaded");
        return;
      }
    }

    var geo = formats.GEOJSON.parseGeometry(W.model.topState.attributes.geometry);
    var json = formats.GEOJSON.write(geo);
    var obj = new layerStoreObj(json, "grey", "GEOJSON", layerName);
    parseFile(obj);
  }

  // import selected file as a vector layer
  function addGeometryLayer() {
    // get the selected file from user
    var fileList = document.getElementById('GeometryFile');
    var file = fileList.files[0];
    fileList.value = '';

    var fileext = file.name.split('.').pop();
    var filename = file.name.replace('.' + fileext, '');
    fileext = fileext.toUpperCase();

    // add list item
    var color = colorlist[(layerindex++) % colorlist.length];
    var fileitem = document.createElement('li');
    fileitem.id = file.name.toLowerCase();
    fileitem.style.color = color;
    fileitem.innerHTML = 'Loading...';
    geolist.appendChild(fileitem);

    // check if format is supported
    var parser = formats[fileext];
    if (typeof parser == 'undefined') {
      fileitem.innerHTML = fileext.toUpperCase() + ' format not supported :(';
      fileitem.style.color = 'red';
      return;
    }

    // read the file into the new layer, and update the localStorage layer cache
    var reader = new FileReader();
    reader.onload = (function(theFile) {
      return function(e) {
        var tObj = new layerStoreObj(e.target.result, color, fileext, filename);
        storedLayers.push(tObj);
        parseFile(tObj);
        localStorage.WMEGeoLayers = LZString.compress(JSON.stringify(storedLayers));
        console.info(`WME Geometries stored ${localStorage.WMEGeoLayers.length/1000} kB in localStorage`);
      };
    })(file);

    reader.readAsText(file);
  }

  // Renders a layer object
  function parseFile(layerObj) {
    var layerStyle = {
      strokeColor: layerObj.color,
      strokeOpacity: 0.75,
      strokeWidth: 3,
      fillColor: layerObj.color,
      fillOpacity: 0.1,
      pointRadius: 6,
      fontColor: 'white',
      labelOutlineColor: layerObj.color,
      labelOutlineWidth: 4,
      labelAlign: 'left'
    };

    var parser = formats[layerObj.fileext];
    parser.internalProjection = W.map.getProjectionObject();
    parser.externalProjection = EPSG_4326;

    // add a new layer for the geometry
    var layerid = 'wme_geometry_' + layerindex;
    var WME_Geometry = new OpenLayers.Layer.Vector(
      "Geometry: " + layerObj.filename, {
        rendererOptions: {
          zIndexing: true
        },
        uniqueName: layerid,
        shortcutKey: "S+" + layerindex,
        layerGroup: 'wme_geometry'
      }
    );

    WME_Geometry.setZIndex(-9999);
    WME_Geometry.displayInLayerSwitcher = true;

    // hack in translation:
    I18n.translations[I18n.locale].layers.name[layerid] = "WME Geometries: " + layerObj.filename;

    if (/"EPSG:3857"|:EPSG::3857"/.test(layerObj.fileContent)) {
      parser.externalProjection = EPSG_3857;
    }
    else if (/"EPSG:4269"|:EPSG::4269"/.test(layerObj.fileContent)) {
      parser.externalProjection = EPSG_4269;
    }
    // else default to EPSG:4326

    // load geometry files
    var features = parser.read(layerObj.fileContent);

    // check we have features to render
    if (features.length > 0) {
      // check which attribute can be used for labels
      var labelwith = '(no labels)';
      if (features.length <= maxlabels) {
        for (var attrib in features[0].attributes) {
          if (labelname.test(attrib.toLowerCase()) === true) {
            if (typeof features[0].attributes[attrib] == 'string') {
              labelwith = 'Labels: ' + attrib;
              layerStyle.label = '${' + attrib + '}';
              break;
            }
          }
        }
      }
      WME_Geometry.styleMap = new OpenLayers.StyleMap(layerStyle);

      // add data to the map
      WME_Geometry.addFeatures(features);
      W.map.addLayer(WME_Geometry);
    }

    // When called as part of loading a new file, the list object will already have been created,
    // whereas if called as part of reloding cached data we need to create it here...
    var liObj = document.getElementById((layerObj.filename + '.' + layerObj.fileext).toLowerCase());
    if (liObj === null) {
      liObj = document.createElement('li');
      liObj.id = (layerObj.filename + '.' + layerObj.fileext).toLowerCase();
      liObj.style.color = layerObj.color;
      geolist.appendChild(liObj);
    }

    if (features.length === 0) {
      liObj.innerHTML = 'No features loaded :(';
      liObj.style.color = 'red';
      WME_Geometry.destroy();
    } else {
      liObj.innerHTML = layerObj.filename;
      liObj.title = layerObj.fileext.toUpperCase() + " " + parser.externalProjection.projCode +
        ": " + features.length + " features loaded\n" + labelwith;

      console.info("WME Geometries: Loaded " + liObj.title);
    }
  }

  // clear all
  function removeGeometryLayers() {
    var layers = W.map.getLayersBy("layerGroup", "wme_geometry");
    for (var i = 0; i < layers.length; i++) {
      layers[i].destroy();
    }
    geolist.innerHTML = '';
    layerindex = 0;
    // Clear the cached layers
    localStorage.removeItem('WMEGeoLayers');
    storedLayers = [];
    return false;
  }

  // ------------------------------------------------------------------------------------

  // replace missing functions in OpenLayers 2.13.1
  function patchOpenLayers() {
    console.group("WME Geometries: Patching missing features...");
    if (!OpenLayers.VERSION_NUMBER.match(/^Release [0-9.]*$/)) {
      console.error("WME Geometries: OpenLayers version mismatch (" + OpenLayers.VERSION_NUMBER + ") - cannot apply patch");
      return;
    }

    loadOLScript("lib/OpenLayers/Format/KML", function() {formats.KML = new OpenLayers.Format.KML(); addFormat("KML");} );
    loadOLScript("lib/OpenLayers/Format/GPX", function() {formats.GPX = new OpenLayers.Format.GPX(); addFormat("GPX");} );
    loadOLScript("lib/OpenLayers/Format/GML", function() {formats.GML = new OpenLayers.Format.GML(); addFormat("GML");} );
    console.groupEnd();
  }
};
// ------------------------------------------------------------------------------------

// https://cdnjs.com/libraries/openlayers/x.y.z/
function loadOLScript(filename, callback) {
  var version = OpenLayers.VERSION_NUMBER.replace(/Release /, '');
  console.info("Loading openlayers/" + version + "/" + filename + ".js");

  var openlayers = document.createElement('script');
  openlayers.src = "https://cdnjs.cloudflare.com/ajax/libs/openlayers/" + version + "/" + filename + ".js";
  openlayers.type = "text/javascript";
  openlayers.onload = callback;
  document.head.appendChild(openlayers);
}

geometries();

// ------------------------------------------------------------------------------------