WME Address Point Helper

Creates point with same address

目前为 2020-08-22 提交的版本。查看 最新版本

// ==UserScript==
// @name           WME Address Point Helper
// @author         Andrei Pavlenko
// @version        1.12.4
// @include 	   /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor.*$/
// @exclude        https://www.waze.com/user/*editor/*
// @exclude        https://www.waze.com/*/user/*editor/*
// @grant          none
// @description    Creates point with same address
// @namespace https://gf.qytechs.cn/users/182795
// @require https://gf.qytechs.cn/scripts/24851-wazewrap/code/WazeWrap.js
// @require https://gf.qytechs.cn/scripts/16071-wme-keyboard-shortcuts/code/WME%20Keyboard%20Shortcuts.js
// ==/UserScript==

var locale;

var settings = {
    addNavigationPoint: false,
    inheritNavigationPoint: false,
    autoSetHNToName: false,
    noDuplicates: false
};

var translations = {
    'en': {
        createPoint: 'Create POI',
        createResidential: 'Create residential',
        addEntryPoint: 'Add entry point',
        inheritEntryPoint: 'Inherit parent\'s landmark entry point',
        copyHNToName: 'Copy house number into name',
        noDuplicates: 'Do not create duplicates'
    },
    'uk': {
        createPoint: 'Створити POI точку',
        createResidential: 'Створити АТ',
        addEntryPoint: 'Додавати точку в\'їзду',
        inheritEntryPoint: 'Наслідувати точку в\'їзду батьківського ПОІ',
        copyHNToName: 'Копіювати номер будинку в назву',
        noDuplicates: 'Не створювати дублікатів'
    },
    'ru': {
        createPoint: 'Создать POI точку',
        createResidential: 'Создать АТ',
        addEntryPoint: 'Создавать точку въезда',
        inheritEntryPoint: 'Наследовать точку въезда родительского ПОИ',
        copyHNToName: 'Копировать номер дома в название',
        noDuplicates: 'Не создавать дубликатов'
    }
};

var hnValidators = {
    'Ukraine': hn => {
        let valid = false
        try {
            valid = /^\d+[А-ЯЇІЄ]{0,3}$/i.test(hn)
        } catch (e) { /* Do nothing */ }
        return valid;
    },
    'default': hn => {
        return /.+/.test(hn);
    }
};

(function() {
    setTimeout(init, 1000);
})();

function init() {
    try {
        if (
            document.getElementById('sidebarContent') !== null &&
            document.getElementById('user-tabs') !== null && WazeWrap.Ready
        ) {
            initLocale();
            createScriptTab();
            initSettings();
            registerKeyboardShortcuts();
            registerEventListeners();
        } else {
            setTimeout(init, 1000);
            return;
        }
    } catch (err) {
        setTimeout(1000, init);
        return;
    }
}

function createScriptTab() {
    const html = `
    <div id="sidepanel-aph">
        <p>WME Address Point Helper 📍</p>
        <div class="controls-container"><input type="checkbox" id="aph-add-navigation-point"><label for="aph-add-navigation-point">${translate('addEntryPoint')}</label></div>
        <div class="controls-container"><input type="checkbox" id="aph-inherit-navigation-point"><label for="aph-inherit-navigation-point">${translate('inheritEntryPoint')}</label></div>
        <div class="controls-container"><input type="checkbox" id="aph-set-name"><label for="aph-set-name">${translate('copyHNToName')}</label></div>
        <div class="controls-container"><input type="checkbox" id="aph-no-duplicates"><label for="aph-no-duplicates">${translate('noDuplicates')}</label></div>
    </div>
    `;

    new WazeWrap.Interface.Tab('APH📍', html);
    var APHAddNavigationPoint = $('#aph-add-navigation-point');
    var APHInheritNavigationPoint = $('#aph-inherit-navigation-point');
    var APHSetName = $('#aph-set-name');
    var APHNoDuplicates = $('#aph-no-duplicates')
    APHAddNavigationPoint.change(() => {
        settings.addNavigationPoint = APHAddNavigationPoint.prop('checked');
    });
    APHInheritNavigationPoint.change(() => {
        settings.inheritNavigationPoint = APHInheritNavigationPoint.prop('checked');
    });
    APHSetName.change(() => {
        settings.autoSetHNToName = APHSetName.prop('checked');
    });
    APHNoDuplicates.change(() => {
        settings.noDuplicates = APHNoDuplicates.prop('checked');
    });
}

function initSettings() {
    var savedSettings = localStorage.getItem('aph-settings');
    if (savedSettings) {
        settings = JSON.parse(savedSettings);
    }
    setChecked('aph-add-navigation-point', settings.addNavigationPoint);
    setChecked('aph-inherit-navigation-point', settings.inheritNavigationPoint);
    setChecked('aph-set-name', settings.autoSetHNToName);
    setChecked('aph-no-duplicates', settings.noDuplicates);
    window.addEventListener('beforeunload', saveSettings);
}

function initLocale() {
    locale = I18n.currentLocale();
}

function translate(keyword) {
    let translation = translations[locale] || translations['en'];
    return translation[keyword] || translations['en'][keyword] || 'Unknown';
}

function saveSettings() {
    if (localStorage) {
        localStorage.setItem('aph-settings', JSON.stringify(settings));
    }
}

function isValidSelection() {
    if (!W.selectionManager.hasSelectedFeatures()) return false;
    if (W.selectionManager.getSelectedFeatures().length !== 1) return false;
    if (W.selectionManager.getSelectedFeatures()[0].model.type !== 'venue') return false;
    return true;
}

function showButtons() {
    if (!isValidSelection()) return;

    var buttons = `
        <div id="aph-buttons" style="margin-top: 8px">
        <div class="btn-toolbar">
        <input type="button" id="aph-create-point" class="aph-btn btn btn-default" value="${translate('createPoint')}">
        <input type="button" id="aph-create-residential" class="aph-btn btn btn-default" value="${translate('createResidential')}">
        </div>
        </div>
    `;

    if (!$('#aph-buttons').length) {
      $('#venue-edit-general .address-edit').append(buttons);
      $('#aph-create-point').click(createPoint);
      $('#aph-create-residential').click(createResidential);
    }

    const valid = validateSelectedPoiHN();
    $('#aph-create-point').prop('disabled', !valid.validForPoint);
    $('#aph-create-residential').prop('disabled', !valid.vadlidForResidential);
}

function validateSelectedPoiHN() {
    let result = {
      validForPoint: false,
      vadlidForResidential: false
    };
    let country = W.model.getTopCountry().name;
    let validator = hnValidators[country] || hnValidators['default'];
    let selectedPoiHN = getSelectedLandmarkAddress().attributes.houseNumber;
    result.vadlidForResidential = validator(selectedPoiHN);
    result.validForPoint = /\d+/.test(selectedPoiHN);
    return result;
}

function createResidential() {
    isValidSelection() && createPoint({isResidential: true});
}

function createPoint({isResidential = false} = {}) {
    if (!isValidSelection()) return;
    var LandmarkFeature = require('Waze/Feature/Vector/Landmark');
    var AddLandmarkAction = require('Waze/Action/AddLandmark');
    var UpdateFeatureAddressAction = require('Waze/Action/UpdateFeatureAddress');
    var NewPoint = new LandmarkFeature();
    var { lat, lon } = getPointCoordinates();
    var address = getSelectedLandmarkAddress();
    var lockRank = getPointLockRank();
    var pointGeometry = new OpenLayers.Geometry.Point(lon, lat);

    NewPoint.geometry = pointGeometry;
    NewPoint.attributes.categories.push('OTHER');
    NewPoint.attributes.lockRank = lockRank;
    NewPoint.attributes.residential = isResidential;

    if (settings.addNavigationPoint) {
        var entryPoint, parentEntryPoint = W.selectionManager.getSelectedFeatures()[0].model.attributes.entryExitPoints[0];
        if (settings.inheritNavigationPoint && parentEntryPoint !== undefined) {
            entryPoint = new NavigationPoint(parentEntryPoint.getPoint());
        } else {
            entryPoint = new NavigationPoint(pointGeometry.clone());
        }
        NewPoint.attributes.entryExitPoints.push(entryPoint);
    }

    if (!!address.attributes.houseNumber) {
        NewPoint.attributes.name = address.attributes.houseNumber;
        NewPoint.attributes.houseNumber = address.attributes.houseNumber;
    }

    var newAddressAttributes = {
        streetName: address.getStreetName(),
        emptyStreet: false,
        cityName: address.getCityName(),
        emptyCity: false,
        stateID: address.getState().getID(),
        countryID: address.getCountry().getID(),
    };

    if (settings.noDuplicates && hasDuplicate(NewPoint, newAddressAttributes)) {
      console.log("This point already exists.");
      return;
    }

    W.selectionManager.unselectAll();
    var addedLandmark = new AddLandmarkAction(NewPoint);
    W.model.actionManager.add(addedLandmark);
    W.model.actionManager.add(new UpdateFeatureAddressAction(NewPoint, newAddressAttributes));
    W.selectionManager.setSelectedModels([addedLandmark.venue]);
}

function hasDuplicate(poi, addr) {
    const venues = W.model.venues.objects;
    for (let key in venues) {
        if (!venues.hasOwnProperty(key)) continue;
        const currentVenue = venues[key];
        const currentAddress = currentVenue.getAddress();
        let equalNames = true;
        if (!!currentVenue.attributes.name && !!poi.attributes.name) {
            if (currentVenue.attributes.name != poi.attributes.name) {
              equalNames = false;
            }
        }
        if (
            equalNames
            && poi.attributes.houseNumber == currentVenue.attributes.houseNumber
            && poi.attributes.residential == currentVenue.attributes.residential
            && addr.streetName == currentAddress.getStreetName()
            && addr.cityName == currentAddress.getCityName()
            && addr.countryID == currentAddress.getCountry().getID()
        ) return true;
    }
    return false;
}

// Высчитываем координаты центра выбраного лэндмарка
function getPointCoordinates() {
    const selectedLandmarkGeometry = W.selectionManager.getSelectedFeatures()[0].geometry;

    var coords;
    if (/polygon/i.test(selectedLandmarkGeometry.id)) {
        var polygonCenteroid = selectedLandmarkGeometry.components[0].getCentroid();
        var geometryComponents = selectedLandmarkGeometry.components[0].components;
        var flatComponentsCoords = [];
        geometryComponents.forEach(c => flatComponentsCoords.push(c.x, c.y));
        var interiorPoint = getInteriorPointOfArray(
            flatComponentsCoords,
            2, [polygonCenteroid.x, polygonCenteroid.y]
        );

        coords = {
            lon: interiorPoint[0],
            lat: interiorPoint[1]
        };
    } else {
        coords = {
            lon: selectedLandmarkGeometry.x,
            lat: selectedLandmarkGeometry.y
        };
    }

    coords = addRandomOffsetToCoords(coords);
    return coords;
}

function addRandomOffsetToCoords(coords) {
    var { lat, lon } = coords;
    lat += Math.random() * 2 + 1;
    lon += Math.random() * 2 + 1;
    return { lat, lon };
}

function getSelectedLandmarkAddress() {
    const selectedLandmark = W.selectionManager.getSelectedFeatures()[0];
    const address = selectedLandmark.model.getAddress();
    return address;
}

function getPointLockRank() {
    const selectedLandmark = W.selectionManager.getSelectedFeatures()[0];
    const userRank = W.loginManager.user.rank;
    const parentFeatureLockRank = selectedLandmark.model.getLockRank();

    if (userRank >= parentFeatureLockRank) {
        return parentFeatureLockRank;
    } else if (userRank >= 1) {
        return 1;
    } else {
        return 0;
    }
}

function setChecked(checkboxId, checked) {
    $('#' + checkboxId).prop('checked', checked);
}

function registerKeyboardShortcuts() {
    const scriptName = 'AddressPointHelper';

    WMEKSRegisterKeyboardShortcut(scriptName, 'Address Point Helper', 'APHCreatePoint', translate('createPoint'), createPoint, '-1');
    WMEKSRegisterKeyboardShortcut(scriptName, 'Address Point Helper', 'APHCreateResidential', translate('createResidential'), createResidential, '-1');
    WMEKSLoadKeyboardShortcuts(scriptName);

    window.addEventListener('beforeunload', function() {
        WMEKSSaveKeyboardShortcuts(scriptName);
    }, false);
}

function registerEventListeners() {
    let UpdateObjectAction = require("Waze/Action/UpdateObject")

    W.model.actionManager.events.register("afteraction", null, action => {
        // Задаем номер дома в название, если нужно. Пока не нашел более лаконичного способа определить что
        // произошло именно изменение адреса. Можно тестить регуляркой поле _description, но будут проблемы с
        // нюансами содержания этого поля на разных языках
        if (settings.autoSetHNToName) {
            try {
                let subAction = action.action.subActions[0];
                let houseNumber = subAction.attributes.houseNumber;
                let feature = subAction.feature;
                if (feature.attributes.categories.includes('OTHER') && feature.attributes.name === "") {
                    W.model.actionManager.add(new UpdateObjectAction(feature, { name: houseNumber }));
                }
            } catch (e) { /* Do nothing */ }
        }
    });

    W.selectionManager.events.register("selectionchanged", null, showButtons);
    W.model.actionManager.events.register("afteraction", null, showButtons);
    setTimeout(wrapSelectionHandlers, 2000);
}

function wrapSelectionHandlers() {
    // POI Helper trows error and breaks event handlers execution
    // Wrap each handler in try/catch to fix this
    let wrappedHandlers = W.selectionManager.events.listeners.selectionchanged.map(listener => {
        return {
          obj: listener.obj,
          func: function() {
            try {
              listener.func.apply(this, arguments)
            } catch(error) {
              console.error(error)
            }
          }
        };
    })
    W.selectionManager.events.listeners.selectionchanged = wrappedHandlers;
}

/*
* https://github.com/openlayers/openlayers
*/
function getInteriorPointOfArray(flatCoordinates, stride, flatCenters) {
    let offset = 0;
    let flatCentersOffset = 0;
    let ends = [flatCoordinates.length];
    let i, ii, x, x1, x2, y1, y2;
    const y = flatCenters[flatCentersOffset + 1];
    const intersections = [];
    // Calculate intersections with the horizontal line
    for (let r = 0, rr = ends.length; r < rr; ++r) {
        const end = ends[r];
        x1 = flatCoordinates[end - stride];
        y1 = flatCoordinates[end - stride + 1];
        for (i = offset; i < end; i += stride) {
            x2 = flatCoordinates[i];
            y2 = flatCoordinates[i + 1];
            if ((y <= y1 && y2 <= y) || (y1 <= y && y <= y2)) {
                x = (y - y1) / (y2 - y1) * (x2 - x1) + x1;
                intersections.push(x);
            }
            x1 = x2;
            y1 = y2;
        }
    }
    // Find the longest segment of the horizontal line that has its center point
    // inside the linear ring.
    let pointX = NaN;
    let maxSegmentLength = -Infinity;
    intersections.sort(numberSafeCompareFunction);
    x1 = intersections[0];
    for (i = 1, ii = intersections.length; i < ii; ++i) {
        x2 = intersections[i];
        const segmentLength = Math.abs(x2 - x1);
        if (segmentLength > maxSegmentLength) {
            x = (x1 + x2) / 2;
            if (linearRingsContainsXY(flatCoordinates, offset, ends, stride, x, y)) {
                pointX = x;
                maxSegmentLength = segmentLength;
            }
        }
        x1 = x2;
    }
    if (isNaN(pointX)) {
        // There is no horizontal line that has its center point inside the linear
        // ring.  Use the center of the the linear ring's extent.
        pointX = flatCenters[flatCentersOffset];
    }

    return [pointX, y, maxSegmentLength];
}

function numberSafeCompareFunction(a, b) {
    return a > b ? 1 : a < b ? -1 : 0;
}

function linearRingContainsXY(flatCoordinates, offset, end, stride, x, y) {
    // http://geomalgorithms.com/a03-_inclusion.html
    // Copyright 2000 softSurfer, 2012 Dan Sunday
    // This code may be freely used and modified for any purpose
    // providing that this copyright notice is included with it.
    // SoftSurfer makes no warranty for this code, and cannot be held
    // liable for any real or imagined damage resulting from its use.
    // Users of this code must verify correctness for their application.
    let wn = 0;
    let x1 = flatCoordinates[end - stride];
    let y1 = flatCoordinates[end - stride + 1];
    for (; offset < end; offset += stride) {
      const x2 = flatCoordinates[offset];
      const y2 = flatCoordinates[offset + 1];
      if (y1 <= y) {
        if (y2 > y && ((x2 - x1) * (y - y1)) - ((x - x1) * (y2 - y1)) > 0) {
          wn++;
        }
      } else if (y2 <= y && ((x2 - x1) * (y - y1)) - ((x - x1) * (y2 - y1)) < 0) {
        wn--;
      }
      x1 = x2;
      y1 = y2;
    }
    return wn !== 0;
}

function linearRingsContainsXY(flatCoordinates, offset, ends, stride, x, y) {
    if (ends.length === 0) {
      return false;
    }
    if (!linearRingContainsXY(flatCoordinates, offset, ends[0], stride, x, y)) {
      return false;
    }
    for (let i = 1, ii = ends.length; i < ii; ++i) {
      if (linearRingContainsXY(flatCoordinates, ends[i - 1], ends[i], stride, x, y)) {
        return false;
      }
    }
    return true;
}
/* **************************************** */

var _createClass=function(){function a(b,c){for(var f,d=0;d<c.length;d++)f=c[d],f.enumerable=f.enumerable||!1,f.configurable=!0,"value"in f&&(f.writable=!0),Object.defineProperty(b,f.key,f)}return function(b,c,d){return c&&a(b.prototype,c),d&&a(b,d),b}}();function _classCallCheck(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}var NavigationPoint=function(){function a(b){_classCallCheck(this,a),this._point=b.clone(),this._entry=!0,this._exit=!0,this._isPrimary=!0,this._name=""}return _createClass(a,[{key:"with",value:function _with(){var b=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{};return null==b.point&&(b.point=this.toJSON().point),new this.constructor((this.toJSON().point,b.point))}},{key:"getPoint",value:function getPoint(){return this._point.clone()}},{key:"getEntry",value:function getEntry(){return this._entry}},{key:"getExit",value:function getExit(){return this._exit}},{key:"getName",value:function getName(){return this._name}},{key:"isPrimary",value:function isPrimary(){return this._isPrimary}},{key:"toJSON",value:function toJSON(){return{point:this._point,entry:this._entry,exit:this._exit,primary:this._isPrimary,name:this._name}}},{key:"clone",value:function clone(){return this.with()}}]),a}();

QingJ © 2025

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