WME MagicPlaces

Clone, Orthogonalize (mini version from MagicPlaces)

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name                WME MagicPlaces
// @description         Clone, Orthogonalize (mini version from MagicPlaces)
// @include             https://www.waze.com/editor/*
// @include             https://www.waze.com/*/editor/*
// @include             https://beta.waze.com/*
// @version             1.1.1
// @grant               none
// @license             CC BY 4.0
// @namespace https://greasyfork.org/users/68507
// ==/UserScript==


function run_magicwand() {
	var wmelmw_version = "1.1.0";

	/* bootstrap, will call initialiseHighlights() */
	function bootstraMagicPlaces() {
		var bGreasemonkeyServiceDefined = false;

		/* begin running the code! */
		setTimeout(initialiseMagicPlaces, 500);
	}

	/* helper function */
	function getElClass(classname, node) {
		if (!node) node = document.getElementsByTagName("body")[0];
		var a = [];
		var re = new RegExp('\\b' + classname + '\\b');
		var els = node.getElementsByTagName("*");
		for (var i = 0, j = els.length; i < j; i++)
			if (re.test(els[i].className)) a.push(els[i]);
		return a;
	}

	function getElId(node) {
		return document.getElementById(node);
	}

	/* =========================================================================== */

	function initialiseMagicPlaces() {
		try {
			if (!((typeof window.Waze.map != undefined) && (undefined != typeof window.Waze.map.events.register) && (undefined != typeof window.Waze.selectionManager.events.register ) && (undefined != typeof window.Waze.loginManager.events.register) )) {
				setTimeout(initialiseMagicPlaces, 1000);
				return;
			}
		} catch (err) {
			setTimeout(initialiseMagicPlaces, 1000);
			return;
		}

		var userInfo = getElId('user-info');
		var userTabs = getElId('user-tabs');

		if(!getElClass('nav-tabs', userTabs)[0]) {
			setTimeout(initialiseMagicPlaces, 1000);
			return;
		}

		var navTabs = getElClass('nav-tabs', userTabs)[0];
		var tabContent = getElClass('tab-content', userInfo)[0];

		var newtab = document.createElement('li');
		newtab.innerHTML = '<a href="#sidepanel-magicwand" data-toggle="tab">MagicPlaces</a>';
		navTabs.appendChild(newtab);

		 // add new box to left of the map
		var addon = document.createElement('section');
		addon.innerHTML = '<b>WME Magic Wand</b> v' + wmelmw_version + '<br>'
			+ '<label>Максимальный угол <input type="text" id="_cMagicPlacesAngleThreshold" name="_cMagicPlacesAngleThreshold" value="12" size="3" maxlength="2" /></label><br/>'
			+ 'Значение, на которое скрипт может исправить угол, если для выпремления тербуется больше - не меняет (по умолчанию 12)<br><br>'
			+ '<label>Степень выпрямления <input type="text" id="_cMagicPlacesSimplification" name="_cMagicPlacesSimplification" value="3" size="5" maxlength="4" /></label><br/><br/>'
			+ 'Значение угла со значение котрого имли меньше, убираются узлы рекомендовано от 0 до 5 (по умолчанию 4)<br><br>';

		addon.id = "sidepanel-magicwand";
		addon.className = "tab-pane";
		tabContent.appendChild(addon);

		loadWMEMagicPlacesSettings();

		// Event listeners
		Waze.selectionManager.events.register("selectionchanged", null, insertLandmarkSelectedButtons);
		window.addEventListener("beforeunload", saveWMEMagicPlacesOptions, false);

		// Hotkeys
		registerKeyShortcut("WMEMagicPlaces_CloneLandmark", "Clone Landmark", cloneLandmark, {"C+c": "WMEMagicPlaces_CloneLandmark"});
		registerKeyShortcut("WMEMagicPlaces_OrthogonalizeLandmark", "Orthogonalize Landmark", Orthogonalize, {"C+x": "WMEMagicPlaces_OrthogonalizeLandmark"});
		registerKeyShortcut("WMEMagicPlaces_SimplifyLandmark", "Simplify Landmark", simplifySelectedLandmark, {"C+j": "WMEMagicPlaces_SimplifyLandmark"});
	}


	function registerKeyShortcut(action_name, annotation, callback, key_map) {
		Waze.accelerators.addAction(action_name, {group: 'default'});
		Waze.accelerators.events.register(action_name, null, callback);
		Waze.accelerators._registerShortcuts(key_map);
	}

	function loadWMEMagicPlacesSettings () {
		if (localStorage.WMEMagicPlacesScript) {
			console.log("WME MagicPlaces: loading options");
			var options = JSON.parse(localStorage.WMEMagicPlacesScript);

			getElId('_cMagicPlacesAngleThreshold').value = typeof options[0] != 'undefined' ? options[0] : 12;
			getElId('_cMagicPlacesSimplification').value = typeof options[1] != 'undefined' ? options[1] : 4;
		}
	}

	function saveWMEMagicPlacesOptions() {
		if (localStorage) {
			console.log("WME MagicPlaces: saving options");
			var options = [];

			// preserve previous options which may get lost after logout
			if (localStorage.WMEMagicPlacesScript)
				options = JSON.parse(localStorage.WMEMagicPlacesScript);

			options[0] = getElId('_cMagicPlacesAngleThreshold').value;
			options[1] = getElId('_cMagicPlacesSimplification').value;

			localStorage.WMEMagicPlacesScript = JSON.stringify(options);
		}
	}

	var insertLandmarkSelectedButtons = function(e)
	{
		if(Waze.selectionManager.selectedItems.length == 0 || Waze.selectionManager.selectedItems[0].model.type != 'venue') return;
		if(getElId('_bMagicPlacesEdit_CloneLandmark') != null) return;

		$('#landmark-edit-general').prepend(
			'<div class="form-group"> \
			  <div class="controls"> \
				<input style="padding: 6px 8px;" type="button" id="_bMagicPlacesEdit_CloneLandmark" name="_bMagicPlacesEdit_CloneLandmark" class="btn btn-default" value="Клонировать" title="Ctrl+C (default)" /> \
				<input style="padding: 6px 8px;" type="button" id="_bMagicPlacesEdit_Corners" name="_bMagicPlacesEdit_Corners" class="btn btn-default" value="Выровнять" title="Ctrl+X (default)"/>\
				<input style="padding: 6px 8px;" type="button" id="_bMagicPlacesEdit_Simplify" name="_bMagicPlacesEdit_Simplify" class="btn btn-default" value="Упростить" title="Ctrl+J (default)"/>\
			  </div> \
			</div>'
		);

		$('#_bMagicPlacesEdit_CloneLandmark').click(cloneLandmark);
		$('#_bMagicPlacesEdit_Corners').click(Orthogonalize);
		$('#_bMagicPlacesEdit_Simplify').click(simplifySelectedLandmark);
	};

	var simplifySelectedLandmark = function () {
		var selectorManager = Waze.selectionManager;
		if (!selectorManager.hasSelectedItems() || selectorManager.selectedItems[0].model.type !== "venue" || !selectorManager.selectedItems[0].model.isGeometryEditable()) {
			return;
		}
		var simplifyFactor = $('#_cMagicPlacesSimplification').val();
		var SelectedLandmark = selectorManager.selectedItems[0];
		var oldGeometry = SelectedLandmark.geometry.clone();

		var LineString = new OpenLayers.Geometry.LineString(oldGeometry.components[0].components);
		LineString = LineString.simplify(simplifyFactor);
		var newGeometry = new OpenLayers.Geometry.Polygon(new OpenLayers.Geometry.LinearRing(LineString.components));

		if (newGeometry.components[0].components.length < oldGeometry.components[0].components.length) {
			var UpdateFeatureGeometry = require("Waze/Action/UpdateFeatureGeometry");
			W.model.actionManager.add(new UpdateFeatureGeometry(SelectedLandmark.model, W.model.venues, oldGeometry, newGeometry));
		}
	};

	var cloneLandmark = function () {
		var selectorManager = Waze.selectionManager;
		if (!selectorManager.hasSelectedItems() || selectorManager.selectedItems[0].model.type != 'venue') {
			return;
		}

		var SelectedLandmark = selectorManager.selectedItems[0];
		var ClonedLandmark = SelectedLandmark.clone();
		ClonedLandmark.geometry.move(50, 50); // move to some offset
		ClonedLandmark.geometry.clearBounds();

		var wazefeatureVectorLandmark = require("Waze/Feature/Vector/Landmark");
		var wazeActionAddLandmark = require("Waze/Action/AddLandmark");

		var NewLandmark = new wazefeatureVectorLandmark();
		NewLandmark.geometry = ClonedLandmark.geometry;
		NewLandmark.attributes.categories = SelectedLandmark.model.attributes.categories;

		Waze.model.actionManager.add(new wazeActionAddLandmark(NewLandmark));
		selectorManager.select([NewLandmark]);
	};

	var Orthogonalize = function() {
		if (Waze.selectionManager.selectedItems.length <= 0 || Waze.selectionManager.selectedItems[0].model.type != 'venue') {
			return;
		}

		var SelectedLandmark = Waze.selectionManager.selectedItems[0];

		var geom = SelectedLandmark.geometry.clone();
		var components = geom.components[0].components;
		var functor = new OrthogonalizeId(components);

		var newWay = functor.action();
		var wazeActionUpdateFeatureGeometry = require("Waze/Action/UpdateFeatureGeometry");

		var removeVertices = [];
		var undoGeometry = SelectedLandmark.geometry.clone();
		for (var i = 0; i < newWay.length; i++) {
			if (newWay[i] === false) {
				removeVertices.push(SelectedLandmark.geometry.components[0].components[i]);
			} else {
				SelectedLandmark.geometry.components[0].components[i].x = newWay[i].x;
				SelectedLandmark.geometry.components[0].components[i].y = newWay[i].y;
			}
		}

		if (removeVertices) {
			SelectedLandmark.geometry.components[0].removeComponents(removeVertices);
		}

		SelectedLandmark.geometry.components[0].clearBounds();

		var action = new wazeActionUpdateFeatureGeometry(SelectedLandmark.model, Waze.model.venues, undoGeometry, SelectedLandmark.geometry);
		Waze.model.actionManager.add(action);

		delete undoGeometry;
	};

	var OrthogonalizeId = function (way) {
		var threshold = getElId('_cMagicPlacesAngleThreshold').value, // degrees within right or straight to alter
			lowerThreshold = Math.cos((90 - threshold) * Math.PI / 180),
			upperThreshold = Math.cos(threshold * Math.PI / 180);

		this.way = way;

		this.action = function () {
			var nodes = this.way,
				points = nodes.slice(0, nodes.length - 1).map(function (n) {
					var t = n.clone();
					var p = t.transform(new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326"));
					p.y = lat2latp(p.y);
					return p;
				}),
				corner = {i: 0, dotp: 1},
				epsilon = 1e-4,
				i, j, score, motions;

			// Triangle
			if (nodes.length === 4) {
				for (i = 0; i < 1000; i++) {
					motions = points.map(calcMotion);

					var tmp = addPoints(points[corner.i], motions[corner.i]);
					points[corner.i].x = tmp.x;
					points[corner.i].y = tmp.y;

					score = corner.dotp;
					if (score < epsilon) {
						break;
					}
				}

				var n = points[corner.i];
				n.y = latp2lat(n.y);
				var pp = n.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));

				var id = nodes[corner.i].id;
				for (i = 0; i < nodes.length; i++) {
					if (nodes[i].id != id) {
						continue;
					}

					nodes[i].x = pp.x;
					nodes[i].y = pp.y;
				}

				return nodes;
			} else {
				var best,
					originalPoints = nodes.slice(0, nodes.length - 1).map(function (n) {
						var t = n.clone();
						var p = t.transform(new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326"));
						p.y = lat2latp(p.y);
						return p;
					});
					score = Infinity;

				for (i = 0; i < 1000; i++) {
					motions = points.map(calcMotion);
					for (j = 0; j < motions.length; j++) {
						var tmp = addPoints(points[j], motions[j]);
						points[j].x = tmp.x;
						points[j].y = tmp.y;
					}
					var newScore = squareness(points);
					if (newScore < score) {
						best = points.clone();
						score = newScore;
					}
					if (score < epsilon) {
						break;
					}
				}

				points = best;

				for (i = 0; i < points.length; i++) {
					// only move the points that actually moved
					if (originalPoints[i].x !== points[i].x || originalPoints[i].y !== points[i].y) {
						var n = points[i];
						n.y = latp2lat(n.y);
						var pp = n.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));

						var id = nodes[i].id;
						for (j = 0; j < nodes.length; j++) {
							if (nodes[j].id != id) {
								continue;
							}

							nodes[j].x = pp.x;
							nodes[j].y = pp.y;
						}
					}
				}

				// remove empty nodes on straight sections
				for (i = 0; i < points.length; i++) {
					var dotp = normalizedDotProduct(i, points);
					if (dotp < -1 + epsilon) {
						id = nodes[i].id;
						for (j = 0; j < nodes.length; j++) {
							if (nodes[j].id != id) {
								continue;
							}

							nodes[j] = false;
						}
					}
				}

				return nodes;
			}

			function calcMotion(b, i, array) {
				var a = array[(i - 1 + array.length) % array.length],
					c = array[(i + 1) % array.length],
					p = subtractPoints(a, b),
					q = subtractPoints(c, b),
					scale, dotp;

				scale = 2 * Math.min(euclideanDistance(p, {x: 0, y: 0}), euclideanDistance(q, {x: 0, y: 0}));
				p = normalizePoint(p, 1.0);
				q = normalizePoint(q, 1.0);

				dotp = filterDotProduct(p.x * q.x + p.y * q.y);

				// nasty hack to deal with almost-straight segments (angle is closer to 180 than to 90/270).
				if (array.length > 3) {
					if (dotp < -0.707106781186547) {
						dotp += 1.0;
					}
				} else if (dotp && Math.abs(dotp) < corner.dotp) {
					corner.i = i;
					corner.dotp = Math.abs(dotp);
				}

				return normalizePoint(addPoints(p, q), 0.1 * dotp * scale);
			}
		};

		function squareness(points) {
			return points.reduce(function (sum, val, i, array) {
				var dotp = normalizedDotProduct(i, array);

				dotp = filterDotProduct(dotp);
				return sum + 2.0 * Math.min(Math.abs(dotp - 1.0), Math.min(Math.abs(dotp), Math.abs(dotp + 1)));
			}, 0);
		}

		function normalizedDotProduct(i, points) {
			var a = points[(i - 1 + points.length) % points.length],
				b = points[i],
				c = points[(i + 1) % points.length],
				p = subtractPoints(a, b),
				q = subtractPoints(c, b);

			p = normalizePoint(p, 1.0);
			q = normalizePoint(q, 1.0);

			return p.x * q.x + p.y * q.y;
		}

		function subtractPoints(a, b) {
			return {x: a.x - b.x, y: a.y - b.y};
		}

		function addPoints(a, b) {
			return {x: a.x + b.x, y: a.y + b.y};
		}

		function euclideanDistance(a, b) {
			var x = a.x - b.x, y = a.y - b.y;
			return Math.sqrt((x * x) + (y * y));
		}

		function normalizePoint(point, scale) {
			var vector = {x: 0, y: 0};
			var length = Math.sqrt(point.x * point.x + point.y * point.y);
			if (length !== 0) {
				vector.x = point.x / length;
				vector.y = point.y / length;
			}

			vector.x *= scale;
			vector.y *= scale;

			return vector;
		}

		function filterDotProduct(dotp) {
			if (lowerThreshold > Math.abs(dotp) || Math.abs(dotp) > upperThreshold) {
				return dotp;
			}

			return 0;
		}

		this.isDisabled = function (nodes) {
			var points = nodes.slice(0, nodes.length - 1).map(function (n) {
				var p = n.toLonLat().transform(new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326"));
				return {x: p.lat, y: p.lon};
			});

			return squareness(points);
		};
	};

	function lat2latp(lat) {
		return 180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * (Math.PI / 180) / 2));
	}

	function latp2lat(a) {
		return 180 / Math.PI * (2 * Math.atan(Math.exp(a * Math.PI / 180)) - Math.PI / 2);
	}

	// Point class
	function Point(x, y) {
		this.x = x;
		this.y = y;

		this.toString = function () {
			return "x: " + x + ", y: " + y;
		};
		this.rotateRight = function (p1, p2) {
			// cross product, + is counterclockwise, - is clockwise
			return ((p2.x * y - p2.y * x) - (p1.x * y - p1.y * x) + (p1.x * p2.y - p1.y * p2.x)) < 0;
		};
	}

	Point.prototype.add = function(v){
		return new Point(this.x + v.x, this.y + v.y);
	};
	Point.prototype.clone = function(){
		return new Point(this.x, this.y);
	};
	Point.prototype.degreesTo = function(v){
		var dx = this.x - v.x;
		var dy = this.y - v.y;
		var angle = Math.atan2(dy, dx); // radians
		return angle * (180 / Math.PI); // degrees
	};
	Point.prototype.distance = function(v){
		var x = this.x - v.x;
		var y = this.y - v.y;
		return Math.sqrt(x * x + y * y);
	};
	Point.prototype.equals = function(toCompare){
		return this.x == toCompare.x && this.y == toCompare.y;
	};
	Point.prototype.interpolate = function(v, f){
		return new Point((this.x + v.x) * f, (this.y + v.y) * f);
	};
	Point.prototype.length = function(){
		return Math.sqrt(this.x * this.x + this.y * this.y);
	};
	Point.prototype.normalize = function(thickness){
		var l = this.length();
		this.x = this.x / l * thickness;
		this.y = this.y / l * thickness;
	};
	Point.prototype.orbit = function(origin, arcWidth, arcHeight, degrees){
		var radians = degrees * (Math.PI / 180);
		this.x = origin.x + arcWidth * Math.cos(radians);
		this.y = origin.y + arcHeight * Math.sin(radians);
	};
	Point.prototype.offset = function(dx, dy){
		this.x += dx;
		this.y += dy;
	};
	Point.prototype.subtract = function(v){
		return new Point(this.x - v.x, this.y - v.y);
	};
	Point.prototype.toString = function(){
		return "(x=" + this.x + ", y=" + this.y + ")";
	};

	Point.interpolate = function(pt1, pt2, f){
		return new Point((pt1.x + pt2.x) * f, (pt1.y + pt2.y) * f);
	};
	Point.polar = function(len, angle){
		return new Point(len * Math.cos(angle), len * Math.sin(angle));
	};
	Point.distance = function(pt1, pt2){
		var x = pt1.x - pt2.x;
		var y = pt1.y - pt2.y;
		return Math.sqrt(x * x + y * y);
	};

	/* engage! =================================================================== */
	bootstraMagicPlaces();
}

/* end ======================================================================= */

var DLscript = document.createElement("script");
DLscript.textContent = run_magicwand.toString() + ' \n' + 'run_magicwand();';
DLscript.setAttribute("type", "application/javascript");
document.body.appendChild(DLscript);