Setup road properties with templates
// ==UserScript==// @name WME E95// @name:uk WME 🇺🇦 E95// @version 0.8.3// @description Setup road properties with templates// @description:uk Швидке налаштування атрибутів вулиці за шаблонами// @license MIT License// @author Anton Shevchuk// @namespace https://greasyfork.org/users/227648-anton-shevchuk// @supportURL https://github.com/AntonShevchuk/wme-e95/issues// @match https://*.waze.com/editor*// @match https://*.waze.com/*/editor*// @exclude https://*.waze.com/user/editor*// @icon data:image/png;base64,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// @grant none// @require https://update.greasyfork.org/scripts/389765/1090053/CommonUtils.js// @require https://update.greasyfork.org/scripts/450160/1218867/WME-Bootstrap.js// @require https://update.greasyfork.org/scripts/452563/1218878/WME.js// @require https://update.greasyfork.org/scripts/450221/1137043/WME-Base.js// @require https://update.greasyfork.org/scripts/450320/1555446/WME-UI.js// ==/UserScript==/* jshint esversion: 8 *//* global require *//* global $, jQuery *//* global W *//* global I18n *//* global WME, WMEBase, WMEUI, WMEUIHelper, WMEUIShortcut, WMEUIHelperControlButton *//* global Container, Settings, SimpleCache, Tools */(function () {'use strict'// Script name, uses as unique indexconst NAME = 'E95'// Translationsconst TRANSLATION = {'en': {title: 'Quick Properties',description: 'Apply the road\'s settings by one click',help: 'You can use the <a href="#keyboard-dialog" target="_blank" rel="noopener noreferrer" data-toggle="modal">Keyboard shortcuts</a> to apply the settings. It\'s more convenient than clicking on the buttons.',},'uk': {title: 'Швидкі налаштування',description: 'Застосовуйте швидкі налаштування для доріг за один клік',help: 'Використовуйте <a href="#keyboard-dialog" target="_blank" rel="noopener noreferrer" data-toggle="modal">гарячі клавіши</a>, це значно швидше ніж використовувати кнопку',},'ru': {title: 'Быстрые настройки',description: 'Применяйте быстрые настройки для дорог в один клик',help: 'Используйте <a href="#keyboard-dialog" target="_blank" rel="noopener noreferrer" data-toggle="modal">комбинации клавиш</a>, и не надо будет клацать кнопку',}}const STYLE = '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-J { margin-right: 42px; }' +'p.e95-info { border-top: 1px solid #ccc; color: #777; font-size: x-small; margin-top: 15px; padding-top: 10px; text-align: center; }'WMEUI.addTranslation(NAME, TRANSLATION)WMEUI.addStyle(STYLE)// Road Types// I18n.translations.uk.segment.road_types// I18n.translations.en.segment.road_typesconst TYPES = {street: 1,primary: 2,freeway: 3,ramp: 4,trail: 5,major: 6,minor: 7,offroad: 8,walkway: 9,boardwalk: 10,ferry: 15,stairway: 16,private: 17,railroad: 18,runway: 19,parking: 20,narrow: 22}// Road colors by typeconst COLORS = {'1': '#ffffeb','2': '#f0ea58',// ...'8': '#867342',// ...'17': '#beba6c',// ...'20': '#ababab'}// Road Flags// for setup flags use binary operators// e.g. flags.tunnel | flags.headlightsconst FLAGS = {tunnel: 0b00000001,// ???// a : 0b00000010,// b : 0b00000100,// c : 0b00001000,unpaved: 0b00010000,headlights: 0b00100000,}// Buttons:// title - for buttons// shortcut - keys for shortcuts, by default is Alt + (1..9)// options:// - detectCity - try to detect the city name by closures segments// - clearCity - clear the city name// - clearStreet - clear the street name// attributes - native settings for model object// TODO:// – check permissions for user level lower than 2const BUTTONS = {A: {title: 'PR 5',shortcut: 'A+1',options: {detectCity: true,},attributes: {flags: 0,fwdMaxSpeed: 5,revMaxSpeed: 5,fwdMaxSpeedUnverified: false,revMaxSpeedUnverified: false,roadType: TYPES.private,lockRank: 0,}},B: {title: 'PR20',shortcut: 'A+2',options: {detectCity: true,},attributes: {flags: 0,fwdMaxSpeed: 20,revMaxSpeed: 20,fwdMaxSpeedUnverified: false,revMaxSpeedUnverified: false,roadType: TYPES.private,lockRank: 0,}},C: {title: 'PR50',shortcut: 'A+3',options: {detectCity: true,},attributes: {flags: 0,fwdMaxSpeed: 50,revMaxSpeed: 50,fwdMaxSpeedUnverified: false,revMaxSpeedUnverified: false,roadType: TYPES.private,lockRank: 0,}},D: {title: 'St50',shortcut: 'A+4',options: {detectCity: true,},attributes: {flags: 0,fwdMaxSpeed: 50,revMaxSpeed: 50,fwdMaxSpeedUnverified: false,revMaxSpeedUnverified: false,roadType: TYPES.street,lockRank: 0,}},E: {title: 'PS50',shortcut: 'A+5',options: {detectCity: true,},attributes: {flags: 0,fwdMaxSpeed: 50,revMaxSpeed: 50,fwdMaxSpeedUnverified: false,revMaxSpeedUnverified: false,roadType: TYPES.primary,lockRank: 1,}},F: {title: 'PLR',shortcut: 'A+6',options: {detectCity: true,},attributes: {flags: 0,fwdMaxSpeed: 5,revMaxSpeed: 5,fwdMaxSpeedUnverified: false,revMaxSpeedUnverified: false,roadType: TYPES.parking,lockRank: 0,}},G: {title: 'OR',shortcut: 'A+7',options: {clearCity: true,clearStreet: false,},attributes: {flags: 0,fwdMaxSpeed: 90,revMaxSpeed: 90,fwdMaxSpeedUnverified: false,revMaxSpeedUnverified: false,roadType: TYPES.offroad,lockRank: 0,}},H: {title: 'PR90',shortcut: 'A+8',options: {clearCity: true,},attributes: {flags: 0,fwdMaxSpeed: 90,revMaxSpeed: 90,fwdMaxSpeedUnverified: false,revMaxSpeedUnverified: false,roadType: TYPES.private,lockRank: 0,}},I: {title: 'St90',shortcut: 'A+9',options: {clearCity: true,},attributes: {flags: 0,fwdMaxSpeed: 90,revMaxSpeed: 90,fwdMaxSpeedUnverified: false,revMaxSpeedUnverified: false,roadType: TYPES.street,lockRank: 0,}},J: {title: 'PS90',shortcut: 'A+0',options: {clearCity: true,},attributes: {flags: 0,fwdMaxSpeed: 90,revMaxSpeed: 90,fwdMaxSpeedUnverified: false,revMaxSpeedUnverified: false,roadType: TYPES.primary,lockRank: 1,}}}// codes of countriesconst COUNTRIES = {ukraine: 232}// country specified buttons configconst CONFIGS = {// Ukraine232: {G: {attributes: {flags: FLAGS.headlights}},H: {attributes: {flags: FLAGS.headlights}},I: {attributes: {flags: FLAGS.headlights}},J: {attributes: {flags: FLAGS.headlights}},}}// Require Waze APIlet WazeActionUpdateObjectlet WazeActionUpdateFeatureAddressclass E95 extends WMEBase {constructor (name, buttons, config ) {super(name)this.helper = new WMEUIHelper(name)this.panel = nullthis.buttons = buttonsthis.config = configlet tab = this.helper.createTab(I18n.t(name).title,{image: GM_info.script.icon})tab.addText('description', I18n.t(name).description)tab.addDiv('text', I18n.t(name).help)tab.addText('info','<a href="' + GM_info.scriptUpdateURL + '">' + GM_info.script.name + '</a> ' + GM_info.script.version)tab.inject()}getPanel () {if (this.panel) {return this.panel}// Build panel// Container for buttonslet controls = document.createElement('div')controls.className = 'controls'// Create buttonsfor (let btn in this.buttons) {let config = this.getButtonConfig(btn)let title = config.titlelet color = COLORS[config.attributes.roadType]let description = config.title + ' - ' +I18n.t('segment.road_types')[config.attributes.roadType] + '; ' +I18n.t('edit.segment.fields.speed_limit') + ' ' +I18n.t('measurements.speed.km', { speed: config.attributes.fwdMaxSpeed })let UIButton = new WMEUIHelperControlButton(NAME,btn,title,description,() => this.buttonCallback(config),config.shortcut)let button = UIButton.html()button.dataset[NAME] = btnbutton.style.backgroundColor = colorcontrols.appendChild(button)}let label = document.createElement('label')label.className = 'control-label'label.innerText = I18n.t(NAME).titlethis.panel = document.createElement('div')this.panel.className = 'form-group ' + NAMEthis.panel.appendChild(label)this.panel.appendChild(controls)return this.panel}// Get Button settingsgetButtonConfig (index) {// Load settings for current country by call method W.model.getTopCountry().getID()// Then mixed it with default settings by Tools.mergeDeep() methodreturn Tools.mergeDeep(this.buttons[index], this.config[index])}// Handler for Road buttonsbuttonCallback (button) {// Get all selected segmentslet segments = WME.getSelectedSegments()// Try to detect city, if neededif (button.options.detectCity) {let cityName = nullfor (let i = 0, total = segments.length; i < total; i++) {cityName = this.detectCity(segments[i])if (cityName) {button.options.cityName = cityNamebreak}}}for (let i = 0, total = segments.length; i < total; i++) {this.updateSegment(segments[i], button.options, button.attributes)}}/*** Update segment attributes* @param {Object} segment* @param {Object} options* @param {Object} attributes*/updateSegment (segment, options, attributes = {}) {// current segment addresslet addr = segment.getAddress()// fill address informationlet address = {countryID: addr.getCountry()?.getID() || W.model.getTopCountry().getID(),stateID: addr.getState()?.getID() || W.model.getTopState().getID(),cityName: addr.getCity()?.getName() || '',streetName: addr.getStreet()?.getName() || '',}// options: detect cityif (!address.cityName && options.detectCity && options.cityName) {this.log('detected city name "' + options.cityName + '"')address.cityName = options.cityName}// options: clear cityif (options.clearCity) {this.log('clear city name')address.cityName = null}// options: clear streetif (options.clearStreet) {this.log('clear street name')address.streetName = null}// set city flagaddress.emptyCity = (address.cityName === null)// set street flagaddress.emptyStreet = (address.streetName === null) || (address.streetName === '')let updateFeatureAddress = new WazeActionUpdateFeatureAddress(segment,address,{streetIDField: 'primaryStreetID'})// update segment's addressW.model.actionManager.add(updateFeatureAddress)// keep the current lock level if it is higher than in the config's attributesif (segment.attributes.lockRank > attributes.lockRank) {attributes.lockRank = segment.attributes.lockRank}// need more logsthis.log('set road type to ' + I18n.t('segment.road_types')[attributes.roadType])// update segment's propertieslet updateObject = new WazeActionUpdateObject(segment, attributes)W.model.actionManager.add(updateObject)}/*** Detect city name by connected segments* @param {Object} segment* @return {String|null}*/detectCity (segment) {// check cityName of the segmentif (segment.getAddress().getCity() && !segment.getAddress().getCity().isEmpty()) {return segment.getAddress().getCity().getName()}// TODO: replace follow magic with methods getConnectedSegments() and getConnectedSegmentsByDirection()// W.selectionManager.getSelectedDataModelObjects()[0].getConnectedSegments().map(x => x.attributes.id)// W.selectionManager.getSelectedDataModelObjects()[0].getConnectedSegmentsByDirection().map(x => x.attributes.id)// when it will work// last check - 2023.11.20// connected segmentslet connected = []connected = connected.concat(segment.getFromNode().getSegmentIds()) // segments from point Aconnected = connected.concat(segment.getToNode().getSegmentIds()) // segments from point Bconnected = connected.filter(id => id !== segment.getID()) // filter himself// cities of the connected segmentslet cities = connected.map(id => W.model.segments.getObjectById(id).getAddress().getCity())cities = cities.filter(city => city) // filter segments w/out citycities = cities.map(city => city.getName()) // extract cities namecities = cities.filter(city => city) // filter empty city nameif (cities.length) {return cities.shift()}return null}/*** Handler for `segment.wme` event* Create UI controls every time when updated DOM of sidebar* Uses native JS function for better performance** @param {jQuery.Event} event* @param {HTMLElement} element* @param {W.model} model* @return {void}*/onSegment (event, element, model) {// Skip for walking trails and blocked roadsif (model.isWalkingRoadType()|| model.isLockedByHigherRank()|| !model.isGeometryEditable()) {return}// Panel can be already existselement.querySelector('div.form-group.E95') ||element.prepend(this.getPanel())}/*** Handler for `segments.wme` event* Create UI controls every time when updated DOM of sidebar* Uses native JS function for better performance** @param {jQuery.Event} event* @param {HTMLElement} element* @param {Array} models* @return {void}*/onSegments (event, element, models) {// Skip for walking trails or locked roadsif (models.filter((model) => model.isWalkingRoadType() || model.isLockedByHigherRank() || !model.isGeometryEditable()).length > 0) {element.querySelector('div.form-group.E95')?.remove()return}// Panel can be already existselement.querySelector('div.form-group.E95') ||element.prepend(this.getPanel())}}$(document).on('bootstrap.wme', () => {// Require scriptsWazeActionUpdateObject = require('Waze/Action/UpdateObject')WazeActionUpdateFeatureAddress = require('Waze/Action/UpdateFeatureAddress')// check country configurationlet country = W.model.getTopCountry()?.getID() || COUNTRIES.ukrainelet config = CONFIGS[country] ? CONFIGS[country] : CONFIGS[COUNTRIES.ukraine]new E95(NAME, BUTTONS, config)WMEUIShortcut.setGroupTitle(NAME, I18n.t(NAME).title)})})()