WME E40

Setup POI geometry properties in one click

目前为 2019-08-07 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name WME E40
  3. // @version 0.0.1
  4. // @description Setup POI geometry properties in one click
  5. // @author Anton Shevchuk
  6. // @license MIT License
  7. // @include https://www.waze.com/editor*
  8. // @include https://www.waze.com/*/editor*
  9. // @include https://beta.waze.com/editor*
  10. // @include https://beta.waze.com/*/editor*
  11. // @exclude https://www.waze.com/user/editor*
  12. // @exclude https://beta.waze.com/user/editor*
  13. // @grant none
  14. // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
  15. // @namespace https://greasyfork.org/users/227648
  16. // ==/UserScript==
  17.  
  18. /* jshint esversion: 6 */
  19. /* global require, window, WazeWrap, OL */
  20. (function ($, WazeApi, I18n) {
  21. 'use strict';
  22.  
  23. // Script name, uses as unique index
  24. const NAME = 'E40';
  25.  
  26. // Translations
  27. const LOCALE = I18n.currentLocale();
  28. const translation = {
  29. 'en': {
  30. title: 'Geometry'
  31. },
  32. 'uk': {
  33. title: 'Геометрія',
  34. },
  35. 'ru': {
  36. title: 'Геометрия'
  37. }
  38. };
  39.  
  40. const buttons = {
  41. A: {
  42. title: '🔲',
  43. shortcut: 'S+49',
  44. callback: () => orthogonalize()
  45. },
  46. B: {
  47. title: '〽️',
  48. shortcut: 'S+50',
  49. callback: () => simplify()
  50. },
  51. C: {
  52. title: '500m²',
  53. shortcut: 'S+51',
  54. callback: () => scale(500)
  55. },
  56. D: {
  57. title: '650m²',
  58. shortcut: 'S+52',
  59. callback: () => scale(650)
  60. },
  61. E: {
  62. title: '>650m²',
  63. shortcut: 'S+53',
  64. callback: () => scale(650, true)
  65. }
  66. };
  67.  
  68. let WazeActionUpdateFeatureGeometry = require('Waze/Action/UpdateFeatureGeometry');
  69.  
  70. // Scale place to X m²
  71. function scale(x, orMore = false) {
  72. if (!WazeWrap.hasPlaceSelected()) {
  73. return;
  74. }
  75. let selected = WazeApi.selectionManager.getSelectedFeatures().map((x) => x.model);
  76. scaleArray(selected, x, orMore);
  77. return false;
  78. }
  79. function scaleArray(elements, x, orMore = false) {
  80. for (let i = 0; i < elements.length; i++) {
  81. let selected = elements[i];
  82. if (!selected.isGeometryEditable() || selected.isPoint()) {
  83. continue;
  84. }
  85. try {
  86. let oldGeometry = selected.geometry.clone();
  87. let newGeometry = selected.geometry.clone();
  88.  
  89. let scale = Math.sqrt((x + 5) / oldGeometry.getGeodesicArea(WazeApi.map.getProjectionObject()));
  90. if (scale < 1 && orMore) {
  91. continue;
  92. }
  93. newGeometry.resize(scale, newGeometry.getCentroid());
  94.  
  95. let action = new WazeActionUpdateFeatureGeometry(selected, WazeApi.model.venues, oldGeometry, newGeometry);
  96. WazeApi.model.actionManager.add(action);
  97.  
  98. } catch (e) {
  99. console.error(e);
  100. }
  101. }
  102. }
  103. // Orthogonalize place
  104. function orthogonalize() {
  105. if (!WazeWrap.hasPlaceSelected()) {
  106. return;
  107. }
  108.  
  109. let selected = WazeApi.selectionManager.getSelectedFeatures().map((x) => x.model);
  110. orthogonalizeArray(selected);
  111. return false;
  112. }
  113. function orthogonalizeArray(elements) {
  114. for (let i = 0; i < elements.length; i++) {
  115. let selected = elements[i];
  116. if (!selected.isGeometryEditable() || selected.isPoint()) {
  117. continue;
  118. }
  119.  
  120. let oldGeometry = selected.geometry.clone();
  121.  
  122. try {
  123. let newGeometry = WazeWrap.Util.OrthogonalizeGeometry(selected.geometry.clone().components[0].components);
  124.  
  125. if (!compare(oldGeometry.components[0].components, newGeometry)) {
  126. selected.geometry.components[0].components = [].concat(newGeometry);
  127. selected.geometry.components[0].clearBounds();
  128.  
  129. let action = new WazeActionUpdateFeatureGeometry(selected, WazeApi.model.venues, oldGeometry, selected.geometry);
  130. WazeApi.model.actionManager.add(action);
  131. }
  132. } catch (e) {
  133. console.log(selected);
  134. console.error(e);
  135. }
  136. }
  137. return false;
  138. }
  139. // Simplify place
  140. function simplify(factor = 8) {
  141. if (!WazeWrap.hasPlaceSelected()) {
  142. return;
  143. }
  144.  
  145. let selected = WazeApi.selectionManager.getSelectedFeatures().map((x) => x.model);
  146. simplifyArray(selected, factor);
  147. return false;
  148. }
  149. function simplifyArray(elements, factor = 8) {
  150. for (let i = 0; i < elements.length; i++) {
  151. let selected = elements[i];
  152. if (!selected.isGeometryEditable() || selected.isPoint()) {
  153. continue;
  154. }
  155.  
  156. try {
  157. let oldGeometry = selected.geometry.clone();
  158. let ls = new OL.Geometry.LineString(oldGeometry.components[0].components);
  159. ls = ls.simplify(factor);
  160. let newGeometry = new OL.Geometry.Polygon(new OL.Geometry.LinearRing(ls.components));
  161.  
  162. if (newGeometry.components[0].components.length < oldGeometry.components[0].components.length) {
  163. WazeApi.model.actionManager.add(new WazeActionUpdateFeatureGeometry(selected, WazeApi.model.venues, oldGeometry, newGeometry));
  164. }
  165. } catch (e) {
  166. console.error(e);
  167. }
  168. }
  169. return false;
  170. }
  171. // Compare two polygons point-by-point
  172. function compare(geo1, geo2) {
  173. if (geo1.length !== geo2.length) {
  174. return false;
  175. }
  176. for (let i = 0; i < geo1.length; i++) {
  177. if (Math.abs(geo1[i].x - geo2[i].x) > .1
  178. || Math.abs(geo1[i].y - geo2[i].y) > .1) {
  179. return false;
  180. }
  181. }
  182. return true;
  183. }
  184. // Bootstrap plugin
  185. function bootstrap(tries = 1) {
  186. log('attempt ' + tries);
  187. if (WazeApi &&
  188. WazeApi.map &&
  189. WazeApi.model &&
  190. WazeApi.loginManager.user &&
  191. WazeWrap.Ready) {
  192. log('was initialized');
  193. init();
  194. } else if (tries < 100) {
  195. tries++;
  196. setTimeout(() => bootstrap(tries), 500);
  197. } else {
  198. console.error('initialization failed');
  199. }
  200. }
  201.  
  202. function init() {
  203. // Initial Mutation Observer
  204. initObserver();
  205. // Initial Translation
  206. initTranslation();
  207. // Initial Tab
  208. initUI();
  209. // Initial Handlers
  210. initHandlers();
  211. // Initial Shortcuts
  212. initShortcuts();
  213. // Apply CSS styles
  214. appendStyle(
  215. 'button.waze-btn.E40 { margin: 0 4px 4px 0; padding: 2px; width: 42px; } ' +
  216. '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); } '
  217. );
  218. }
  219.  
  220. // Initial Mutation Observer
  221. // #segment-edit-general - for segment tab
  222. // #landmark-edit-general - for POI tab
  223. function initObserver() {
  224. // Check for changes in the edit-panel
  225. let speedLimitsObserver = new MutationObserver(function (mutations) {
  226. mutations.forEach(function (mutation) {
  227. for (let i = 0, total = mutation.addedNodes.length; i < total; i++) {
  228. let node = mutation.addedNodes[i];
  229. // Only fire up if it's a node
  230. if (node.nodeType === Node.ELEMENT_NODE &&
  231. node.querySelector('div.selection') &&
  232. (node.querySelector('#landmark-edit-general') || node.querySelector('#mergeLandmarksCollection')) && // segment or landmark tab
  233. !node.querySelector('div.form-group.' + NAME)) {
  234. createUI();
  235. }
  236. }
  237. });
  238. });
  239.  
  240. speedLimitsObserver.observe(document.getElementById('edit-panel'), {childList: true, subtree: true});
  241. log('observer was run');
  242. }
  243. // Create UI controls everytime when updated DOM of sidebar
  244. // Uses native JS function for better performance
  245. function createUI() {
  246. // Container for buttons
  247. let controls = document.createElement('div');
  248. controls.className = 'controls';
  249. // Create buttons and append it to panel
  250. // Create buttons
  251. for (let btn in buttons) {
  252. let button = document.createElement('button');
  253. button.className = 'waze-btn waze-btn-small ' + NAME + ' ' + NAME + '-' + btn;
  254. button.innerHTML = buttons[btn].title;
  255. button.title = buttons[btn].title;
  256. button.dataset[NAME] = btn;
  257. controls.appendChild(button);
  258. }
  259.  
  260. let label = document.createElement('label');
  261. label.className = 'control-label';
  262. label.innerHTML = I18n.translate(NAME).title;
  263.  
  264. let group = document.createElement('div');
  265. group.className = 'form-group ' + NAME;
  266. group.appendChild(label);
  267. group.appendChild(controls);
  268.  
  269. if (document.getElementById('landmark-edit-general')) {
  270. document.getElementById('landmark-edit-general').prepend(group)
  271. }
  272.  
  273. if (document.getElementById('mergeLandmarksCollection')) {
  274. document.getElementById('mergeLandmarksCollection').prepend(group)
  275. }
  276. }
  277. // Initial Translation for UI and Shortcuts
  278. function initTranslation() {
  279. I18n.translations[LOCALE][NAME] = translation[LOCALE] || translation.en;
  280.  
  281. // Translation for Shortcuts
  282. I18n.translations[LOCALE].keyboard_shortcuts.groups[NAME] = [];
  283. I18n.translations[LOCALE].keyboard_shortcuts.groups[NAME].description = NAME;
  284. I18n.translations[LOCALE].keyboard_shortcuts.groups[NAME].members = [];
  285. }
  286. // Initial UI
  287. function initUI() {
  288. let html =
  289. '<div class="form-group">'+
  290. '<label class="control-label">'+ NAME +'</label>' +
  291. '<div class="button-toolbar">' +
  292. '<p><button type="button" id="E40-orthogonalize" class="btn btn-default">🔲</button> Orthogonalize</p>' +
  293. '<p><button type="button" id="E40-simplify" class="btn btn-default">〽️</button> Simplify</p>' +
  294. '<p><button type="button" id="E40-650" class="btn btn-default">&gt;650m²</button> Change Square</p>' +
  295. '</div>' +
  296. '</div>'
  297. ;
  298.  
  299. new WazeWrap.Interface.Tab(NAME, html, function() {
  300. log('tab');
  301. });
  302. }
  303. // Initial button handlers, init it once
  304. function initHandlers() {
  305. $('#edit-panel').on('click', 'button.'+NAME, function() {
  306. let btn = $(this).data(NAME);
  307. return buttons[btn].callback();
  308. });
  309. $('#E40-orthogonalize').on('click', function() {
  310. orthogonalizeArray(WazeApi.model.venues.getObjectArray());
  311. return false;
  312. });
  313. $('#simplify').on('click', function() {
  314. simplifyArray(WazeApi.model.venues.getObjectArray());
  315. return false;
  316. });
  317. $('#E40-650').on('click', function() {
  318. scaleArray(WazeApi.model.venues.getObjectArray(), 650, true);
  319. return false;
  320. })
  321. }
  322. // Initial shortcuts
  323. function initShortcuts() {
  324. WazeApi.accelerators.Groups[NAME] = [];
  325. WazeApi.accelerators.Groups[NAME].members = [];
  326.  
  327. for (let btn in buttons) {
  328. let name = NAME + 'Button' + buttons[btn].title;
  329. WazeApi.accelerators.addAction(name, { group: NAME });
  330. WazeApi.accelerators.events.register(name, null, buttons[btn].callback);
  331. WazeApi.accelerators.registerShortcut(buttons[btn].shortcut, name);
  332. }
  333. }
  334. // Apply CSS styles
  335. function appendStyle(css) {
  336. let style = document.createElement('style');
  337. style.type = 'text/css';
  338. style.innerHTML = css;
  339. document.getElementsByTagName('head')[0].appendChild(style);
  340. }
  341. // Simple console.log wrapper
  342. function log(message) {
  343. console.log(NAME + ': ' + message);
  344. }
  345. log('initialization');
  346. bootstrap();
  347. })(window.jQuery, window.W, window.I18n);