Merge pull request #1826 from paulmach/straighten

Add straighten operation and action
This commit is contained in:
Tom MacWright
2013-09-20 14:04:23 -07:00
6 changed files with 164 additions and 0 deletions

View File

@@ -63,6 +63,13 @@ en:
line: Squared the corners of a line.
area: Squared the corners of an area.
not_closed: This can't be made square because it's not a loop.
straighten:
title: Straighten
description: Straighten this line.
key: S
annotation: Straightened the line.
is_closed: This can't be straightened because it's a loop.
too_bendy: This can't be straightened because it's too bendy.
delete:
title: Delete
description: Remove this from the map.

View File

@@ -146,6 +146,7 @@
<script src='js/id/actions/rotate_way.js'></script>
<script src='js/id/actions/circularize.js'></script>
<script src='js/id/actions/orthogonalize.js'></script>
<script src='js/id/actions/straighten.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>
@@ -179,6 +180,7 @@
<script src='js/id/operations/continue.js'></script>
<script src='js/id/operations/circularize.js'></script>
<script src='js/id/operations/orthogonalize.js'></script>
<script src='js/id/operations/straighten.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>

View File

@@ -0,0 +1,75 @@
/*
* Based on https://github.com/openstreetmap/potlatch2/net/systemeD/potlatch2/tools/Straighten.as
*/
iD.actions.Straighten = function(wayId, projection) {
function positionAlongWay(n, s, e) {
return ((n[0] - s[0]) * (e[0] - s[0]) + (n[1] - s[1]) * (e[1] - s[1]))/
(Math.pow(e[0] - s[0], 2) + Math.pow(e[1] - s[1], 2));
}
var action = function(graph) {
var way = graph.entity(wayId),
nodes = graph.childNodes(way),
points = nodes.map(function(n) { return projection(n.loc); }),
startPoint = points[0],
endPoint = points[points.length-1],
toDelete = [],
i;
for (i = 1; i < points.length-1; i++) {
var node = nodes[i],
point = points[i];
if (graph.parentWays(node).length > 1 || (node.tags && Object.keys(node.tags).length)) {
var u = positionAlongWay(point, startPoint, endPoint),
p0 = startPoint[0] + u * (endPoint[0] - startPoint[0]),
p1 = startPoint[1] + u * (endPoint[1] - startPoint[1]),
graph = graph.replace(graph.entity(node.id)
.move(projection.invert([p0, p1])));
} else {
// safe to delete
if (toDelete.indexOf(node) == -1) {
toDelete.push(node);
}
}
}
for (i = 0; i < toDelete.length; i++) {
graph = iD.actions.DeleteNode(toDelete[i].id)(graph);
}
return graph;
};
action.disabled = function(graph) {
if (graph.entity(wayId).isClosed()) {
return 'is_closed';
}
// check way isn't too bendy
var way = graph.entity(wayId),
nodes = graph.childNodes(way),
points = nodes.map(function(n) { return projection(n.loc); }),
startPoint = points[0],
endPoint = points[points.length-1],
threshold = 0.2 * Math.sqrt(Math.pow(startPoint[0] - endPoint[0], 2) + Math.pow(startPoint[1] - endPoint[1], 2)),
i;
for (i = 1; i < points.length-1; i++) {
var point = points[i],
u = positionAlongWay(point, startPoint, endPoint),
p0 = startPoint[0] + u * (endPoint[0] - startPoint[0]),
p1 = startPoint[1] + u * (endPoint[1] - startPoint[1]),
dist = Math.sqrt(Math.pow(p0 - point[0], 2) + Math.pow(p1 - point[1], 2));
// to bendy if point is off by 20% of total start/end distance in projected space
if (dist > threshold) {
return 'too_bendy';
}
}
};
return action;
};

View File

@@ -0,0 +1,33 @@
iD.operations.Straighten = function(selectedIDs, context) {
var entityId = selectedIDs[0],
action = iD.actions.Straighten(entityId, context.projection);
var operation = function() {
var annotation = t('operations.straighten.annotation');
context.perform(action, annotation);
};
operation.available = function() {
return selectedIDs.length === 1 &&
context.entity(entityId).type === 'way' &&
_.uniq(context.entity(entityId).nodes).length > 2;
};
operation.disabled = function() {
return action.disabled(context.graph());
};
operation.tooltip = function() {
var disable = operation.disabled();
return disable ?
t('operations.straighten.' + disable) :
t('operations.straighten.description');
};
operation.id = "straighten";
operation.keys = [t('operations.straighten.key')];
operation.title = "title";
operation.description = "description";
return operation;
};

View File

@@ -128,6 +128,7 @@
<script src='../js/id/actions/rotate_way.js'></script>
<script src='../js/id/actions/circularize.js'></script>
<script src='../js/id/actions/orthogonalize.js'></script>
<script src='../js/id/actions/straighten.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>
@@ -160,6 +161,7 @@
<script src='../js/id/operations/continue.js'></script>
<script src='../js/id/operations/circularize.js'></script>
<script src='../js/id/operations/orthogonalize.js'></script>
<script src='../js/id/operations/straighten.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>
@@ -203,6 +205,7 @@
<script src="spec/actions/change_tags.js"></script>
<script src='spec/actions/circularize.js'></script>
<script src='spec/actions/orthogonalize.js'></script>
<script src='spec/actions/straighten.js'></script>
<script src='spec/actions/connect.js'></script>
<script src='spec/actions/delete_member.js'></script>
<script src="spec/actions/delete_multiple.js"></script>

View File

@@ -0,0 +1,44 @@
describe("iD.actions.Straighten", function () {
var projection = d3.geo.mercator();
it("deletes empty nodes", function() {
var graph = iD.Graph({
'a': iD.Node({id: 'a', loc: [0, 0]}),
'b': iD.Node({id: 'b', loc: [2, 0], tags: {}}),
'c': iD.Node({id: 'c', loc: [2, 2]}),
'-': iD.Way({id: '-', nodes: ['a', 'b', 'c']})
});
graph = iD.actions.Straighten('-', projection)(graph);
expect(graph.hasEntity('b')).to.be.undefined;
});
it("does not delete tagged nodes", function() {
var graph = iD.Graph({
'a': iD.Node({id: 'a', loc: [0, 0]}),
'b': iD.Node({id: 'b', loc: [2, 0], tags: {foo: 'bar'}}),
'c': iD.Node({id: 'c', loc: [2, 2]}),
'-': iD.Way({id: '-', nodes: ['a', 'b', 'c']})
});
graph = iD.actions.Straighten('-', projection)(graph);
expect(graph.entity('-').nodes.sort()).to.eql(['a', 'b', 'c']);
});
it("does not delete nodes connected to other ways", function() {
var graph = iD.Graph({
'a': iD.Node({id: 'a', loc: [0, 0]}),
'b': iD.Node({id: 'b', loc: [2, 0]}),
'c': iD.Node({id: 'c', loc: [2, 2]}),
'd': iD.Node({id: 'd', loc: [0, 2]}),
'-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd']}),
'=': iD.Way({id: '=', nodes: ['b']})
});
graph = iD.actions.Straighten('-', projection)(graph);
expect(graph.entity('-').nodes).to.have.length(3);
});
});