WME E87 Inconsistent direction

Solves the inconsistent direction problem

当前为 2022-12-20 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         WME E87 Inconsistent direction
// @version      0.0.1
// @description  Solves the inconsistent direction problem
// @license      MIT License
// @author       Anton Shevchuk
// @namespace    https://greasyfork.org/users/227648-anton-shevchuk
// @supportURL   https://github.com/AntonShevchuk/wme-template/issues
// @match        https://*.waze.com/editor*
// @match        https://*.waze.com/*/editor*
// @exclude      https://*.waze.com/user/editor*
// @icon         https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://anton.shevchuk.name&size=64
// @grant        none
// @require      https://greasyfork.org/scripts/389765-common-utils/code/CommonUtils.js?version=1090053
// @require      https://greasyfork.org/scripts/450160-wme-bootstrap/code/WME-Bootstrap.js?version=1128320
// @require      https://greasyfork.org/scripts/452563-wme/code/WME.js?version=1101598
// @require      https://greasyfork.org/scripts/450221-wme-base/code/WME-Base.js?version=1129908
// @require      https://greasyfork.org/scripts/450320-wme-ui/code/WME-UI.js?version=1128560
// ==/UserScript==

/* jshint esversion: 8 */

/* global require */
/* global $, jQuery */
/* global W */
/* global I18n */
/* global OpenLayers */
/* global WME, WMEBase */
/* global WMEUI, WMEUIHelper, WMEUIHelperPanel, WMEUIHelperModal, WMEUIHelperTab, WMEUIShortcut, WMEUIHelperFieldset */
/* global Container, Settings, SimpleCache, Tools  */

(function () {
  'use strict'

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

  // Translations
  const TRANSLATION = {
    'en': {
      title: 'Direction',
      description: 'Plugin WME E87 solves the inconsistent direction problem',
      buttons: {
        A: 'A → B',
        B: 'B → A',
      },
    },
    'uk': {
      title: 'Напрямки →',
      description: 'Плагін WME E87 для вирішиння проблеми різно направленних вулиць',
      buttons: {
        A: 'A → B',
        B: 'B → A',
      },
    },
    'ru': {
      title: 'Направления →',
      description: 'Плагин WME E87 для решения проблемы разнонаправленных улиц',
      buttons: {
        A: 'A → B',
        B: 'B → A',
      },
    }
  }

  const STYLE =
    'button.waze-btn.e87 { background: #f2f4f7; border: 1px solid #ccc; margin: 2px 8px; } ' +
    'button.waze-btn.e87:hover { background: #ffffff; transition: background-color 100ms linear; 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.e87:focus { background: #f2f4f7; } ' +
    'div.e87-container { display: flex; } ' +
    'p.e87-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)

  const BUTTONS = {
    A: {
      title: I18n.t(NAME).buttons.A,
      description: I18n.t(NAME).buttons.A,
      shortcut: '',
    },
    B: {
      title: I18n.t(NAME).buttons.B,
      description: I18n.t(NAME).buttons.B,
      shortcut: '',
    },
  }

  // Default settings
  const SETTINGS = {}

  class E87 extends WMEBase {
    /**
     * Initial UI elements
     * @param {Object} buttons
     */
    init (buttons) {
      /** @type {WMEUIHelper} */
      this.helper = new WMEUIHelper(this.name)

      /** @type {WMEUIHelperTab} */
      this.tab = this.helper.createTab(
        I18n.t(this.name).title,
        I18n.t(this.name).description,
        {
          'icon': '<i class="w-icon panel-header-component-icon w-icon-route"></i>'
        }
      )

      buttons.A.callback = () => this.onButtonA()
      buttons.B.callback = () => this.onButtonB()

      this.tab.addButtons(buttons)

      this.tab.addText(
        'info',
        '<a href="' + GM_info.scriptUpdateURL + '">' + GM_info.script.name + '</a> ' + GM_info.script.version
      )

      // Inject custom HTML to container in the WME interface
      this.tab.inject()
    }

    onButtonA () {
      this.log('Button A')
    }

    onButtonB () {
      this.log('Button B')
    }

    /**
     * Handler for `segment.wme` event
     * @param {jQuery.Event} event
     * @param {HTMLElement} element
     * @param {W.model} model
     * @return {void}
     */
    onSegment (event, element, model) {
      this.log('Selected one segment')
    }

    /**
     * Handler for `segments.wme` event
     * @param {jQuery.Event} event
     * @param {HTMLElement} element
     * @param {Array} models
     * @return {void}
     */
    onSegments (event, element, models) {
      this.log('Check selected segments')
      this.detect(models)
    }

    /**
     * Detect directions
     * @param {Array<Object>} segments
     */
    detect (segments) {
      let forward = [], backward = [], node
      for (let i = 0; i < segments.length; i++) {
        // check first segment direction
        let A = segments[i].getFromNode().getID()
        let B = segments[i].getToNode().getID()
        let nextA, nextB

        // check next segment
        if (i + 1 < segments.length) {
          nextA = segments[i + 1].getFromNode().getID()
          nextB = segments[i + 1].getToNode().getID()
        } else {
          if (node === A) {
            forward.push(segments[i].getID())
          } else if (node === B) {
            backward.push(segments[i].getID())
          }
          continue
        }

        // looking for intersection of two segments
        if (A === nextA || A === nextB) {
          // B-A × A-B or B-A × B-A
          node = A
          backward.push(segments[i].getID())
        } else if (B === nextA || B === nextB) {
          // A-B × A-B or A-B × B-A
          node = B
          forward.push(segments[i].getID())
        } else {
          this.log('Segments doesn\'t have intersection')
          return
        }
      }

      if (forward.length && backward.length) {
        this.log('Inconsistent direction detected: forward = ' + forward.length + ' backward = ' + backward.length)

        let buttonToForward = document.createElement('button')
        buttonToForward.type = 'button'
        buttonToForward.className = 'waze-btn waze-btn-small waze-btn-white e87'
        buttonToForward.innerText = 'Make all forward (' + backward.length + ')'
        buttonToForward.onclick = (e) => {
          e.preventDefault()
          backward.forEach(el => this.invert(el))
        }
        let buttonToBackward = document.createElement('button')
        buttonToBackward.type = 'button'
        buttonToBackward.className = 'waze-btn waze-btn-small waze-btn-white e87'
        buttonToBackward.innerText = 'Make all backward (' + forward.length + ')'
        buttonToBackward.onclick = (e) => {
          e.preventDefault()
          forward.forEach(el => this.invert(el))
        }

        let container = document.createElement('div')
        container.className = 'e87-container'
        container.append(buttonToForward)
        container.append(buttonToBackward)

        $('wz-alert.sidebar-alert.inconsistent-direction-alert .sidebar-alert-content').after(container)
      }
    }

    /**
     * Invert direction of the segment
     * @param {Number} id of the segment
     */
    invert (id) {
      let segment = W.model.segments.getObjectById(id)
      if (segment.isLockedByHigherRank()) {
        this.log('Locked by higher rank')
        return
      }
      console.groupCollapsed(
        '%c' + this.name + ':%c invert segment',
        'color: #0DAD8D; font-weight: bold',
        'color: dimgray; font-weight: normal'
      )

      console.log('segment', segment)

      let attributes = {}
      attributes.fwdDirection = segment.attributes.revDirection
      attributes.revDirection = segment.attributes.fwdDirection
      // attributes.fwdTurnsLocked = segment.revTurnsLocked // ???
      // attributes.revTurnsLocked = segment.fwdTurnsLocked // ???
      attributes.fwdMaxSpeed = segment.attributes.revMaxSpeed
      attributes.revMaxSpeed = segment.attributes.fwdMaxSpeed
      attributes.fwdMaxSpeedUnverified = segment.attributes.revMaxSpeedUnverified
      attributes.revMaxSpeedUnverified = segment.attributes.fwdMaxSpeedUnverified
      attributes.fwdLaneCount = segment.attributes.revLaneCount
      attributes.revLaneCount = segment.attributes.fwdLaneCount

      attributes.restrictions = []

      for (let i = 0; i < segment.attributes.restrictions.length; i++) {
        attributes.restrictions[i] = segment.attributes.restrictions[i].withReverseDirection()
      }

      console.log('attributes', attributes)

      let fromNode = segment.getFromNode()
      let toNode = segment.getToNode()

      let onA = {}
      let toConnections = {}
      fromNode.getSegmentIds().forEach(segId => {
        if (segId !== id) {
          onA[segId] = W.model.getTurnGraph().getTurnThroughNode(fromNode, W.model.segments.getObjectById(segId), segment)
          onA[segId].toVertex.direction = onA[segId].toVertex.direction === 'fwd' ? 'rev' : 'fwd'
        }

        toConnections[segId] = W.model.getTurnGraph().getTurnThroughNode(fromNode, segment, W.model.segments.getObjectById(segId))
        toConnections[segId].fromVertex.direction = toConnections[segId].fromVertex.direction === 'fwd' ? 'rev' : 'fwd'

        // u-turn
        if (segId === id) {
          toConnections[segId].toVertex.direction = toConnections[segId].toVertex.direction === 'fwd' ? 'rev' : 'fwd'
        }
      })

      let onB = {}
      let fromConnections = {}
      toNode.getSegmentIds().forEach(segId => {
        if (segId !== id) {
          onB[segId] = W.model.getTurnGraph().getTurnThroughNode(toNode, W.model.segments.getObjectById(segId), segment)
          onB[segId].toVertex.direction = onB[segId].toVertex.direction === 'fwd' ? 'rev' : 'fwd'
        }

        fromConnections[segId] = W.model.getTurnGraph().getTurnThroughNode(toNode, segment, W.model.segments.getObjectById(segId))
        fromConnections[segId].fromVertex.direction = fromConnections[segId].fromVertex.direction === 'fwd' ? 'rev' : 'fwd'

        // u-turn
        if (segId === id) {
          fromConnections[segId].toVertex.direction = fromConnections[segId].toVertex.direction === 'fwd' ? 'rev' : 'fwd'
        }
      })

      // on inverse la geometrie du segment
      let geometry = segment.geometry.clone()
      geometry.components.reverse()

      // controle de position
      let nbPoints = geometry.components.length - 1
      if (!geometry.components[0].equals(toNode.attributes.geometry)) {
        let delta = { x: 0, y: 0 }
        delta.x = toNode.attributes.geometry.x - geometry.components[0].x
        delta.y = toNode.attributes.geometry.y - geometry.components[0].y
        geometry.components[0].move(delta.x, delta.y)
      }
      if (!geometry.components[nbPoints].equals(fromNode.attributes.geometry)) {
        let delta = { x: 0, y: 0 }
        delta.x = fromNode.attributes.geometry.x - geometry.components[nbPoints].x
        delta.y = fromNode.attributes.geometry.y - geometry.components[nbPoints].y
        geometry.components[nbPoints].move(delta.x, delta.y)
      }

      // On deconnecte le segment
      W.model.actionManager.add(new WazeActionMultiAction([new WazeActionDisconnectSegment(segment, fromNode), new WazeActionDisconnectSegment(segment, toNode)]))

      // maj de la geo du seg
      W.model.actionManager.add(new WazeActionUpdateSegmentGeometry(segment, segment.geometry, geometry))

      // On reconnecte le segment
      W.model.actionManager.add(new WazeActionMultiAction([new WazeActionConnectSegment(toNode, segment), new WazeActionConnectSegment(fromNode, segment)]))

      this.applyTurns(fromConnections)
      this.applyTurns(toConnections)
      this.applyTurns(onA)
      this.applyTurns(onB)

      console.groupEnd()
    }

    /**
     * Apply turns for segments
     * @param segments
     */
    applyTurns (segments) {
      let actions = []
      for (let sid in segments) {
        let segment = segments[sid]
        let turn
        switch (segment.turnData.state) {
          case 0 :
          case 1 :
            turn = WazeModelGraphTurnData.create()
            turn = turn.withState(segment.turnData.state)
            turn = turn.withRestrictions(segment.turnData.restrictions)
            turn = turn.withInstructionOpcode(segment.turnData.instructionOpcode)
            turn = turn.withLanes(segment.turnData.lanes)

            actions.push(new WazeModelGraphActionsSetTurn(W.model.getTurnGraph(), segment.withTurnData(turn)))
            break
        }
      }
      W.model.actionManager.add(new WazeActionMultiAction(actions))
    }
  }

  let WazeActionConnectSegment
  let WazeActionDisconnectSegment
  let WazeActionModifyAllConnections
  let WazeActionMultiAction
  let WazeActionUpdateObject
  let WazeActionUpdateSegmentGeometry
  let WazeModelGraphTurnData
  let WazeModelGraphActionsSetTurn

  $(document).on('bootstrap.wme', () => {
    let Instance = new E87(NAME, SETTINGS)
    Instance.init(BUTTONS)

    WazeActionConnectSegment = require('Waze/Action/ConnectSegment')
    WazeActionDisconnectSegment = require('Waze/Action/DisconnectSegment')
    WazeActionModifyAllConnections = require('Waze/Action/ModifyAllConnections')
    WazeActionMultiAction = require('Waze/Action/MultiAction')
    WazeActionUpdateObject = require('Waze/Action/UpdateObject')
    WazeActionUpdateSegmentGeometry = require('Waze/Action/UpdateSegmentGeometry')
    WazeModelGraphTurnData = require('Waze/Model/Graph/TurnData')
    WazeModelGraphActionsSetTurn = require('Waze/Model/Graph/Actions/SetTurn')
  })
})()