diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index 41fca79e3..d04317d78 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -6,34 +6,48 @@ iD.actions.Orthogonalize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), nodes = graph.childNodes(way), - points = nodes.map(function(n) { return projection(n.loc); }), - best, i, j; + corner = {i: 0, dotp: 1}, + points, i, j, score, motions; - var score = squareness(); - for (i = 0; i < 1000; i++) { - var motions = points.map(stepMap); - for (j = 0; j < motions.length; j++) { - points[j] = addPoints(points[j],motions[j]); + 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) { + break; + } } - var newScore = squareness(); - if (newScore < score) { - best = _.clone(points); - score = newScore; + graph = graph.replace(graph.entity(nodes[corner.i].id) + .move(projection.invert(points[corner.i]))); + } else { + var best; + points = nodes.map(function(n) { return projection(n.loc); }); + score = squareness(); + for (i = 0; i < 1000; i++) { + motions = points.map(calcMotion); + for (j = 0; j < motions.length; j++) { + points[j] = addPoints(points[j],motions[j]); + } + var newScore = squareness(); + if (newScore < score) { + best = _.clone(points); + score = newScore; + } + if (score < 1.0e-8) { + break; + } } - if (score < 1.0e-8) { - break; + points = best; + for (i = 0; i < points.length - 1; i++) { + graph = graph.replace(graph.entity(nodes[i].id) + .move(projection.invert(points[i]))); } } - points = best; - - for (i = 0; i < points.length - 1; i++) { - graph = graph.replace(graph.entity(nodes[i].id) - .move(projection.invert(points[i]))); - } - return graph; - function stepMap(b, i, array) { + function calcMotion(b, i, array) { var a = array[(i - 1 + array.length) % array.length], c = array[(i + 1) % array.length], p = subtractPoints(a, b), @@ -44,9 +58,17 @@ iD.actions.Orthogonalize = function(wayId, projection) { 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; + if (array.length > 3) { + if (dotp < -0.707106781186547) { + dotp += 1.0; + } + } else { + if( Math.abs(dotp) < corner.dotp){ + corner.i = i; + corner.dotp = Math.abs(dotp); + } } return normalizePoint(addPoints(p, q), 0.1 * dotp * scale); @@ -86,7 +108,7 @@ iD.actions.Orthogonalize = function(wayId, projection) { return [a[0] + b[0], a[1] + b[1]]; } - function normalizePoint(point, thickness) { + function normalizePoint(point, scale) { var vector = [0, 0]; var length = Math.sqrt(point[0] * point[0] + point[1] * point[1]); if (length !== 0) { @@ -94,8 +116,8 @@ iD.actions.Orthogonalize = function(wayId, projection) { vector[1] = point[1] / length; } - vector[0] *= thickness; - vector[1] *= thickness; + vector[0] *= scale; + vector[1] *= scale; return vector; } diff --git a/js/id/operations/orthogonalize.js b/js/id/operations/orthogonalize.js index 482c4ba5b..2e5844398 100644 --- a/js/id/operations/orthogonalize.js +++ b/js/id/operations/orthogonalize.js @@ -10,7 +10,7 @@ iD.operations.Orthogonalize = function(selection, context) { operation.available = function() { return selection.length === 1 && context.entity(entityId).type === 'way' && - _.uniq(context.entity(entityId).nodes).length > 3; + _.uniq(context.entity(entityId).nodes).length > 2; }; operation.enabled = function() { diff --git a/test/index.html b/test/index.html index 173e22592..c0f7be5f2 100644 --- a/test/index.html +++ b/test/index.html @@ -184,6 +184,7 @@ + diff --git a/test/spec/actions/orthogonalize.js b/test/spec/actions/orthogonalize.js new file mode 100644 index 000000000..690d73140 --- /dev/null +++ b/test/spec/actions/orthogonalize.js @@ -0,0 +1,30 @@ +describe("iD.actions.Orthogonalize", function () { + var projection = d3.geo.mercator(); + + it("orthoganalizes a quad", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [0, 0]}), + 'b': iD.Node({id: 'b', loc: [4, 0]}), + 'c': iD.Node({id: 'c', loc: [3, 2]}), + 'd': iD.Node({id: 'd', loc: [0, 2]}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + graph = iD.actions.Orthogonalize('-', projection)(graph); + + expect(graph.entity('-').nodes).to.have.length(5); + }); + + it("orthoganalizes a triangle", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [0, 0]}), + 'b': iD.Node({id: 'b', loc: [3, 0]}), + 'c': iD.Node({id: 'c', loc: [2, 2]}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'a']}) + }); + + graph = iD.actions.Orthogonalize('-', projection)(graph); + + expect(graph.entity('-').nodes).to.have.length(4); + }); +});