WME E95

Setup road properties in one click

当前为 2019-08-28 提交的版本,查看 最新版本

// ==UserScript==
// @name         WME E95
// @version      0.4.16
// @description  Setup road properties in one click
// @author       Anton Shevchuk
// @license      MIT License
// @grant        none
// @include      https://www.waze.com/editor*
// @include      https://www.waze.com/*/editor*
// @include      https://beta.waze.com/editor*
// @include      https://beta.waze.com/*/editor*
// @exclude      https://www.waze.com/user/editor*
// @exclude      https://beta.waze.com/user/editor*
// @icon         data:image/png;base64,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
// @supportURL   https://github.com/AntonShevchuk/wme-e95/issues
// @namespace    https://gf.qytechs.cn/users/227648
// ==/UserScript==
/* jshint esversion: 6 */
/* global require, window, W, I18n */

(function ($) {
    'use strict';

    // Script name, uses as unique index
    const NAME = 'E95';

    // Translations
    const LOCALE = I18n.currentLocale();
    const translation = {
        'en': {
            title: 'Quick Properties'
        },
        'uk': {
            title: 'Швидкі налаштування',
        },
        'ru': {
            title: 'Быстрые настройки'
        }
    };
    // Road Types
    //   I18n.translations.uk.segment.road_types
    const types = {
        street: 1,
        primary: 2,
        // ...
        offroad: 8,
        // ...
        private: 17,
        // ...
        parking: 20,
    };
    // Road colors by type
    const colors = {
        '1': '#ffffeb',
        '2': '#f0ea58',
        // ...
        '8': '#867342',
        // ...
        '17': '#beba6c',
        // ...
        '20': '#ababab'
    };
    // Road Flags
    //   for setup flags use binary operators
    //   e.g. flags.tunnel | flags.headlights
    const flags = {
        tunnel: 0b00000001,
        // ???     : 0b00000010,
        // ???     : 0b00000100,
        // ???     : 0b00001000,
        unpaved: 0b00010000,
        headlights: 0b00100000,
    };
    // Buttons:
    //   title - for buttons
    //   shortcut - keys for shortcuts, by default is Alt + (1..9)
    //   detectCity - try to detect city name by closures segments
    //   clearCity - clear city name
    //   attributes - native settings for model object
    // TODO:
    //   – check permissions for user level lower than 2
    const buttons = {
        A: {
            title: 'PLR',
            shortcut: 'A+49',
            callback: process,
            detectCity: true,
            attributes: {
                fwdMaxSpeed: 5,
                revMaxSpeed: 5,
                fwdMaxSpeedUnverified: false,
                revMaxSpeedUnverified: false,
                roadType: types.parking,
                flags: 0,
                lockRank: 0,
            }
        },
        B: {
            title: 'Pr20',
            shortcut: 'A+50',
            callback: process,
            detectCity: true,
            attributes: {
                fwdMaxSpeed: 20,
                revMaxSpeed: 20,
                fwdMaxSpeedUnverified: false,
                revMaxSpeedUnverified: false,
                roadType: types.private,
                flags: 0,
                lockRank: 0,
            }
        },
        C: {
            title: 'Pr50',
            shortcut: 'A+51',
            callback: process,
            detectCity: true,
            attributes: {
                fwdMaxSpeed: 50,
                revMaxSpeed: 50,
                fwdMaxSpeedUnverified: false,
                revMaxSpeedUnverified: false,
                roadType: types.private,
                flags: 0,
                lockRank: 0,
            }
        },
        D: {
            title: 'St50',
            shortcut: 'A+52',
            callback: process,
            detectCity: true,
            attributes: {
                fwdMaxSpeed: 50,
                revMaxSpeed: 50,
                roadType: types.street,
                flags: 0,
                lockRank: 0,
            }
        },
        E: {
            title: 'PS50',
            shortcut: 'A+53',
            callback: process,
            detectCity: true,
            attributes: {
                fwdMaxSpeed: 50,
                revMaxSpeed: 50,
                fwdMaxSpeedUnverified: false,
                revMaxSpeedUnverified: false,
                roadType: types.primary,
                flags: 0,
                lockRank: 1,
            }
        },
        F: {
            title: 'OR',
            shortcut: 'A+54',
            callback: process,
            clearCity: true,
            attributes: {
                fwdMaxSpeed: 90,
                revMaxSpeed: 90,
                fwdMaxSpeedUnverified: false,
                revMaxSpeedUnverified: false,
                roadType: types.offroad,
                lockRank: 0,
            }
        },
        G: {
            title: 'Pr90',
            shortcut: 'A+55',
            callback: process,
            clearCity: true,
            attributes: {
                fwdMaxSpeed: 90,
                revMaxSpeed: 90,
                fwdMaxSpeedUnverified: false,
                revMaxSpeedUnverified: false,
                roadType: types.private,
                lockRank: 0,
            }
        },
        H: {
            title: 'St90',
            shortcut: 'A+56',
            callback: process,
            clearCity: true,
            attributes: {
                fwdMaxSpeed: 90,
                revMaxSpeed: 90,
                fwdMaxSpeedUnverified: false,
                revMaxSpeedUnverified: false,
                roadType: types.street,
                lockRank: 0,
            }
        },
        I: {
            title: 'PS90',
            shortcut: 'A+57',
            callback: process,
            clearCity: true,
            attributes: {
                fwdMaxSpeed: 90,
                revMaxSpeed: 90,
                fwdMaxSpeedUnverified: false,
                revMaxSpeedUnverified: false,
                roadType: types.primary,
                lockRank: 1,
            }
        },
        J: {
            title: 'U',
            shortcut: 'A+U',
            callback: function (button) {
                console.log(button);
                console.log(W.selectionManager.getSegmentSelection());
            }
        }
    };
    // Regions settings, will be merged with default values
    // Default values is actual for Ukraine
    const speed = {
        '20': {
            fwdMaxSpeed: 20,
            revMaxSpeed: 20,
        },
        '60': {
            fwdMaxSpeed: 60,
            revMaxSpeed: 60,
        }
    };
    const preset = {
        headlights: {
            attributes: {
                flags: flags.headlights
            }
        },
        pr60: {
            title: 'Pr60',
            attributes: speed["60"]
        },
        st60: {
            title: 'St60',
            attributes: speed["60"]
        },
        ps60: {
            title: 'PS60',
            attributes: speed["60"]
        },
    };
    const region = {
        // Belarus
        37: {
            A: {
                attributes: speed["20"]
            },
            C: preset.pr60,
            D: preset.st60,
            E: preset.ps60,
            F: {
                title: 'SUP',
                attributes: {
                    roadType: types.street,
                    flags: flags.unpaved,
                }
            }
        },
        // Russian Federation
        186: {
            C: preset.pr60,
            D: preset.st60,
            E: preset.ps60,
        },
        // Ukraine
        232: {
            F: preset.headlights,
            G: preset.headlights,
            H: preset.headlights,
            I: preset.headlights,
        },
    };

    // Require Waze API
    let WazeActionUpdateObject = require('Waze/Action/UpdateObject');
    let WazeActionUpdateFeatureAddress = require('Waze/Action/UpdateFeatureAddress');

    // Get Button settings
    function getButtonConfig(index) {
        let btn = {};
        let abbr = W.model.getTopCountry().getID();
        if (region[abbr] && region[abbr][index]) {
            // Merge default settings with region settings
            $.extend(true, btn, buttons[index], region[abbr][index]);
        } else {
            btn = buttons[index];
        }
        return btn;
    }

    // Update segment attributes
    function setupRoad(segment, settings, options = []) {
        let addr = segment.getAddress().attributes;
        // Change address
        let address = {
            countryID: addr.country ? addr.country.id : W.model.getTopCountry().getID(),
            stateID: addr.state ? addr.state.id : W.model.getTopState().getID(),
            cityName: addr.city ? addr.city.attributes.name : null,
            streetName: addr.street ? addr.street.name : null,
        };
        // Settings: Clear city
        if (settings.clearCity) {
            address.cityName = null;
        }
        // Settings: Detect city
        if (settings.detectCity && options.cityName) {
            address.cityName = options.cityName;
        }
        // Check city
        address.emptyCity = (address.cityName === null);
        // Check street
        address.emptyStreet = (address.streetName === null) || (address.streetName === '');
        // Update segment properties
        W.model.actionManager.add(
          new WazeActionUpdateObject(
            segment,
            settings.attributes
          )
        );
        // Update segment address
        W.model.actionManager.add(
          new WazeActionUpdateFeatureAddress(
            segment,
            address,
            {
                streetIDField: 'primaryStreetID'
            }
          )
        );
    }

    // Update handler
    function processHandler() {
        let button = getButtonConfig(this.dataset[NAME]);
        // run callback
        return button.callback(button);
    }

    function process(button) {
        // Get all selected segments
        let selected = W.selectionManager.getSelectedFeatures();
        let segments = [];
        let options = {};
        // Fill segments array
        for (let i = 0, total = selected.length; i < total; i++) {
            segments.push(W.model.segments.getObjectById(selected[i].model.attributes.id))
        }
        // Filter segments array
        segments = segments.filter(segment => segment && segment.getPermissions());
        // Try to detect city
        if (button.detectCity) {
            let cityName = null;
            for (let i = 0, total = segments.length; i < total; i++) {
                cityName = detectCity(segments[i]);
                if (cityName) {
                    options.cityName = cityName;
                    break;
                }
            }
            log('detected city ' + cityName);
        }

        for (let i = 0, total = segments.length; i < total; i++) {
            setupRoad(segments[i], button, options);
        }
    }

    // Detect city name by connected segments
    function detectCity(segment) {
        // Check cityName of the segment
        if (segment.getAddress().getCity() && !segment.getAddress().getCity().isEmpty()) {
            return segment.getAddress().getCity().getName();
        }
        let cityName = null;
        // TODO: replace follow magic with
        //  segment.getConnectedSegments() and segment.getConnectedSegmentsByDirection() when it will work
        //  last check - 30.07.19
        let connected = W.model.nodes.getObjectById(segment.getAttributes().fromNodeID).getSegmentIds(); // segments from point A
        connected = connected.concat(W.model.nodes.getObjectById(segment.getAttributes().toNodeID).getSegmentIds()); // segments from point B
        connected.filter(id => id !== segment.getID());

        for (let i = 0, total = connected.length; i < total; i++) {
            let city = W.model.segments.getObjectById(connected[i]).getAddress().getCity();
            // skip segments with empty cities
            if (city && !city.isEmpty()) {
                cityName = city.getName();
                break;
            }
        }
        return cityName;
    }

    // Create UI controls everytime when updated DOM of sidebar
    // Uses native JS function for better performance
    function createUI() {
        // Container for buttons
        let controls = document.createElement('div');
        controls.className = 'controls';
        // Create buttons
        for (let btn in buttons) {
            let config = getButtonConfig(btn);
            let button = document.createElement('button');
                button.dataset[NAME] = btn;
                button.className = 'waze-btn waze-btn-small ' + NAME + ' ' + NAME + '-' + btn;
                button.innerHTML = config.title;
                if (config.attributes) {
                    button.title = I18n.t('segment.road_types')[config.attributes.roadType];
                    button.style.backgroundColor = colors[config.attributes.roadType];
                } else {
                    button.title = config.title;
                }

            controls.appendChild(button);
        }

        let label = document.createElement('label');
            label.className = 'control-label';
            label.innerHTML = I18n.t(NAME).title;

        let group = document.createElement('div');
            group.className = 'form-group ' + NAME;
            group.appendChild(label);
            group.appendChild(controls);

        document.getElementById('segment-edit-general').prepend(group);
    }

    // Apply CSS styles
    function appendStyle(css) {
        let style = document.createElement('style');
            style.type = 'text/css';
            style.innerHTML = css;
        document.getElementsByTagName('head')[0].appendChild(style);
    }

    // Simple console.log wrapper
    function log(message) {
        console.log(NAME + ': ' + message);
    }

    // Initial Translation for UI and Shortcuts
    function initTranslation() {
        I18n.translations[LOCALE][NAME] = translation[LOCALE] || translation.en;

        // Translation for Shortcuts
        I18n.translations[LOCALE].keyboard_shortcuts.groups[NAME] = [];
        I18n.translations[LOCALE].keyboard_shortcuts.groups[NAME].description = NAME;
        I18n.translations[LOCALE].keyboard_shortcuts.groups[NAME].members = [];

        // Create description for every button
        for (let btn in buttons) {
            let name = NAME + 'Button' + buttons[btn].title;
            // Build description
            I18n.translations[LOCALE].keyboard_shortcuts.groups[NAME].members[name] = buttons[btn].title;
            if (buttons[btn].attributes) {
                I18n.translations[LOCALE].keyboard_shortcuts.groups[NAME].members[name] += ' - ' +
                I18n.t('segment.road_types')[buttons[btn].attributes.roadType] + '; ' +
                I18n.t('edit.segment.fields.speed_limit') + ' ' +
                I18n.t('measurements.speed.km', {speed: buttons[btn].attributes.fwdMaxSpeed})
                ;
            }
        }
    }

    // Initial Mutation Observer
    // #segment-edit-general - for segment tab
    // #landmark-edit-general - for POI tab
    function initObserver() {
        // Check for changes in the edit-panel
        // TODO: try to find solutions to handle native event
        let speedLimitsObserver = new MutationObserver(function (mutations) {
            mutations.forEach(function (mutation) {
                for (let i = 0, total = mutation.addedNodes.length; i < total; i++) {
                    let node = mutation.addedNodes[i];
                    // Only fire up if it's a node
                    if (node.nodeType === Node.ELEMENT_NODE &&
                      node.querySelector('div.selection') &&
                      node.querySelector('#segment-edit-general') && // segment tab
                      node.querySelector('div.hide-walking-trail').style.display !== 'none' && // skip for walking trails
                      !node.querySelector('div.form-group.' + NAME)) {
                        createUI();
                    }
                }
            });
        });

        speedLimitsObserver.observe(document.getElementById('edit-panel'), {childList: true, subtree: true});
        log('observer was run');
    }

    function initButtons() {
        $('#edit-panel').on('click', 'button.'+NAME, processHandler);
    }

    function initShortcuts() {
        W.accelerators.Groups[NAME] = [];
        W.accelerators.Groups[NAME].members = [];

        for (let btn in buttons) {
            let name = NAME + 'Button' + buttons[btn].title;
            W.accelerators.addAction(name, { group: NAME });
            W.accelerators.events.register(name, null, () => buttons[btn].callback(buttons[btn]));
            W.accelerators.registerShortcut(buttons[btn].shortcut, name);
        }
    }

    function init() {
        // Initial Translation
        initTranslation();

        // Initial Mutation Observer
        initObserver();

        // Handler for all buttons
        initButtons();

        // Handler for button shortcuts
        initShortcuts();

        // Apply CSS styles
        appendStyle(
          'button.waze-btn.E95 { margin: 0 4px 4px 0; padding: 2px; width: 42px; } ' +
          'button.waze-btn.E95:hover { box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1), inset 0 0 100px 100px rgba(255, 255, 255, 0.3); } ' +
          'button.waze-btn.E95-E { margin-right: 42px; }' +
          'button.waze-btn.E95-F { margin-right: 50px; }' +
          'button.waze-btn.E95-J { display: none }'
        );
    }

    // Bootstrap plugin
    function bootstrap(tries = 1) {
        log('attempt ' + tries);
        if (W &&
          W.map &&
          W.model &&
          W.loginManager.user) {
            log('was initialized');
            init();
        } else if (tries < 100) {
            tries++;
            setTimeout(() => bootstrap(tries), 500);
        } else {
            console.error('initialization failed');
        }
    }

    log('initialization');
    bootstrap();
})(window.jQuery);

QingJ © 2025

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