您需要先安装一个扩展,例如 篡改猴、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.1 // @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 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_TOGGLE_ID = "wmecolbasemap-settingsLayerToggle"; const ELEMENT_SETTINGS_DATE_PICKER_ID = "wmecolbasemap-settingsDatePicker"; const ELEMENT_SETTINGS_RELOAD_ID = "wmecolbasemap-settingsRefreshBtn"; const DEBUG = true; 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 bubbleEnabled = JSON.parse(localStorage.getItem(STORAGE_BUBBLE_KEY)) ?? 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); 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() { 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="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(".", "")); 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) addSettingsBubble(); localStorage.setItem(STORAGE_BUBBLE_KEY, bubbleEnabled); } /* 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; document.getElementById(ELEMENT_SETTINGS_TOGGLE_ID).checked = layerEnabled; // toggles date picker if (layerEnabled && bubbleEnabled) addSettingsBubble(); else document.getElementById(ELEMENT_BUBBLE_ID)?.remove(); } /* 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"); tabLabel.innerText = 'COL'; tabLabel.title = NAME; tabPane.style = "display: flex; flex-direction: column; height: 100%; gap: 5px;"; tabPane.parentElement.style.height = "calc(100% - 30px)"; tabPane.innerHTML = policy.createHTML(` <h3>WME COL Basemap <small>v3.1</small></h3> <p>provided by <a href="https://maps.cityoflewisville.com" target="_blank">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_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">Note that aerial dates are not the date the imagery was taken, but the date that Lewisville updated their site</i> <p style="margin-top: auto;"><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 - nearmap.com</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_RELOAD_ID).addEventListener("click", () => updateLayerDate(currentDate ?? 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; }); 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 }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址