WME Simplify Place Geometry

Simplifies geometry of area places in WME

  1. /* global simplify */
  2. // ==UserScript==
  3. // @name WME Simplify Place Geometry
  4. // @description Simplifies geometry of area places in WME
  5. // @version 2018.05.07.02
  6. // @author SAR85
  7. // @copyright SAR85
  8. // @license CC BY-NC-ND
  9. // @grant none
  10. // @include https://www.waze.com/editor*
  11. // @include https://www.waze.com/*/editor*
  12. // @include https://beta.waze.com/*
  13. // @namespace https://greatest.deepsurf.us/en/scripts/367660-wme-simplify-place-geometry
  14. // @require https://greatest.deepsurf.us/scripts/24851-wazewrap/code/WazeWrap.js
  15. // ==/UserScript==
  16. /* global W */
  17. /* global OpenLayers */
  18.  
  19. (function () {
  20. 'use strict';
  21. /* Global vars */
  22. var DEFAULT_SIMPLIFICATION_FACTOR = 5;
  23. var simplify;
  24. var simplifyVersion = '2018.05.07.01';
  25. var simplifyChanges = 'WME Simplify Area Geometry has been updated to version ' +
  26. simplifyVersion + '.\n' +
  27. '[*] Updated for editor compatibility.';
  28. var UpdateFeatureGeometry = require('Waze/Action/UpdateFeatureGeometry');
  29. var UpdateObject = require('Waze/Action/UpdateObject');
  30.  
  31. function simpBootstrap() {
  32. if (W && W.loginManager && W.loginManager.events &&
  33. W.loginManager.events.register && $ && WazeWrap.Ready &&
  34. $('#map').size()) {
  35. simpInit();
  36. } else {
  37. window.setTimeout(function () {
  38. simpBootstrap();
  39. }, 1000);
  40. }
  41. }
  42.  
  43. function initializeSimplifyFunc() {
  44. /*
  45. (c) 2013, Vladimir Agafonkin
  46. Simplify.js, a high-performance JS polyline simplification library
  47. mourner.github.io/simplify-js
  48. */
  49. function getSqDist(p1, p2) {
  50.  
  51. var dx = p1.x - p2.x,
  52. dy = p1.y - p2.y;
  53.  
  54. return dx * dx + dy * dy;
  55. }
  56. function getSqSegDist(p, p1, p2) {
  57.  
  58. var x = p1.x,
  59. y = p1.y,
  60. dx = p2.x - x,
  61. dy = p2.y - y;
  62.  
  63. if (dx !== 0 || dy !== 0) {
  64.  
  65. var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);
  66.  
  67. if (t > 1) {
  68. x = p2.x;
  69. y = p2.y;
  70.  
  71. } else if (t > 0) {
  72. x += dx * t;
  73. y += dy * t;
  74. }
  75. }
  76.  
  77. dx = p.x - x;
  78. dy = p.y - y;
  79.  
  80. return dx * dx + dy * dy;
  81. }
  82. function simplifyRadialDist(points, sqTolerance) {
  83.  
  84. var prevPoint = points[0],
  85. newPoints = [prevPoint],
  86. point;
  87.  
  88. for (var i = 1, len = points.length; i < len; i++) {
  89. point = points[i];
  90.  
  91. if (getSqDist(point, prevPoint) > sqTolerance) {
  92. newPoints.push(point);
  93. prevPoint = point;
  94. }
  95. }
  96.  
  97. if (prevPoint !== point) { newPoints.push(point); }
  98. return newPoints;
  99. }
  100. function simplifyDouglasPeucker(points, sqTolerance) {
  101.  
  102. var len = points.length,
  103. MarkerArray = typeof Uint8Array !== 'undefined' ? Uint8Array : Array,
  104. markers = new MarkerArray(len),
  105. first = 0,
  106. last = len - 1,
  107. stack = [],
  108. newPoints = [],
  109. i,
  110. maxSqDist,
  111. sqDist,
  112. index;
  113.  
  114. markers[first] = markers[last] = 1;
  115. while (last) {
  116.  
  117. maxSqDist = 0;
  118.  
  119. for (i = first + 1; i < last; i++) {
  120. sqDist = getSqSegDist(points[i], points[first], points[last]);
  121.  
  122. if (sqDist > maxSqDist) {
  123. index = i;
  124. maxSqDist = sqDist;
  125. }
  126. }
  127.  
  128. if (maxSqDist > sqTolerance) {
  129. markers[index] = 1;
  130. stack.push(first, index, index, last);
  131. }
  132.  
  133. last = stack.pop();
  134. first = stack.pop();
  135. }
  136.  
  137. for (i = 0; i < len; i++) {
  138. if (markers[i]) { newPoints.push(points[i]); }
  139. }
  140.  
  141. return newPoints;
  142. }
  143. function simplify(points, tolerance, highestQuality) {
  144. if (points.length <= 1) { return points; }
  145. var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
  146. points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
  147. points = simplifyDouglasPeucker(points, sqTolerance);
  148. return points;
  149. }
  150. return simplify;
  151. }
  152.  
  153. function simpInit() {
  154. /* HTML */
  155. var content = '<div id="simplifyarea"><p id="simplifyhelp" style="text-align: center; margin-bottom: 2px; text-decoration: underline; font-weight: bold; cursor: help;">WME Simplify Area Geometry</p><p style="text-align: center; margin: 0px;">Simplification factor: <input type="number" min="1" max="20" id="simpE" style="height: 20px; background-color: rgba(0,0,0,0.8); padding-left: 2px; border: 1px solid white; color: white; width: 50px"></p><p style="color: white;margin: 2px 0 0 0;"><a id="simplifylink" style="cursor:pointer; color: rgb(27,237,30)">Simplify Geometry</a> | <a id="clearlink" style="cursor:pointer; color: red;">Clear Geometry</a></p></div>';
  156. var css = {
  157. "display": "none",
  158. "position": "absolute",
  159. "top": "120px",
  160. "left": "73px",
  161. "padding": "4px",
  162. "background-color": "rgba(0,0,0,0.8)",
  163. "border-radius": "5px",
  164. "border": "none",
  165. "color": "white",
  166. "font-size": "0.9em"
  167. };
  168.  
  169. /* Initialize simplification library */
  170. simplify = initializeSimplifyFunc();
  171.  
  172. /* Add HTML to page and initialize*/
  173. $('#map').append(content);
  174. $('#simplifyarea').css(css);
  175. $('#simpE').val(localStorage.simplifyE ||
  176. DEFAULT_SIMPLIFICATION_FACTOR);
  177. $('#simplifylink').click(simplifyFeatureGeometry);
  178. $('#clearlink').click(clearFeatureGeometry);
  179. try {
  180. $('#simplifyarea').draggable();
  181. } catch (err) { }
  182.  
  183. /* Event listeners */
  184. W.loginManager.events.register('afterloginchanged', null, simpInit);
  185. $('#simplifyhelp').click(function () {
  186. alert('To use WME Simplify Place Geometry: \n' +
  187. '1. Select an area place \n' +
  188. '2. Select an appropriate simplification factor (usually 5-10) \n' +
  189. '3. Click the link to simplify or clear the geometry');
  190. });
  191. $('#simpE').change(function () {
  192. localStorage.simplifyE = $('#simpE').val();
  193. });
  194. W.selectionManager.events.register('selectionchanged', null,
  195. function () {
  196. if (W.selectionManager.hasSelectedFeatures()) {
  197. var selectedItem = W.selectionManager.getSelectedFeatures()[0].model;
  198. if (!(selectedItem.geometry instanceof OpenLayers.Geometry.Polygon)) {
  199. return;
  200. }
  201. $('#simplifyarea').fadeIn('fast');
  202. } else {
  203. $('#simplifyarea').fadeOut('fast');
  204. }
  205. });
  206.  
  207. /* Shortcut key = shift+j for simplifying */
  208. new WazeWrap.Interface.Shortcut('simplifyFeatureGeometry', 'editing', 'S+j', simplifyFeatureGeometry, this).add();
  209.  
  210. /* Shortcut key = ctrl+shift+j for clearing */
  211. new WazeWrap.Interface.Shortcut('clearFeatureGeometry', 'editing', 'CS+j', clearFeatureGeometry, this).add();
  212.  
  213. console.log('WME Simplify Area Geometry Initialized');
  214.  
  215. /* Update Alert */
  216. if (typeof window.localStorage.simplifyVersion === 'undefined' || window.localStorage.simplifyVersion !== simplifyVersion) {
  217. alert(simplifyChanges);
  218. window.localStorage.simplifyVersion = simplifyVersion;
  219. }
  220. }
  221.  
  222. function simplifyFeatureGeometry(e) {
  223. var place = W.selectionManager.getSelectedFeatures()[0];
  224. var oldGeometry = place.geometry.clone();
  225. var newGeometry = oldGeometry.clone();
  226.  
  227. if (!W.selectionManager.hasSelectedFeatures() || W.selectionManager.getSelectedFeatures()[0].model.type !== 'venue' ||
  228. !W.selectionManager.getSelectedFeatures()[0].model.isGeometryEditable() ||
  229. !(W.selectionManager.getSelectedFeatures()[0].model.geometry instanceof OpenLayers.Geometry.Polygon)) {
  230. return;
  231. }
  232. e = $('#simpE').val() || DEFAULT_SIMPLIFICATION_FACTOR;
  233.  
  234. newGeometry.components[0].components = simplify(oldGeometry.components[0].components, e, false);
  235. if (newGeometry.components[0].components.length <
  236. oldGeometry.components[0].components.length &&
  237. newGeometry.components[0].components.length > 2) {
  238. W.model.actionManager.add(new UpdateFeatureGeometry(
  239. place.model, W.model.venues, oldGeometry, newGeometry));
  240. console.log('WME Simplify Area Geometry: ' +
  241. place.model.attributes.name + ' simplified from ' +
  242. oldGeometry.components[0].components.length + ' to ' +
  243. newGeometry.components[0].components.length +
  244. ' geo nodes using factor ' + e + '.');
  245. } else {
  246. console.log('Geo nodes cannot be simplified from ' +
  247. oldGeometry.components[0].components.length + ' to ' +
  248. newGeometry.components[0].components.length + '.');
  249. }
  250. }
  251.  
  252. function clearFeatureGeometry() {
  253. var newGeometry,
  254. navAction;
  255. var venue = W.selectionManager.getSelectedFeatures()[0].model;
  256. var newEntryExitPoint = {
  257. entry: true,
  258. exit: true
  259. };
  260. var oldGeometry = venue.geometry;
  261.  
  262. if (!W.selectionManager.hasSelectedFeatures() ||
  263. W.selectionManager.getSelectedFeatures()[0].model.type !== 'venue' ||
  264. !W.selectionManager.getSelectedFeatures()[0].model.isGeometryEditable() ||
  265. !(W.selectionManager.getSelectedFeatures()[0].model.geometry instanceof
  266. OpenLayers.Geometry.Polygon)) {
  267. return;
  268. }
  269.  
  270. if (oldGeometry.components[0].components.length > 4) {
  271. newGeometry = oldGeometry.getBounds().toGeometry();
  272. if (newGeometry.getArea() > 160) {
  273. newGeometry.resize(0.5, newGeometry.getCentroid());
  274. }
  275. newEntryExitPoint.point = newGeometry.getCentroid();
  276. W.model.actionManager.add(new UpdateFeatureGeometry(
  277. venue, W.model.venues, oldGeometry, newGeometry));
  278. navAction = new UpdateObject(venue, {
  279. entryExitPoints: [newEntryExitPoint]
  280. });
  281. navAction.eachGeometryField = function (e, t) {
  282. var i,
  283. n,
  284. s,
  285. r,
  286. o;
  287. for (r = e.entryExitPoints, o = [], n = 0, s = r.length;
  288. s > n; n++) {
  289. i = r[n], o.push(t.call(this, 'point', i.point, i));
  290. }
  291. return o;
  292. };
  293. W.model.actionManager.add(navAction);
  294. }
  295. }
  296. simpBootstrap();
  297. } ());