Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion WazeWrap.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// ==UserScript==
// @name WazeWrap
// @namespace https://greasyfork.org/users/30701-justins83-waze
// @version 2025.03.22.00
// @version 2025.06.13.000
// @description A base library for WME script writers
// @author JustinS83/MapOMatic
// @include https://beta.waze.com/*editor*
Expand Down
216 changes: 215 additions & 1 deletion WazeWrapLib.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
RestoreMissingSegmentFunctions();
RestoreMissingNodeFunctions();
RestoreMissingOLKMLSupport();
RestoreMissingWRule();
RestoreMissingWRule();

WazeWrap.Geometry = new Geometry();
WazeWrap.Model = new Model();
Expand Down Expand Up @@ -1563,6 +1563,220 @@
return squareness(points);
};

return Orthogonalize();
};

/**
* Returns orthogonalized geometry for the given geometry and threshold
* @function WazeWrap.Util.GeoJSONOrthogonalizeGeometry
* @param {GeoJSON.Geometry} The SDK/GeoJSON.Geometry to orthogonalize
* @param {integer} threshold to use for orthogonalization - the higher the threshold, the more nodes that will be removed
* @return {GeoJSON.Geometry } Orthogonalized geometry
**/
this.GeoJSONOrthogonalizeGeometry = function (geometry, threshold = 12) {
const nomthreshold = threshold, // degrees within right or straight to alter
lowerThreshold = Math.cos((90 - nomthreshold) * Math.PI / 180),
upperThreshold = Math.cos(nomthreshold * Math.PI / 180);

function Orthogonalize() {
let nodes = structuredClone(geometry[0]),
points = nodes.slice(0, -1).map((n) => {
const p = [...n];
p[1] = lat2latp(p[1]);
return p;
}),
corner = { i: 0, dotp: 1 },
epsilon = 1e-4,
i, j, score, motions;

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

const tmp = addPoints(points[corner.i], motions[corner.i]);
points[corner.i][0] = tmp[0];
points[corner.i][1] = tmp[1];

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

const n = points[corner.i];
n[1] = latp2lat(n[1]);
const pp = n;

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

nodes[i][0] = pp[0];
nodes[i][1] = pp[1];
}

return nodes;
}

const originalPoints = nodes.slice(0, -1).map((n) => {
const p = [...n];
p[1] = lat2latp(p[1]);
return p;
});
score = Number.POSITIVE_INFINITY;

for (i = 0; i < 1000 && !(score < epsilon); i++) {
motions = points.map(calcMotion);
for (j = 0; j < motions.length; j++) {
const tmp = addPoints(points[j], motions[j]);
points[j][0] = tmp[0];
points[j][1] = tmp[1];
}
const newScore = squareness(points);
if (newScore < score) {
// best = [].concat(points);
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][0] !== points[i][0] || originalPoints[i][1] !== points[i][1]) {
const n = points[i];
n[1] = latp2lat(n[1]);
const pp = n;

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

nodes[j][0] = pp[0];
nodes[j][1] = pp[1];
}
}
}

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

nodes[j] = false;
}
}
}

return nodes.filter(item => item !== false);

function calcMotion(b, i, array) {
let 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, [0, 0]), euclideanDistance(q, [0, 0]));
p = normalizePoint(p, 1.0);
q = normalizePoint(q, 1.0);

dotp = filterDotProduct(p[0] * q[0] + p[1] * q[1]);

// nasty hack to deal with almost-straight segments (angle is closer to 180 than to 90/270).
if (array.length > 3) {
if (dotp < -Math.SQRT1_2)
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 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);
}

function squareness(points) {
return points.reduce((sum, _val, i, array) => {
let 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) {
let 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[0] * q[0] + p[1] * q[1];
}

function subtractPoints(a, b) {
return [a[0] - b[0], a[1] - b[1]];
}

function addPoints(a, b) {
return [a[0] + b[0], a[1] + b[1]];
}

function euclideanDistance(a, b) {
const x = a[0] - b[0], y = a[1] - b[1];
return Math.sqrt((x * x) + (y * y));
}

function normalizePoint(point, scale) {
const vector = [0, 0];
const length = Math.sqrt(point[0] * point[0] + point[1] * point[1]);
if (length !== 0) {
vector[0] = point[0] / length;
vector[1] = point[1] / length;
}

vector[0] *= scale;
vector[1] *= scale;

return vector;
}

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

return 0;
}

this.isDisabled = (nodes) => {
const points = nodes.slice(0, -1).map((n) => {
const p = n;
return [p[0], p[1]];
});

return squareness(points);
};

return Orthogonalize();
};

Expand Down