From b8efb00cfcc780375c643b283e1b442a77d6dcff Mon Sep 17 00:00:00 2001 From: Paul Mach Date: Thu, 19 Sep 2013 13:01:08 -0700 Subject: [PATCH] Add straighten way operation and action --- data/core.yaml | 7 +++ index.html | 2 + js/id/actions/straighten.js | 75 +++++++++++++++++++++++++++++++++ js/id/operations/straighten.js | 33 +++++++++++++++ test/index.html | 3 ++ test/spec/actions/straighten.js | 44 +++++++++++++++++++ 6 files changed, 164 insertions(+) create mode 100644 js/id/actions/straighten.js create mode 100644 js/id/operations/straighten.js create mode 100644 test/spec/actions/straighten.js diff --git a/data/core.yaml b/data/core.yaml index f048fb0cb..2c1c29bc3 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -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. diff --git a/index.html b/index.html index 490ee3843..3b387de5e 100644 --- a/index.html +++ b/index.html @@ -146,6 +146,7 @@ + @@ -179,6 +180,7 @@ + diff --git a/js/id/actions/straighten.js b/js/id/actions/straighten.js new file mode 100644 index 000000000..c891cbcce --- /dev/null +++ b/js/id/actions/straighten.js @@ -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; +}; diff --git a/js/id/operations/straighten.js b/js/id/operations/straighten.js new file mode 100644 index 000000000..44d98b95e --- /dev/null +++ b/js/id/operations/straighten.js @@ -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; +}; diff --git a/test/index.html b/test/index.html index 1ecab3f75..7836b34b2 100644 --- a/test/index.html +++ b/test/index.html @@ -128,6 +128,7 @@ + @@ -160,6 +161,7 @@ + @@ -203,6 +205,7 @@ + diff --git a/test/spec/actions/straighten.js b/test/spec/actions/straighten.js new file mode 100644 index 000000000..d1f6ca43c --- /dev/null +++ b/test/spec/actions/straighten.js @@ -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); + }); +});