WME E40

Setup POI geometry properties in one click

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         WME E40
// @version      0.0.2
// @description  Setup POI geometry properties in one click
// @author       Anton Shevchuk
// @license      MIT License
// @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*
// @grant        none
// @require      https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// @supportURL   https://github.com/AntonShevchuk/wme-e40/issues
// @namespace    https://greasyfork.org/users/227648
// ==/UserScript==

/* jshint esversion: 6 */
/* global require, window, WazeWrap, OL */
(function ($, WazeApi, I18n) {
  'use strict';

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

  // Translations
  const LOCALE = I18n.currentLocale();
  const translation = {
    'en': {
      title: 'Geometry',
      orthogonalize: 'Orthogonalize',
      simplify: 'Simplify',
      scale: 'Scale',
    },
    'uk': {
      title: 'Геометрія',
      orthogonalize: 'Вирівняти',
      simplify: 'Спростити',
      scale: 'Масштабувати',
    },
    'ru': {
      title: 'Геометрия',
      orthogonalize: 'Выровнять',
      simplify: 'Упростить',
      scale: 'Масштабировать',
    }
  };

  const buttons = {
    A: {
      title: '🔲',
      shortcut: 'S+49',
      callback: () => orthogonalize()
    },
    B: {
      title: '〽️',
      shortcut: 'S+50',
      callback: () => simplify()
    },
    C: {
      title: '500m²',
      shortcut: 'S+51',
      callback: () => scaleSelected(500)
    },
    D: {
      title: '650m²',
      shortcut: 'S+52',
      callback: () => scaleSelected(650)
    },
    E: {
      title: '>650',
      shortcut: 'S+53',
      callback: () => scaleSelected(650, true)
    }
  };

  let WazeActionUpdateFeatureGeometry = require('Waze/Action/UpdateFeatureGeometry');

  // Scale place to X m²
  function scaleSelected(x, orMore = false) {
    if (!WazeWrap.hasPlaceSelected()) {
      return;
    }
    let selected = WazeApi.selectionManager.getSelectedFeatures().map((x) => x.model);
    scaleArray(selected, x, orMore);
    return false;
  }
  function scaleAll(x = 650) {
    scaleArray(WazeApi.model.venues.getObjectArray(), x, true);
    return false;
  }
  function scaleArray(elements, x, orMore = false) {
    for (let i = 0; i < elements.length; i++) {
      let selected = elements[i];
      if (!selected.isGeometryEditable() || selected.isPoint()) {
        continue;
      }
      try {
        let oldGeometry = selected.geometry.clone();
        let newGeometry = selected.geometry.clone();

        let scale = Math.sqrt((x + 5) / oldGeometry.getGeodesicArea(WazeApi.map.getProjectionObject()));
        if (scale < 1 && orMore) {
          continue;
        }
        newGeometry.resize(scale, newGeometry.getCentroid());

        let action = new WazeActionUpdateFeatureGeometry(selected, WazeApi.model.venues, oldGeometry, newGeometry);
        WazeApi.model.actionManager.add(action);

      } catch (e) {
        log('skipped');
      }
    }
  }
  // Orthogonalize place
  function orthogonalize() {
    if (!WazeWrap.hasPlaceSelected()) {
      return;
    }

    let selected = WazeApi.selectionManager.getSelectedFeatures().map((x) => x.model);
    orthogonalizeArray(selected);
    return false;
  }
  function orthogonalizeAll() {
    let selected = WazeApi.model.venues.getObjectArray();
    // skip parking, natural and outdoors
    // TODO: make options for filters
    selected = selected.filter(model => model.getMainCategory() !== 'OUTDOORS');
    selected = selected.filter(model => model.getMainCategory() !== 'PARKING_LOT');
    selected = selected.filter(model => model.getMainCategory() !== 'NATURAL_FEATURES');
    orthogonalizeArray(selected);
    return false;
  }
  function orthogonalizeArray(elements) {
    for (let i = 0; i < elements.length; i++) {
      let selected = elements[i];
      if (!selected.isGeometryEditable() || selected.isPoint()) {
        continue;
      }

      try {
        let oldGeometry = selected.geometry.clone();
        let newGeometry = WazeWrap.Util.OrthogonalizeGeometry(selected.geometry.clone().components[0].components);

        if (!compare(oldGeometry.components[0].components, newGeometry)) {
          selected.geometry.components[0].components = [].concat(newGeometry);
          selected.geometry.components[0].clearBounds();

          let action = new WazeActionUpdateFeatureGeometry(selected, WazeApi.model.venues, oldGeometry, selected.geometry);
          WazeApi.model.actionManager.add(action);
        }
      } catch (e) {
        log('skipped');
      }
    }
    return false;
  }
  // Simplify place
  function simplify(factor = 8) {
    if (!WazeWrap.hasPlaceSelected()) {
      return;
    }

    let selected = WazeApi.selectionManager.getSelectedFeatures().map((x) => x.model);
    simplifyArray(selected, factor);
    return false;
  }
  function simplifyAll() {
    simplifyArray(WazeApi.model.venues.getObjectArray());
    return false;
  }
  function simplifyArray(elements, factor = 8) {
    for (let i = 0; i < elements.length; i++) {
      let selected = elements[i];
      if (!selected.isGeometryEditable() || selected.isPoint()) {
        continue;
      }

      try {
        let oldGeometry = selected.geometry.clone();
        let ls = new OL.Geometry.LineString(oldGeometry.components[0].components);
        ls = ls.simplify(factor);
        let newGeometry = new OL.Geometry.Polygon(new OL.Geometry.LinearRing(ls.components));

        if (newGeometry.components[0].components.length < oldGeometry.components[0].components.length) {
          WazeApi.model.actionManager.add(new WazeActionUpdateFeatureGeometry(selected, WazeApi.model.venues, oldGeometry, newGeometry));
        }
      } catch (e) {
        log('skipped');
      }
    }
    return false;
  }
  // Compare two polygons point-by-point
  function compare(geo1, geo2) {
    if (geo1.length !== geo2.length) {
      return false;
    }
    for (let i = 0; i < geo1.length; i++) {
      if (Math.abs(geo1[i].x - geo2[i].x) > .1
        || Math.abs(geo1[i].y - geo2[i].y) > .1) {
        return false;
      }
    }
    return true;
  }
  // Bootstrap plugin
  function bootstrap(tries = 1) {
    log('attempt ' + tries);
    if (WazeApi &&
      WazeApi.map &&
      WazeApi.model &&
      WazeApi.loginManager.user &&
      WazeWrap.Ready) {
      log('was initialized');
      init();
    } else if (tries < 100) {
      tries++;
      setTimeout(() => bootstrap(tries), 500);
    } else {
      console.error('initialization failed');
    }
  }

  function init() {
    // Initial Mutation Observer
    initObserver();
    // Initial Translation
    initTranslation();
    // Initial Tab
    initUI();
    // Initial Handlers
    initHandlers();
    // Initial Shortcuts
    initShortcuts();
    // Apply CSS styles
    appendStyle(
      'button.waze-btn.E40 { margin: 0 4px 4px 0; padding: 2px; width: 42px; } ' +
      'button.waze-btn.E40:hover { box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1), inset 0 0 100px 100px rgba(255, 255, 255, 0.3); } '
    );
  }

  // Initial Mutation Observer
  // #segment-edit-general - for segment tab
  // #landmark-edit-general - for POI tab
  function initObserver() {
    // Check for changes in the edit-panel
    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('#landmark-edit-general') || node.querySelector('#mergeLandmarksCollection')) && // segment or landmark tab
            !node.querySelector('div.form-group.' + NAME)) {
            createUI();
          }
        }
      });
    });

    speedLimitsObserver.observe(document.getElementById('edit-panel'), {childList: true, subtree: true});
    log('observer was run');
  }
  // 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 and append it to panel
    // Create buttons
    for (let btn in buttons) {
      let button = document.createElement('button');
          button.className = 'waze-btn waze-btn-small ' + NAME + ' ' + NAME + '-' + btn;
          button.innerHTML = buttons[btn].title;
          button.title = buttons[btn].title;
          button.dataset[NAME] = btn;
      controls.appendChild(button);
    }

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

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

    if (document.getElementById('landmark-edit-general')) {
      document.getElementById('landmark-edit-general').prepend(group)
    }

    if (document.getElementById('mergeLandmarksCollection')) {
      document.getElementById('mergeLandmarksCollection').prepend(group)
    }
  }
  // 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 = [];
  }
  // Initial UI
  function initUI() {
    let html =
      '<div class="form-group">'+
      '<label class="control-label">'+ NAME +'</label>' +
      '<div class="button-toolbar">' +
      '<p><button type="button" id="E40-orthogonalize" class="btn btn-default">🔲</button> ' + I18n.t(NAME +'.orthogonalize') + '</p>' +
      '<p><button type="button" id="E40-simplify" class="btn btn-default">〽️</button> ' + I18n.t(NAME +'.simplify') + '</p>' +
      '<p><button type="button" id="E40-650" class="btn btn-default">&gt;650m²</button> ' + I18n.t(NAME +'.scale') + '</p>' +
      '</div>' +
      '</div>'
    ;

    new WazeWrap.Interface.Tab(NAME, html, function() {
      log('tab');
    });
  }
  // Initial button handlers, init it once
  function initHandlers() {
    $('#edit-panel').on('click', 'button.'+NAME, function() {
      let btn = $(this).data(NAME);
      return buttons[btn].callback();
    });
    $('#E40-orthogonalize').on('click', orthogonalizeAll);
    $('#E40-simplify').on('click', simplifyAll);
    $('#E40-650').on('click', () => scaleAll(650));
  }
  // Initial shortcuts
  function initShortcuts() {
    WazeApi.accelerators.Groups[NAME] = [];
    WazeApi.accelerators.Groups[NAME].members = [];

    for (let btn in buttons) {
      let name = NAME + 'Button' + buttons[btn].title;
      WazeApi.accelerators.addAction(name, { group: NAME });
      WazeApi.accelerators.events.register(name, null, buttons[btn].callback);
      WazeApi.accelerators.registerShortcut(buttons[btn].shortcut, name);
    }
  }
  // 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);
  }
  log('initialization');
  bootstrap();
})(window.jQuery, window.W, window.I18n);