WME E40 Geometry

Setup POI geometry properties in one click

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

  1. // ==UserScript==
  2. // @name WME E40 Geometry
  3. // @version 0.3.0
  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. // @icon data:image/png;base64,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
  15. // @require https://gf.qytechs.cn/scripts/24851-wazewrap/code/WazeWrap.js
  16. // @require https://gf.qytechs.cn/scripts/389117-apihelper/code/APIHelper.js?version=837602
  17. // @require https://gf.qytechs.cn/scripts/389577-apihelperui/code/APIHelperUI.js?version=812941
  18. // @supportURL https://github.com/AntonShevchuk/wme-e40/issues
  19. // @namespace https://gf.qytechs.cn/users/227648
  20. // ==/UserScript==
  21.  
  22. /* jshint esversion: 6 */
  23. /* global require */
  24. /* global $ */
  25. /* global W */
  26. /* global OpenLayers */
  27. /* global I18n */
  28. /* global WazeWrap */
  29. /* global APIHelper */
  30. /* global APIHelperUI */
  31.  
  32. (function ($) {
  33. 'use strict';
  34.  
  35. let helper;
  36. let panel;
  37. let tab;
  38.  
  39. let OL = OpenLayers;
  40.  
  41. // Script name, uses as unique index
  42. const NAME = 'E40';
  43.  
  44. // Translations
  45. const TRANSLATION = {
  46. 'en': {
  47. title: 'Geometry',
  48. orthogonalize: 'Orthogonalize',
  49. simplify: 'Simplify',
  50. scale: 'Scale',
  51. },
  52. 'uk': {
  53. title: 'Геометрія',
  54. orthogonalize: 'Вирівняти',
  55. simplify: 'Спростити',
  56. scale: 'Масштабувати',
  57. },
  58. 'ru': {
  59. title: 'Геометрия',
  60. orthogonalize: 'Выровнять',
  61. simplify: 'Упростить',
  62. scale: 'Масштабировать',
  63. }
  64. };
  65.  
  66. APIHelper.bootstrap();
  67. APIHelper.addTranslation(NAME, TRANSLATION);
  68. APIHelper.addStyle(
  69. 'button.waze-btn.e40 { margin: 0 4px 4px 0; padding: 2px; width: 42px; } ' +
  70. '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); } '
  71. );
  72.  
  73. const panelButtons = {
  74. A: {
  75. title: '🔲',
  76. description: I18n.t(NAME).orthogonalize,
  77. shortcut: 'S+49',
  78. callback: () => orthogonalize()
  79. },
  80. B: {
  81. title: '〽️',
  82. description: I18n.t(NAME).simplify,
  83. shortcut: 'S+50',
  84. callback: () => simplify()
  85. },
  86. C: {
  87. title: '500m²',
  88. description: I18n.t(NAME).scale,
  89. shortcut: 'S+51',
  90. callback: () => scaleSelected(500)
  91. },
  92. D: {
  93. title: '650m²',
  94. description: I18n.t(NAME).scale,
  95. shortcut: 'S+52',
  96. callback: () => scaleSelected(650)
  97. },
  98. E: {
  99. title: '>650',
  100. description: I18n.t(NAME).scale,
  101. shortcut: 'S+53',
  102. callback: () => scaleSelected(650, true)
  103. }
  104. };
  105.  
  106. const tabButtons = {
  107. A: {
  108. title: '🔲',
  109. description: I18n.t(NAME).orthogonalize,
  110. shortcut: null,
  111. callback: () => orthogonalizeAll()
  112. },
  113. B: {
  114. title: '〽️',
  115. description: I18n.t(NAME).simplify,
  116. shortcut: null,
  117. callback: () => simplifyAll()
  118. },
  119. C: {
  120. title: '>500',
  121. description: I18n.t(NAME).scale,
  122. shortcut: null,
  123. callback: () => scaleAll(500, true)
  124. }
  125. };
  126.  
  127. let WazeActionUpdateFeatureGeometry;
  128.  
  129. /**
  130. * Get selected Area POI
  131. * @return {Array}
  132. */
  133. function getSelectedPlaces() {
  134. let selected;
  135. selected = APIHelper.getSelectedVenues();
  136. selected = selected.filter((el) => !el.isPoint());
  137. return selected;
  138. }
  139. // Scale selected place(s) to X m²
  140. function scaleSelected(x, orMore = false) {
  141. scaleArray(getSelectedPlaces(), x, orMore);
  142. return false;
  143. }
  144. // Scale all places in the editor area to X m²
  145. function scaleAll(x = 650, orMore = true) {
  146. scaleArray(APIHelper.getVenues(), x, orMore);
  147. return false;
  148. }
  149. function scaleArray(elements, x, orMore = false) {
  150. for (let i = 0; i < elements.length; i++) {
  151. let selected = elements[i];
  152. try {
  153. let oldGeometry = selected.geometry.clone();
  154. let newGeometry = selected.geometry.clone();
  155.  
  156. let scale = Math.sqrt((x + 5) / oldGeometry.getGeodesicArea(W.map.getProjectionObject()));
  157. if (scale < 1 && orMore) {
  158. continue;
  159. }
  160. newGeometry.resize(scale, newGeometry.getCentroid());
  161.  
  162. let action = new WazeActionUpdateFeatureGeometry(selected, W.model.venues, oldGeometry, newGeometry);
  163. W.model.actionManager.add(action);
  164. } catch (e) {
  165. log('skipped');
  166. }
  167. }
  168. }
  169. // Orthogonalize selected place(s)
  170. function orthogonalize() {
  171. orthogonalizeArray(getSelectedPlaces());
  172. return false;
  173. }
  174. // Orthogonalize all places in the editor area
  175. function orthogonalizeAll() {
  176. // skip parking, natural and outdoors
  177. // TODO: make options for filters
  178. orthogonalizeArray(APIHelper.getVenues(['OUTDOORS', 'PARKING_LOT', 'NATURAL_FEATURES']));
  179. return false;
  180. }
  181. function orthogonalizeArray(elements) {
  182. for (let i = 0; i < elements.length; i++) {
  183. let selected = elements[i];
  184. try {
  185. let oldGeometry = selected.geometry.clone();
  186. let newGeometry = WazeWrap.Util.OrthogonalizeGeometry(selected.geometry.clone().components[0].components);
  187.  
  188. if (!compare(oldGeometry.components[0].components, newGeometry)) {
  189. selected.geometry.components[0].components = [].concat(newGeometry);
  190. selected.geometry.components[0].clearBounds();
  191.  
  192. let action = new WazeActionUpdateFeatureGeometry(selected, W.model.venues, oldGeometry, selected.geometry);
  193. W.model.actionManager.add(action);
  194. }
  195. } catch (e) {
  196. log('skipped');
  197. }
  198. }
  199. return false;
  200. }
  201. // Simplify selected place(s)
  202. function simplify(factor = 8) {
  203. simplifyArray(getSelectedPlaces(), factor);
  204. return false;
  205. }
  206. // Simplify all places in the editor area
  207. function simplifyAll() {
  208. // skip parking, natural and outdoors
  209. // TODO: make options for filters
  210. simplifyArray(APIHelper.getVenues(['OUTDOORS', 'PARKING_LOT', 'NATURAL_FEATURES']));
  211. return false;
  212. }
  213. function simplifyArray(elements, factor = 8) {
  214. for (let i = 0; i < elements.length; i++) {
  215. let selected = elements[i];
  216. try {
  217. let oldGeometry = selected.geometry.clone();
  218. let ls = new OL.Geometry.LineString(oldGeometry.components[0].components);
  219. ls = ls.simplify(factor);
  220. let newGeometry = new OL.Geometry.Polygon(new OL.Geometry.LinearRing(ls.components));
  221.  
  222. if (newGeometry.components[0].components.length < oldGeometry.components[0].components.length) {
  223. W.model.actionManager.add(new WazeActionUpdateFeatureGeometry(selected, W.model.venues, oldGeometry, newGeometry));
  224. }
  225. } catch (e) {
  226. log('skipped');
  227. }
  228. }
  229. return false;
  230. }
  231. // Compare two polygons point-by-point
  232. function compare(geo1, geo2) {
  233. if (geo1.length !== geo2.length) {
  234. return false;
  235. }
  236. for (let i = 0; i < geo1.length; i++) {
  237. if (Math.abs(geo1[i].x - geo2[i].x) > .1
  238. || Math.abs(geo1[i].y - geo2[i].y) > .1) {
  239. return false;
  240. }
  241. }
  242. return true;
  243. }
  244.  
  245. // Simple console.log wrapper
  246. function log(message) {
  247. console.log(NAME + ': ' + message);
  248. }
  249.  
  250. $(document)
  251. .on('init.apihelper', ready)
  252. .on('landmark.apihelper', createPanel)
  253. .on('landmark-collection.apihelper', createPanel)
  254. ;
  255.  
  256. function ready() {
  257. // Require Waze component
  258. WazeActionUpdateFeatureGeometry = require('Waze/Action/UpdateFeatureGeometry');
  259.  
  260. helper = new APIHelperUI(NAME);
  261.  
  262. panel = helper.createPanel(I18n.t(NAME).title);
  263. panel.addButtons(panelButtons);
  264.  
  265. if (W.loginManager.user.getRank() > 2) {
  266. tab = helper.createTab(I18n.t(NAME).title);
  267. tab.addButtons(tabButtons);
  268. tab.inject();
  269. }
  270.  
  271. WazeWrap.Events.register('afterundoaction', null, updateLabel);
  272. WazeWrap.Events.register('afterclearactions', null, updateLabel);
  273. WazeWrap.Events.register('afteraction', null, updateLabel);
  274. }
  275. function createPanel(event, element) {
  276. if (element.querySelector('div.form-group.e40')) {
  277. return;
  278. }
  279. let places = getSelectedPlaces();
  280. if (places.length === 0) {
  281. return;
  282. }
  283.  
  284. element.prepend(panel.html());
  285. updateLabel();
  286. }
  287.  
  288. function updateLabel() {
  289. let places = getSelectedPlaces();
  290. if (places.length === 0) {
  291. return;
  292. }
  293. let info = [];
  294. for (let i = 0; i < places.length; i++) {
  295. let selected = places[i];
  296. info.push(Math.round(selected.geometry.getGeodesicArea(W.map.getProjectionObject())) + 'm²');
  297. }
  298. let label = I18n.t(NAME).title;
  299. if (info.length) {
  300. label += ' (' + info.join(', ') + ')';
  301. }
  302. panel.html().querySelector('label').innerText = label;
  303. }
  304.  
  305. // external API
  306. window.E40 = {
  307. scale: function (x) {
  308. scaleSelected(x);
  309. }
  310. };
  311. })(window.jQuery);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址