WME Simplify Place Geometry

Simplifies geometry of area places in WME

目前為 2015-03-25 提交的版本,檢視 最新版本

// ==UserScript==
// @name         WME Simplify Place Geometry
// @description  Simplifies geometry of area places in WME
// @version      1.01
// @author       SAR85
// @copyright	 SAR85
// @license		 CC BY-NC-ND
// @grant		 none
// @include      https://www.waze.com/editor/*
// @include      https://www.waze.com/*/editor/*
// @include      https://editor-beta.waze.com/*
// @namespace 	 https://greasyfork.org/users/9321
// ==/UserScript==

/* Global vars */
var simplifyVersion = "1.01";
var simplifyChanges = "WME Simplify Area Geometry has been updated to version " +
	simplifyVersion + ".\n" +
	"*Minor bug fix.";
var simpUpdateFeatureGeometry = require("Waze/Action/UpdateFeatureGeometry");
var simpUpdateObject = require("Waze/Action/UpdateObject");

function simpBootstrap() {
	var bGreasemonkeyServiceDefined = false;
	try {
		if ("object" === typeof Components.interfaces.gmIGreasemonkeyService) {
			bGreasemonkeyServiceDefined = true;
		}
	} catch (err) {
		// Ignore.
	}
	if ("undefined" === typeof unsafeWindow || !bGreasemonkeyServiceDefined) {
		unsafeWindow = (function () {
			var dummyElem = document.createElement('p');
			dummyElem.setAttribute('onclick', 'return window;');
			return dummyElem.onclick();
		})();
	}
	/* begin running the code! */
	window.setTimeout(simpInit, 3000);
	/*doesn't work in FF: $(document).ready(simpInit); */
}

function addSimplifyFunc() {
	/*
	(c) 2013, Vladimir Agafonkin
	Simplify.js, a high-performance JS polyline simplification library
	mourner.github.io/simplify-js
	 */
	(function () {
		'use strict';

		/* // to suit your point format, run search/replace for '.x' and '.y';
		// for 3D version, see 3d branch (configurability would draw significant performance overhead) */

		/* // square distance between 2 points */
		function getSqDist(p1, p2) {

			var dx = p1.x - p2.x,
			dy = p1.y - p2.y;

			return dx * dx + dy * dy;
		}

		/* // square distance from a point to a segment */
		function getSqSegDist(p, p1, p2) {

			var x = p1.x,
			y = p1.y,
			dx = p2.x - x,
			dy = p2.y - y;

			if (dx !== 0 || dy !== 0) {

				var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);

				if (t > 1) {
					x = p2.x;
					y = p2.y;

				} else if (t > 0) {
					x += dx * t;
					y += dy * t;
				}
			}

			dx = p.x - x;
			dy = p.y - y;

			return dx * dx + dy * dy;
		}
		/* // rest of the code doesn't care about point format */

		/* // basic distance-based simplification */
		function simplifyRadialDist(points, sqTolerance) {

			var prevPoint = points[0],
			newPoints = [prevPoint],
			point;

			for (var i = 1, len = points.length; i < len; i++) {
				point = points[i];

				if (getSqDist(point, prevPoint) > sqTolerance) {
					newPoints.push(point);
					prevPoint = point;
				}
			}

			if (prevPoint !== point)
				newPoints.push(point);

			return newPoints;
		}

		/* // simplification using optimized Douglas-Peucker algorithm with recursion elimination */
		function simplifyDouglasPeucker(points, sqTolerance) {

			var len = points.length,
			MarkerArray = typeof Uint8Array !== 'undefined' ? Uint8Array : Array,
			markers = new MarkerArray(len),
			first = 0,
			last = len - 1,
			stack = [],
			newPoints = [],
			i,
			maxSqDist,
			sqDist,
			index;

			markers[first] = markers[last] = 1;
			while (last) {

				maxSqDist = 0;

				for (i = first + 1; i < last; i++) {
					sqDist = getSqSegDist(points[i], points[first], points[last]);

					if (sqDist > maxSqDist) {
						index = i;
						maxSqDist = sqDist;
					}
				}

				if (maxSqDist > sqTolerance) {
					markers[index] = 1;
					stack.push(first, index, index, last);
				}

				last = stack.pop();
				first = stack.pop();
			}

			for (i = 0; i < len; i++) {
				if (markers[i])
					newPoints.push(points[i]);
			}

			return newPoints;
		}

		/* // both algorithms combined for awesome performance */
		function simplify(points, tolerance, highestQuality) {

			if (points.length <= 1)
				return points;

			var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;

			points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
			points = simplifyDouglasPeucker(points, sqTolerance);

			return points;
		}

		/* // export as AMD module / Node module / browser or worker variable */
		if (typeof define === 'function' && define.amd)
			define(function () {
				return simplify;
			});
		else if (typeof module !== 'undefined')
			module.exports = simplify;
		else if (typeof self !== 'undefined')
			self.simplify = simplify;
		else
			window.simplify = simplify;

	})();
}

function simpInit() {
	/* HTML */
	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>';
	var css = {
		"display" : "none",
		"position" : "absolute",
		"top" : "120px",
		"left" : "73px",
		"padding" : "4px",
		"background-color" : "rgba(0,0,0,0.8)",
		"border-radius" : "5px",
		"border" : "none",
		"color" : "white",
		"font-size" : "0.9em"
	};

	/* Initialize simplification library */
	addSimplifyFunc();

	/* Add HTML to page and initialize*/
	$('#map').append(content);
	$('#simplifyarea').css(css);
	$('#simpE').val(localStorage.simplifyE || '5');
	$('#simplifylink').click(simplifyFeatureGeometry);
	$('#clearlink').click(clearFeatureGeometry);
	try {
		$('#simplifyarea').draggable();
	} catch (err) {}

	/* Event listeners */
	$('#simplifyhelp').click(function () {
		alert('To use WME Simplify Place Geometry: \n' +
			'1. Select an area place \n' +
			'2. Select an appropriate simplification factor (usually 5-10) \n' +
			'3. Click the link to simplify or clear the geometry');
	});
	$('#simpE').change(function () {
		localStorage.simplifyE = $('#simpE').val();
	});
	W.selectionManager.events.register("selectionchanged", null, function () {
		if (W.selectionManager.hasSelectedItems()) {
			var selectedItem = W.selectionManager.selectedItems[0].model;
			if (!(selectedItem.geometry instanceof OpenLayers.Geometry.Polygon))
				return;
			$('#simplifyarea').fadeIn('fast');
		} else {
			$('#simplifyarea').fadeOut('fast');
		}
	});

	/* Add functions to page */
	self.simplifyFeatureGeometry = simplifyFeatureGeometry;
	self.clearFeatureGeometry = clearFeatureGeometry;

	/* Shortcut key = shift+j for simplifying */
	W.accelerators.addAction('simplifyFeatureGeometry', {
		group : "editing"
	});
	W.accelerators.events.register('simplifyFeatureGeometry', null, function () {
		simplifyFeatureGeometry();
	});
	W.accelerators.registerShortcuts({
		'S+j' : "simplifyFeatureGeometry"
	});

	/* Shortcut key = ctrl-shift-j for clearing */
	W.accelerators.addAction('clearFeatureGeometry', {
		group : "editing"
	});
	W.accelerators.events.register('clearFeatureGeometry', null, function () {
		clearFeatureGeometry();
	});
	W.accelerators.registerShortcuts({
		'CS+j' : "clearFeatureGeometry"
	});
	console.log("WME Simplify Area Geometry Initialized");

	/* Update Alert */
	if (window.localStorage.simplifyVersion == 'undefined' || window.localStorage.simplifyVersion !== simplifyVersion) {
		alert(simplifyChanges);
		window.localStorage.simplifyVersion = simplifyVersion;
	}
}

function simplifyFeatureGeometry(e) {
	if (!W.selectionManager.hasSelectedItems() || W.selectionManager.selectedItems[0].model.type !== "venue" || !W.selectionManager.selectedItems[0].model.isGeometryEditable() || !W.selectionManager.selectedItems[0].model.geometry instanceof OpenLayers.Geometry.Polygon)
		return;
	e = $('#simpE').val() || 5;
	var place = W.selectionManager.selectedItems[0];
	var oldGeometry = place.geometry.clone();
	var newGeometry = oldGeometry.clone();
	newGeometry.components[0].components = simplify(oldGeometry.components[0].components, e, false);
	if (newGeometry.components[0].components.length < oldGeometry.components[0].components.length) {
		W.model.actionManager.add(new simpUpdateFeatureGeometry(place.model, W.model.venues, oldGeometry, newGeometry));
		console.log("WME Simplify Area Geometry: " + place.model.attributes.name + " simplified from " + oldGeometry.components[0].components.length + " to " + newGeometry.components[0].components.length + " geo nodes using factor " + e + ".");
	} else {
		console.log("Geo nodes cannot be simplified from " + oldGeometry.components[0].components.length + " to " + newGeometry.components[0].components.length + ".");
	}
}

function clearFeatureGeometry() {
	if (!W.selectionManager.hasSelectedItems() || W.selectionManager.selectedItems[0].model.type !== "venue" || !W.selectionManager.selectedItems[0].model.isGeometryEditable() || !W.selectionManager.selectedItems[0].model.geometry instanceof OpenLayers.Geometry.Polygon)
		return;
	var newGeometry,
	navAction;
	var venue = W.selectionManager.selectedItems[0].model;
	var newEntryExitPoint = {
		entry : true,
		exit : true
	};
	var oldGeometry = venue.geometry;

	if (oldGeometry.components[0].components.length > 4) {
		newGeometry = oldGeometry.getBounds().toGeometry();
		if (newGeometry.getArea() > 160)
			newGeometry.resize(0.5, newGeometry.getCentroid());
		newEntryExitPoint.point = newGeometry.getCentroid();
		W.model.actionManager.add(new simpUpdateFeatureGeometry(venue, W.model.venues, oldGeometry, newGeometry));
		navAction = new simpUpdateObject(venue, {
				entryExitPoints : [newEntryExitPoint]
			});
		navAction.eachGeometryField = function (e, t) {
			var i,
			n,
			s,
			r,
			o;
			for (r = e.entryExitPoints, o = [], n = 0, s = r.length; s > n; n++)
				i = r[n], o.push(t.call(this, "point", i.point, i));
			return o;
		};
		W.model.actionManager.add(navAction);
	}
}

simpBootstrap();