您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds aerials from the COL GIS as a basemap for WME
// ==UserScript== // @name WME COL Basemap // @namespace https://fxzfun.com/ // @version 3.1.4 // @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 STORAGE_BUBBLE_KEY = "wmecolbasemap-settingsBubbleEnabled"; const STORAGE_PERSISTENT_BUBBLE_KEY = "wmecolbasemap-settingsBubblePersistent"; const ELEMENT_BUBBLE_ID = "wmecolbasemap-datePickerContainer"; const ELEMENT_DATE_PICKER_ID = "wmecolbasemap-datePicker"; const ELEMENT_REFRESH_ID = "wmecolbasemap-refreshBtn"; const ELEMENT_POPUP_CHECKBOX_ID = "wmecolbasemap-popupCheckbox"; const ELEMENT_SETTINGS_BUBBLE_ID = "wmecolbasemap-settingsBubble"; const ELEMENT_SETTINGS_COL_LINK_ID = "wmecolbasemap-settingsColLink"; const ELEMENT_SETTINGS_TOGGLE_ID = "wmecolbasemap-settingsLayerToggle"; const ELEMENT_SETTINGS_DATE_PICKER_ID = "wmecolbasemap-settingsDatePicker"; const ELEMENT_SETTINGS_RELOAD_ID = "wmecolbasemap-settingsRefreshBtn"; const ELEMENT_SETTINGS_PERSISTENT_BUBBLE_ID = "wmecolbasemap-settingsBubblePersistent"; 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)); const getMostRecentDate = () => localStorage.getItem(STORAGE_MOST_RECENT_DATE_KEY)?.replaceAll(".", "") ?? "20230531"; const createURL = (date, ticket) => "https://us0.nearmap.com/maps/?z=${z}&x=${x}&y=${y}&nml=V&version=2&nmd=" + date + "&ticket=" + ticket; let layer; let layerEnabled = false; let bubbleEnabled = JSON.parse(localStorage.getItem(STORAGE_BUBBLE_KEY)) ?? false; let persistentBubble = JSON.parse(localStorage.getItem(STORAGE_PERSISTENT_BUBBLE_KEY)) ?? true; 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; } /* 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); if (refreshBtn) { 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 element to the top left of the map */ function addSettingsBubble() { if (!!document.getElementById(ELEMENT_BUBBLE_ID)) return; var dates = JSON.parse(localStorage.getItem(STORAGE_DATES_KEY) ?? "[]"); const div = document.createElement("div"); div.id = ELEMENT_BUBBLE_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="${layerEnabled ? "on" : "off"}" name="" ${layerEnabled ? "checked=''" : ''} style="display: inline-block; vertical-align: middle; margin-left: 0.5em;"></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(".", "")); let settingsSelect = document.getElementById(ELEMENT_SETTINGS_DATE_PICKER_ID) if (!!settingsSelect) settingsSelect.value = currentDate; }); const refreshBtn = document.getElementById(ELEMENT_REFRESH_ID); refreshBtn.addEventListener("click", async () => { refreshBtn.style.animation = "1s wmecolbasemapRefresh"; updateLayerDate(currentDate ?? getMostRecentDate()); await sleep(1000); div.remove(); addSettingsBubble(); }); } function toggleBubble() { bubbleEnabled = !bubbleEnabled; const bubble = document.getElementById(ELEMENT_BUBBLE_ID); if (!!bubble && !bubbleEnabled) bubble.remove(); else if (layerEnabled || persistentBubble) addSettingsBubble(); localStorage.setItem(STORAGE_BUBBLE_KEY, bubbleEnabled); } function toggleBubblePersistence() { persistentBubble = !persistentBubble; if (!document.getElementById(ELEMENT_BUBBLE_ID) && bubbleEnabled) addSettingsBubble(); else if (!layerEnabled) document.getElementById(ELEMENT_BUBBLE_ID)?.remove(); localStorage.setItem(STORAGE_PERSISTENT_BUBBLE_KEY, persistentBubble); } /* Callback for shortcut and layer checkbox, toggles the visibility of the layer */ function toggleBasemap() { layerEnabled = !layerEnabled; layer?.setVisibility(layerEnabled); const power = document.getElementById(ELEMENT_SETTINGS_TOGGLE_ID + '_power'); power.style.color = layerEnabled ? '#00bd00' : '#bdbdbd'; document.querySelector("#layer-switcher-item_wme_col_basemap").checked = layerEnabled; document.getElementById(ELEMENT_SETTINGS_TOGGLE_ID).checked = layerEnabled; // toggles date picker if (bubbleEnabled && (layerEnabled || persistentBubble)) addSettingsBubble(); if (!persistentBubble) { if (layerEnabled && bubbleEnabled) addSettingsBubble(); else document.getElementById(ELEMENT_BUBBLE_ID)?.remove(); } else if (bubbleEnabled) document.getElementById(ELEMENT_POPUP_CHECKBOX_ID).checked = layerEnabled; } /* Main entry point of the script Adds layer checkbox, shortcut, and layer */ async function initialize() { console.log("WME COL Basemap: Start"); if (DEBUG) pageWindow.wmeColBasemap = { getJSON, getTicket, refreshTicket, getMostRecentDate, createURL, addLayer, updateLayerDate, addSettingsBubble, toggleBasemap, 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); const dates = JSON.parse(localStorage.getItem(STORAGE_DATES_KEY) ?? "[]"); const { tabLabel, tabPane } = W.userscripts.registerSidebarTab("wmeColBasemap"); const label = document.createElement('span'); label.id = ELEMENT_SETTINGS_TOGGLE_ID + '_power'; label.classList = 'fa fa-power-off'; label.style = 'margin-right: 5px;cursor: pointer;color: #ccc;font-size: 13px;'; label.onclick = (ev) => { ev.stopPropagation(); toggleBasemap(); } tabLabel.appendChild(label); tabLabel.appendChild(document.createTextNode(' COL')); tabLabel.title = NAME; tabPane.style = "display: flex; flex-direction: column; height: 100%; gap: 5px;"; tabPane.innerHTML = policy.createHTML(` <h3>WME COL Basemap <small>v3.1</small></h3> <p>provided by <a href="https://maps.cityoflewisville.com" target="_blank" id="${ELEMENT_SETTINGS_COL_LINK_ID}">maps.cityoflewisville.com</a></p> <b style="margin-top: 20px">Options</b> <wz-checkbox id="${ELEMENT_SETTINGS_BUBBLE_ID}" value="${bubbleEnabled ? "on" : "off"}" name="" ${bubbleEnabled ? "checked=''" : ''}>Show Settings Bubble</wz-checkbox> <wz-checkbox id="${ELEMENT_SETTINGS_PERSISTENT_BUBBLE_ID}" value="${persistentBubble ? "on" : "off"}" name="" ${persistentBubble ? "checked=''" : ''}>Persistent Bubble</wz-checkbox> <wz-checkbox id="${ELEMENT_SETTINGS_TOGGLE_ID}" value="off" name="">Toggle Basemap Layer</wz-checkbox> <span style="margin-top:20px">Aerial Date</span> <wz-select id="${ELEMENT_SETTINGS_DATE_PICKER_ID}" value="${currentDate ?? dates[0]}" style="display: inline-block"> <div class="select-wrapper" id="select-wrapper" style="display: none"><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> <wz-button id="${ELEMENT_SETTINGS_RELOAD_ID}" color="text" size="sm">Clear token and reload layer</wz-button> <p style="margin-top: 20px">Current Shortcut: ${localStorage.getItem(STORAGE_SHORTCUT_KEY) ?? "None"}</p> <i style="margin-top: 20px">Please note that the dates shown correspond to when Nearmap imagery was taken over the City of Lewisville, TX and represent the state of all Nearmap imagery as of that date. The date shown of Lewisville imagery does not indicate that areas outside it's city limits were also updated on said date.</i> <p style="margin-top: 20px;"><b>Note:</b> please do not use as your default basemap - only enable when needed, as we do not want to abuse the service provided by the City of Lewisville GIS.</p> <em>Aerial Imagery © Nearmap - <a href="https://nearmap.com" target="_blank">nearmap.com</a></em> `); await W.userscripts.waitForElementConnected(tabPane); document.getElementById(ELEMENT_SETTINGS_BUBBLE_ID).addEventListener("click", toggleBubble); document.getElementById(ELEMENT_SETTINGS_TOGGLE_ID).addEventListener("click", toggleBasemap); document.getElementById(ELEMENT_SETTINGS_PERSISTENT_BUBBLE_ID).addEventListener("click", toggleBubblePersistence); document.getElementById(ELEMENT_SETTINGS_RELOAD_ID).addEventListener("click", () => updateLayerDate(currentDate ?? getMostRecentDate())); const colLink = document.getElementById(ELEMENT_SETTINGS_COL_LINK_ID); colLink.addEventListener("mousedown", () => { const center = W.map.getCenter(); const lonlat = new OpenLayers.LonLat(center.lon, center.lat); lonlat.transform(new OpenLayers.Projection('EPSG:900913'), new OpenLayers.Projection('EPSG:4326')); colLink.href = `https://maps.cityoflewisville.com/?&zoom=${W.map.getZoom()}¢er=${lonlat.lat},${lonlat.lon}&basemap=nearmap_${currentDate?.replaceAll(".", "") ?? getMostRecentDate()}`; }); const select = document.getElementById(ELEMENT_SETTINGS_DATE_PICKER_ID); select.addEventListener("optionClicked", (evt) => { currentDate = evt.detail.value ?? currentDate; select.value = currentDate; updateLayerDate(select.value.replaceAll(".", "")); let bubbleSelect = document.getElementById(ELEMENT_DATE_PICKER_ID) if (!!bubbleSelect) bubbleSelect.value = currentDate; }); if (bubbleEnabled && (layerEnabled || persistentBubble)) addSettingsBubble(); 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 }); pageWindow.colInit = initialize; })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址