🏠 Home 

Greasy Fork is available in English.

WME Wide-Angle Lens

Scan a large area

/// <reference path="../typescript-typings/globals/openlayers/index.d.ts" />
/// <reference path="../typescript-typings/I18n.d.ts" />
/// <reference path="../typescript-typings/waze.d.ts" />
/// <reference path="../typescript-typings/globals/jquery/index.d.ts" />
/// <reference path="../typescript-typings/globals/geojson/index.d.ts" />
/// <reference path="../typescript-typings/wazewrap.d.ts" />
/// <reference path="../typescript-typings/greasyfork.d.ts" />
// ==UserScript==
// @name                WME Wide-Angle Lens
// @namespace           https://greasyfork.org/en/users/19861-vtpearce
// @description         Scan a large area
// @author              vtpearce and crazycaveman (progress bar from dummyd2 & seb-d59)
// @match               https://*.waze.com/*editor*
// @exclude             https://*.waze.com/user/editor*
// @exclude             https://www.waze.com/discuss/*
// @grant               GM_xmlhttpRequest
// @version             2025.03.14.001
// @copyright           2020 vtpearce
// @license             CC BY-SA 4.0
// @require             https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// @connect             greasyfork.org
// ==/UserScript==
// @updateURL           https://greasyfork.org/scripts/418291-wme-wide-angle-lens-beta/code/WME%20Wide-Angle%20Lens.meta.js
// @downloadURL         https://greasyfork.org/scripts/418291-wme-wide-angle-lens-beta/code/WME%20Wide-Angle%20Lens.user.js
var WMEWAL;
(function (WMEWAL) {
const SCRIPT_NAME = GM_info.script.name;
const SCRIPT_VERSION = GM_info.script.version.toString();
const DOWNLOAD_URL = GM_info.script.downloadURL;
const updateText = '<ul>'
+ '<li>Fixes for getting stuck in some situations.</li>'
+ '</ul>';
const greasyForkPage = 'https://greasyfork.org/scripts/40641';
const wazeForumThread = 'https://www.waze.com/discuss/t/script-wme-wide-angle-lens/77807';
const debug = false;
class ProgressBar {
root;
div;
information;
counts;
streets;
places;
mapComments;
noData;
errLoad;
constructor(id) {
this.root = $(id);
this.div = this.root.children('#wal-progressBar');
this.information = this.root.children("#wal-info");
this.counts = this.root.children("#wal-counts");
this.streets = null;
this.places = null;
this.mapComments = null;
this.noData = null;
this.errLoad = null;
this.div.children().hide();
this.root.children().hide();
}
isShown() {
return this.root.is(":visible");
}
show() {
this.root.show();
}
hide() {
this.root.hide();
this.root.children().hide();
}
update(value) {
log("debug", "Percent complete = " + value.toString());
if (value > 100) {
value = 100;
}
if (value === -1) {
this.div.hide();
this.div.children().hide();
return;
}
this.div.children(".wal-progressBarBG").css("width", value.toString() + "%");
this.div.children(".wal-progressBarFG").text(value.toString() + "%");
this.div.children().show();
this.div.show();
}
setCount(streets, places, mapComments) {
if (streets != null) {
this.streets = streets;
}
if (places != null) {
this.places = places;
}
if (mapComments != null) {
this.mapComments = mapComments;
}
this.updateCounts();
}
addCount(streets, places, mapComments) {
if (streets != null) {
if (this.streets != null) {
this.streets += streets;
}
else {
this.streets = streets;
}
}
if (places != null) {
if (this.places != null) {
this.places += places;
}
else {
this.places = places;
}
}
if (mapComments != null) {
if (this.mapComments != null) {
this.mapComments += mapComments;
}
else {
this.mapComments = mapComments;
}
}
this.updateCounts();
}
addErrCount(noDat, erLd) {
if (noDat != null) {
if (this.noData != null) {
this.noData += noDat;
}
else {
this.noData = noDat;
}
}
if (erLd != null) {
if (this.errLoad != null) {
this.errLoad += erLd;
}
else {
this.errLoad = erLd;
}
}
this.updateCounts();
}
showInfo(show) {
if (show) {
this.information.show();
}
else {
this.information.hide();
}
}
info(text) {
text = (typeof text !== "undefined" ? text : "");
this.information.text(text);
}
updateCounts() {
let outputText = "";
if (this.streets != null) {
outputText += `S: ${this.streets.toLocaleString()}`;
}
if (this.places != null) {
outputText += (outputText.length > 0 ? ' ' : '') + `P: ${this.places.toLocaleString()}`;
}
if (this.mapComments != null) {
outputText += (outputText.length > 0 ? ' ' : '') + `MC: ${this.mapComments.toLocaleString()}`;
}
if (this.noData != null) {
outputText += (outputText.length > 0 ? ' ' : '') + `ND: ${this.noData.toLocaleString()}`;
}
if (this.errLoad != null) {
outputText += (outputText.length > 0 ? ' ' : '') + `Er: ${this.errLoad.toLocaleString()}`;
}
this.counts.text(outputText);
this.counts.show();
}
}
let RoadType;
(function (RoadType) {
RoadType[RoadType["Unknown"] = 0] = "Unknown";
RoadType[RoadType["Street"] = 1] = "Street";
RoadType[RoadType["PrimaryStreet"] = 2] = "PrimaryStreet";
RoadType[RoadType["MinorHighway"] = 4] = "MinorHighway";
RoadType[RoadType["MajorHighway"] = 8] = "MajorHighway";
RoadType[RoadType["Freeway"] = 16] = "Freeway";
RoadType[RoadType["Ramp"] = 32] = "Ramp";
RoadType[RoadType["PrivateRoad"] = 64] = "PrivateRoad";
RoadType[RoadType["WalkingTrail"] = 128] = "WalkingTrail";
RoadType[RoadType["Unpaved"] = 256] = "Unpaved";
RoadType[RoadType["PedestrianBoardwalk"] = 512] = "PedestrianBoardwalk";
RoadType[RoadType["Ferry"] = ####] = "Ferry";
RoadType[RoadType["Stairway"] = 2048] = "Stairway";
RoadType[RoadType["Railroad"] = 4096] = "Railroad";
RoadType[RoadType["RunwayTaxiway"] = 8192] = "RunwayTaxiway";
RoadType[RoadType["ParkingLotRoad"] = 16384] = "ParkingLotRoad";
RoadType[RoadType["Alley"] = 32768] = "Alley";
})(RoadType = WMEWAL.RoadType || (WMEWAL.RoadType = {}));
let OutputTo;
(function (OutputTo) {
OutputTo[OutputTo["CSV"] = 1] = "CSV";
OutputTo[OutputTo["Tab"] = 2] = "Tab";
})(OutputTo = WMEWAL.OutputTo || (WMEWAL.OutputTo = {}));
let ScanStatus;
(function (ScanStatus) {
ScanStatus[ScanStatus["Continue"] = 1] = "Continue";
ScanStatus[ScanStatus["Complete"] = 2] = "Complete";
ScanStatus[ScanStatus["Abort"] = 3] = "Abort";
})(ScanStatus || (ScanStatus = {}));
let topLeft = null;
let bottomRight = null;
WMEWAL.areaToScan = null;
let height;
let width;
// let segments: Array<string> = null;
// let venues: Array<string> = null;
WMEWAL.areaName = null;
const defaultOutputFields = ['CreatedEditor', 'LastEditor', 'LockLevel', 'Lat', 'Lon'];
let currentLon;
let currentLat;
let currentCenter = null;
let currentZoom = null;
let layerToggle = null;
let needSegments = false;
let needVenues = false;
let needSuggestedSegments = false;
let cancelled = false;
let totalViewports;
let countViewports;
let mapReady = false;
let modelReady = false;
let settings = null;
let plugins = [];
const settingsKey = "WMEWAL_Settings";
const layerName = "WMEWAL_Areas";
let pb = null;
let initCount = 0;
let layerCheckboxAdded = false;
let WALMap;
let errList = [];
function onWmeReady() {
initCount++;
if (WazeWrap && WazeWrap.Ready) {
log('debug', 'WazeWrap ready.');
init();
}
else {
if (initCount < 60) {
log('debug', 'WazeWrap not ready. Trying again...');
setTimeout(onWmeReady, 1000);
}
else {
log('error', 'WazeWrap not ready. Giving up.');
}
}
}
function bootstrap() {
if (W?.userscripts?.state.isReady) {
onWmeReady();
}
else {
document.addEventListener('wme-ready', onWmeReady, { once: true });
}
}
function loadScriptUpdateMonitor() {
let updateMonitor;
try {
updateMonitor = new WazeWrap.Alerts.ScriptUpdateMonitor(SCRIPT_NAME, SCRIPT_VERSION, DOWNLOAD_URL, GM_xmlhttpRequest);
updateMonitor.start();
}
catch (ex) {
log('error', ex);
}
}
async function init() {
const sandboxed = typeof unsafeWindow !== 'undefined';
const pageWindow = sandboxed ? unsafeWindow : window;
const walAvailable = pageWindow.WMEWAL;
loadScriptUpdateMonitor();
if (typeof (Storage) !== "undefined") {
if (localStorage[settingsKey]) {
let settingsString = localStorage[settingsKey];
if (settingsString.substring(0, 1) === "~") {
// Compressed value - decompress
//console.log("Decompress UTF16 settings");
settingsString = WMEWAL.LZString.decompressFromUTF16(settingsString.substring(1));
}
try {
settings = JSON.parse(settingsString);
}
catch (e) { }
if (typeof settings === "undefined" || settings === null) {
settings = null;
log("debug", "Using old decompress method");
localStorage[settingsKey + "Backup"] = localStorage[settingsKey];
settingsString = localStorage[settingsKey];
if (settingsString.substring(0, 1) === "~") {
// Compressed value - decompress
settingsString = WMEWAL.LZString.decompress(settingsString.substring(1));
}
try {
settings = JSON.parse(settingsString);
}
catch (e) { }
if (typeof settings === "undefined" || settings === null) {
log("warning", "Unable to decompress! Using empty settings");
WMEWAL.outputTo = OutputTo.CSV;
WMEWAL.addBOM = false;
settings = {
SavedAreas: [],
ActivePlugins: [],
OutputTo: "csv",
Version: SCRIPT_VERSION,
showLayer: false,
AddBOM: WMEWAL.addBOM,
OutputFields: defaultOutputFields
};
}
}
settings.SavedAreas.sort(function (a, b) {
return a.name.localeCompare(b.name);
});
delete this.settingsString;
if (!Object.prototype.hasOwnProperty.call(settings, 'AddBOM')) {
settings.AddBOM = false;
}
if (!Object.prototype.hasOwnProperty.call(settings, 'Version')) {
settings.Version = SCRIPT_VERSION;
}
if (!Object.prototype.hasOwnProperty.call(settings, 'showLayer')) {
settings.showLayer = false;
}
if (!Object.prototype.hasOwnProperty.call(settings, 'OutputFields')) {
settings.OutputFields = defaultOutputFields;
}
for (let ix = 0; ix < settings.SavedAreas.length; ix++) {
if (settings.SavedAreas[ix].geometryText) {
settings.SavedAreas[ix].geometry = OpenLayers.Geometry.fromWKT(settings.SavedAreas[ix].geometryText);
while ((settings.SavedAreas[ix].geometry.CLASS_NAME === "OL.Geometry.Collection" ||
settings.SavedAreas[ix].geometry.CLASS_NAME === "OpenLayers.Geometry.Collection") &&
settings.SavedAreas[ix].geometry.components.length === 1) {
settings.SavedAreas[ix].geometry = settings.SavedAreas[ix].geometry.components[0];
}
delete settings.SavedAreas[ix].geometryText;
}
}
}
else if (localStorage["WMEMSL_areaList"]) {
// Import settings from old MSL script
const savedAreas = JSON.parse(localStorage["WMEMSL_areaList"]);
savedAreas.sort(function (a, b) {
return a.name.localeCompare(b.name);
});
WMEWAL.outputTo = OutputTo.CSV;
WMEWAL.addBOM = false;
settings = {
SavedAreas: savedAreas,
ActivePlugins: [],
OutputTo: "csv",
Version: SCRIPT_VERSION,
showLayer: false,
AddBOM: WMEWAL.addBOM,
OutputFields: defaultOutputFields
};
for (let ix = 0; ix < settings.SavedAreas.length; ix++) {
if (settings.SavedAreas[ix].geometryText) {
settings.SavedAreas[ix].geometry = OpenLayers.Geometry.fromWKT(settings.SavedAreas[ix].geometryText);
delete settings.SavedAreas[ix].geometryText;
}
}
}
else {
WMEWAL.outputTo = OutputTo.CSV;
WMEWAL.addBOM = false;
settings = {
SavedAreas: [],
ActivePlugins: [],
OutputTo: "csv",
Version: SCRIPT_VERSION,
showLayer: false,
AddBOM: false,
OutputFields: defaultOutputFields
};
}
}
WazeWrap.Interface.ShowScriptUpdate(SCRIPT_NAME, SCRIPT_VERSION, updateText, greasyForkPage, wazeForumThread);
let style = document.createElement("style");
//style.type = "text/css";
let css = ".wal-heading { font-size: 1.2em; font-weight: bold }";
css += ".wal-indent { padding-left: 20px }";
css += ".wal-label { margin-left: 8px; font-weight: normal; margin-bottom: 0px }";
css += '.wal-check { margin-top: 0px }';
css += "#wal-progressBarInfo { display: none; width: 90%; float: left; position: absolute; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; margin-bottom: -100%; background-color: #c9e1e9; z-index: 999; margin: 5px; margin-right: 20px; }";
css += ".wal-progressBarBG { margin-top: 2px; margin-bottom: 2px; margin-left: 2px; margin-right: 2px; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; padding-right: 0px; width: 33%; background-color: #93c4d3; border: 3px rgb(147, 196, 211); border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; height: 22px;}";
css += ".wal-progressBarFG { float: left; position: relative; bottom: 22px; height: 0px; text-align: center; width: 100% }";
css += ".wal-textbox { width: 100% }";
css += '#wal-info { text-align: center }';
css += '#wal-counts { text-align: center }';
css += '#wal-tabPane { font-size: 10pt }';
css += "#wal-tabPane hr { border: 1px inset; margin-top: 10px; margin-bottom: 10px }";
css += "#wal-tabPane .tab-pane { margin-left: -15px }";
css += '#wal-tabPane .tab-pane table { width: 100%; table-layout: fixed }';
style.innerHTML = css;
document.body.appendChild(style);
log('log', 'Initialized');
await makeTab();
//recreate tab here
// Unit switched (imperial/metric)
if (W.prefs) {
W.prefs.on("change:isImperial", recreateTab);
}
// Create map object
WALMap = W.map.getOLMap();
if (!walAvailable) {
pageWindow.WMEWAL = WMEWAL;
}
if (sandboxed)
window.WMEWAL = WMEWAL;
}
async function makeTab() {
const { tabLabel, tabPane } = W.userscripts.registerSidebarTab('WMEWAL');
tabLabel.innerText = 'WAL';
tabLabel.title = 'Wide-Angle Lens';
// const userTabs = $("#user-info");
// const navTabs = $("ul.nav-tabs", userTabs).filter(":first");
// const tabContent = $(".tab-content", userTabs).filter(":first");
// navTabs.append("<li><a href='#sidepanel-wme-wal' data-toggle='tab'>WAL</a></li>");
const tab = $("<div id='wal-tabPane'><h4>Wide-Angle Lens <span style='font-size:11px;'>v" + SCRIPT_VERSION + "</span></h4></div>");
// const addon = $("").appendTo(tab);
const pbi = $("<div/>").attr("id", "wal-progressBarInfo").addClass("wal-ProgressBarInfo").appendTo(tab);
const pb$ = $("<div/>").attr("id", "wal-progressBar").css({ width: "100%", display: "none" }).appendTo(pbi);
pb$.append($("<div/>").addClass("wal-progressBarBG"));
pb$.append($("<span/>").addClass("wal-progressBarFG").text("100%"));
pbi.append("<div id='wal-info'/>");
pbi.append("<div id='wal-counts'/>");
const addonTabs = $("<ul id='wmewal-tabs' class='nav nav-tabs' style='width: 95%;'/>").appendTo(tab);
addonTabs.append("<li class='active'><a data-toggle='tab' href='#sidepanel-wmewal-scan'>Scan</a></li>");
addonTabs.append("<li><a data-toggle='tab' href='#sidepanel-wmewal-areas'>Areas</a></li>");
addonTabs.append("<li><a data-toggle='tab' href='#sidepanel-wmewal-output'>Output</a></li>");
const addonTabContent = $("<div class='tab-content'/>").appendTo(tab);
const tabScan = $("<div class='tab-pane active' id='sidepanel-wmewal-scan'/>").appendTo(addonTabContent);
tabScan.append("<div><b>Output to: </b><select class='form-control' id='_wmewalScanOutputTo'><option value='csv'>CSV File</option><option value='tab'>Browser Tab</option>" +
"<option value='both'>Both CSV File and Browser Tab</option></select></div>");
tabScan.append("<div><input type='checkbox' id='_wmewalAddBOM'><label for='_wmewalAddBOM' class='wal-label'>Add Byte Order Mark to CSV</label></div><hr/>");
tabScan.append("<div><b>Active Plug-Ins</b><div id='_wmewalPlugins'></div>");
tabScan.append("<div><b>Scan</b><div id='_wmewalOptionsSavedAreas' name='_wmewalSavedAreas'/></div>");
tabScan.append("<hr/>");
const divButtons = $("<div/>").appendTo(tabScan);
divButtons.append("<button class='btn btn-primary' id='_wmewalScan' title='Scan' style='margin-right: 8px'>Scan</button>");
divButtons.append("<button class='btn btn-primary' id='_wmewalCancel' title='Cancel' disabled='disabled'>Cancel</button>");
const tabAreas = $("<div class='tab-pane' id='sidepanel-wmewal-areas'/>").appendTo(addonTabContent);
tabAreas.append("<div id='_wmewalAreasSavedAreas' name='_wmewalSavedAreas'/>");
const divAreaButtons = $("<div/>").appendTo(tabAreas);
divAreaButtons.append("<button class='btn btn-primary' id='_wmewalDeleteArea' title='Delete' style='margin-right: 4px'>Delete</button>");
divAreaButtons.append("<button class='btn btn-primary' id='_wmewalExport' title='Export' style='margin-right: 4px'>Export</button>");
divAreaButtons.append("<button class='btn btn-primary' id='_wmewalRenameArea' title='Rename'>Rename</button>");
tabAreas.append("<div style='margin-top: 12px'><b>Add custom area</b>");
tabAreas.append("<div>From an unsaved area place<div>Name area: <input type='text' id='_wmewalNewAreaName'></div><div>Then <button id='_wmewalAddNewArea' class='btn btn-primary' title='Add'>Add</button></div></div></div>");
const divImportArea = $("<div style='margin-top: 12px'/>").appendTo(tabAreas);
divImportArea.append("<b>Import area</b>");
divImportArea.append("<div><input type='file' id='_wmewalImportFileName' accept='.wkt'/></div><div><button class='btn btn-primary' id='_wmewalImportFile' title='Import'>Import</input></div>");
const tabOutput = $("<div class='tab-pane' id='sidepanel-wmewal-output'/>").appendTo(addonTabContent);
tabOutput.append('<div>Select optional fields to include in the output. Fewer fields may r###lt in fewer lines of output as segments can be combined. Note that some fields will automatically be included if they are specified in filters.</div>');
const divFields = $('<div/>').appendTo(tabOutput);
const selectFields = $("<select name='outputFields' id='_wmewalOutputFields' multiple style='width: 100%; height: 10em'/>").appendTo(divFields);
let outputField = $("<option value='CreatedEditor'>Created By</option>").appendTo(selectFields);
if (settings.OutputFields.indexOf('CreatedEditor') > -1) {
outputField.attr('selected', 'selected');
}
outputField = $("<option value='LastEditor'>Updated By</option>").appendTo(selectFields);
if (settings.OutputFields.indexOf('LastEditor') > -1) {
outputField.attr('selected', 'selected');
}
outputField = $("<option value='LockLevel'>Lock Level</option>").appendTo(selectFields);
if (settings.OutputFields.indexOf('LockLevel') > -1) {
outputField.attr('selected', 'selected');
}
outputField = $("<option value='Lat'>Latitude</option>").appendTo(selectFields);
if (settings.OutputFields.indexOf('Lat') > -1) {
outputField.attr('selected', 'selected');
}
outputField = $("<option value='Lon'>Longitude</option>").appendTo(selectFields);
if (settings.OutputFields.indexOf('Lon') > -1) {
outputField.attr('selected', 'selected');
}
tabPane.innerHTML = $(tab)[0].outerHTML;
await W.userscripts.waitForElementConnected(tabPane);
$("#_wmewalScanOutputTo").val(settings.OutputTo || "csv");
WMEWAL.outputTo = parseOutputTo(settings.OutputTo || "csv");
$('#_wmewalAddBOM').prop('checked', settings.AddBOM);
WMEWAL.addBOM = settings.AddBOM;
updateSavedAreasList();
$("#_wmewalScanOutputTo").on("change", updateSettings);
$('#_wmewalAddBOM').on('change', updateSettings);
$("#_wmewalAddNewArea").on("click", addNewArea);
$("#_wmewalCancel").on("click", cancelScan);
$("#_wmewalScan").on("click", scanArea);
$("#_wmewalExport").on("click", exportArea);
$("#_wmewalRenameArea").on("click", renameArea);
$("#_wmewalDeleteArea").on("click", deleteArea);
$("#_wmewalImportFile").on("click", importFile);
$('#_wmewalOutputFields').on('change', updateSettings);
$("#_wmewalPlugins").on("click", function (e) {
$("input[name=_wmewalPlugin]").each(function (ix, item) {
const i = $(item);
const id = i.attr("data-id");
for (let index = 0; index < plugins.length; index++) {
if (plugins[index].Id === parseInt(id)) {
plugins[index].Active = i.prop("checked");
}
}
});
settings.ActivePlugins = [];
for (let ix = 0; ix < plugins.length; ix++) {
if (plugins[ix].Active) {
settings.ActivePlugins.push(plugins[ix].Title);
}
}
updateSettings();
});
}
async function recreateTab() {
log("Debug", "Tab stuff");
W.userscripts.removeSidebarTab('WMEWAL');
await makeTab();
plugins.forEach(function (plugin) {
log("Debug", "Running for plugin: " + plugin.Title);
updatePluginList();
addPluginTab(plugin);
});
}
function info(text) {
text = (typeof text !== "undefined" ? text : "");
$("#wal-info").text(text);
}
function showPBInfo(show) {
if (show) {
$("#wal-progressBarInfo").show();
}
else {
$("#wal-progressBarInfo").hide();
}
}
function addPluginTab(plugin) {
const sidepanel = $("#wal-tabPane");
const tabs = $("#wmewal-tabs", sidepanel);
tabs.append("<li><a data-toggle='tab' href='#" + plugin.Id + "'>" + plugin.Title + "</a></li>");
const tabContent = $("div.tab-content", sidepanel);
const tab = $("<div class='tab-pane' id='" + plugin.Id + "'/>");
tab.append(plugin.GetTab());
tabContent.append(tab);
if (plugin.TabLoaded) {
plugin.TabLoaded();
}
}
function updatePluginList() {
const list = $("#_wmewalPlugins");
list.empty();
for (let ix = 0; ix < plugins.length; ix++) {
const id = "_wmewalPlugin_" + plugins[ix].Id.toString();
if (ix > 0) {
list.append("<br/>");
}
const c = $("<input type='checkbox' name='_wmewalPlugin'/>")
.attr({ id: id, title: plugins[ix].Title, "data-id": plugins[ix].Id }).appendTo(list);
if (plugins[ix].Active) {
c.attr("checked", "checked");
}
list.append($("<label/>").attr("for", id).addClass('wal-label').text(plugins[ix].Title));
}
}
function RegisterPlugIn(plugin) {
const p = plugin;
let found = false;
let r;
do {
r = Math.ceil(Math.random() * 1000);
for (let ix = 0; ix < plugins.length; ix++) {
if (plugins[ix].Id === r) {
found = true;
break;
}
}
} while (found);
p.Id = r;
p.Active = (settings.ActivePlugins.indexOf(plugin.Title) !== -1);
plugins.push(p);
updatePluginList();
addPluginTab(p);
}
WMEWAL.RegisterPlugIn = RegisterPlugIn;
function IsSegmentInArea(segment) {
return WMEWAL.areaToScan.intersects(segment.getAttribute('geometry'));
}
WMEWAL.IsSegmentInArea = IsSegmentInArea;
function getVenueGeometry(venue) {
if (venue.isPoint()) {
return venue.getOLGeometry();
}
else {
return venue.getOLGeometry();
}
}
function IsVenueInArea(venue) {
return WMEWAL.areaToScan.intersects(getVenueGeometry(venue));
}
WMEWAL.IsVenueInArea = IsVenueInArea;
function getMapCommentGeometry(mapComment) {
if (mapComment.isPoint()) {
return mapComment.getOLGeometry();
}
else {
return mapComment.getOLGeometry();
}
}
function IsMapCommentInArea(mapComment) {
return WMEWAL.areaToScan.intersects(getMapCommentGeometry(mapComment));
}
WMEWAL.IsMapCommentInArea = IsMapCommentInArea;
function updateLayer() {
const features = [];
let maLayer = W.map.getLayerByName(layerName);
if (maLayer === null || typeof maLayer === "undefined") {
maLayer = new OpenLayers.Layer.Vector(layerName, {});
I18n.translations[I18n.currentLocale()].layers.name[layerName] = "Wide-Angle Lens Areas";
W.map.addLayer(maLayer);
// W.map.addUniqueLayer(maLayer);
maLayer.setVisibility(settings.showLayer);
}
maLayer.removeAllFeatures({
silent: true
});
for (let ixA = 0; ixA < settings.SavedAreas.length; ixA++) {
const style = {
strokeColor: "#FF6600",
strokeOpacity: 0.8,
strokeWidth: 3,
fillOpacity: 0.00,
label: settings.SavedAreas[ixA].name,
labelOutlineColor: "Black",
labelOutlineWidth: 3,
fontSize: 14,
fontColor: "#FF6600",
fontOpacity: 0.85,
fontWeight: "bold"
};
features.push(new OpenLayers.Feature.Vector(settings.SavedAreas[ixA].geometry.clone(), {
areaName: settings.SavedAreas[ixA].name,
}, style));
}
maLayer.addFeatures(features);
if (!layerCheckboxAdded) {
WazeWrap.Interface.AddLayerCheckbox("display", "Wide-Angle Lens Areas", settings.showLayer, function (checked) {
maLayer.setVisibility(checked);
settings.showLayer = checked;
updateSettings();
});
layerCheckboxAdded = true;
}
}
// function addLatLonArray(latLonArray, arrayName): void
// {
//     let points: Array<OpenLayers.Geometry> = [];
//     for (let i = 0; i < latLonArray.length; i++)
//     {
//         points.push(new OpenLayers.Geometry.Point(latLonArray[i].lon, latLonArray[i].lat).transform(new OpenLayers.Projection("EPSG:4326"), W.map.getProjectionObject()));
//     }
//     let ring = new OpenLayers.Geometry.LinearRing(points);
//     let polygon = new OpenLayers.Geometry.Polygon([ring]);
//     savedAreas.push({name: arrayName, geometry: polygon});
// }
function addNewArea() {
let theVenue = null;
let count = 0;
for (let v in W.model.venues.objects) {
if (W.model.venues.objects.hasOwnProperty(v) === false) {
continue;
}
const venue = W.model.venues.objects[v];
if (venue.isPoint() === true) {
continue;
}
if ($.isNumeric(venue.attributes.id) && parseInt(venue.attributes.id) <= 0) {
theVenue = venue;
count++;
}
}
if (count > 1) {
alert("There must be only one unsaved area place.\n" + count + " detected.\nDraw only one area place to scan.");
return;
}
if (count === 0) {
alert("You must drawn an area place and not save it.");
return;
}
if (theVenue.getAttribute('geometry').components.length !== 1) {
alert("Can't parse the geometry");
return;
}
const nameBox = $("#_wmewalNewAreaName")[0];
if (nameBox.value.trim().length === 0) {
alert("Please provide a name for the new area.");
return;
}
const savedArea = {
name: nameBox.value.trim(),
geometry: theVenue.getAttribute('geometry').clone()
};
settings.SavedAreas.push(savedArea);
updateSavedAreasList();
if (W.model.actionManager.canUndo()) {
if (confirm("Undo all edits (OK=Yes, Cancel=No)?")) {
/* tslint:disable:no-empty */
while (W.model.actionManager.undo()) {
}
}
}
return;
}
function removeSavedArea(index) {
if (index >= settings.SavedAreas.length) {
return;
}
if (confirm("Removed saved area?")) {
settings.SavedAreas.splice(index, 1);
updateSavedAreasList();
}
}
function updateSavedAreasList() {
function getCenterFunc(index) {
return function () {
const center = settings.SavedAreas[index].geometry.getCentroid();
const lonlat = new OpenLayers.LonLat(center.x, center.y);
W.map.moveTo(lonlat);
// W.map.setCenter(lonlat);
};
}
settings.SavedAreas.sort(function (a, b) {
return a.name.localeCompare(b.name);
});
const list = $("div[name=_wmewalSavedAreas]");
list.empty();
list.each(function (eIx, e) {
for (let ix = 0; ix < settings.SavedAreas.length; ix++) {
const id = "_wmewalScanArea_" + eIx.toString() + "_" + ix.toString();
const input = $("<input/>").attr({ type: "radio", name: "_wmewalScanArea", id: id, value: ix.toString() });
e.appendChild(input[0]);
const label = $("<label/>").attr("for", id).addClass('wal-label').text(settings.SavedAreas[ix].name);
e.appendChild(label[0]);
const center = $("<i/>").addClass("fa").addClass("fa-crosshairs").css("margin-left", "4px").on("click", getCenterFunc(ix));
e.appendChild(center[0]);
// const div = document.createElement('div');
// const link = document.createElement('a');
// link.href = '#';
// link.onclick = (function (index) {
//     return function() {
//         scanArea(index);
//     };
// })(ix);
// link.text = savedAreas[ix].name;
// div.appendChild(link);
// e.appendChild(document.createTextNode("\u00A0"));
// e.appendChild(delLink);
const br = $("<br/>");
e.appendChild(br[0]);
}
if (e.id != '_wmewalAreasSavedAreas') {
const ix = 999;
const id = `wmewalScanArea_${eIx}_${ix}`;
const input = $("<input/>").attr({ type: "radio", name: "_wmewalScanArea", id: id, value: ix.toString() });
e.appendChild(input[0]);
const label = $("<label/>").attr("for", id).addClass('wal-label').text('Current window');
e.appendChild(label[0]);
}
});
updateSettings();
updateLayer();
}
function updateSettings() {
if (typeof Storage !== "undefined") {
WMEWAL.outputTo = parseOutputTo($("#_wmewalScanOutputTo").val());
WMEWAL.addBOM = $('#_wmewalAddBOM').prop('checked');
// Get optional fields to include in output
WMEWAL.outputFields = $('#_wmewalOutputFields option:selected').map(function () { return $(this).attr('value'); }).get();
const newSettings = {
SavedAreas: [],
ActivePlugins: settings.ActivePlugins,
OutputTo: $("#_wmewalScanOutputTo").val(),
Version: settings.Version,
showLayer: settings.showLayer,
AddBOM: WMEWAL.addBOM,
OutputFields: WMEWAL.outputFields
};
for (let ix = 0; ix < settings.SavedAreas.length; ix++) {
newSettings.SavedAreas.push({
name: settings.SavedAreas[ix].name,
geometryText: settings.SavedAreas[ix].geometry.toString()
});
}
localStorage[settingsKey] = "~" + WMEWAL.LZString.compressToUTF16(JSON.stringify(newSettings));
}
}
function importFile() {
const input = $("#_wmewalImportFileName")[0];
if (input.files.length === 0) {
alert("Select a file to import.");
return;
}
const fileName = input.files[0].name;
const fileExt = fileName.split(".").pop();
const name = fileName.replace("." + fileExt, "");
const reader = new FileReader();
reader.onload = function (e) {
const parser = new OpenLayers.Format.WKT();
let features = parser.read(e.target.r###lt);
let feature;
while (features instanceof Array && features.length === 1) {
features = features[0];
}
if (features instanceof OpenLayers.Feature.Vector) {
feature = features;
}
else {
alert("Could not parse geometry.");
return;
}
// Assume geometry is in EPSG:4326 and reproject to Spherical Mercator
const fromProj = new OpenLayers.Projection("EPSG:4326");
const c = feature.geometry.clone();
c.transform(fromProj, W.map.getProjectionObject());
const savedArea = {
name: name,
geometry: c
};
settings.SavedAreas.push(savedArea);
updateSavedAreasList();
};
reader.readAsText(input.files[0]);
}
function getBounds() {
if (WMEWAL.areaToScan == null) {
return;
}
WMEWAL.areaToScan.calculateBounds();
const bounds = WMEWAL.areaToScan.getBounds();
topLeft = new OpenLayers.Geometry.Point(bounds.left, bounds.top);
bottomRight = new OpenLayers.Geometry.Point(bounds.right, bounds.bottom);
}
// function onOperationDone(context: any): void {
//     log("Debug","onOperationDone started");
//     // Handle situation where onOperationDone is triggered twice.
//     if (!cancelled) {
//         scanExtent()
//             .done(function () {
//                 log("Debug","scanExtent deferred done.");
//                 let progress = Math.floor(countViewports / totalViewports * 100);
//                 pb.update(progress);
//                 moveToNextLocation();
//             })
//             .fail(function() {
//                 log("Debug","scanExtent deferred failed.");
//                 alert("There was a problem with one of the plugins and the scan is being canceled.");
//                 cancel();
//             });
//     }
// }
function onModelReadyWW() {
return new Promise(resolve => {
WazeWrap.Model.onModelReady(function () {
resolve();
}, false, null);
});
}
function onModelReady(now) {
const modelPromise = new Promise(resolve => {
const mergeend = function () {
resolve();
W.model.events.unregister("mergeend", null, mergeend);
};
W.model.events.register("mergeend", null, mergeend);
});
const mapPromise = new Promise(resolve => {
const operationDone = function () {
resolve();
W.app.layout.model.off('operationDone', operationDone);
};
W.app.layout.model.on('operationDone', operationDone);
});
// const featuresPromise : Promise<void> = new Promise(resolve => {
//     const loadingFeatures = function () {
//         resolve();
//         W.app.layout.model.off('loadingFeatures', loadingFeatures);
//     };
//     W.app.layout.model.on('loadingFeatures', loadingFeatures);
// });
if (now && WazeWrap.Util.mapReady() && WazeWrap.Util.modelReady()) {
return Promise.resolve();
}
else {
return Promise.all([modelPromise, mapPromise]).then(() => {
console.log('All promises resolved');
});
}
}
;
async function waitFeaturesLoaded() {
var ldf;
for (let j = 0; j < 100; j++) {
ldf = W.app.layout.model.attributes.loadingFeatures;
if (!ldf)
break;
//log("debug", "wait for features " + j);
await new Promise(r => setTimeout(r, 200));
}
if (!ldf) {
await new Promise(r => setTimeout(r, 50));
//log("debug", "features loaded" );
}
}
function cancelScan() {
cancelled = true;
}
function cancel() {
for (let ix = 0; ix < plugins.length; ix++) {
if (plugins[ix].Active && plugins[ix].ScanCancelled) {
try {
plugins[ix].ScanCancelled();
}
catch (e) {
log("warning", `Trouble cancelling plugin ${plugins[ix].Title}\n${e.message}`);
}
}
}
resetState();
}
function processComplete() {
pb.update(100);
for (let ix = 0; ix < plugins.length; ix++) {
if (plugins[ix].Active && plugins[ix].ScanComplete) {
plugins[ix].ScanComplete();
}
}
resetState();
}
function alertBeforeClose(e) {
if (WMEWAL.areaToScan !== null) {
log("Debug", 'Alerting user before closing page');
e.preventDefault();
e.returnValue = 'Scan running. Cancel and leave the page?';
return e.returnValue;
}
else {
return false;
}
}
function resetState() {
pb.hide();
pb.showInfo(false);
pb.info("");
errList = [];
WMEWAL.areaToScan = null;
// Return to previous state
if (layerToggle != null) {
while (layerToggle.length > 0) {
const ln = layerToggle.pop();
$("#" + ln).trigger("click");
}
layerToggle = null;
}
if (currentCenter != null) {
log("Debug", "Moving back to original location");
W.map.moveTo(currentCenter);
// W.map.setCenter(currentCenter);
}
if (currentZoom != null) {
log("Debug", "Resetting zoom");
WALMap.zoomTo(currentZoom);
}
// segments = null;
// venues = null;
$("#_wmewalCancel").attr("disabled", "disabled");
// Remove listeners for unloading page
window.removeEventListener('beforeunload', alertBeforeClose);
window.removeEventListener('unload', cancel);
}
function exportArea() {
let index = -1;
const nodes = $("input[name=_wmewalScanArea]", "#_wmewalAreasSavedAreas");
for (let ix = 0; ix < nodes.length; ix++) {
if (nodes[ix].checked) {
index = ix;
break;
}
}
if (index === -1) {
alert("Please select an area to export.");
return;
}
else if (index >= settings.SavedAreas.length) {
return;
}
const c = new OpenLayers.Geometry.Collection([settings.SavedAreas[index].geometry.clone()]);
// Transform the collection to EPSG:4326
const toProj = new OpenLayers.Projection("EPSG:4326");
c.transform(W.map.getProjectionObject(), toProj);
const geoText = c.toString();
const encodedUri = "data:text/plain;charset=utf-8," + encodeURIComponent(geoText);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", settings.SavedAreas[index].name + ".wkt");
const node = document.body.appendChild(link);
link.click();
document.body.removeChild(node);
}
function deleteArea() {
let index = -1;
const nodes = $("input[name=_wmewalScanArea]", "#_wmewalAreasSavedAreas");
for (let ix = 0; ix < nodes.length; ix++) {
if (nodes[ix].checked) {
index = ix;
break;
}
}
if (index === -1) {
alert("Please select an area to delete.");
return;
}
else if (index >= settings.SavedAreas.length) {
return;
}
removeSavedArea(index);
}
function renameArea() {
let index = -1;
const nodes = $("input[name=_wmewalScanArea]", "#_wmewalAreasSavedAreas");
for (let ix = 0; ix < nodes.length; ix++) {
if (nodes[ix].checked) {
index = ix;
break;
}
}
if (index === -1) {
alert("Please select an area to rename.");
return;
}
else if (index >= settings.SavedAreas.length) {
return;
}
const newName = prompt("Enter a new name");
if (newName == null) {
return;
}
settings.SavedAreas[index].name = newName;
updateSavedAreasList();
}
async function scanArea() {
let index = -1;
const nodes = $("input[name=_wmewalScanArea]", "#_wmewalOptionsSavedAreas");
for (let ix = 0; ix < nodes.length; ix++) {
if (nodes[ix].checked) {
index = ix;
break;
}
}
if (index === -1) {
alert("Please select an area to scan.");
return;
}
else if (index > settings.SavedAreas.length) {
return;
}
let name;
if (index == settings.SavedAreas.length) {
// Scanning current window
WMEWAL.areaToScan = W.map.getOLExtent().toGeometry();
name = 'Current window';
}
else {
WMEWAL.areaToScan = settings.SavedAreas[index].geometry;
name = settings.SavedAreas[index].name;
}
await scan(name);
}
async function scan(name) {
getBounds();
if (topLeft == null || bottomRight == null) {
alert("No bounds");
return;
}
let anyActivePlugins = false;
for (let ix = 0; ix < plugins.length; ix++) {
if (plugins[ix].Active) {
anyActivePlugins = true;
break;
}
}
if (!anyActivePlugins) {
alert("Please make sure at least one plug-in is active.");
return;
}
WMEWAL.areaName = name;
// segments = [];
// venues = [];
let allOk = true;
pb = new ProgressBar("#wal-progressBarInfo");
pb.update(0);
pb.show();
pb.showInfo(true);
for (let ix = 0; ix < plugins.length; ix++) {
if (plugins[ix].Active) {
pb.info("Initializing plugin " + plugins[ix].Title);
allOk = allOk && plugins[ix].ScanStarted();
}
}
pb.info("");
if (!allOk) {
pb.hide();
return;
}
needSegments = false;
needVenues = false;
needSuggestedSegments = false;
let needMapComments = false;
for (let ix = 0; ix < plugins.length; ix++) {
if (plugins[ix].Active) {
needSegments = needSegments || plugins[ix].SupportsSegments;
needVenues = needVenues || plugins[ix].SupportsVenues;
needSuggestedSegments = needSuggestedSegments || plugins[ix].SupportsSuggestedSegments;
if (plugins[ix].Title === "Map Comments") {
needMapComments = true;
}
}
}
pb.info("Please don't touch anything during the scan");
$("#_wmewalCancel").removeAttr("disabled");
// Alert user if they try to leave the page before scan is finished
window.addEventListener('beforeunload', alertBeforeClose);
//Cleanup when closing page
window.addEventListener('unload', cancel);
// Save current state
currentCenter = W.map.getCenter();
currentZoom = W.map.zoom;
layerToggle = [];
const groups = $("div.layer-switcher li.group");
groups.each(function (ix, g) {
const groupToggle = $(g).find("wz-toggle-switch");
if (groupToggle.length > 0) {
switch ($(groupToggle).attr("id")) {
case "layer-switcher-group_places":
if (needVenues) {
if (!$(groupToggle).prop("checked")) {
$(groupToggle).trigger("click");
layerToggle.push($(groupToggle).attr("id"));
}
// Loop through each child in the group
$(g).find("ul > li > wz-checkbox").each(function (ixChild, c) {
switch ($(c).attr("id")) {
case "layer-switcher-item_venues":
case "layer-switcher-item_residential_places":
case "layer-switcher-item_parking_places":
if (!$(c).prop("checked")) {
$(c).trigger("click");
layerToggle.push($(c).attr("id"));
}
break;
default:
if ($(c).prop("checked")) {
$(c).trigger("click");
layerToggle.push($(c).attr("id"));
}
break;
}
});
}
else {
if ($(groupToggle).prop("checked")) {
$(groupToggle).trigger("click");
layerToggle.push($(groupToggle).attr("id"));
}
}
break;
case "layer-switcher-group_road":
if ($(groupToggle).prop("checked")) {
$(groupToggle).trigger("click");
layerToggle.push($(groupToggle).attr("id"));
}
break;
case "layer-switcher-group_display":
if (needMapComments) {
if (!$(groupToggle).prop("checked")) {
$(groupToggle).trigger("click");
layerToggle.push($(groupToggle).attr("id"));
}
// Loop through each child in the group
$(g).find("ul > li > wz-checkbox").each(function (ixChild, c) {
switch ($(c).attr("id")) {
case "layer-switcher-item_map_comments":
if (!$(c).prop("checked")) {
$(c).trigger("click");
layerToggle.push($(c).attr("id"));
}
break;
default:
if ($(c).prop("checked")) {
$(c).trigger("click");
layerToggle.push($(c).attr("id"));
}
break;
}
});
}
else {
if ($(groupToggle).prop("checked")) {
$(groupToggle).trigger("click");
layerToggle.push($(groupToggle).attr("id"));
}
$(g).find("ul > li > wz-checkbox").each(function (ixChild, c) {
if (!$(c).prop("checked")) {
$(c).trigger("click");
layerToggle.push($(c).attr("id"));
}
});
}
break;
case "layer-switcher-group_map_suggestions":
if (needSuggestedSegments) {
if (!$(groupToggle).prop("checked")) {
$(groupToggle).trigger("click");
layerToggle.push($(groupToggle).attr("id"));
}
}
else {
if ($(groupToggle).prop("checked")) {
$(groupToggle).trigger("click");
layerToggle.push($(groupToggle).attr("id"));
}
}
break;
default:
if ($(groupToggle).prop("checked")) {
$(groupToggle).trigger("click");
layerToggle.push($(groupToggle).attr("id"));
}
break;
}
}
});
// Turn off Issue Tracker //
if ($('#layer-switcher-group_issues_tracker').prop('checked')) {
$('#layer-switcher-group_issues_tracker').trigger('click');
layerToggle.push('layer-switcher-group_issues_tracker');
}
// Turn off Road Shield Assistant if currently enabled
if ($('#rsa-enableScript').prop('checked')) {
$('#rsa-enableScript').trigger('click');
layerToggle.push('rsa-enableScript');
}
// Turn off Lane Tools if currently enabled
if ($('#lt-ScriptEnabled').prop('checked')) {
$('#lt-ScriptEnabled').trigger('click');
layerToggle.push('lt-ScriptEnabled');
}
// Turn off WMEPH Highlighting if currently enabled
if ($('#WMEPH-ColorHighlighting').prop('checked')) {
$('#WMEPH-ColorHighlighting').trigger('click');
layerToggle.push('WMEPH-ColorHighlighting');
}
// Turn off Magic if currently enabled because it doesn't respect the overall Display group toggle
if ($('#layer-switcher-item_magic').prop('checked')) {
$('#layer-switcher-item_magic').trigger('click');
layerToggle.push('layer-switcher-item_magic');
}
// Reload road layers
if (!W.model.actionManager.canUndo()) {
for (let ix = 0; ix < W.map.roadLayers.length; ix++) {
W.map.roadLayers[ix].redraw(true);
}
if (typeof W.controller.reloadData === "function") {
W.controller.reloadData();
}
else {
W.controller.reload();
}
}
let minZoomLevel = 0;
for (let ix = 0; ix < plugins.length; ix++) {
if (plugins[ix].Active) {
if (plugins[ix].MinimumZoomLevel > minZoomLevel) {
minZoomLevel = plugins[ix].MinimumZoomLevel;
}
}
}
WMEWAL.zoomLevel = minZoomLevel;
log('info', `Zooming to ${WMEWAL.zoomLevel}`);
WALMap.zoomTo(WMEWAL.zoomLevel);
const extent = W.map.getOLExtent();
height = extent.getHeight();
width = extent.getWidth();
// Figure out how many horizontal and vertical viewports there are
const horizontalSpan = Math.floor((bottomRight.x - topLeft.x) / width) + 2;
const verticalSpan = Math.floor((topLeft.y - bottomRight.y) / height) + 2;
totalViewports = horizontalSpan * verticalSpan + 1;
countViewports = 0;
log("Debug", "Horizontal span = " + horizontalSpan.toString());
log("Debug", "Vertical span = " + verticalSpan.toString());
log("Debug", "Total viewports = " + totalViewports.toString());
currentLon = topLeft.x - width;
currentLat = topLeft.y;
pb.show();
cancelled = false;
let status;
do {
status = await moveToNextLocation();
if (!cancelled && status === ScanStatus.Continue) {
status = await scanExtent();
}
if (!cancelled && status === ScanStatus.Continue) {
const progress = Math.floor(countViewports / totalViewports * 100);
pb.update(progress);
}
} while (status === ScanStatus.Continue && !cancelled);
if (status === ScanStatus.Abort || cancelled) {
log("Debug", "scan: scan aborted or canceled");
cancel();
}
else {
processComplete();
}
}
async function moveToNextLocation() {
let done = false;
let inGeometry = false;
do {
if (WMEWAL.areaToScan == null) {
done = true;
}
else {
countViewports += 1;
log("Debug", "Count viewports = " + countViewports.toString());
currentLon += width;
if (currentLon > bottomRight.x + width) {
log("Debug", "New row");
// Start at next row
currentLon = topLeft.x;
currentLat -= height;
if (currentLat < bottomRight.y - height) {
done = true;
}
}
if (!done) {
// Check to see if the new window would be within the boundaries of the original area
// Create a geometry object for the window boundaries
const points = [];
points.push(new OpenLayers.Geometry.Point(currentLon - (width / 2), currentLat + (height / 2)));
points.push(new OpenLayers.Geometry.Point(currentLon + (width / 2), currentLat + (height / 2)));
points.push(new OpenLayers.Geometry.Point(currentLon - (width / 2), currentLat - (height / 2)));
points.push(new OpenLayers.Geometry.Point(currentLon + (width / 2), currentLat - (height / 2)));
const lr = new OpenLayers.Geometry.LinearRing(points);
const poly = new OpenLayers.Geometry.Polygon([lr]);
inGeometry = WMEWAL.areaToScan && WMEWAL.areaToScan.intersects(poly);
}
}
if (!inGeometry) {
const progress = Math.floor(countViewports / totalViewports * 100);
pb.update(progress);
}
} while (!inGeometry && !done);
if (!done) {
return await moveMap();
}
else {
return ScanStatus.Complete;
}
}
function getErrCsvText() {
let errstr = '';
errList.forEach(item => { errstr += item.Message + ',' + item.Location + '\n'; });
return errstr;
}
WMEWAL.getErrCsvText = getErrCsvText;
function writeErrText(win) {
errList.forEach(item => { win.document.write(`<tr><td>${item.Message}</td><td><a href='${item.Location}' target='_blank'>Permalink</a></td></tr>\n`); });
}
WMEWAL.writeErrText = writeErrText;
async function moveMap() {
let abort;
let retry;
let abortOnFailure;
do {
abort = false;
abortOnFailure = true;
let retryCount = 0;
const latlon = OpenLayers.Layer.SphericalMercator.inverseMercator(currentLon, currentLat);
const url = WMEWAL.GenerateBasePL(latlon.lat, latlon.lon, WMEWAL.zoomLevel);
do {
retry = false;
if (!cancelled) {
try {
var p = onModelReady(false);
W.map.moveTo({ lon: currentLon, lat: currentLat });
// W.map.setCenter(new OpenLayers.LonLat(currentLon, currentLat));
try {
await promiseTimeout(10000, p);
waitFeaturesLoaded();
const ven = W.model.venues.getObjectArray();
const cityc = W.model.cities.getObjectArray().length;
const cntryc = W.model.countries.getObjectArray().length;
const segc = W.model.segments.getObjectArray().length;
const usrc = W.model.users.getObjectArray().length;
log("debug", "venues " + ven.length + " segs " + segc + " cntry " + cntryc + " users " + usrc);
if (usrc < 2) {
log("warn", "SKIP - no data at location " + url);
retryCount++;
errList.push({ Message: 'no data', Location: url });
pb.addErrCount(1, null);
//retry = true;
abortOnFailure = false;
}
/*
// Check to see if there's anything in segments. If not, try moving the map again
if (needSegments && W.model.segments.getObjectArray().length == 0) {
retryCount++;
retry = true;
abortOnFailure = false;
}
if (needVenues && W.model.venues.getObjectArray().length == 0) {
retryCount++;
retry = true;
abortOnFailure = false;
}
if (needSuggestedSegments && W.model.segmentSuggestions.getObjectArray().length == 0) {
retryCount++;
retry = true;
abortOnFailure = false;
}
*/
}
catch (e) {
log("warning", "moveMap: Timer triggered after map not successfully moved within 10 seconds");
errList.push({ Message: 'timeout', Location: url });
pb.addErrCount(null, 1);
retryCount++;
//retry = true;
abortOnFailure = true;
}
}
catch (e) {
log("warning", "moveMap: Exception thrown trying to move map.  Will retry up to 5 times (with a 1-second delay).");
log("error", e);
await new Promise(resolve => {
setTimeout(function () {
resolve();
}, 1000);
});
errList.push({ Message: 'exception', Location: url });
pb.addErrCount(null, 1);
retry = true;
retryCount++;
abortOnFailure = true;
}
}
} while (retry && retryCount < 5);
if (retry && abortOnFailure) {
abort = !confirm("Exceeded maximum allowable attempts to move the map. Do you want to continue trying?");
}
} while (retry && !abort);
if (abort) {
return ScanStatus.Abort;
}
else {
return ScanStatus.Continue;
}
}
async function scanExtent() {
let keepScanning = true;
if (!cancelled) {
let extentSegments = [];
let extentVenues = [];
let extentSuggestedSegments = [];
// Check to see if the current extent is completely within the area being searched
// let allIn = true;
// let vertices = W.map.getExtent().toGeometry().getVertices();
// for (let ix = 0; ix < vertices.length && allIn; ix++) {
//     allIn = allIn && geoCollection.intersects(vertices[ix]);
// }
// log("Debug","Extent is " + (!allIn ? "NOT " : "") + "entirely within area");
if (needSegments) { // && segments != null) {
log("Debug", "scanExtent: Collecting segments");
extentSegments = W.model.segments.getObjectArray();
// for (let seg in W.model.segments.objects) {
//     // if (segments.indexOf(seg) === -1) {
//         const segment = W.model.segments.getObjectById(parseInt(seg));
//         if (segment != null) {
//             // segments.push(seg);
//             extentSegments.push(segment);
//         }
//     // }
// }
log("Debug", `scanExtent: Done collecting segments (${extentSegments.length})`);
}
if (needVenues) { //} && venues != null) {
log("Debug", "scanExtent: Collecting venues");
extentVenues = W.model.venues.getObjectArray();
// for (let ven in W.model.venues.objects) {
//     // if (venues.indexOf(ven) === -1) {
//         const venue = W.model.venues.getObjectById(ven);
//         if (venue != null) {
//             // venues.push(ven);
//             extentVenues.push(venue);
//         }
//     // }
// }
log("Debug", `scanExtent: Done collecting venues (${extentVenues.length})`);
}
if (needSuggestedSegments) { // && segments != null) {
log("Debug", "scanExtent: Collecting suggested segments");
extentSuggestedSegments = W.model.segmentSuggestions.getObjectArray();
// for (let seg in W.model.segmentSuggestions.objects) {
//     // if (segments.indexOf(seg) === -1) {
//         const segment = W.model.segmentSuggestions.getObjectById(parseInt(seg));
//         if (segment != null) {
//             // segments.push(seg);
//             extentSuggestedSegments.push(segment);
//         }
//     // }
// }
log("Debug", `scanExtent: Done collecting suggested segments (${extentSuggestedSegments.length})`);
}
const promises = [];
for (let ix = 0; ix < plugins.length; ix++) {
if (plugins[ix].Active && !cancelled) {
log("Debug", "scanExtent: Calling plugin " + plugins[ix].Title);
promises.push(plugins[ix].ScanExtent(extentSegments, extentVenues, extentSuggestedSegments));
}
}
log("Debug", "scanExtent: Awaiting all promises");
const pluginR###lts = await Promise.allSettled(promises);
let anyErrors = false;
for (let ix = 0; ix < pluginR###lts.length; ix++) {
log("Debug", `scanExtent: Plugin ${ix}: ${pluginR###lts[ix].status}`);
if (pluginR###lts[ix].status === "rejected") {
log("error", pluginR###lts[ix].reason);
anyErrors = true;
}
else {
const r###lts = pluginR###lts[ix].value;
if (r###lts != null) {
pb.setCount(r###lts.Streets, r###lts.Places, r###lts.MapComments);
}
}
}
if (anyErrors) {
keepScanning = confirm("An error occurred in the one of the plugins. Do you want to continue?");
}
}
if (keepScanning) {
return ScanStatus.Continue;
}
else {
return ScanStatus.Abort;
}
}
function log(level, ...args) {
switch (level.toLocaleLowerCase()) {
case "debug":
case "verbose":
console.debug(`${SCRIPT_NAME}:`, ...args);
break;
case "info":
case "information":
console.info(`${SCRIPT_NAME}:`, ...args);
break;
case "warning":
case "warn":
console.warn(`${SCRIPT_NAME}:`, ...args);
break;
case "error":
console.error(`${SCRIPT_NAME}:`, ...args);
break;
case "log":
console.log(`${SCRIPT_NAME}:`, ...args);
break;
default:
break;
}
}
function parseOutputTo(outputTo) {
let ot;
switch ((outputTo ?? '').toLowerCase()) {
case "csv":
ot = OutputTo.CSV;
break;
case "tab":
ot = OutputTo.Tab;
break;
case "both":
ot = OutputTo.CSV | OutputTo.Tab;
break;
default:
break;
}
return ot;
}
function WazeRoadTypeToRoadTypeBitmask(roadType) {
switch (roadType) {
case 1:
return RoadType.Street;
case 2:
return RoadType.PrimaryStreet;
case 3:
return RoadType.Freeway;
case 4:
return RoadType.Ramp;
case 5:
return RoadType.WalkingTrail;
case 6:
return RoadType.MajorHighway;
case 7:
return RoadType.MinorHighway;
case 8:
return RoadType.Unpaved;
case 10:
return RoadType.PedestrianBoardwalk;
case 15:
return RoadType.Ferry;
case 16:
return RoadType.Stairway;
case 17:
return RoadType.PrivateRoad;
case 18:
return RoadType.Railroad;
case 19:
return RoadType.RunwayTaxiway;
case 20:
return RoadType.ParkingLotRoad;
case 22:
return RoadType.Alley;
default:
return RoadType.Unknown;
}
}
WMEWAL.WazeRoadTypeToRoadTypeBitmask = WazeRoadTypeToRoadTypeBitmask;
function RoadTypeBitmaskToWazeRoadType(roadType) {
switch (roadType) {
case RoadType.Street:
return 1;
case RoadType.PrimaryStreet:
return 2;
case RoadType.Freeway:
return 3;
case RoadType.Ramp:
return 4;
case RoadType.WalkingTrail:
return 5;
case RoadType.MajorHighway:
return 6;
case RoadType.MinorHighway:
return 7;
case RoadType.Unpaved:
return 8;
case RoadType.PedestrianBoardwalk:
return 10;
case RoadType.Ferry:
return 15;
case RoadType.Stairway:
return 16;
case RoadType.PrivateRoad:
return 17;
case RoadType.Railroad:
return 18;
case RoadType.RunwayTaxiway:
return 19;
case RoadType.ParkingLotRoad:
return 20;
case RoadType.Alley:
return 22;
default:
return 0;
}
}
WMEWAL.RoadTypeBitmaskToWazeRoadType = RoadTypeBitmaskToWazeRoadType;
function WazeRoadTypeToRoutingPreference(roadType) {
switch (roadType) {
case 1:
return 1;
case 2:
return 2;
case 7:
return 3;
case 6:
return 4;
case 3:
return 5;
default:
return 0;
}
}
WMEWAL.WazeRoadTypeToRoutingPreference = WazeRoadTypeToRoutingPreference;
function TranslateRoadType(wazeRoadType) {
return I18n.t("segment.road_types." + wazeRoadType.toString());
}
WMEWAL.TranslateRoadType = TranslateRoadType;
function GenerateBasePL(lat, lon, zoom) {
if (zoom < 12) {
zoom += 12;
}
return "https://www.waze.com/editor/?env=" + W.app.getAppRegionCode() + "&lon=" + lon + "&lat=" + lat + "&zoomLevel=" + zoom;
}
WMEWAL.GenerateBasePL = GenerateBasePL;
function CompareVersions(v1, v2) {
const v1Parts = v1.split(".");
const v2Parts = v2.split(".");
for (; v1Parts.length < v2Parts.length;) {
v1Parts.push(".0");
}
for (; v2Parts.length < v1Parts.length;) {
v2Parts.push(".0");
}
for (let ix = 0; ix < v1Parts.length; ix++) {
const v1Element = parseInt(v1Parts[ix]);
const v2Element = parseInt(v2Parts[ix]);
if (v1Element < v2Element) {
return -1;
}
else if (v1Element > v2Element) {
return 1;
}
}
return 0;
}
WMEWAL.CompareVersions = CompareVersions;
function IsAtMinimumVersion(version) {
return (CompareVersions(getVersion(), version) >= 0);
}
WMEWAL.IsAtMinimumVersion = IsAtMinimumVersion;
function getVersion() {
let version = GM_info.script.version;
if (version.startsWith("v")) {
version = version.substring(1);
}
return version;
}
function promiseTimeout(ms, promise) {
// Create a promise that rejects in <ms> milliseconds
const timeout = new Promise((resolve, reject) => {
const id = setTimeout(() => {
clearTimeout(id);
reject();
}, ms);
});
// Returns a race between our timeout and the passed in promise
return Promise.race([
promise,
timeout
]);
}
// Copyright (c) 2013 Pieroxy <[email protected]>
// This work is free. You can redistribute it and/or modify it
// under the terms of the WTFPL, Version 2
// For more information see LICENSE.txt or http://www.wtfpl.net/
//
// For more information, the home page:
// http://pieroxy.net/blog/pages/lz-string/testing.html
//
// LZ-based compression algorithm, version 1.4.4
/* tslint:disable */
WMEWAL.LZString = (function () {
// private property
const f = String.fromCharCode;
const keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
const keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
const baseReverseDic = {};
function getBaseValue(alphabet, character) {
if (!baseReverseDic[alphabet]) {
baseReverseDic[alphabet] = {};
for (let i = 0; i < alphabet.length; i++) {
baseReverseDic[alphabet][alphabet.charAt(i)] = i;
}
}
return baseReverseDic[alphabet][character];
}
const LZString = {
compressToBase64: function (input) {
if (input == null)
return "";
let res = LZString._compress(input, 6, function (a) { return keyStrBase64.charAt(a); });
switch (res.length % 4) {
default: // When could this happen ?
case 0: return res;
case 1: return res + "===";
case 2: return res + "==";
case 3: return res + "=";
}
},
decompressFromBase64: function (input) {
if (input == null)
return "";
if (input == "")
return null;
return LZString._decompress(input.length, 32, function (index) { return getBaseValue(keyStrBase64, input.charAt(index)); });
},
compressToUTF16: function (input) {
if (input == null)
return "";
return LZString._compress(input, 15, function (a) { return f(a + 32); }) + " ";
},
decompressFromUTF16: function (compressed) {
if (compressed == null)
return "";
if (compressed == "")
return null;
return LZString._decompress(compressed.length, 16384, function (index) { return compressed.charCodeAt(index) - 32; });
},
//compress into uint8array (UCS-2 big endian format)
compressToUint8Array: function (uncompressed) {
const compressed = LZString.compress(uncompressed);
const buf = new Uint8Array(compressed.length * 2); // 2 bytes per character
for (let i = 0, TotalLen = compressed.length; i < TotalLen; i++) {
const current_value = compressed.charCodeAt(i);
buf[i * 2] = current_value >>> 8;
buf[i * 2 + 1] = current_value % 256;
}
return buf;
},
//decompress from uint8array (UCS-2 big endian format)
decompressFromUint8Array: function (compressed) {
if (compressed == null || compressed === undefined) {
return LZString.decompress(compressed);
}
else {
const buf = new Array(compressed.length / 2); // 2 bytes per character
for (let i = 0, TotalLen = buf.length; i < TotalLen; i++) {
buf[i] = compressed[i * 2] * 256 + compressed[i * 2 + 1];
}
const r###lt_1 = [];
buf.forEach(function (c) {
r###lt_1.push(f(c));
});
return LZString.decompress(r###lt_1.join(''));
}
},
//compress into a string that is already URI encoded
compressToEncodedURIComponent: function (input) {
if (input == null)
return "";
return LZString._compress(input, 6, function (a) { return keyStrUriSafe.charAt(a); });
},
//decompress from an output of compressToEncodedURIComponent
decompressFromEncodedURIComponent: function (input) {
if (input == null)
return "";
if (input == "")
return null;
input = input.replace(/ /g, "+");
return LZString._decompress(input.length, 32, function (index) { return getBaseValue(keyStrUriSafe, input.charAt(index)); });
},
compress: function (uncompressed) {
return LZString._compress(uncompressed, 16, function (a) { return f(a); });
},
_compress: function (uncompressed, bitsPerChar, getCharFromInt) {
if (uncompressed == null)
return "";
let i, value, context_dictionary = {}, context_dictionaryToCreate = {}, context_c = "", context_wc = "", context_w = "", context_enlargeIn = 2, // Compensate for the first entry which should not count
context_dictSize = 3, context_numBits = 2, context_data = [], context_data_val = 0, context_data_position = 0, ii;
for (let ii_1 = 0; ii_1 < uncompressed.length; ii_1 += 1) {
context_c = uncompressed.charAt(ii_1);
if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) {
context_dictionary[context_c] = context_dictSize++;
context_dictionaryToCreate[context_c] = true;
}
context_wc = context_w + context_c;
if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) {
context_w = context_wc;
}
else {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
if (context_w.charCodeAt(0) < 256) {
for (let i_1 = 0; i_1 < context_numBits; i_1++) {
context_data_val = (context_data_val << 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
}
else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (let i_2 = 0; i_2 < 8; i_2++) {
context_data_val = (context_data_val << 1) | (value & 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
}
else {
context_data_position++;
}
value = value >> 1;
}
}
else {
value = 1;
for (let i_3 = 0; i_3 < context_numBits; i_3++) {
context_data_val = (context_data_val << 1) | value;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
}
else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (let i_4 = 0; i_4 < 16; i_4++) {
context_data_val = (context_data_val << 1) | (value & 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
}
else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
}
else {
value = context_dictionary[context_w];
for (let i_5 = 0; i_5 < context_numBits; i_5++) {
context_data_val = (context_data_val << 1) | (value & 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
}
else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
// Add wc to the dictionary.
context_dictionary[context_wc] = context_dictSize++;
context_w = String(context_c);
}
}
// Output the code for w.
if (context_w !== "") {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
if (context_w.charCodeAt(0) < 256) {
for (let i_6 = 0; i_6 < context_numBits; i_6++) {
context_data_val = (context_data_val << 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
}
else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (let i_7 = 0; i_7 < 8; i_7++) {
context_data_val = (context_data_val << 1) | (value & 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
}
else {
context_data_position++;
}
value = value >> 1;
}
}
else {
value = 1;
for (let i_8 = 0; i_8 < context_numBits; i_8++) {
context_data_val = (context_data_val << 1) | value;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
}
else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (let i_9 = 0; i_9 < 16; i_9++) {
context_data_val = (context_data_val << 1) | (value & 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
}
else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
}
else {
value = context_dictionary[context_w];
for (let i_10 = 0; i_10 < context_numBits; i_10++) {
context_data_val = (context_data_val << 1) | (value & 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
}
else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
}
// Mark the end of the stream
value = 2;
for (let i_11 = 0; i_11 < context_numBits; i_11++) {
context_data_val = (context_data_val << 1) | (value & 1);
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
}
else {
context_data_position++;
}
value = value >> 1;
}
// Flush the last char
while (true) {
context_data_val = (context_data_val << 1);
if (context_data_position == bitsPerChar - 1) {
context_data.push(getCharFromInt(context_data_val));
break;
}
else
context_data_position++;
}
return context_data.join('');
},
decompress: function (compressed) {
if (compressed == null)
return "";
if (compressed == "")
return null;
return LZString._decompress(compressed.length, 32768, function (index) { return compressed.charCodeAt(index); });
},
_decompress: function (length, resetValue, getNextValue) {
let dictionary = [], next, enlargeIn = 4, dictSize = 4, numBits = 3, entry = "", r###lt = [], i, w, bits, resb, maxpower, power, c, data = { val: getNextValue(0), position: resetValue, index: 1 };
for (let i_12 = 0; i_12 < 3; i_12 += 1) {
dictionary[i_12] = i_12;
}
bits = 0;
maxpower = Math.pow(2, 2);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
switch (next = bits) {
case 0:
bits = 0;
maxpower = Math.pow(2, 8);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
c = f(bits);
break;
case 1:
bits = 0;
maxpower = Math.pow(2, 16);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
c = f(bits);
break;
case 2:
return "";
}
dictionary[3] = c;
w = c;
r###lt.push(c);
while (true) {
if (data.index > length) {
return "";
}
bits = 0;
maxpower = Math.pow(2, numBits);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
switch (c = bits) {
case 0:
bits = 0;
maxpower = Math.pow(2, 8);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
dictionary[dictSize++] = f(bits);
c = dictSize - 1;
enlargeIn--;
break;
case 1:
bits = 0;
maxpower = Math.pow(2, 16);
power = 1;
while (power != maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = resetValue;
data.val = getNextValue(data.index++);
}
bits |= (resb > 0 ? 1 : 0) * power;
power <<= 1;
}
dictionary[dictSize++] = f(bits);
c = dictSize - 1;
enlargeIn--;
break;
case 2:
return r###lt.join('');
}
if (enlargeIn == 0) {
enlargeIn = Math.pow(2, numBits);
numBits++;
}
if (dictionary[c]) {
entry = dictionary[c];
}
else {
if (c === dictSize) {
entry = w + w.charAt(0);
}
else {
return null;
}
}
r###lt.push(entry);
// Add w+entry[0] to the dictionary.
dictionary[dictSize++] = w + entry.charAt(0);
enlargeIn--;
w = entry;
if (enlargeIn == 0) {
enlargeIn = Math.pow(2, numBits);
numBits++;
}
}
}
};
return LZString;
})();
/* tslint:enable */
bootstrap();
})(WMEWAL || (WMEWAL = {}));