// ==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();
// ------------------------------------------------------------------------------------