WME MagicPlaces

Clone, Orthogonalize (mini version from MagicPlaces)

// ==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://gf.qytechs.cn/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);

QingJ © 2025

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