WME HN NavPoints

Shows navigation points of all house numbers in WME

目前為 2020-06-16 提交的版本,檢視 最新版本

/* eslint-disable no-template-curly-in-string */
// ==UserScript==
// @name            WME HN NavPoints
// @namespace       https://gf.qytechs.cn/users/166843
// @description     Shows navigation points of all house numbers in WME
// @version         2020.06.16.01
// @author          dBsooner
// @grant           none
// @require         https://gf.qytechs.cn/scripts/24851-wazewrap/code/WazeWrap.js
// @license         GPLv3
// @include         /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
// @contributionURL https://github.com/WazeDev/Thank-The-Authors
// ==/UserScript==

/* global _, $, document, GM_info, localStorage, MutationObserver, OpenLayers, performance, W, WazeWrap, window */

/*
 * Original concept and code for WME HN NavPoints was written by MajkiiTelini. After version 0.6.6, this
 * script is maintained by the WazeDev team. Special thanks is definitely given to MajkiiTelini for his
 * hard work and dedication to the original script.
 *
 */

const ALERT_UPDATE = true,
    DEBUG = false,
    LOAD_BEGIN_TIME = performance.now(),
    SCRIPT_FORUM_URL = 'https://www.waze.com/forum/viewtopic.php?f=819&t=289116',
    SCRIPT_GF_URL = 'https://gf.qytechs.cn/en/scripts/390565-wme-hn-navpoints',
    SCRIPT_NAME = GM_info.script.name.replace('(beta)', 'β'),
    SCRIPT_VERSION = GM_info.script.version,
    SCRIPT_VERSION_CHANGES = ['<b>NEW:</b> HN number mouseover tooltip (zooms 6-10). Edit button for easy HN mode access.',
        '<b>CHANGE:</b> Lots of under-the-hood stuff to increase performance.',
        '<b>CHANGE:</b> Latest WME update compatibility.',
        '<b>BUGFIX:</b> Reloads not properly refreshing HN and lines.'],
    SETTINGS_STORE_NAME = 'WMEHNNavPoints',
    _timeouts = {
        bootstrap: undefined,
        checkZIndex: undefined,
        hideTooltip: undefined,
        saveSettingsToStorage: undefined,
        setMarkerEvents: undefined
    };

let _settings = {},
    _scriptActive = false,
    _spinners = 0,
    _HNLayerObserver,
    _HNNavPointsLayer,
    _HNNavPointsNumbersLayer,
    _processedSegments = [],
    _segmentsToProcess = [],
    _$hnNavPointsTooltipDiv,
    _popup = {
        inUse: false,
        hnNumber: -1,
        streetId: -1
    };

function log(message) { console.log('WME-HN-NavPoints:', message); }
function logError(message) { console.error('WME-HN-NavPoints:', message); }
// function logWarning(message) { console.warn('WME-HN-NavPoints:', message); }
function logDebug(message) {
    if (DEBUG)
        console.log('WME-HN-NavPoints:', message);
}

async function loadSettingsFromStorage() {
    const defaultSettings = {
            disableBelowZoom: 5,
            hnLines: true,
            hnNumbers: true,
            toggleHNNavPointsShortcut: '',
            toggleHNNavPointsNumbersShortcut: '',
            lastSaved: 0,
            lastVersion: undefined
        },
        loadedSettings = $.parseJSON(localStorage.getItem(SETTINGS_STORE_NAME));
    _settings = $.extend({}, defaultSettings, loadedSettings);
    const serverSettings = await WazeWrap.Remote.RetrieveSettings(SETTINGS_STORE_NAME);
    if (serverSettings && (serverSettings.lastSaved > _settings.lastSaved))
        $.extend(_settings, serverSettings);
    _timeouts.saveSettingsToStorage = window.setTimeout(saveSettingsToStorage, 5000);

    return Promise.resolve();
}

function saveSettingsToStorage() {
    checkTimeout({ timeout: 'saveSettingsToStorage' });
    if (localStorage) {
        _settings.lastVersion = SCRIPT_VERSION;
        _settings.lastSaved = Date.now();
        localStorage.setItem(SETTINGS_STORE_NAME, JSON.stringify(_settings));
        WazeWrap.Remote.SaveSettings(SETTINGS_STORE_NAME, _settings);
        logDebug('Settings saved.');
    }
}

function showScriptInfoAlert() {
    if (ALERT_UPDATE && SCRIPT_VERSION !== _settings.lastVersion) {
        let releaseNotes = '';
        releaseNotes += '<p>What\'s New:</p>';
        if (SCRIPT_VERSION_CHANGES.length > 0) {
            releaseNotes += '<ul>';
            for (let idx = 0; idx < SCRIPT_VERSION_CHANGES.length; idx++)
                releaseNotes += `<li>${SCRIPT_VERSION_CHANGES[idx]}`;
            releaseNotes += '</ul>';
        }
        else {
            releaseNotes += '<ul><li>Nothing major.</ul>';
        }
        WazeWrap.Interface.ShowScriptUpdate(SCRIPT_NAME, SCRIPT_VERSION, releaseNotes, SCRIPT_GF_URL, SCRIPT_FORUM_URL);
    }
}

function checkShortcutsChanged() {
    let triggerSave = false;
    ['toggleHNNavPointsShortcut', 'toggleHNNavPointsNumbersShortcut'].forEach(k => {
        let keys = '';
        const { shortcut } = W.accelerators.Actions[k];
        if (shortcut) {
            if (shortcut.altKey)
                keys += 'A';
            if (shortcut.shiftKey)
                keys += 'S';
            if (shortcut.ctrlKey)
                keys += 'C';
            if (keys !== '')
                keys += '+';
            if (shortcut.keyCode)
                keys += shortcut.keyCode;
        }
        else {
            keys = '';
        }
        if (_settings[k] !== keys) {
            _settings[k] = keys;
            triggerSave = true;
        }
    });
    if (triggerSave)
        saveSettingsToStorage();
}

function checkTimeout(obj) {
    if (obj.toIndex) {
        if (_timeouts[obj.timeout] && (_timeouts[obj.timeout][obj.toIndex] !== undefined)) {
            window.clearTimeout(_timeouts[obj.timeout][obj.toIndex]);
            _timeouts[obj.timeout][obj.toIndex] = undefined;
        }
    }
    else {
        if (_timeouts[obj.timeout] !== undefined)
            window.clearTimeout(_timeouts[obj.timeout]);
        _timeouts[obj.timeout] = undefined;
    }
}

function doSpinner(stop = false) {
    const $btn = $('#hnNPSpinner');
    if (stop) {
        _spinners--;
        if (_spinners === 0) {
            if ($btn.length > 0) {
                $btn.removeClass('fa-spin');
                $('#divHnNPSpinner').hide();
            }
            else {
                $('#topbar-container .topbar').prepend(
                    '<div id="divHnNPSpinner" title="WME HN NavPoints is currently processing house numbers." style="font-size:20px;background:white;float:left;margin-left:-20px;display:none;">'
                    + '<i id="hnNPSpinner" class="fa fa-spinner"></i></div>'
                );
            }
        }
        return;
    }
    _spinners++;
    if ($btn.length === 0) {
        $('#topbar-container .topbar').prepend(
            '<div id="divHnNPSpinner" title="WME HN NavPoints is currently processing house numbers." style="font-size:20px;background:white;float:left;margin-left:-20px;">'
            + '<i id="hnNPSpinner" class="fa fa-spinner fa-spin"></i></div>'
        );
    }
    else if (!$btn.hasClass('fa-spin')) {
        $btn.addClass('fa-spin');
        $('#divHnNPSpinner').show();
    }
}

async function hnLayerToggled(checked) {
    _HNNavPointsLayer.setVisibility(checked);
    _settings.hnLines = checked;
    saveSettingsToStorage();
    if (checked) {
        if (!_scriptActive)
            await initBackgroundTasks('enable');
        processSegs('hnLayerToggled', W.model.segments.getByAttributes({ hasHNs: true }));
    }
    else if (!_settings.hnNumbers && _scriptActive) {
        initBackgroundTasks('disable');
    }
}

async function hnNumbersLayerToggled(checked) {
    _HNNavPointsNumbersLayer.setVisibility(checked);
    _settings.hnNumbers = checked;
    saveSettingsToStorage();
    if (checked) {
        if (!_scriptActive)
            await initBackgroundTasks('enable');
        processSegs('hnNumbersLayerToggled', W.model.segments.getByAttributes({ hasHNs: true }));
    }
    else if (!_settings.hnLines && _scriptActive) {
        initBackgroundTasks('disable');
    }
}

function observeHNLayer() {
    if (W.editingMediator.attributes.editingHouseNumbers && !_HNLayerObserver.observing) {
        _HNLayerObserver.observe($('div.olLayerDiv.house-numbers-layer')[0], { childList: false, subtree: true, attributes: true });
        _HNLayerObserver.observing = true;
    }
    else if (_HNLayerObserver.observing) {
        _HNLayerObserver.disconnect();
        _HNLayerObserver.observing = false;
    }
    setMarkersEvents();
}

function removeHNLines(hnObj) {
    if (!hnObj)
        return;
    const streetId = W.model.segments.getObjectById(hnObj.getSegmentId()).attributes.primaryStreetID,
        featureId = `HNNavPoints|${streetId}|${hnObj.getNumber()}|${hnObj.getSegmentId()}`,
        linesToRemove = _HNNavPointsLayer.getFeaturesByAttribute('featureId', featureId),
        markerIdx = _HNNavPointsNumbersLayer.markers.map(marker => marker.featureId).indexOf(featureId),
        hnToRemove = (markerIdx > -1) ? _HNNavPointsNumbersLayer.markers[markerIdx] : null;
    if (linesToRemove.length > 0) {
        _HNNavPointsLayer.removeFeatures(linesToRemove);
        if (hnToRemove)
            _HNNavPointsNumbersLayer.removeMarker(hnToRemove);
    }
    setMarkersEvents();
}

function processEvent(evt) {
    if (!evt)
        return;
    if (!_settings.hnLines && !_settings.hnNumbers) {
        if (_scriptActive)
            initBackgroundTasks('disable');
        return;
    }
    if (W.map.getOLMap().getZoom() < _settings.disableBelowZoom) {
        destroyAllHNs();
        return;
    }
    if (evt.type === 'reloadData') {
        destroyAllHNs();
    }
    else if ((evt.length === 1) && (evt[0].type === 'houseNumber')) {
        if (_segmentsToProcess.indexOf(evt[0].getSegmentId()) === -1)
            _segmentsToProcess.push(evt[0].getSegmentId());
    }
    else if (evt.type === 'zoomend') {
        if ((W.map.getOLMap().getZoom() < _settings.disableBelowZoom))
            destroyAllHNs();
        if ((W.map.getOLMap().getZoom() > (_settings.disableBelowZoom - 1)) && (_processedSegments.length === 0))
            processSegs('zoomend', W.model.segments.getByAttributes({ hasHNs: true }), true);
    }
    else if (evt.action && ((evt.type === 'afterundoaction') || (evt.type === 'afteraction'))) {
        if (evt.action._description && (evt.action._description.indexOf('Deleted house number') > -1)) {
            if (evt.type === 'afterundoaction')
                drawHNLines([evt.action.object]);
            else
                removeHNLines(evt.action.object);
            setMarkersEvents();
        }
        else if (evt.action._description && (evt.action._description.indexOf('Updated house number') > -1)) {
            const tempEvt = _.cloneDeep(evt);
            if (evt.type === 'afterundoaction') {
                if (tempEvt.action.newAttributes && tempEvt.action.newAttributes.number)
                    tempEvt.action.attributes.number = tempEvt.action.newAttributes.number;
            }
            else if (evt.type === 'afteraction') {
                if (tempEvt.action.oldAttributes && tempEvt.action.oldAttributes.number)
                    tempEvt.action.attributes.number = tempEvt.action.oldAttributes.number;
            }
            removeHNLines(tempEvt.action.object);
            drawHNLines([evt.action.object]);
            setMarkersEvents();
        }
        else if (evt.action._description && (evt.action._description.indexOf('Added house number') > -1)) {
            if (evt.type === 'afterundoaction')
                removeHNLines(evt.action.houseNumber);
            else
                drawHNLines([evt.action.houseNumber]);
        }
        else if (evt.action._description && (evt.action._description.indexOf('Moved house number') > -1)) {
            drawHNLines([evt.action.newHouseNumber]);
        }
        else if (evt.action && evt.action.houseNumber) {
            drawHNLines((evt.action.newHouseNumber ? [evt.action.newHouseNumber] : [evt.action.houseNumber]));
            setMarkersEvents();
        }
    }
    else if (evt.type === 'click:input') {
        if (evt.object && evt.object.dragging && !evt.object.dragging.last)
            removeHNLines(evt.object.model);
    }
    else if (evt.type === 'delete') {
        removeHNLines(evt.object.model);
    }
}

function setMarkersEvents() {
    if (W.editingMediator.attributes.editingHouseNumbers) {
        checkTimeout({ timeout: 'setMarkerEvents' });
        hideTooltip();
        if (W.map.getOLMap().getLayersByName('houseNumberMarkers')[0].markers.length === 0) {
            _timeouts.setMarkerEvents = window.setTimeout(setMarkersEvents, 50);
            return;
        }
        W.map.getOLMap().getLayersByName('houseNumberMarkers')[0].markers.forEach(marker => {
            marker.events.unregister('click:input', null, processEvent);
            marker.events.unregister('delete', null, processEvent);
            marker.events.on({ 'click:input': processEvent, delete: processEvent });
        });
    }
    else if (W.map.getOLMap().getLayersByName('houseNumberMarkers').length > 0) {
        W.map.getOLMap().getLayersByName('houseNumberMarkers')[0].markers.forEach(marker => {
            marker.events.unregister('click:input', null, processEvent);
            marker.events.unregister('delete', null, processEvent);
        });
        processSegs('exithousenumbers', W.model.segments.getByIds(_segmentsToProcess), true);
        _segmentsToProcess = [];
        _HNNavPointsLayer.setZIndex(1000);
        _HNNavPointsNumbersLayer.setZIndex(1000);
    }
}

function drawHNLines(houseNumberArr) {
    if (houseNumberArr.length === 0)
        return;
    const lineFeatures = [],
        svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
        svgText = document.createElementNS('http://www.w3.org/2000/svg', 'text'),
        invokeTooltip = evt => { showTooltip(evt); };
    svg.setAttribute('xlink', 'http://www.w3.org/1999/xlink');
    svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
    svg.setAttribute('viewBox', '0 0 40 10');
    svgText.setAttribute('text-anchor', 'middle');
    svgText.setAttribute('x', '50%');
    svgText.setAttribute('y', '70%');
    for (let i = 0; i < houseNumberArr.length; i++) {
        const hnObj = houseNumberArr[i],
            seg = W.model.segments.objects[hnObj.getSegmentId()];
        if (seg) {
            const streetId = seg.attributes.primaryStreetID,
                featureId = `HNNavPoints|${streetId}|${hnObj.getNumber()}|${hnObj.getSegmentId()}`,
                markerIdx = _HNNavPointsNumbersLayer.markers.map(marker => marker.featureId).indexOf(featureId),
                hnToRemove = (markerIdx > -1) ? _HNNavPointsNumbersLayer.markers[markerIdx] : null;
            if (hnToRemove)
                _HNNavPointsNumbersLayer.removeMarker(hnToRemove);
            _HNNavPointsLayer.removeFeatures(_HNNavPointsLayer.getFeaturesByAttribute('featureId', featureId));
            const p1 = new OpenLayers.Geometry.Point(hnObj.getFractionPoint().x, hnObj.getFractionPoint().y),
                p2 = new OpenLayers.Geometry.Point(hnObj.getGeometry().x, hnObj.getGeometry().y),
                // eslint-disable-next-line no-nested-ternary
                strokeColor = (hnObj.isForced()
                    ? (!hnObj.getUpdatedBy()) ? 'red' : 'orange'
                    : (!hnObj.getUpdatedBy()) ? 'yellow' : 'white'
                );
            let lineString = new OpenLayers.Geometry.LineString([p1, p2]),
                lineFeature = new OpenLayers.Feature.Vector(
                    lineString,
                    { streetId, segmentId: hnObj.getSegmentId(), featureId },
                    {
                        strokeWidth: 4, strokeColor: 'black', strokeOpacity: 0.5, strokeDashstyle: 'dash', strokeDashArray: '8, 8'
                    }
                );
            lineFeatures.push(lineFeature);
            lineString = new OpenLayers.Geometry.LineString([p1, p2]);
            lineFeature = new OpenLayers.Feature.Vector(
                lineString,
                { streetId, segmentId: hnObj.getSegmentId(), featureId },
                {
                    strokeWidth: 2, strokeColor, strokeOpacity: 1, strokeDashstyle: 'dash', strokeDashArray: '8, 8'
                }
            );
            lineFeatures.push(lineFeature);
            svg.setAttribute('style', `text-shadow:0 0 3px ${strokeColor},0 0 3px ${strokeColor},0 0 3px ${strokeColor},0 0 3px ${strokeColor},0 0 3px ${strokeColor},0 0 3px ${strokeColor},0 0 3px ${strokeColor},0 0 3px ${strokeColor},0 0 3px ${strokeColor},0 0 3px ${strokeColor};font-size:12px;font-weight:bold;font-family:Arial Black, monospace;`);
            svgText.textContent = hnObj.getNumber();
            svg.innerHTML = svgText.outerHTML;
            const svgIcon = new OpenLayers.Icon(`data:image/svg+xml,${svg.outerHTML}`, { w: 40, h: 15 }),
                markerFeature = new OpenLayers.Marker(new OpenLayers.LonLat(p2.x, p2.y), svgIcon);
            markerFeature.events.register('mouseover', null, invokeTooltip);
            markerFeature.events.register('mouseout', null, hideTooltipDelay);
            markerFeature.featureId = featureId;
            markerFeature.segmentId = hnObj.getSegmentId();
            _HNNavPointsNumbersLayer.addMarker(markerFeature);
        }
    }
    if (lineFeatures.length > 0)
        _HNNavPointsLayer.addFeatures(lineFeatures);
}

function destroyAllHNs() {
    _HNNavPointsLayer.destroyFeatures();
    _HNNavPointsNumbersLayer.clearMarkers();
    _processedSegments = [];
    return Promise.resolve();
}

async function processSegs(action, arrSegObjs, processAll = false, retry = 0) {
    /* As of 2020.06.08 (sometime before this date) updatedOn does not get updated when updating house numbers. Looking for a new
     * way to track which segments have been updated most recently to prevent a total refresh of HNs after an event.
     * Changed to using a global to keep track of segmentIds touched during HN edit mode.
     */
    if (!_settings.hnLines && !_settings.hnNumbers) {
        if (_scriptActive)
            initBackgroundTasks('disable');
        return;
    }
    if ((action === 'settingChanged') && (W.map.getOLMap().getZoom() < _settings.disableBelowZoom)) {
        doSpinner(false);
        await destroyAllHNs();
        doSpinner(true);
        return;
    }
    if (!arrSegObjs || (arrSegObjs.length === 0) || (W.map.getOLMap().getZoom() < _settings.disableBelowZoom))
        return;
    doSpinner(false);
    const eg = W.map.getOLMap().getExtent().toGeometry(),
        findObjIndex = (array, fldName, value) => array.map(a => a[fldName]).indexOf(value),
        processError = (err, chunk) => {
            logDebug(`Retry: ${retry}`);
            if (retry < 5)
                processSegs(action, chunk, true, ++retry);
            else
                logError(`Get HNs for ${chunk.length} segments failed. Code: ${err.status} - Text: ${err.responseText}`);
        };
    if (action === 'objectsremoved') {
        if (arrSegObjs && (arrSegObjs.length > 0)) {
            arrSegObjs.forEach(segObj => {
                if (!eg.intersects(segObj.geometry)) {
                    _HNNavPointsLayer.removeFeatures(_HNNavPointsLayer.getFeaturesByAttribute('segmentId', segObj.getID()));
                    _HNNavPointsNumbersLayer.markers.forEach(marker => {
                        if (marker.segmentId === segObj.getID())
                            _HNNavPointsNumbersLayer.removeMarker(marker);
                    });
                    const segIdx = findObjIndex(_processedSegments, 'segId', segObj.getID());
                    if (segIdx > -1)
                        _processedSegments.splice(segIdx, 1);
                }
            });
        }
    }
    else { // action = 'objectsadded', 'zoomend', 'init', 'exithousenumbers', 'hnLayerToggled', 'hnNumbersLayerToggled', 'settingChanged'
        let i = arrSegObjs.length,
            hnObjs = [];
        while (i--) {
            const segIdx = findObjIndex(_processedSegments, 'segId', arrSegObjs[i].getID());
            if (segIdx > -1) {
                if (arrSegObjs[i].getUpdatedOn() > _processedSegments[segIdx].updatedOn)
                    _processedSegments[segIdx].updatedOn = arrSegObjs[i].getUpdatedOn();
                else if (!processAll)
                    arrSegObjs.splice(i, 1);
            }
            else {
                _processedSegments.push({ segId: arrSegObjs[i].getID(), updatedOn: arrSegObjs[i].getUpdatedOn() });
            }
        }
        while (arrSegObjs.length > 0) {
            let jsonData,
                chunk;
            if (retry === 1)
                chunk = arrSegObjs.splice(0, 250);
            else if (retry === 2)
                chunk = arrSegObjs.splice(0, 125);
            else if (retry === 3)
                chunk = arrSegObjs.splice(0, 100);
            else if (retry === 4)
                chunk = arrSegObjs.splice(0, 50);
            else
                chunk = arrSegObjs.splice(0, 500);
            try {
                jsonData = await W.controller.descartesClient.getHouseNumbers(chunk.map(segObj => segObj.getID()));
            }
            catch (error) {
                processError(error, [...chunk]);
            }
            if (jsonData && (jsonData.error === undefined) && (typeof jsonData.segmentHouseNumbers.objects !== 'undefined'))
                hnObjs = hnObjs.concat(jsonData.segmentHouseNumbers.objects);
        }
        if (hnObjs.length > 0)
            drawHNLines(hnObjs);
    }
    doSpinner(true);
}

function segmentsEvent(objSegs) {
    processSegs('objectsadded', objSegs.filter(seg => seg.attributes.hasHNs));
}

function initBackgroundTasks(status) {
    if (status === 'enable') {
        _HNLayerObserver = new MutationObserver(mutationsList => {
            mutationsList.forEach(() => {
                const input = $('div.olLayerDiv.house-numbers-layer div.house-number div.content.active:not(".new") input.number');
                if (input.val() === '')
                    input[0].addEventListener('change', setMarkersEvents);
            });
        });
        W.accelerators.events.on({ reloadData: processEvent });
        $('#overlay-buttons div.reload-button-region').on('click', null, destroyAllHNs);
        W.model.segments.on({ objectsadded: segmentsEvent, objectsremoved: segmentsEvent });
        W.model.segmentHouseNumbers.on({
            objectsadded: processEvent,
            objectsremoved: processEvent,
            objectschanged: processEvent,
            'objectschanged-id': processEvent,
            'objects-state-deleted': processEvent
        });
        W.editingMediator.on({ 'change:editingHouseNumbers': observeHNLayer });
        W.map.events.on({ zoomend: processEvent });
        WazeWrap.Events.register('afterundoaction', this, processEvent);
        WazeWrap.Events.register('afteraction', this, processEvent);
        _timeouts.checkZIndex = window.setInterval(() => {
            if (`${_HNNavPointsNumbersLayer.div.style.zIndex}` !== '1000')
                _HNNavPointsNumbersLayer.setZIndex(1000);
        }, 1000);
        _scriptActive = true;
    }
    else if (status === 'disable') {
        _HNLayerObserver = undefined;
        W.accelerators.events.on('reloadData', null, processEvent);
        $('#overlay-buttons div.reload-button-region').off('click', null, destroyAllHNs);
        W.model.segments.off({ objectsadded: segmentsEvent, objectsremoved: segmentsEvent });
        W.model.segmentHouseNumbers.off({
            objectsadded: processEvent,
            objectsremoved: processEvent,
            objectschanged: processEvent,
            'objectschanged-id': processEvent,
            'objects-state-deleted': processEvent
        });
        W.editingMediator.off({ 'change:editingHouseNumbers': observeHNLayer });
        W.map.events.unregister('zoomend', null, processEvent);
        WazeWrap.Events.unregister('afterundoaction', this, processEvent);
        WazeWrap.Events.unregister('afteraction', this, processEvent);
        window.clearInterval(_timeouts.checkZIndex);
        _timeouts.checkZIndex = undefined;
        _scriptActive = false;
    }
    return Promise.resolve();
}

function enterHNEditMode(evt) {
    if (evt && evt.data && evt.data.segment) {
        if (evt.data.moveMap)
            W.map.setCenter(new OpenLayers.LonLat(evt.data.segment.getCenter().x, evt.data.segment.getCenter().y), W.map.getOLMap().getZoom());
        W.selectionManager.setSelectedModels(evt.data.segment);
        $('#segment-edit-general .edit-house-numbers').click();
    }
}

function showTooltip(evt) {
    if ((W.map.getOLMap().getZoom() < 6) || (W.map.getOLMap().getLayersByName('houseNumberMarkers').length > 0))
        return;
    if (evt && evt.object && evt.object.featureId) {
        checkTooltip();
        const featureArr = evt.object.featureId.split('|'),
            streetId = featureArr[1],
            hnNumber = featureArr[2],
            segmentId = featureArr[3];
        if (_popup.inUse && (_popup.hnNumber === hnNumber) && (_popup.streetId === streetId))
            return;
        const street = W.model.streets.getObjectById(streetId),
            segment = W.model.segments.getObjectById(segmentId),
            popupPixel = W.map.getOLMap().getPixelFromLonLat(evt.object.lonlat),
            htmlOut = ''
                + '<div class="tippy-tooltip light-border-theme" id="hnNavPointsTooltipDiv-tooltip" data-size="large" data-animation="shift-away" data-state="visible"'
                + '     data-interactive="" style="transition-duration:325ms; top:0px;">'
                + ' <div class="tippy-arrow" id="hnNavPointsTooltipDiv-arrow" style="left:83px;"></div>'
                + ' <div class="tippy-content" id="hnNavPointsTooltipDiv-content" data-state="visible" style="transition-duration: 325ms;">'
                + '     <div>'
                + '         <div class="house-number-marker-tooltip">'
                + `             <div class="title">${(hnNumber > 0 ? `${hnNumber} ` : '')}${(street ? street.name : '')}</div>`
                + '             <div class="edit-button fa fa-pencil" id="hnNavPointsTooltipDiv-edit"></div>'
                + '         </div>'
                + '     </div>'
                + ' </div>'
                + '</div>';
        _$hnNavPointsTooltipDiv.html(htmlOut);
        popupPixel.origX = popupPixel.x;
        const popupWidthHalf = (_$hnNavPointsTooltipDiv.width() / 2);
        let arrowOffset = (popupWidthHalf - 15),
            xPlacement = 'top',
            moveMap = false;
        popupPixel.x = ((popupPixel.x - popupWidthHalf) > 0) ? (popupPixel.x - popupWidthHalf) : 10;
        if (popupPixel.x === 10)
            arrowOffset = popupPixel.origX - 22;
        if ((popupPixel.x + (popupWidthHalf * 2)) > $('#map')[0].clientWidth) {
            popupPixel.x = (popupPixel.origX - _$hnNavPointsTooltipDiv.width() + 8);
            arrowOffset = (_$hnNavPointsTooltipDiv.width() - 30);
            moveMap = true;
        }
        if (popupPixel.y - _$hnNavPointsTooltipDiv.height() < 0) {
            popupPixel.y += 10;
            xPlacement = 'bottom';
        }
        else {
            popupPixel.y -= (_$hnNavPointsTooltipDiv.height() + 4);
        }
        $('#hnNavPointsTooltipDiv-edit').on('click', { segment, moveMap }, enterHNEditMode);
        _$hnNavPointsTooltipDiv.css({ transform: `translate3d(${Math.round(popupPixel.x)}px, ${Math.round(popupPixel.y)}px, 0px)` });
        $('#hnNavPointsTooltipDiv-arrow').css('left', Math.round(arrowOffset));
        _$hnNavPointsTooltipDiv.attr('x-placement', xPlacement);
        _$hnNavPointsTooltipDiv.css({ visibility: 'visible' });
        _popup = { streetId, hn_number: hnNumber, inUse: true };
    }
}

function hideTooltip() {
    checkTimeout({ timeout: 'hideTooltip' });
    _$hnNavPointsTooltipDiv.css({ visibility: 'hidden' });
    _$hnNavPointsTooltipDiv.html('');
    _popup = { streetId: -1, hnNumber: -1, inUse: false };
}

function hideTooltipDelay(evt) {
    if (!evt)
        return;
    checkTimeout({ timeout: 'hideTooltip' });
    const parentsArr = (evt.toElement && evt.toElement.offsetParent) ? [evt.toElement.offsetParent, evt.toElement.offsetParent.offSetParent] : [];
    if (evt.toElement && ((parentsArr.indexOf(_HNNavPointsNumbersLayer.div) > -1) || (parentsArr.indexOf(_$hnNavPointsTooltipDiv[0]) > -1)))
        return;
    _timeouts.hideTooltip = window.setTimeout(hideTooltip, 100, evt);
}

function checkTooltip() {
    checkTimeout({ timeout: 'hideTooltip' });
}

function injectOLIcon() {
    logDebug('Injecting OpenLayers.Icon');
    // eslint-disable-next-line
    OpenLayers.Icon=OpenLayers.Class({url:null,size:null,offset:null,calculateOffset:null,imageDiv:null,px:null,initialize:function(a,b,c,d){this.url=a;this.size=b||{w:20,h:20};this.offset=c||{x:-(this.size.w/2),y:-(this.size.h/2)};this.calculateOffset=d;a=OL.Util.createUniqueID("OL_Icon_");this.imageDiv=OL.Util.createAlphaImageDiv(a)},destroy:function(){this.erase();OL.Event.stopObservingElement(this.imageDiv.firstChild);this.imageDiv.innerHTML="";this.imageDiv=null},clone:function(){returnnewOL.Icon(this.url,this.size,this.offset,this.calculateOffset)},setSize:function(a){null!=a&&(this.size=a);this.draw()},setUrl:function(a){null!=a&&(this.url=a);this.draw()},draw:function(a){OL.Util.modifyAlphaImageDiv(this.imageDiv,null,null,this.size,this.url,"absolute");this.moveTo(a);return this.imageDiv},erase:function(){null!=this.imageDiv&&null!=this.imageDiv.parentNode&&OL.Element.remove(this.imageDiv)},setOpacity:function(a){OL.Util.modifyAlphaImageDiv(this.imageDiv,null,null,null,null,null,null,null,a)},moveTo:function(a){null!=a&&(this.px=a);null!=this.imageDiv&&(null==this.px?this.display(!1):(this.calculateOffset&&(this.offset=this.calculateOffset(this.size)),OL.Util.modifyAlphaImageDiv(this.imageDiv,null,{x:this.px.x+this.offset.x,y:this.px.y+this.offset.y})))},display:function(a){this.imageDiv.style.display=a?"":"none"},isDrawn:function(){return this.imageDiv&&this.imageDiv.parentNode&&11!=this.imageDiv.parentNode.nodeType},CLASS_NAME:"OL.Icon"});
}

async function init() {
    log('Initializing.');
    await loadSettingsFromStorage();
    WazeWrap.Interface.AddLayerCheckbox('display', 'HN NavPoints', _settings.hnLines, hnLayerToggled);
    WazeWrap.Interface.AddLayerCheckbox('display', 'HN NavPoints Numbers', _settings.hnNumbers, hnNumbersLayerToggled);

    _HNNavPointsLayer = new OpenLayers.Layer.Vector('HN NavPoints Layer', {
        displayInLayerSwitcher: true,
        uniqueName: '__HNNavPointsLayer'
    });
    _HNNavPointsNumbersLayer = new OpenLayers.Layer.Markers('HN NavPoints Numbers Layer', {
        displayInLayerSwitcher: true,
        uniqueName: '__HNNavPointsNumbersLayer'
    });
    if (!OpenLayers.Icon)
        injectOLIcon();
    W.map.getOLMap().addLayer(_HNNavPointsLayer);
    W.map.getOLMap().addLayer(_HNNavPointsNumbersLayer);
    _HNNavPointsLayer.setVisibility(_settings.hnLines);
    _HNNavPointsNumbersLayer.setVisibility(_settings.hnNumbers);
    _HNNavPointsLayer.setZIndex(500);
    _HNNavPointsNumbersLayer.setZIndex(500);
    window.addEventListener('beforeunload', () => { checkShortcutsChanged(); }, false);
    new WazeWrap.Interface.Shortcut(
        'toggleHNNavPointsShortcut',
        'Toggle HN NavPoints layer',
        'layers',
        'layersToggleHNNavPoints',
        _settings.toggleHNNavPointsShortcut,
        () => { $('#layer-switcher-item_hn_navpoints').click(); },
        null
    ).add();
    new WazeWrap.Interface.Shortcut(
        'toggleHNNavPointsNumbersShortcut',
        'Toggle HN NavPoints Numbers layer',
        'layers',
        'layersToggleHNNavPointsNumbers',
        _settings.toggleHNNavPointsNumbersShortcut,
        () => { $('#layer-switcher-item_hn_navpoints_numbers').click(); },
        null
    ).add();
    $('#sidepanel-prefs').append(() => {
        let htmlOut = '<div style="border-bottom:1px solid black; padding-bottom:10px;';
        if ($('#sidepanel-prefs')[0].lastChild.tagName.search(/HR/gi) > -1) {
            const elmnt = $('#sidepanel-prefs')[0].lastChild;
            elmnt.style.borderTopColor = 'black';
            elmnt.style.color = 'black';
        }
        else {
            htmlOut += 'border-top:1px solid black;';
        }
        htmlOut += '"><h4>WME HN NavPoints</h4>'
            + '<div style="font-size:12px; margin-left:6px;" title="Disable NavPoints and house numbers when zoom level is less than specified number.\r\nMinimum: 4\r\nDefault: 5">'
            + `Disable when zoom level <<input type="text" id="HNNavPoints_disableBelowZoom" style="width:24px; height:20px; margin-left:4px;" value="${_settings.disableBelowZoom}"></input>`
            + '</div>'
            + '<div style="margin:0 10px 0 10px; width:130px; text-align:center; font-size:12px; background:black; font-weight:600;">'
            + ' <div style="text-shadow:0 0 3px white,0 0 3px white,0 0 3px white,0 0 3px white,0 0 3px white,0 0 3px white,0 0 3px white,0 0 3px white,0 0 3px white,0 0 3px white;">Touched</div>'
            + ' <div style="text-shadow:0 0 3px orange,0 0 3px orange,0 0 3px orange,0 0 3px orange,0 0 3px orange,0 0 3px orange,0 0 3px orange,0 0 3px orange,0 0 3px orange,0 0 3px orange;'
            + '     ">Touched forced</div>'
            + ' <div style="text-shadow:0 0 3px yellow,0 0 3px yellow,0 0 3px yellow, 0 0 3px yellow,0 0 3px yellow,0 0 3px yellow,0 0 3px yellow,0 0 3px yellow,0 0 3px yellow,0 0 3px yellow;'
            + '     ">Untouched</div>'
            + ' <div style="text-shadow:0 0 3px red,0 0 3px red,0 0 3px red,0 0 3px red,0 0 3px red,0 0 3px red,0 0 3px red,0 0 3px red,0 0 3px red,0 0 3px red;">Untouched forced</div>'
            + '</div></div>';
        return htmlOut;
    });
    $('#HNNavPoints_disableBelowZoom').on('change', function () {
        const newVal = Math.min(10, Math.max(4, parseInt(this.value)));
        if (newVal !== _settings.disableBelowZoom) {
            if (newVal !== parseInt(this.value))
                this.value = newVal;
            _settings.disableBelowZoom = newVal;
            saveSettingsToStorage();
            if ((W.map.getOLMap().getZoom() < newVal) && (_settings.hnLines || _settings.hnNumbers))
                processSegs('settingChanged', null, true, 0);
            else if (_settings.hnLines || _settings.hnNumbers)
                processSegs('settingChanged', W.model.segments.getByAttributes({ hasHNs: true }), true, 0);
        }
    });
    if (!_$hnNavPointsTooltipDiv) {
        $('#map').append(
            '<div id="hnNavPointsTooltipDiv" class="tippy-popper" role="tooltip" x-placement="top" style="z-index:9999; transition-duration:0ms; position:absolute;'
            + 'will-change:transform; top:0px; left:0px; visibility:none;"></div>'
        );
        _$hnNavPointsTooltipDiv = $('#hnNavPointsTooltipDiv');
        _$hnNavPointsTooltipDiv.on('mouseleave', null, hideTooltipDelay);
        _$hnNavPointsTooltipDiv.on('mouseenter', null, checkTooltip);
    }
    await initBackgroundTasks('enable');
    log(`Fully initialized in ${Math.round(performance.now() - LOAD_BEGIN_TIME)} ms.`);
    showScriptInfoAlert();
    if (_scriptActive)
        processSegs('init', W.model.segments.getByAttributes({ hasHNs: true }));
    setTimeout(checkShortcutsChanged, 10000);
}

function bootstrap(tries) {
    if (W && W.map && W.model && $ && WazeWrap.Ready) {
        checkTimeout({ timeout: 'bootstrap' });
        log('Bootstrapping.');
        init();
    }
    else if (tries < 1000) {
        logDebug(`Bootstrap failed. Retrying ${tries} of 1000`);
        _timeouts.bootstrap = window.setTimeout(bootstrap, 200, ++tries);
    }
    else {
        logError('Bootstrap timed out waiting for WME to become ready.');
    }
}

bootstrap(1);

QingJ © 2025

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