// ==UserScript==
// @name WME COL Basemap
// @namespace https://fxzfun.com/
// @version 3.0
// @description Adds aerials from the COL GIS as a basemap for WME
// @author FXZFun
// @match https://*.waze.com/*/editor*
// @match https://*.waze.com/editor*
// @exclude https://*.waze.com/user/editor*
// @connect query.cityoflewisville.com
// @icon https://www.google.com/s2/favicons?sz=64&domain=waze.com
// @require https://gf.qytechs.cn/scripts/24851-wazewrap/code/WazeWrap.js
// @grant GM_xmlhttpRequest
// @license GNU GPLv3
// ==/UserScript==
/* global W, OpenLayers, WazeWrap, trustedTypes */
(function() {
'use strict';
const STORAGE_TICKET_KEY = "wmecolbasemap-ticket";
const STORAGE_EXPIRE_KEY = "wmecolbasemap-ticketExpiryDate";
const STORAGE_DATES_KEY = "wmecolbasemap-aerialDates";
const STORAGE_MOST_RECENT_DATE_KEY = "wmecolbasemap-mostRecentDate";
const STORAGE_SHORTCUT_KEY = "wmecolbasemap-shortcut";
const ELEMENT_DATE_PICKER_CONTAINER_ID = "wmecolbasemap-datePickerContainer";
const ELEMENT_DATE_PICKER_ID = "wmecolbasemap-datePicker";
const ELEMENT_REFRESH_ID = "wmecolbasemap-refreshBtn";
const ELEMENT_POPUP_CHECKBOX_ID = "wmecolbasemap-popupCheckbox";
const DEBUG = false;
const NAME = "WME COL Basemap";
const pageWindow = unsafeWindow ?? window;
const policy = trustedTypes?.createPolicy('wmecolbasemapPolicy', { createHTML: (input) => input});
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
let layer;
let layerEnabled = false;
let currentDate;
let errorCount = 0;
function getJSON(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: async function(response) {
resolve(JSON.parse(response.response));
},
onerror: function(error) {
reject(error);
}
});
});
}
async function getTicket() {
let ticket = localStorage.getItem(STORAGE_TICKET_KEY);
let ticketExpired = (localStorage.getItem(STORAGE_EXPIRE_KEY) ?? 0) < (new Date().getTime() + (60 * 60 * 1000)); // get new ticket if less than an hour left
if (!ticket || ticketExpired) ticket = await refreshTicket();
return ticket;
}
async function refreshTicket() {
const json = await getJSON("https://query.cityoflewisville.com/v2/?webservice=NearmapTicketAndDates");
const data = json[0][0];
const dates = JSON.parse(data.aerialdates).map(d => d.date);
localStorage.setItem(STORAGE_TICKET_KEY, data?.ticket);
localStorage.setItem(STORAGE_DATES_KEY, JSON.stringify(dates));
localStorage.setItem(STORAGE_MOST_RECENT_DATE_KEY, dates[0]);
localStorage.setItem(STORAGE_EXPIRE_KEY, new Date().getTime() + (24 * 60 * 60 * 1000)); // expires every 24 hours
return data?.ticket;
}
function getMostRecentDate() {
return localStorage.getItem(STORAGE_MOST_RECENT_DATE_KEY)?.replaceAll(".", "") ?? "20230531";
}
function createURL(date, ticket) {
return "https://us0.nearmap.com/maps/?z=${z}&x=${x}&y=${y}&nml=V&version=2&nmd=" + date + "&ticket=" + ticket;
}
/*
Add the layer to the map if it does not exist
*/
async function addLayer() {
const date = getMostRecentDate();
const ticket = await getTicket();
const url = createURL(date, ticket);
layer = new OpenLayers.Layer.XYZ(
NAME,
url,
{
isBaseLayer: false,
uniqueName: 'colgis',
tileSize: new OpenLayers.Size(256,256),
transitionEffect: 'resize',
displayInLayerSwitcher: true,
opacity: 1,
visibility: false
});
layer.events.on({
'loadend': async function (evt) {
if (DEBUG) console.log("WME COL Basemap: Loaded tiles");
const refreshBtn = document.getElementById(ELEMENT_REFRESH_ID);
refreshBtn.style.animation = "3s wmecolbasemapRefresh";
await sleep(3000);
refreshBtn.style.animation = "";
},
'loaderror': async function(evt) {
if (errorCount++ === 0) {
console.error("WME COL Basemap: Error loading tile");
await refreshTicket();
updateLayerDate(currentDate ?? getMostRecentDate());
} else if (errorCount === 50) {
toggleBasemap();
alert("WME COL Basemap failed to reach imagery endpoint");
}
}
});
W.map.addLayer(layer);
W.map.setLayerIndex(layer, 3);
}
async function updateLayerDate(date) {
if (!layer) return;
let ticket = await getTicket();
layer.url = createURL(date, ticket);
layer.redraw();
}
/*
Add the date picker ui element to the top left of the map
*/
function addDatePicker() {
var dates = JSON.parse(localStorage.getItem(STORAGE_DATES_KEY) ?? "[]");
const div = document.createElement("div");
div.id = ELEMENT_DATE_PICKER_CONTAINER_ID;
div.innerHTML = policy.createHTML(
`<style>
.wmecolbasemap { position: absolute; top: 40px; left: 60px; background-color: #fafafa; padding: 0.5em 0.75em; border-radius: 2em; }
.wmecolbasemap i { padding: 0.5em; vertical-align: middle; }
.wmecolbasemap #select-wrapper { display: none; }
@keyframes wmecolbasemapRefresh { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }
</style>
<div class="wmecolbasemap">
<wz-checkbox id="${ELEMENT_POPUP_CHECKBOX_ID}" value="on" style="display: inline-block; vertical-align: middle; margin-left: 0.5em;" checked></wz-checkbox>
<wz-select id="${ELEMENT_DATE_PICKER_ID}" value="${currentDate ?? dates[0]}" style="display: inline-block">
<div class="select-wrapper" id="select-wrapper"><div tabindex="0" class="select-box"><div class="selected-value-wrapper"><span class="selected-value">${currentDate ?? dates[0]}</span></div></div></div>
${dates.map(d => "<wz-option value=" + d + ">" + d + "</wz-option>").join("")}
</wz-select>
<i id="${ELEMENT_REFRESH_ID}" class="w-icon w-icon-refresh"></i>
</div>`);
document.querySelector(".olMap").appendChild(div);
document.getElementById(ELEMENT_POPUP_CHECKBOX_ID).addEventListener("click", toggleBasemap);
const select = document.getElementById(ELEMENT_DATE_PICKER_ID);
select.addEventListener("optionClicked", (evt) => {
currentDate = evt.detail.value ?? currentDate;
select.value = currentDate;
updateLayerDate(select.value.replaceAll(".", ""))
});
const refreshBtn = document.getElementById(ELEMENT_REFRESH_ID);
refreshBtn.addEventListener("click", async () => {
refreshBtn.style.animation = "1s wmecolbasemapRefresh";
await sleep(1000);
updateLayerDate(currentDate ?? getMostRecentDate());
div.remove();
addDatePicker();
});
}
/*
Callback for shortcut and layer checkbox, toggles the visibility of the layer
*/
function toggleBasemap() {
layerEnabled = !layerEnabled;
layer?.setVisibility(layerEnabled);
document.querySelector("#layer-switcher-item_wme_col_basemap").checked = layerEnabled;
// toggles date picker
if (layerEnabled) addDatePicker();
else document.getElementById(ELEMENT_DATE_PICKER_CONTAINER_ID).remove();
}
/*
Main entry point of the script
Adds layer checkbox, shortcut, and layer
*/
function initialize() {
console.log("WME COL Basemap: Start");
if (DEBUG) pageWindow.wmeColBasemap = { getJSON: getJSON, getTicket: getTicket, refreshTicket: refreshTicket, getMostRecentDate: getMostRecentDate, createURL: createURL, addLayer: addLayer, updateLayerDate: updateLayerDate, addDatePicker: addDatePicker, toggleBasemap: toggleBasemap, layer: layer };
addLayer();
console.log("WME COL Basemap: Added Layer");
const i = setInterval(() => {
if (WazeWrap?.Ready) {
clearInterval(i);
WazeWrap.Interface.AddLayerCheckbox(
"display",
NAME,
false,
toggleBasemap,
layer ?? W.map.getLayerByName(NAME));
new WazeWrap.Interface.Shortcut('COLBasemapDisplay', 'Toggle COL Basemap',
'layers', 'layersToggleCOLBasemapDisplay', localStorage.getItem(STORAGE_SHORTCUT_KEY) ?? "", toggleBasemap, null).add();
}
}, 500);
pageWindow.addEventListener("beforeunload", () => {
const shortcut = W.accelerators.Actions.COLBasemapDisplay?.shortcut;
if (!shortcut) return;
const modifiers = [];
if (shortcut.ctrlKey) modifiers.push("Ctrl");
if (shortcut.altKey) modifiers.push("Alt");
if (shortcut.shiftKey) modifiers.push("Shift");
const newShortcut = `${modifiers.join("+")}${String.fromCharCode(shortcut.keyCode)}`;
localStorage.setItem(STORAGE_SHORTCUT_KEY, newShortcut);
console.log("Saved WME COL Basemap settings");
});
}
W?.userscripts?.state?.isReady ? initialize() : document.addEventListener("wme-ready", initialize, { once: true });
})();