Merge pull request #612 from porjo/master

Added orthogonalize function
This commit is contained in:
John Firebaugh
2013-02-04 09:14:49 -08:00
5 changed files with 177 additions and 0 deletions
+2
View File
@@ -85,6 +85,7 @@
<script src='js/id/actions/move_node.js'></script>
<script src='js/id/actions/move_way.js'></script>
<script src='js/id/actions/circularize.js'></script>
<script src='js/id/actions/orthogonalize.js'></script>
<script src='js/id/actions/noop.js'></script>
<script src='js/id/actions/reverse.js'></script>
<script src='js/id/actions/split.js'></script>
@@ -112,6 +113,7 @@
<script src='js/id/operations.js'></script>
<script src='js/id/operations/circularize.js'></script>
<script src='js/id/operations/orthogonalize.js'></script>
<script src='js/id/operations/delete.js'></script>
<script src='js/id/operations/disconnect.js'></script>
<script src='js/id/operations/merge.js'></script>
+139
View File
@@ -0,0 +1,139 @@
iD.actions.Orthogonalize = function(wayId, projection) {
var action = function(graph) {
var way = graph.entity(wayId),
nodes = graph.childNodes(way);
var points = nodes.map(function(n) {
return projection(n.loc);
}),
quad_nodes = [];
var score = squareness();
for (var i = 0; i < 1000; i++) {
var motions = points.map(stepMap);
for (var j = 0; j < motions.length; j++) {
points[j] = addPoints(points[j],motions[j]);
}
var newScore = squareness();
if (newScore > score) {
return graph;
}
score = newScore;
if (score < 1.0e-8) {
break;
}
}
for (i = 0; i < points.length - 1; i++) {
quad_nodes.push(iD.Node({ loc: projection.invert(points[i]) }));
}
for (i = 0; i < nodes.length; i++) {
if (graph.parentWays(nodes[i]).length > 1) {
var closest, closest_dist = Infinity, dist;
for (var j = 0; j < quad_nodes.length; j++) {
dist = iD.geo.dist(quad_nodes[j].loc, nodes[i].loc);
if (dist < closest_dist) {
closest_dist = dist;
closest = j;
}
}
quad_nodes.splice(closest, 1, nodes[i]);
}
}
for (i = 0; i < quad_nodes.length; i++) {
graph = graph.replace(quad_nodes[i]);
}
var ids = _.pluck(quad_nodes, 'id'),
difference = _.difference(_.uniq(way.nodes), ids);
ids.push(ids[0]);
graph = graph.replace(way.update({nodes: ids}));
for (i = 0; i < difference.length; i++) {
graph = iD.actions.DeleteNode(difference[i])(graph);
}
return graph;
function stepMap(b, i, array) {
var a, c, p, q = [];
a = array[(i - 1 + array.length) % array.length];
c = array[(i + 1) % array.length];
p = subtractPoints(a, b);
q = subtractPoints(c, b);
var scale = p.length + q.length;
p = normalizePoint(p, 1.0);
q = normalizePoint(q, 1.0);
var dotp = 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 (dotp < -0.707106781186547) {
dotp += 1.0;
}
var v = [];
v = addPoints(p, q);
v = normalizePoint(v, 0.1 * dotp * scale);
return v;
}
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, q = [];
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.
var score = 2.0 * Math.min(Math.abs(dotp - 1.0), Math.min(Math.abs(dotp), Math.abs(dotp + 1)));
return score;
}
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, thickness) {
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] *= thickness;
vector[1] *= thickness;
return vector;
}
};
action.enabled = function(graph) {
return graph.entity(wayId).isClosed();
};
return action;
};
+25
View File
@@ -0,0 +1,25 @@
iD.operations.Orthogonalize = function(selection, context) {
var entityId = selection[0],
action = iD.actions.Orthogonalize(entityId, context.projection);
var operation = function() {
var annotation = t('operations.orthogonalize.annotation.' + context.geometry(entityId));
context.perform(action, annotation);
};
operation.available = function() {
return selection.length === 1 &&
context.entity(entityId).type === 'way';
};
operation.enabled = function() {
return action.enabled(context.graph());
};
operation.id = "orthogonalize";
operation.key = t('operations.orthogonalize.key');
operation.title = t('operations.orthogonalize.title');
operation.description = t('operations.orthogonalize.description');
return operation;
};
+9
View File
@@ -65,6 +65,15 @@ locale.en = {
area: "Made an area circular."
}
},
orthogonalize: {
title: "Orthogonalize",
description: "Square these corners.",
key: "Q",
annotation: {
line: "Squared the corners of a line.",
area: "Squared the corners of an area."
}
},
delete: {
title: "Delete",
description: "Remove this from the map.",
+2
View File
@@ -73,6 +73,7 @@
<script src='../js/id/actions/add_vertex.js'></script>
<script src='../js/id/actions/change_tags.js'></script>
<script src='../js/id/actions/circularize.js'></script>
<script src='../js/id/actions/orthogonalize.js'></script>
<script src="../js/id/actions/delete_multiple.js"></script>
<script src="../js/id/actions/delete_node.js"></script>
<script src="../js/id/actions/delete_relation.js"></script>
@@ -108,6 +109,7 @@
<script src='../js/id/operations.js'></script>
<script src='../js/id/operations/circularize.js'></script>
<script src='../js/id/operations/orthogonalize.js'></script>
<script src='../js/id/operations/delete.js'></script>
<script src='../js/id/operations/disconnect.js'></script>
<script src='../js/id/operations/merge.js'></script>