WME E85 Simplify Street Geometry

Simplify Street Geometry, looks like fork

安装此脚本?
作者推荐脚本

你可能也喜欢 WME E50 Fetch POI Data

安装此脚本
  1. // ==UserScript==
  2. // @name WME E85 Simplify Street Geometry
  3. // @name:uk WME 🇺🇦 E85 Simplify Street Geometry
  4. // @version 0.2.4
  5. // @description Simplify Street Geometry, looks like fork
  6. // @description:uk Спрощуємо та вирівнюємо геометрію вулиць
  7. // @license MIT License
  8. // @author Anton Shevchuk
  9. // @namespace https://greasyfork.org/users/227648-anton-shevchuk
  10. // @supportURL https://github.com/AntonShevchuk/wme-e85/issues
  11. // @match https://*.waze.com/editor*
  12. // @match https://*.waze.com/*/editor*
  13. // @exclude https://*.waze.com/user/editor*
  14. // @icon data:image/png;base64,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cmUgNC40LjAtRXhpdjIiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6R0lNUD0iaHR0cDovL3d3dy5naW1wLm9yZy94bXAvIgogICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgIHhtcE1NOkRvY3VtZW50SUQ9ImdpbXA6ZG9jaWQ6Z2ltcDo3MDc5ZWFhZi0yYmFiLTQyYWQtODVkMi00MDQ0MWEyMjdmZTkiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDU4ODQ0OGYtZTI5OS00MTY2LTk2ZmQtY2Y1MDI1ZGFmM2JmIgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MjdlZDgzZjItYTQ3OC00MjI1LTlkOTYtNjYwZGUyMzk5ZmFhIgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAgR0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iTWFjIE9TIgogICBHSU1QOlRpbWVTdGFtcD0iMTY3MDg0NjMzNzI5MjY2NyIKICAgR0lNUDpWZXJzaW9uPSIyLjEwLjMyIgogICB0aWZmOk9yaWVudGF0aW9uPSIxIgogICB4bXA6Q3JlYXRvclRvb2w9IkdJTVAgMi4xMCIKICAgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMjoxMjoxMlQxMjo1ODo1NiswMTowMCIKICAgeG1wOk1vZGlmeURhdGU9IjIwMjI6MTI6MTJUMTI6NTg6NTYrMDE6MDAiPgogICA8eG1wTU06SGlzdG9yeT4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iLyIKICAgICAgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplZDRiNjEzOS1iYmU3LTQ3MzgtODZkYi1kZTYwMzk2OWM3NjciCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkdpbXAgMi4xMCAoTWFjIE9TKSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMi0xMi0xMlQxMjo1ODo1NyswMTowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz4PVOYgAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5gwMCzo59Ra5FgAABz9JREFUeNrtmntMVFcexz+8QXQE1GEGmFVR44NHLXEayqPBWLdpt6nUB1oXLbQ10G1KbZvQVLtAXItNY//QbBraVB7bVNpx1bGGTReycbUzhAUJ0YLSAiXKY4b3Y2GFheHuH8iFy/CYZbWyw/0mk8zvnN95/H73nN/ve869IEOGDBkyZMiQIUOGjIUIB/FfGo448DsgDohCmFBnH5YKgAHQ4cCnZDAyXpmOmnSMuYW5gqnDJNgrTB0mIbcwVyAdI+moJzrAUFZdJiwUlFWXCaRjGF0Y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
  15. // @grant none
  16. // @require https://update.greasyfork.org/scripts/389765/1090053/CommonUtils.js
  17. // @require https://update.greasyfork.org/scripts/450160/1218867/WME-Bootstrap.js
  18. // @require https://update.greasyfork.org/scripts/452563/1218878/WME.js
  19. // @require https://update.greasyfork.org/scripts/450221/1137043/WME-Base.js
  20. // @require https://update.greasyfork.org/scripts/450320/1555446/WME-UI.js
  21. // ==/UserScript==
  22.  
  23. /* jshint esversion: 8 */
  24.  
  25. /* global require */
  26. /* global $, jQuery */
  27. /* global W */
  28. /* global I18n */
  29. /* global OpenLayers */
  30. /* global WME, WMEBase */
  31. /* global WMEUI, WMEUIHelper, WMEUIHelperPanel, WMEUIHelperModal, WMEUIHelperTab, WMEUIShortcut */
  32. /* global Container, Settings, SimpleCache, Tools */
  33.  
  34. (function () {
  35. 'use strict'
  36.  
  37. // Script name, uses as unique index
  38. const NAME = 'E85'
  39.  
  40. // Translations
  41. const TRANSLATION = {
  42. 'en': {
  43. title: 'Street Geometry',
  44. description: 'Simplify and straighten up streets',
  45. buttons: {
  46. A: 'Simplify',
  47. B: 'Straighten',
  48. C: '∡90°',
  49. },
  50. settings: {
  51. title: 'Settings',
  52. description: 'Settings for simplifying segments',
  53. simplifyShort: 'Remove a fragment shorter than',
  54. simplifyAngle: 'If the angle is bigger than',
  55. simplifyTwoShort: 'and fragments shorter than',
  56. },
  57. },
  58. 'uk': {
  59. title: 'Геометрія вулиць',
  60. description: 'Спрощуйте та вирівнюйте вулиці',
  61. buttons: {
  62. A: 'Спростити',
  63. B: 'Вирівняти',
  64. C: '∡90°',
  65. },
  66. settings: {
  67. title: 'Налаштування',
  68. description: 'Для спрощення сегментів будуть враховані наступні параметри',
  69. simplifyShort: 'Видаляти фрагменти менші ніж',
  70. simplifyAngle: 'Або якщо кут більше ніж',
  71. simplifyTwoShort: 'та фрагменти меньші ніж',
  72. },
  73. },
  74. 'ru': {
  75. title: 'Геометрия улиц',
  76. description: 'Упрощайте и выравнивайте геометрию улиц',
  77. buttons: {
  78. A: 'Упростить',
  79. B: 'Выровнять',
  80. C: '∡90°',
  81. },
  82. settings: {
  83. title: 'Настройки',
  84. description: 'Параметры для упрощения геометрии сегмента',
  85. simplifyShort: 'Если фрагмент короче, чем',
  86. simplifyAngle: 'Или угол больше чем',
  87. simplifyTwoShort: 'и фрагменты меньше, чем',
  88. },
  89. }
  90. }
  91.  
  92. const STYLE =
  93. 'button.e85.e85-A { background-color: #0f9; margin-right: 2px }' +
  94. 'button.e85.e85-B { background-color: #09f; color: #fff }' +
  95. 'button.e85.e85-C { background-color: #f99; margin-left: 2px }' +
  96. 'button.e85.e85-A:disabled, button.e85.e85-B:disabled { background-color: #ccc }' +
  97. '.e85 legend { cursor:pointer; font-size: 12px; font-weight: bold; width: auto; text-align: right; border: 0; margin: 0; padding: 0 8px; }' +
  98. '.e85 fieldset { border: 1px solid #ddd; padding: 8px; }' +
  99. '.e85 fieldset.e85 div.controls label { white-space: normal; font-weight: normal; line-height: 32px; font-size: 13px; }' +
  100. '.e85 fieldset.e85 div.controls input[type="number"] { float:right; wight: 32px }' +
  101. 'p.e85-info { border-top: 1px solid #ccc; color: #777; font-size: x-small; margin-top: 15px; padding-top: 10px; text-align: center; }'
  102.  
  103. WMEUI.addTranslation(NAME, TRANSLATION)
  104. WMEUI.addStyle(STYLE)
  105.  
  106. const BUTTONS = {
  107. A: {
  108. title: I18n.t(NAME).buttons.A,
  109. description: I18n.t(NAME).buttons.A,
  110. shortcut: '',
  111. },
  112. B: {
  113. title: I18n.t(NAME).buttons.B,
  114. description: I18n.t(NAME).buttons.B,
  115. shortcut: '',
  116. },
  117. C: {
  118. title: I18n.t(NAME).buttons.C,
  119. description: I18n.t(NAME).buttons.C,
  120. shortcut: '',
  121. },
  122. }
  123.  
  124. // Default settings
  125. const SETTINGS = {
  126. simplifyShort: 5,
  127. simplifyAngle: 176,
  128. simplifyTwoShort: 50,
  129. }
  130.  
  131. let WazeActionAddNode
  132. let WazeActionMoveNode
  133. let WazeActionMultiAction
  134. let WazeActionUpdateSegmentGeometry
  135.  
  136. class E85 extends WMEBase {
  137. /**
  138. * Initial UI elements
  139. * @param {Object} buttons
  140. */
  141. init (buttons) {
  142. /** @type {WMEUIHelper} */
  143. this.helper = new WMEUIHelper(this.name)
  144.  
  145. /** @type {WMEUIHelperTab} */
  146. this.tab = this.helper.createTab(
  147. I18n.t(this.name).title,
  148. {
  149. image: GM_info.script.icon
  150. }
  151. )
  152.  
  153. // Setup options for script
  154. let fieldset = this.helper.createFieldset(I18n.t(NAME).settings.title)
  155. fieldset.addText('description', I18n.t(NAME).settings.description)
  156. let settings = this.settings.get()
  157. for (let item in settings) {
  158. if (settings.hasOwnProperty(item)) {
  159. fieldset.addNumber(
  160. 'settings-' + item,
  161. I18n.t(NAME).settings[item],
  162. event => this.settings.set([item], event.target.value),
  163. this.settings.get(item),
  164. (item === 'simplifyAngle') ? 150 : 0,
  165. (item === 'simplifyAngle') ? 180 : 200,
  166. 1
  167. )
  168. }
  169. }
  170. this.tab.addElement(fieldset)
  171. this.tab.addText(
  172. 'info',
  173. '<a href="' + GM_info.scriptUpdateURL + '">' + GM_info.script.name + '</a> ' + GM_info.script.version
  174. )
  175.  
  176. // Inject custom HTML to container in the WME interface
  177. this.tab.inject()
  178. }
  179.  
  180. /**
  181. * Handler for `segment.wme` event
  182. * @param {jQuery.Event} event
  183. * @param {HTMLElement} element
  184. * @param {W.model} model
  185. * @return {void}
  186. */
  187. onSegment (event, element, model) {
  188. // Skip for blocked roads
  189. if (model.isLockedByHigherRank() || !model.isGeometryEditable()) {
  190. return
  191. }
  192.  
  193. let panel = this.helper.createPanel(I18n.t(this.name).title)
  194. let simplifyButton = panel.addButton(
  195. 'A',
  196. BUTTONS.A.title,
  197. BUTTONS.A.description,
  198. () => this.simplifySegmentGeometry(model),
  199. BUTTONS.A.shortcut
  200. )
  201.  
  202. let straightenButton = panel.addButton(
  203. 'B',
  204. BUTTONS.B.title,
  205. BUTTONS.B.description,
  206. () => this.straightenSegmentGeometry(model),
  207. BUTTONS.B.shortcut
  208. )
  209. if (model.getGeometry().coordinates.length < 3) {
  210. simplifyButton.html().disabled = true
  211. straightenButton.html().disabled = true
  212. }
  213.  
  214. const existingFormGroup = element.querySelector('div.form-group.e85');
  215. if (existingFormGroup) {
  216. existingFormGroup.replaceWith(panel.html());
  217. } else {
  218. element.prepend(panel.html());
  219. }
  220. }
  221.  
  222. /**
  223. * Handler for `segments.wme` event
  224. * @param {jQuery.Event} event
  225. * @param {HTMLElement} element
  226. * @param {Array} models
  227. * @return {void}
  228. */
  229. onSegments (event, element, models) {
  230. // Skip for locked roads
  231. if (models.filter((model) => model.isLockedByHigherRank() || !model.isGeometryEditable()).length > 0) {
  232. element.querySelector('div.form-group.e85')?.remove()
  233. return
  234. }
  235.  
  236. let panel = this.helper.createPanel(I18n.t(this.name).title)
  237. let simplifyButton = panel.addButton(
  238. 'A',
  239. BUTTONS.A.title,
  240. BUTTONS.A.description,
  241. () => this.simplifyStreetGeometry(models),
  242. BUTTONS.A.shortcut
  243. )
  244.  
  245. // Don't straighten multiple components
  246. let straightenButton = panel.addButton(
  247. 'B',
  248. BUTTONS.B.title,
  249. BUTTONS.B.description,
  250. () => this.straightenStreetGeometry(models),
  251. BUTTONS.B.shortcut
  252. )
  253.  
  254. let modelWithComponents = models.filter(model => model.getGeometry().coordinates.length > 2)
  255.  
  256. if (modelWithComponents.length === 0) {
  257. simplifyButton.html().disabled = true
  258. }
  259.  
  260. if (W.selectionManager.getSegmentSelection().multipleConnectedComponents) {
  261. straightenButton.html().disabled = true
  262. }
  263.  
  264. if (!W.selectionManager.getSegmentSelection().multipleConnectedComponents
  265. && models.length === 2) {
  266. panel.addButton(
  267. 'C',
  268. BUTTONS.C.title,
  269. BUTTONS.C.description,
  270. () => this.orthogonalizeStreetGeometry(models[0], models[1]),
  271. BUTTONS.C.shortcut
  272. )
  273. }
  274.  
  275. const existingFormGroup = element.querySelector('div.form-group.e85');
  276. if (existingFormGroup) {
  277. existingFormGroup.replaceWith(panel.html());
  278. } else {
  279. element.prepend(panel.html());
  280. }
  281. }
  282.  
  283. /**
  284. * Remove geometry nodes on the target segment
  285. * @param {Object} model
  286. * @return {void}
  287. */
  288. simplifySegmentGeometry (model) {
  289. if (model.getGeometry().coordinates.length < 3) {
  290. return
  291. }
  292.  
  293. this.group('simplify segment geometry')
  294. this.log('check geometry of the segment with ID ' + model.getID())
  295. let nodes = []
  296.  
  297. // calculate angles for every inside point
  298. for (let i = 0; i < model.getGeometry().coordinates.length - 2; i++) {
  299. let nodeStart = model.getGeometry().coordinates[i],
  300. nodeCenter = model.getGeometry().coordinates[i + 1],
  301. nodeEnd = model.getGeometry().coordinates[i + 2]
  302.  
  303. nodes[i] = {
  304. angle: Math.round(this.findAngle(nodeStart, nodeCenter, nodeEnd)),
  305. start: Math.round(this.findLength(nodeStart, nodeCenter)),
  306. end: Math.round(this.findLength(nodeCenter, nodeEnd)),
  307. }
  308. this.log('point ' + (i+1) + ' : ' + nodes[i].angle + '°, ' + nodes[i].start + 'm, ' + nodes[i].end + 'm')
  309. }
  310.  
  311. let removeNodes = []
  312.  
  313. for (let i = 0; i < nodes.length; i++) {
  314. let node = nodes[i]
  315.  
  316. // mark to remove a node with short START segment
  317. if (node.start < this.settings.get('simplifyShort')) {
  318. this.log('found too short segment: ' + node.start + 'm')
  319. removeNodes.push(i+1)
  320. continue // skip next rule
  321. }
  322. // mark to remove a node with short END segment and big ANGLE
  323. if (node.angle >= this.settings.get('simplifyAngle')
  324. && node.end < this.settings.get('simplifyShort')) {
  325. this.log('found too short fragment: ' + node.end + 'm')
  326. removeNodes.push(i+1)
  327. i++ // skip next node
  328. continue // skip next rule
  329. }
  330. // mark to remove a node with big angle and short segments
  331. if (node.angle >= this.settings.get('simplifyAngle')
  332. && node.start + node.end < this.settings.get('simplifyTwoShort')) {
  333. this.log(
  334. 'found point with short fragment: ' + node.start + ' + ' + node.end + ' = ' +
  335. (node.start + node.end) + 'm and angle equal to ' + node.angle + '°'
  336. )
  337. removeNodes.push(i+1)
  338. // continue // skip next rule
  339. }
  340. }
  341.  
  342. // remove nodes from geometry
  343. if (removeNodes.length) {
  344. let newGeometry = { ... model.getGeometry() }
  345. let coordinates = []
  346. for (let i = 0; i < newGeometry.coordinates.length; i++) {
  347. if (removeNodes.indexOf(i) === -1) {
  348. coordinates.push(newGeometry.coordinates[i])
  349. }
  350. }
  351. newGeometry.coordinates = coordinates
  352. W.model.actionManager.add(new WazeActionUpdateSegmentGeometry(model, model.getGeometry(), newGeometry))
  353. }
  354. this.groupEnd()
  355. }
  356.  
  357. /**
  358. * Calculates the angle (in radians) between two vectors pointing outward from one center
  359. *
  360. * @param {Object} start first point
  361. * @param {Object} center second point
  362. * @param {Object} end third point
  363. */
  364. findAngle (start, center, end) {
  365. let b = Math.pow(center[0] - start[0], 2) + Math.pow(center[1] - start[1], 2),
  366. a = Math.pow(center[0] - end[0], 2) + Math.pow(center[1] - end[1], 2),
  367. c = Math.pow(end[0] - start[0], 2) + Math.pow(end[1] - start[1], 2)
  368. return Math.acos((a + b - c) / Math.sqrt(4 * a * b)) * (180 / Math.PI)
  369. }
  370.  
  371. /**
  372. * Get the length of the line by point coordinates
  373. * @param {Array<number,number>} start point
  374. * @param {Array<number,number>} end point
  375. * @return {Number} length in meters
  376. */
  377. findLength (start, end) {
  378. return distance(start[0], start[1], end[0], end[1])
  379. }
  380.  
  381. /**
  382. * Remove geometry nodes on the target segments
  383. * @param {Array} models
  384. * @return {void}
  385. */
  386. simplifyStreetGeometry (models) {
  387. this.group('simplify street geometry')
  388. for (let i = 0; i < models.length; i++) {
  389. this.simplifySegmentGeometry(models[i])
  390. }
  391. this.groupEnd()
  392. }
  393.  
  394. /**
  395. * Aligns the segments into a straight line by moving the intermediate
  396. * nodes to the intersection points of the perpendiculars with
  397. * the calculated line passing through the start and end nodes of the selection.
  398. *
  399. * A, B, and C are the parameters of the calculated line equation:
  400. * Ax + By + C = 0
  401. *
  402. * @param {Array} models
  403. * @return {void}
  404. */
  405. straightenStreetGeometry (models) {
  406. this.group('straighten street geometry')
  407. this.log('calculating the formula for the straight line')
  408.  
  409. let segmentSelection = W.selectionManager.getSegmentSelection()
  410.  
  411. if (segmentSelection.multipleConnectedComponents) {
  412. this.log('don\'t try to straighten multiple segments without connection')
  413. }
  414.  
  415. let
  416. allNodeIds = [], // all nodes for selected segments
  417. dupNodeIds = [], // only nodes inside connections
  418. virtualNodes = [] // virtual nodes of segments
  419.  
  420. models.forEach(segment => {
  421. this.log('straighten segment #' + segment.getID())
  422.  
  423. // simplify segment to straight
  424. this.straightenSegmentGeometry(segment)
  425.  
  426. // collect the nodes
  427. allNodeIds.push(segment.getFromNode().getID())
  428. allNodeIds.push(segment.getToNode().getID())
  429. virtualNodes = virtualNodes.concat(segment.getVirtualNodes())
  430. })
  431.  
  432. if (virtualNodes.length ) {
  433. this.log('⚠️ virtual nodes are present, please disconnect all trails and rails from the segments and try again')
  434.  
  435. // doesn't work, but why? what is wrong with this code?
  436. // virtualNodes.forEach(node => {
  437. // let element = document.getElementById(node.getOLGeometry.id)
  438. // element.setAttribute("fill","#dd7700")
  439. //
  440. // element.addEventListener("click", () => {
  441. // element.setAttribute("fill","#00ece3")
  442. // });
  443. // })
  444.  
  445. return
  446. }
  447.  
  448. allNodeIds.forEach((nodeId, idx) => {
  449. if (allNodeIds.indexOf(nodeId, idx + 1) > -1) {
  450. if (!dupNodeIds.includes(nodeId))
  451. dupNodeIds.push(nodeId);
  452. }
  453. });
  454.  
  455. let distinctNodeIds = [...new Set(allNodeIds)];
  456. let endPointNodeIds = distinctNodeIds.filter((nodeId) => !dupNodeIds.includes(nodeId));
  457. let endPointNodes = W.model.nodes.getByIds(endPointNodeIds),
  458. endPointNode1Geo = endPointNodes[0].getGeometry().coordinates,
  459. endPointNode2Geo = endPointNodes[1].getGeometry().coordinates
  460.  
  461. const a = endPointNode2Geo[1] - endPointNode1Geo[1],
  462. b = endPointNode1Geo[0] - endPointNode2Geo[0],
  463. c = endPointNode2Geo[0] * endPointNode1Geo[1] - endPointNode1Geo[0] * endPointNode2Geo[1];
  464.  
  465. dupNodeIds.forEach((nodeId) => {
  466. const node = W.model.nodes.getObjectById(nodeId),
  467. nodeCoordinates = node.getGeometry().coordinates;
  468. const d = nodeCoordinates[1] * a - nodeCoordinates[0] * b,
  469. newCoordinates = getIntersectCoordinates(a, b, c, d);
  470.  
  471. this.log('move node #' + nodeId + ' to [' + newCoordinates[0] + ';' + newCoordinates[1] + ']')
  472. this.moveNode(node, newCoordinates)
  473. });
  474.  
  475. // I don't understand why doesn't it work, in the WME all looks good, but it fails when try to save changes
  476. // virtualNodes.forEach((node) => {
  477. // const nodeCoordinates = node.getGeometry().coordinates;
  478. // const d = nodeCoordinates[1] * a - nodeCoordinates[0] * b,
  479. // newCoordinates = getIntersectCoordinates(a, b, c, d);
  480. //
  481. // this.log('move node #' + node.getID() + ' to [' + newCoordinates[0] + ';' + newCoordinates[1] + ']')
  482. // this.moveNode(node, newCoordinates)
  483. // });
  484.  
  485. this.groupEnd()
  486. }
  487.  
  488. /**
  489. * Orthogonalize two segments
  490. * This method move the node to new point
  491. *
  492. * @param {Object} segment1
  493. * @param {Object} segment2
  494. * @return {void}
  495. */
  496. orthogonalizeStreetGeometry (segment1, segment2) {
  497. this.log('orthogonalize street geometry')
  498.  
  499. if (segment1.getType() !== 'segment'
  500. || segment2.getType() !== 'segment') {
  501. this.log('only segments must be selected')
  502. return
  503. }
  504.  
  505. /**
  506. * Extract coordinates from components
  507. * @param {Object} segment
  508. * @param {'first'|'second'|'last-but-one'|'last'} position
  509. * @return {*[]}
  510. */
  511. function getCoordinatesFromComponent(segment, position) {
  512. let pos = 0
  513. switch (position) {
  514. case 'first':
  515. pos = 0
  516. break
  517. case 'second':
  518. pos = 1
  519. break
  520. case 'last-but-one':
  521. pos = segment.getOLGeometry().components.length - 2
  522. break
  523. case 'last':
  524. pos = segment.getOLGeometry().components.length - 1
  525. break
  526. }
  527. return [
  528. segment.getOLGeometry().components[pos].x,
  529. segment.getOLGeometry().components[pos].y,
  530. ]
  531. }
  532.  
  533. let A, B, C, commonNode
  534.  
  535. if (segment1.getToNode().getID() === segment2.getFromNode().getID()) {
  536. // A → B → C
  537. commonNode = segment1.getToNode()
  538. A = getCoordinatesFromComponent(segment1, 'last-but-one')
  539. B = getCoordinatesFromComponent(segment1, 'last')
  540. C = getCoordinatesFromComponent(segment2, 'second')
  541. } else if (segment1.getFromNode().getID() === segment2.getFromNode().getID()) {
  542. // B ← A → C
  543. commonNode = segment1.getFromNode()
  544. A = getCoordinatesFromComponent(segment1, 'second')
  545. B = getCoordinatesFromComponent(segment1, 'first')
  546. C = getCoordinatesFromComponent(segment2, 'second')
  547. } else if (segment1.getToNode().getID() === segment2.getToNode().getID()) {
  548. // A → B ← C
  549. commonNode = segment1.getToNode()
  550. A = getCoordinatesFromComponent(segment1, 'last-but-one')
  551. B = getCoordinatesFromComponent(segment1, 'last')
  552. C = getCoordinatesFromComponent(segment2, 'last-but-one')
  553. } else if (segment1.getFromNode().getID() === segment2.getToNode().getID()) {
  554. // B ← A ← C
  555. commonNode = segment1.getFromNode()
  556. A = getCoordinatesFromComponent(segment1, 'second')
  557. B = getCoordinatesFromComponent(segment1, 'first')
  558. C = getCoordinatesFromComponent(segment2, 'last-but-one')
  559. }
  560.  
  561. if (!commonNode) {
  562. this.log('segments does not have common node')
  563. return
  564. }
  565.  
  566. this.log('common node coords [' + B[0] + ';' + B[1] + ']')
  567.  
  568. // Coordinates of points A, B and C
  569. // First selected segment uses it as line for calculation
  570. let intersection = findIntersection(A, B, C)
  571.  
  572. // Uses OpenLayers with convertation, because
  573. intersection = W.userscripts.toGeoJSONGeometry(new OpenLayers.Geometry.Point( ...intersection ))
  574.  
  575. this.log('point of the intersection is [' + intersection[0] + ', ' + intersection[1] +']')
  576.  
  577. this.moveNode(commonNode, intersection.coordinates)
  578. }
  579.  
  580. /**
  581. * Straighten up segment, remove all geometry nodes except first and last
  582. * @param {Object} segment
  583. */
  584. straightenSegmentGeometry (segment) {
  585. this.group('straighten segment geometry')
  586. if (segment.getGeometry().coordinates.length > 2) {
  587. let multiAction = new WazeActionMultiAction()
  588. let newGeometry = structuredClone(segment.attributes.geoJSONGeometry)
  589. // just left the first and last elements
  590. newGeometry.coordinates.splice(1, newGeometry.coordinates.length - 2)
  591. // W.model.actionManager.add(new WazeActionUpdateSegmentGeometry(segment, segment.getGeometry(), newGeometry))
  592. let updateSegmentGeometry = new WazeActionUpdateSegmentGeometry(segment, segment.attributes.geoJSONGeometry, newGeometry)
  593. updateSegmentGeometry.generateDescription();
  594. multiAction.doSubAction(W.model, updateSegmentGeometry);
  595. W.model.actionManager.add(multiAction);
  596. }
  597. }
  598.  
  599. /**
  600. * Move node to new position
  601. * @param {Object} node target
  602. * @param {Array<2>} coords of the new position, array of the wo elements
  603. */
  604. moveNode (node, coords) {
  605. let nodeGeometry = node.getGeometry()
  606. nodeGeometry.coordinates = coords
  607.  
  608. let connectedSegObjs = {}
  609. let emptyObj = {}
  610.  
  611. node.getSegmentIds().forEach((id) => {
  612. connectedSegObjs[id] = { ...W.model.segments.getObjectById(id).getGeometry() }
  613. })
  614.  
  615. W.model.actionManager.add(
  616. new WazeActionMoveNode(
  617. node,
  618. node.getGeometry(),
  619. nodeGeometry,
  620. connectedSegObjs,
  621. emptyObj
  622. )
  623. )
  624. }
  625. }
  626.  
  627. /**
  628. * @param {Array<number,number>} A point of line
  629. * @param {Array<number,number>} B point of line
  630. * @param {Array<number,number>} C point
  631. * @return {(number|*)[]}
  632. */
  633. function findIntersection(A, B, C) {
  634. // Функция для вычисления углового коэффициента прямой
  635. function slope(point1, point2) {
  636. return (point2[1] - point1[1]) / (point2[0] - point1[0]);
  637. }
  638.  
  639. // Функция для вычисления c (точка пересечения с осью Y) для уравнения прямой
  640. function intercept(point, slope) {
  641. return point[1] - slope * point[0];
  642. }
  643.  
  644. // Вычисляем угловой коэффициент для прямой AB
  645. let mAB = slope(A, B);
  646. // Вычисляем c для прямой AB
  647. let cAB = intercept(A, mAB);
  648.  
  649. // Для перпендикуляра угловой коэффициент будет обратным и противоположным
  650. let mPerpendicular = -1 / mAB;
  651. // Вычисляем c для перпендикулярной прямой, проходящей через C
  652. let cPerpendicular = intercept(C, mPerpendicular);
  653.  
  654. // Находим точку пересечения прямых
  655. let x = (cPerpendicular - cAB) / (mAB - mPerpendicular);
  656. let y = mAB * x + cAB;
  657.  
  658. return [x, y];
  659. }
  660.  
  661.  
  662. /**
  663. * Find intersection point
  664. * @param {Number} A
  665. * @param {Number} B
  666. * @param {Number} C
  667. * @param {Number} D
  668. * @return {Number[]}
  669. */
  670. function getIntersectCoordinates (A, B, C, D) {
  671. // http://rsdn.ru/forum/alg/2589531.hot
  672. let r = [2]
  673. r[1] = -1.0 * (C * B - A * D) / (A * A + B * B)
  674. r[0] = (-r[1] * (B + A) - C + D) / (A - B)
  675.  
  676. return r
  677. }
  678.  
  679. /**
  680. * Detect direction
  681. * @param {Number} A
  682. * @param {Number} B
  683. * @return {Number}
  684. */
  685. function getDeltaDirect (A, B) {
  686. if (A < B) {
  687. return 1.0
  688. } else if (A > B) {
  689. return -1.0
  690. }
  691.  
  692. return 0.0
  693. }
  694.  
  695. /**
  696. * Calculate the approximate distance between two coordinates (lat/lon)
  697. *
  698. * © Chris Veness, MIT-licensed,
  699. * http://www.movable-type.co.uk/scripts/latlong.html#equirectangular
  700. *
  701. * @param {number} λ1 first point latitude
  702. * @param {number} φ1 first point longitude
  703. * @param {number} λ2 second point latitude
  704. * @param {number} φ2 second point longitude
  705. */
  706. function distance 1, φ1, λ2, φ2) {
  707. let R = 6371000;
  708. let Δλ = 2 - λ1) * Math.PI / 180;
  709. φ1 = φ1 * Math.PI / 180;
  710. φ2 = φ2 * Math.PI / 180;
  711. let x = Δλ * Math.cos((φ12)/2);
  712. let y = 21);
  713. let d = Math.sqrt(x*x + y*y);
  714. return R * d;
  715. };
  716.  
  717. $(document).on('bootstrap.wme', () => {
  718.  
  719. WazeActionAddNode = require('Waze/Action/AddNode')
  720. WazeActionMoveNode = require('Waze/Action/MoveNode')
  721. WazeActionMultiAction = require('Waze/Action/MultiAction');
  722. WazeActionUpdateSegmentGeometry = require('Waze/Action/UpdateSegmentGeometry')
  723.  
  724. let Instance = new E85(NAME, SETTINGS)
  725. Instance.init(BUTTONS)
  726.  
  727. // setup name for a shortcut section
  728. WMEUIShortcut.setGroupTitle(NAME, I18n.t(NAME).title)
  729. })
  730. })()