WME COL Basemap

Adds aerials from the COL GIS as a basemap for WME

目前为 2023-07-09 提交的版本。查看 最新版本

// ==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 });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址