// ==UserScript==
// @name WME Edit Area Age
// @namespace https://greatest.deepsurf.us/users/1365511
// @version 2025.01.21.002
// @description Displays age of editable areas
// @author robosphinx_
// @match *://*.waze.com/*editor*
// @exclude *://*.waze.com/user/editor*
// @require https://greatest.deepsurf.us/scripts/24851-wazewrap/code/WazeWrap.js
// @grant none
// @license GPLv3
// ==/UserScript==
/* global W */
/* global WazeWrap */
(function main() {
'use strict';
const SCRIPT_LONG_NAME = GM_info.script.name;
const SCRIPT_SHORT_NAME = "WME-EAA";
const SCRIPT_SHORTEST_NAME = "EAA";
const SCRIPT_VERSION = GM_info.script.version;
const TIME_PER_DRIVE_MS = 250;
const DRIVES_PER_PAGE = 15;
const TIME_PER_PAGE_MS = (DRIVES_PER_PAGE + 5) * TIME_PER_DRIVE_MS;
const MAX_DRIVES = 300;
const EAA_STYLE = {
strokeWidth: 0
};
// For OL geometry conversion
// Grabbing geometry from WME segments gives typical lat/lon coordinates as we humans know them. We must convert to OL scale :)
// Discovered through trial and error :')
// See: https://openlayers.org/en/latest/apidoc/module-ol_proj_Projection-Projection.html
const srcProjection = 'EPSG:4326'; // WGS 84 / Geographic (human-readable degrees lat/lon)
const destProjection = 'EPSG:3857'; // WGS 84 / Spherical Mercator (meters, cartesian/planar?)
// For spherical (ESPG:4326) math. It's what I've worked on before, even if it's mathematically
// more complex than planar math. Why reinvent the wheel when the planar math doesn't seem to
// fully extend to the EA bounds?? Seems Waze is using something other than planar math when
// calculating it...
const EQUATORIAL_RADIUS_METERS = 6378137.0;
const POLAR_RADIUS_METERS = 6356752.31424518;
// This is gross, but I've played with a LOT of projection code to figure out how EA is
// calculated based on drive traces and it's nothing obvious...
// Time to break out the magic numbers </3
// oof even the magic numbers don't fix this because it's varying with latitude still.
// I'm NOT going to find a magic equation to fudge the sizes dynamically. This gets pretty close.
const VERTICAL_SCALAR = 1.12;
const HORIZONTAL_SCALAR = 0.90;
// TODO: Configurable
const DAYS_TO_EXPIRY = 7;
// One-time load/set
let WM;
let DL;
let editRadius = 0;
let wmeeaaEditAgeLayer;
let _$scanDrivesButton = null;
// Volatile things and stuff - changes on user input
let layerEnabled = true;
let centerAtProcessDrivesStart;
let zoomAtProcessDrivesStart;
let numDrives = 0;
let driveDates = [];
let features = [];
/*
* log does what you expect. Intended use is a severity and a message, but it really just
* appends the two strings together. Not really any rules on severity tags, but ERROR will
* be printed as an error to the js console.
*/
function log(tag, message) {
if (tag == "ERROR") {
console.error(SCRIPT_SHORT_NAME + ": " + tag + ": " + message);
}
else {
console.log(SCRIPT_SHORT_NAME + ": " + tag + ": " + message);
}
}
/*
* Event handler for when the map move finishes.
* Set this as an active event handler only when scanning.
* Remove from event handlers when finished to avoid headache.
* Expects a drive to be added to the map from the user drives history list.
* Takes the OpenLayers geometry and saves all points from the drive trace along with the date of the drive.
*/
function mapMoveEnd() {
try {
if (DL.features.length > 0) {
let numFeatures = DL.features.length;
let coordinatesList = [];
// log("DEBUG", "NumFeatures: " + numFeatures);
for (let featureIndex = 0; featureIndex < numFeatures; featureIndex++) {
// log("DEBUG", "Processing feature " + featureIndex);
let specificGeometry = DL.features[featureIndex].attributes.wazeFeature.geometry; // OL.Geometry.LineString
let drivePoints = [];
for (let i = 0; i < specificGeometry.coordinates.length; i++) {
let lon = specificGeometry.coordinates[i][0];
let lat = specificGeometry.coordinates[i][1];
drivePoints.push(new OpenLayers.Geometry.Point(lon, lat));
}
let lineString = new OpenLayers.Geometry.LineString(drivePoints);
// log("DEBUG", "Found " + specificGeometry.coordinates.length + " coordinates.");
// log("DEBUG", "Captured " + drivePoints.length + " of them.");
let age = getDuration(driveDates[numDrives]);
let color = getColorFromAge(age);
let z = (MAX_DRIVES * 5) - (numDrives * 5);
let vertices = lineString.getVertices();
// log("DEBUG", "Creating start cap");
// Create start cap of the polygon
let startCap = getSemiCircularCap(vertices[0], vertices[1], editRadius);
let polygon = new OpenLayers.Geometry.LinearRing(startCap)
// log("DEBUG", "Polygon has " + polygon.getVertices().length + " points and area " + polygon.getArea());
// log("DEBUG", "Creating edges");
for (let i = 1; i < vertices.length - 1; i++) {
// getPolygonEdges gives right and then left point
let edges = getPolygonEdges(vertices[i], vertices[i + 1], editRadius);
// log("DEBUG", "Created edges");
polygon.addComponent(edges[0], 0); // Add to start of list
// log("DEBUG", "Inserted first edge at index 0");
polygon.addComponent(edges[1], polygon.getVertices().length); // Add to end of list
// log("DEBUG", "Appended second edge to end of list");
}
// log("DEBUG", "Polygon has " + polygon.getVertices().length + " points and area " + polygon.getArea());
// log("DEBUG", "Creating end cap");
// Create end cap of polygon
let endCap = getSemiCircularCap(vertices[vertices.length - 1], vertices[vertices.length - 2], editRadius);
// TODO: Do we need to reverse the end cap point order before appending?
for (let i = 0; i < endCap.length; i++) {
polygon.addComponent(endCap[i]); // Add to end of list
}
// log("DEBUG", "Polygon has " + polygon.getVertices().length + " points and area " + polygon.getArea());
// Transform projections after calculating circle to properly stretch to
// EA bounds
// log("DEBUG", "Transforming polygon");
polygon.transform(srcProjection, destProjection);
// log("DEBUG", "Adding polygon to vector");
// Create a vector containing the geometry to add to the map layer
let vector = new OpenLayers.Feature.Vector(polygon, null, {
strokeWidth: 0,
zIndex: z,
fillOpacity: 1.0,
fillColor: color
});
// log("DEBUG", "Adding vector to features");
features.push(vector);
// log("DEBUG", "Drive " + numDrives + " was " + age + " days old. Using color " + color + " at z index " + z);
}
numDrives++;
// log("DEBUG", "Found " + coordinatesList.length + " coordinates in this drive - processed " + fullyProcessedCoordinates + " of them.");
// log("DEBUG", "Processed " + numDrives + " drives.");
}
}
catch (err) {
log("ERROR", "mapMoveEnd returned error " + err);
}
}
function openDrivesTab() {
// Check if div id="sidepanel-drives" class contains active (if not, click drives)
if ( $('[data-for="drives"]').attr('selected').includes("true") ) {
// log("DEBUG", "Pane is active");
}
else {
// log("DEBUG", "Pane is NOT active, clicking");
// Select wz-navigation-item data-for="drives" (drives button in left sidebar)
$('[data-for="drives"]').click();
}
}
function closeDrivesTab() {
// Check if div id="sidepanel-drives" class contains active (if not, click drives)
if ( $('[data-for="drives"]').attr('selected').includes("true") ) {
log("DEBUG", "Pane is active, clicking");
// Select wz-navigation-item data-for="drives" (drives button in left sidebar)
$('[data-for="drives"]').click();
}
else {
// log("DEBUG", "Pane is NOT active");
}
// closeLastDrive();
}
function closeLastDrive() {
let allButtons = $('wz-button')
for (let i = 0; i < allButtons.length; i++) {
if ($(allButtons[i]).attr('color')) {
// log("DEBUG", "Button included color attribute");
if ($(allButtons[i]).attr('color').includes("clear-icon")) {
// log("DEBUG", "Button included correct color attribute value");
if ($(allButtons[i]).attr('size')) {
// log("DEBUG", "Button included size attribute");
if ($(allButtons[i]).attr('size').includes("xs")) {
// log("DEBUG", "Button included correct size attribute value");
$(allButtons[i]).click();
return;
}
}
}
}
}
}
function openScriptsTab() {
// Check if div id="sidepanel-drives" class contains active (if not, click drives)
if ( $('#user-tabs').attr('hidden') ) {
// log("DEBUG", "Pane is NOT active, clicking");
// Select wz-navigation-item data-for="userscript_tab" (scripts button in left sidebar)
$('[data-for="userscript_tab"]').click();
}
// else {
// log("DEBUG", "Pane is active");
// }
}
function clickDriveAndCaptureDate(driveCard) {
let dateString = $(driveCard).find('.list-item-card-title')[0].innerText;
// log("DEBUG", "Date: " + dateString);
driveDates.push(new Date(dateString));
$(driveCard).click();
}
function selectEachDriveAndGoToNextPage() {
try {
let nextPageButtonEnabled = false;
let maxPages = MAX_DRIVES / 15; // 20 pages, 300 drives, up to 3.5 drives per day. Might need more for daily wazers? 90 days' worth
//log("DEBUG", "Selecting all wz-card children of sidepanel-drives");
let visibleCards = $('#sidepanel-drives .drive-list wz-card');
//log("DEBUG", "Selected " + visibleCards.length + " elements");
let numCards = 0;
//log("DEBUG", "Iterating over selected wz-card");
let totalDelay = TIME_PER_DRIVE_MS;
for (let cardIndex = 0; cardIndex < visibleCards.length; cardIndex++) {
//log("DEBUG", "Iterating over card " + cardIndex);
setTimeout(clickDriveAndCaptureDate(visibleCards[cardIndex]), totalDelay);
totalDelay += TIME_PER_DRIVE_MS;
}
//log("DEBUG", "Done with wz-card");
//log("DEBUG", "Looking for paginator");
let pageButtons = $('.drive-list .paginator wz-button');
for (let buttonIndex = 0; buttonIndex < pageButtons.length; buttonIndex++) {
let nextPageButtonIcon = $(pageButtons[buttonIndex]).find('i');
if ($(nextPageButtonIcon).attr('class').split('-').includes("right")) {
//log("DEBUG", "found next page button");
nextPageButtonEnabled = !($(pageButtons[buttonIndex]).prop("disabled"));
//log("DEBUG", "next page button is " + (nextPageButtonEnabled ? "" : "NOT " ) + "enabled.");
if (nextPageButtonEnabled) {
$(pageButtons[buttonIndex]).click();
setTimeout(selectEachDriveAndGoToNextPage, TIME_PER_PAGE_MS);
}
else {
// TODO: Do something after collecting all geoms. Wait with a timeout then call some func
// This executes right after we START the timeout for the last page drives. So we should get the number of drives on the last page and set a delay accordingly, much like the next page timeout
setTimeout(processedAllDrives, TIME_PER_PAGE_MS); // 7 minutes later... Need to insert a Spongebob transition screen here.
// That seems to trigger too early no matter how long the timeout is... SOme other callback?
}
}
}
}
catch (err) {
log("ERROR", "Clicking drives encountered an error: " + err);
}
}
function processedAllDrives() {
try {
// log("DEBUG", "Finished creating gargantuan geoms. Processed " + numDrives + " drives.");
// Update script panel label for number of processed drives. TODO: Probably remove later
$('#wmeeaaDrives').text( numDrives );
WM.events.unregister('moveend', null, mapMoveEnd);
// Return to center and zoom from before we started
W.map.setCenter(centerAtProcessDrivesStart);
W.map.getOLMap().zoomTo(zoomAtProcessDrivesStart);
// log("DEBUG", "Adding all features (" + features.length + " components)");
// Reverse features so newest drive is added last
features.reverse();
// Add accumulated drive features to layer
wmeeaaEditAgeLayer.addFeatures(features);
// log("DEBUG", "Added all features (" + features.length + " components)");
// openScriptsTab();
closeDrivesTab();
// for (let i = 0; i < driveDates.length; i++) {
// log("DEBUG", "Drive " + (i + 1) + " date: " + driveDates[i]);
// }
}
catch (err) {
log("ERROR", "final drives processing encountered an error: " + err);
}
}
function iterateDrives() {
try {
WM.events.register('moveend', null, mapMoveEnd);
// Record current center and zoom level so we can return after scanning drives
centerAtProcessDrivesStart = W.map.getCenter();
zoomAtProcessDrivesStart = W.map.getOLMap().getZoom();
// Clear any previous scans. Drives may have updated
numDrives = 0;
features = [];
wmeeaaEditAgeLayer.removeAllFeatures();
// Start scanning drives
setTimeout(selectEachDriveAndGoToNextPage, 1000);
}
catch (err) {
log("ERROR", "Iterating over drives pages encountered an error: " + err);
}
}
function onScanDrivesButtonClick() {
try {
try {
WM.events.unregister('moveend', null, mapMoveEnd);
}
catch (err) {
log("DEBUG", "MapMoveEnd was not registered");
}
numDrives = 0;
// log("INFO", "Opening drives tab");
openDrivesTab();
// log("DEBUG", "Drives tab should be open now");
setTimeout(iterateDrives, 1000);
}
catch (err) {
log("ERROR", "Button click handler encountered error: " + err);
}
}
// Shamelessly ripped from UR-MP and modified to fit my needs
// Takes a timestamp and calculates its age (delta from now)
function getDuration(ts) {
const aDate = new Date()
const now = aDate.getTime()
const duration = now - ts
aDate.setHours(0)
aDate.setMinutes(0)
aDate.setSeconds(0)
aDate.setMilliseconds(0)
const startOfDay = aDate.getTime()
if (duration < now - startOfDay) {
return 0
}
return Math.ceil((duration - (now - startOfDay)) / 86400000)
}
// Shamelessly ripped from UR-MP and modified to fit my needs
// Takes a decimal value and a total number of characters and returns an equivalent 0-padded hex value
function decimalToHex(d, padding) {
let hex = Number(d).toString(16);
padding = typeof padding === 'undefined' || padding === null ? padding = 2 : padding;
while (hex.length < padding) {
hex = '0' + hex;
}
return hex;
}
// Shamelessly ripped from UR-MP and modified to fit my needs
// Takes a number of days (generally age of a drive) and returns a corresponding color
// Drive-based EA lasts for 90 days
// Newest drives should be green, shifting through yellow @ 45 days, reaching 90 days age at full red.
function getColorFromAge(ageInDays) {
let r = 0;
let g = 0;
let b = 255;
r = -15 + (6 * ageInDays);
g = 525 - (6 * ageInDays);
if (r < 0) {
r = 0;
}
if (r > 255) {
r = 255;
}
if (g < 0) {
g = 0;
}
if (g > 255) {
g = 255;
}
b = 0;
if (ageInDays > (90 - DAYS_TO_EXPIRY)) {
r = 255;
g = 0;
b = 255;
}
return '#' + decimalToHex(r, 2) + decimalToHex(g, 2) + decimalToHex(b, 2);
}
/*
* Creates a pair of points around a given center point, perpendicular to the ray created between the center and a provided reference point, with the specified radius.
* Ex: Providing a center at 0, 0 and a reference at 1,1 will create a pair of points at 135 degrees and 315 degrees
*/
function getPolygonEdges(center, nextPoint, radius_mi) {
// log("DEBUG", "Circle center: " + center);
// For use with EPSG:3857 projection, meter-based cartesian/planar
const radius_m = radius_mi * 1609.344; // miles to meters
let points = [];
let referenceBearing = getBearingFromCoordinatePair(center.y, center.x, nextPoint.y, nextPoint.x);
for (let degree = referenceBearing + 90; degree <= referenceBearing + 270; degree += 180) {
// Degrees use 0-north
// For use with EPSG:4326 projection, WGS-84/human-readable degrees lat/lon
const relativePoint = getNewScaledRelativeCoordinates(center.y, center.x, radius_m, (degree % 360));
const globalPoint = new OpenLayers.Geometry.Point(center.x + relativePoint.x, center.y + relativePoint.y)
// log("DEBUG", "Edge " + (degree % 360) + "\t" + center.x + "\t" + center.y + ":\t" + globalPoint.x + "\t" + globalPoint.y);
points.push(globalPoint);
}
return points;
}
/*
* Creates a semi-circle around a given center point, opposite a provided reference point, with the specified radius.
* Ex: Providing a center at 0, 0 and a reference at 1,1 will create an arc from 135 degrees to 315 degrees
*/
function getSemiCircularCap(center, referencePoint, radius_mi) {
// log("DEBUG", "Circle center: " + center);
// For use with EPSG:3857 projection, meter-based cartesian/planar
const radius_m = radius_mi * 1609.344; // miles to meters
let points = [];
let referenceBearing = getBearingFromCoordinatePair(center.y, center.x, referencePoint.y, referencePoint.x);
for (let degree = referenceBearing + 90; degree <= referenceBearing + 270; degree += 5) {
// Degrees use 0-north
// For use with EPSG:4326 projection, WGS-84/human-readable degrees lat/lon
const relativePoint = getNewScaledRelativeCoordinates(center.y, center.x, radius_m, (degree % 360));
const globalPoint = new OpenLayers.Geometry.Point(center.x + relativePoint.x, center.y + relativePoint.y)
// log("DEBUG", "Cap " + (degree % 360) + "\t" + center.x + "\t" + center.y + ":\t" + globalPoint.x + "\t" + globalPoint.y);
points.push(globalPoint);
}
return points;
}
// Shamelessly ripped from WME-USGB and modified to fit my needs
// Takes a center point and a radius and makes a corresponding circle
function getCircleLinearRing(center, radius_mi) {
// log("DEBUG", "Circle center: " + center);
// For use with EPSG:3857 projection, meter-based cartesian/planar
const radius_m = radius_mi * 1609.344; // miles to meters
const points = [];
for (let degree = 0; degree < 360; degree += 5) {
// Degrees use 0-north
// For use with EPSG:4326 projection, WGS-84/human-readable degrees lat/lon
const relativePoint = getNewScaledRelativeCoordinates(center.y, center.x, radius_m, degree);
const globalPoint = new OpenLayers.Geometry.Point(center.x + relativePoint.x, center.y + relativePoint.y)
points.push(globalPoint);
}
return new OpenLayers.Geometry.LinearRing(points);
}
/*
* Gets an approximate radius in meters of the earth at a given latitude.
* Useful for more locally accurate calculations.
* Would it be nice to be a flat-earther? Yes. It would make this whole thing much easier.
* Unfortunately the sphereical-earthers are wrong, too. Turns out the earth is squishy
* enough to deform along the equator because that's what happens when you spin a really large
* mass really fast. Science is cool.
*/
function getEarthRadiusMeters(latitudeRadians)
{
return Math.sqrt(
(Math.pow(Math.pow(EQUATORIAL_RADIUS_METERS, 2) * Math.cos(latitudeRadians), 2)
+ Math.pow(Math.pow(POLAR_RADIUS_METERS, 2) * Math.sin(latitudeRadians), 2)) /* end numerator */
/ (Math.pow(EQUATORIAL_RADIUS_METERS * Math.cos(latitudeRadians), 2)
+ Math.pow(POLAR_RADIUS_METERS * Math.sin(latitudeRadians), 2)) /* end denominator */
); /* End sqrt */
}
/*
* Calculates new coordinates on a spheroid surface, given some initial point along with a
* direction and distance to the desired new point.
*/
function getNewCoordinates(latDegrees, lonDegrees, distanceMeters, bearingDegrees) {
if (distanceMeters == 0) return new OpenLayers.Geometry.Point(lonDegrees, latDegrees);
let latitudeRadians = toRadians(latDegrees);
let longitudeRadians = toRadians(lonDegrees);
let bearingRadians = toRadiansZeroEastPositiveCCW(bearingDegrees);
let distanceRadians = distanceMeters / getEarthRadiusMeters(latitudeRadians);
let newLatRadians = Math.asin(Math.sin(latitudeRadians) * Math.cos(distanceRadians) + Math.cos(latitudeRadians) * Math.cos(bearingRadians) * Math.sin(distanceRadians));
let newLonRadians = longitudeRadians + Math.atan2(Math.sin(bearingRadians) * Math.sin(distanceRadians) * Math.cos(latitudeRadians), Math.cos(distanceRadians) - Math.sin(latitudeRadians) * Math.sin(newLatRadians));
return new OpenLayers.Geometry.Point(toDegrees(newLonRadians), toDegrees(newLatRadians));
}
/*
* Calculates new coordinates on a spheroid surface, treating the input coordinates as the origin (0, 0)
* Creates relative or offset coordinates.
*/
function getNewRelativeCoordinates(latDegrees, lonDegrees, distanceMeters, bearingDegrees) {
if (distanceMeters == 0) return new OpenLayers.Geometry.Point(lonDegrees, latDegrees);
let latitudeRadians = toRadians(latDegrees);
let longitudeRadians = toRadians(lonDegrees);
let bearingRadians = toRadians(bearingDegrees);
let distanceRadians = distanceMeters / getEarthRadiusMeters(latitudeRadians);
let newLatRadians = Math.asin(Math.sin(latitudeRadians) * Math.cos(distanceRadians) + Math.cos(latitudeRadians) * Math.cos(bearingRadians) * Math.sin(distanceRadians));
let newLonRadians = longitudeRadians + Math.atan2(Math.sin(bearingRadians) * Math.sin(distanceRadians) * Math.cos(latitudeRadians), Math.cos(distanceRadians) - Math.sin(latitudeRadians) * Math.sin(newLatRadians));
// Subtract original coordinates from resultant coordinates to provide relative/offset coordinates
return new OpenLayers.Geometry.Point(toDegrees(newLonRadians) - lonDegrees, toDegrees(newLatRadians) - latDegrees);
}
/*
* Calculates new coordinates on a spheroid surface, treating the input coordinates as the origin (0, 0)
* Creates relative or offset coordinates that include magic number scalars.
*/
function getNewScaledRelativeCoordinates(latDegrees, lonDegrees, distanceMeters, bearingDegrees) {
let originalRelativePoint = getNewRelativeCoordinates(latDegrees, lonDegrees, distanceMeters, bearingDegrees);
return new OpenLayers.Geometry.Point(originalRelativePoint.x * HORIZONTAL_SCALAR, originalRelativePoint.y * VERTICAL_SCALAR);
}
function toRadians(degrees) {
return degrees * Math.PI / 180;
}
function toDegrees(radians) {
return radians * 180 / Math.PI;
}
/*
* Converts from
* degree with zero-north origin, increasing in the clockwise direction (earth, navigation)
* to
* radian with zero-west origin, increasing in the counterclockwise direction (math)
*/
function toRadiansZeroEastPositiveCCW(degrees) {
// Flip across line y = x
let inputRadians = toRadians(degrees);
let inputX = Math.cos(inputRadians);
let inputY = Math.sin(inputRadians);
// Here's the flip ladies and gents
let flippedRadians = Math.atan2( inputX, inputY );
// log("DEBUG", "Input degrees: " + degrees + "\t x: " + inputX + "\ty: " + inputY + "\tflipped: " + toDegrees(flippedRadians) + "\tradians: " + flippedRadians);
return flippedRadians;
}
/*
* Converts from
* radian with zero-west origin, increasing in the counterclockwise direction (math)
* to
* degree with zero-north origin, increasing in the clockwise direction (earth, navigation)
*/
function toDegreesZeroNorthPositiveCW(radians) {
// Flip across line y = x
let inputX = Math.cos(radians);
let inputY = Math.sin(radians);
// Here's the flip ladies and gents
let flippedRadians = Math.atan2( inputX, inputY );
return toDegrees(flippedRadians);
}
/*
* Takes the given pair of coordinates and calculates an approximate bearing betwixt them
*/
function getBearingFromCoordinatePair(lat1, lon1, lat2, lon2) {
let lat1rad = toRadians(lat1);
let lat2rad = toRadians(lat2);
let lon1rad = toRadians(lon1);
let lon2rad = toRadians(lon2);
let y = Math.sin(lon2rad - lon1rad) * Math.cos(lat2rad);
let x = Math.cos(lat1rad) * Math.sin(lat2rad) - Math.sin(lat1rad) * Math.cos(lat2rad) * Math.cos(lon2rad - lon1rad);
return toDegrees(Math.atan2(y, x));
}
function initializeSettings() {
// TODO: All the things
// TODO: loadSettings if we ever have settings to load/save (can we save the area? That's a lot of data but probably nicer than rescanning every time...)
// Layer on/off status?
$('#wmeeaaRadius').text(editRadius + " miles"); // TODO: Support km
$('#wmeeaaDrives').text("Unknown! Please scan.");
// $('#wmeeaaArea').text("Unknown! Please scan.");
}
function onAgeLayerToggleChanged(checked) {
wmeeaaEditAgeLayer.setVisibility(checked);
// log("DEBUG", "Layer checkbox is " + (checked ? "" : "not ") + "checked.");
}
function init() {
try {
log("INFO", SCRIPT_LONG_NAME + " " + SCRIPT_VERSION + " started");
WM = W.map;
DL = WM.driveLayer;
editRadius = W.loginManager.user.attributes.editableMiles;
// WazeWrap and whatever else initialization
// Create our layer
wmeeaaEditAgeLayer = new OpenLayers.Layer.Vector('wmeeaaEditAgeLayer', {
uniqueName: '__wmeeaaEditAgeLayer',
styleMap: new OpenLayers.StyleMap({ default: EAA_STYLE })
} );
// TODO: Set visibility to loaded value
wmeeaaEditAgeLayer.setVisibility(true);
wmeeaaEditAgeLayer.setZIndex(W.map.roadLayer.getZIndex() - 1);
wmeeaaEditAgeLayer.setOpacity(0.3);
// Add the layer checkbox to the Layers menu.
WazeWrap.Interface.AddLayerCheckbox('display', 'Edit Age', layerEnabled, onAgeLayerToggleChanged);
WM.addLayer(wmeeaaEditAgeLayer);
_$scanDrivesButton = $('<button>', { id: 'wmeeaaStartScan', class: 'wmeeaaSettingsButton' }).text('Scan Area');
// Userscripts tab section
var $section = $("<div>");
$section.append([
'<div>',
'<h2>WME Edit Area Age</h2>'
].join(' '));
$section.append(_$scanDrivesButton);
// TODO: Adjustable expiry age to only show area near expiry
$section.append([
'<hr>',
'<div>',
'<h3>Edit Age Info</h3>',
'Editable radius: <span id="wmeeaaRadius"></span></br>',
'Drives: <span id="wmeeaaDrives"></span></br>',
// 'Editable area from drives: <span id="wmeeaaArea"></span></br>',
'</div>',
'</div>'
].join(' '));
WazeWrap.Interface.Tab(SCRIPT_SHORTEST_NAME, $section.html(), initializeSettings);
log("INFO", SCRIPT_LONG_NAME + " initialized!");
$("#wmeeaaStartScan").click(onScanDrivesButtonClick);
}
catch (err) {
log("ERROR", SCRIPT_LONG_NAME + " could not initialize: " + err);
}
}
function onWmeReady() {
if (WazeWrap && WazeWrap.Ready) {
init();
} else {
setTimeout(onWmeReady, 100);
}
}
function bootstrap() {
if (typeof W === 'object' && W.userscripts?.state.isReady) {
onWmeReady();
} else {
document.addEventListener('wme-ready', onWmeReady, { once: true });
}
}
bootstrap();
})();