mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-16 13:59:27 +02:00
Limit squaring to near square or near straight nodes
This commit is contained in:
@@ -64,6 +64,7 @@ en:
|
||||
annotation:
|
||||
line: Squared the corners of a line.
|
||||
area: Squared the corners of an area.
|
||||
not_squarish: This can't be made square because it is not squarish.
|
||||
straighten:
|
||||
title: Straighten
|
||||
description: Straighten this line.
|
||||
|
||||
Vendored
+2
-1
@@ -83,7 +83,8 @@
|
||||
"annotation": {
|
||||
"line": "Squared the corners of a line.",
|
||||
"area": "Squared the corners of an area."
|
||||
}
|
||||
},
|
||||
"not_squarish": "This can't be made square because it is not squarish."
|
||||
},
|
||||
"straighten": {
|
||||
"title": "Straighten",
|
||||
|
||||
@@ -3,21 +3,24 @@
|
||||
*/
|
||||
|
||||
iD.actions.Orthogonalize = function(wayId, projection) {
|
||||
var threshold = 7, // degrees within right or straight to alter
|
||||
lowerThreshold = Math.cos((90 - threshold) * Math.PI / 180),
|
||||
upperThreshold = Math.cos(threshold * Math.PI / 180);
|
||||
|
||||
var action = function(graph) {
|
||||
var way = graph.entity(wayId),
|
||||
nodes = graph.childNodes(way),
|
||||
points = _.uniq(nodes).map(function(n) { return projection(n.loc); }),
|
||||
corner = {i: 0, dotp: 1},
|
||||
epsilon = 1e-8,
|
||||
points, i, j, score, motions;
|
||||
epsilon = 1e-4,
|
||||
i, j, score, motions;
|
||||
|
||||
if (nodes.length === 4) {
|
||||
points = _.uniq(nodes).map(function(n) { return projection(n.loc); });
|
||||
|
||||
for (i = 0; i < 1000; i++) {
|
||||
motions = points.map(calcMotion);
|
||||
points[corner.i] = addPoints(points[corner.i],motions[corner.i]);
|
||||
score = corner.dotp;
|
||||
if (score < 1.0e-8) {
|
||||
if (score < epsilon) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -25,8 +28,8 @@ iD.actions.Orthogonalize = function(wayId, projection) {
|
||||
graph = graph.replace(graph.entity(nodes[corner.i].id)
|
||||
.move(projection.invert(points[corner.i])));
|
||||
} else {
|
||||
var best;
|
||||
points = _.uniq(nodes).map(function(n) { return projection(n.loc); });
|
||||
var best,
|
||||
originalPoints = _.clone(points);
|
||||
score = Infinity;
|
||||
|
||||
for (i = 0; i < 1000; i++) {
|
||||
@@ -34,7 +37,7 @@ iD.actions.Orthogonalize = function(wayId, projection) {
|
||||
for (j = 0; j < motions.length; j++) {
|
||||
points[j] = addPoints(points[j],motions[j]);
|
||||
}
|
||||
var newScore = squareness();
|
||||
var newScore = squareness(points);
|
||||
if (newScore < score) {
|
||||
best = _.clone(points);
|
||||
score = newScore;
|
||||
@@ -47,8 +50,11 @@ iD.actions.Orthogonalize = function(wayId, projection) {
|
||||
points = best;
|
||||
|
||||
for (i = 0; i < points.length; i++) {
|
||||
graph = graph.replace(graph.entity(nodes[i].id)
|
||||
.move(projection.invert(points[i])));
|
||||
// only move the points that actually moved
|
||||
if (originalPoints[i][0] != points[i][0] || originalPoints[i][1] != points[i][1]) {
|
||||
graph = graph.replace(graph.entity(nodes[i].id)
|
||||
.move(projection.invert(points[i])));
|
||||
}
|
||||
}
|
||||
|
||||
// remove empty nodes on straight sections
|
||||
@@ -63,13 +69,7 @@ iD.actions.Orthogonalize = function(wayId, projection) {
|
||||
continue;
|
||||
}
|
||||
|
||||
a = points[(i - 1 + points.length) % points.length];
|
||||
b = points[i];
|
||||
c = points[(i + 1) % points.length];
|
||||
p = normalizePoint(subtractPoints(a, b), 1.0);
|
||||
q = normalizePoint(subtractPoints(c, b), 1.0);
|
||||
dotp = p[0] * q[0] + p[1] * q[1];
|
||||
|
||||
dotp = normalizedDotProduct(i, points);
|
||||
if (dotp < -1 + epsilon) {
|
||||
graph = iD.actions.DeleteNode(nodes[i].id)(graph);
|
||||
}
|
||||
@@ -82,78 +82,91 @@ iD.actions.Orthogonalize = function(wayId, projection) {
|
||||
var a = array[(i - 1 + array.length) % array.length],
|
||||
c = array[(i + 1) % array.length],
|
||||
p = subtractPoints(a, b),
|
||||
q = subtractPoints(c, b);
|
||||
q = subtractPoints(c, b),
|
||||
scale, dotp;
|
||||
|
||||
var scale = 2*Math.min(iD.geo.euclideanDistance(p, [0, 0]), iD.geo.euclideanDistance(q, [0, 0]));
|
||||
scale = 2 * Math.min(iD.geo.euclideanDistance(p, [0, 0]), iD.geo.euclideanDistance(q, [0, 0]));
|
||||
p = normalizePoint(p, 1.0);
|
||||
q = normalizePoint(q, 1.0);
|
||||
|
||||
var dotp = p[0] * q[0] + p[1] * q[1];
|
||||
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 < -0.707106781186547) {
|
||||
dotp += 1.0;
|
||||
}
|
||||
} else if (Math.abs(dotp) < corner.dotp) {
|
||||
} 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() {
|
||||
var g = 0.0;
|
||||
for (var i = 1; i < points.length - 1; i++) {
|
||||
var score = scoreOfPoints(points[i - 1], points[i], points[i + 1]);
|
||||
g += score;
|
||||
}
|
||||
var startScore = scoreOfPoints(points[points.length - 1], points[0], points[1]);
|
||||
var endScore = scoreOfPoints(points[points.length - 2], points[points.length - 1], points[0]);
|
||||
g += startScore;
|
||||
g += endScore;
|
||||
return g;
|
||||
}
|
||||
|
||||
function scoreOfPoints(a, b, c) {
|
||||
var p = subtractPoints(a, b),
|
||||
q = subtractPoints(c, b);
|
||||
|
||||
p = normalizePoint(p, 1.0);
|
||||
q = normalizePoint(q, 1.0);
|
||||
|
||||
var dotp = p[0] * q[0] + p[1] * q[1];
|
||||
// score is constructed so that +1, -1 and 0 are all scored 0, any other angle
|
||||
// is scored higher.
|
||||
return 2.0 * Math.min(Math.abs(dotp - 1.0), Math.min(Math.abs(dotp), Math.abs(dotp + 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 normalizePoint(point, scale) {
|
||||
var vector = [0, 0];
|
||||
var 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 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[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 normalizePoint(point, scale) {
|
||||
var vector = [0, 0];
|
||||
var 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;
|
||||
}
|
||||
|
||||
action.disabled = function(graph) {
|
||||
return false;
|
||||
var way = graph.entity(wayId),
|
||||
nodes = graph.childNodes(way),
|
||||
points = _.uniq(nodes).map(function(n) { return projection(n.loc); });
|
||||
|
||||
if (squareness(points)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return 'not_squarish';
|
||||
};
|
||||
|
||||
return action;
|
||||
|
||||
@@ -106,4 +106,19 @@ describe("iD.actions.Orthogonalize", function () {
|
||||
expect(finalWidth / initialWidth).within(0.90, 1.10);
|
||||
}
|
||||
});
|
||||
|
||||
it("only moves nodes which are near right or near straight", function() {
|
||||
var graph = iD.Graph({
|
||||
'a': iD.Node({id: 'a', loc: [0, 0]}),
|
||||
'b': iD.Node({id: 'b', loc: [3, 0.001]}),
|
||||
'c': iD.Node({id: 'c', loc: [3, 1]}),
|
||||
'd': iD.Node({id: 'd', loc: [2, 1]}),
|
||||
'e': iD.Node({id: 'e', loc: [1, 2]}),
|
||||
'f': iD.Node({id: 'f', loc: [0, 2]}),
|
||||
'-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'a']})
|
||||
}),
|
||||
diff = iD.Difference(graph, iD.actions.Orthogonalize('-', projection)(graph));
|
||||
|
||||
expect(Object.keys(diff.changes()).sort()).to.eql(['a', 'b', 'c', 'f']);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user