Limit squaring to near square or near straight nodes

This commit is contained in:
Paul Mach
2013-09-27 09:40:24 -07:00
parent 75b6ec746b
commit 0e080b3ed6
4 changed files with 101 additions and 71 deletions
+1
View File
@@ -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.
+2 -1
View File
@@ -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",
+83 -70
View File
@@ -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;
+15
View File
@@ -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']);
});
});