diff --git a/js/id/actions/circularize.js b/js/id/actions/circularize.js index 4dcfc0a21..dc936d48f 100644 --- a/js/id/actions/circularize.js +++ b/js/id/actions/circularize.js @@ -21,13 +21,14 @@ iD.actions.Circularize = function(wayId, projection, count) { radius = d3.median(points, function(p) { return iD.geo.dist(centroid, p); }), - ids = []; + ids = [], + sign = d3.geom.polygon(points).area() > 0 ? -1 : 1; for (var i = 0; i < count; i++) { var node, loc = projection.invert([ - centroid[0] + Math.cos((i / 12) * Math.PI * 2) * radius, - centroid[1] + Math.sin((i / 12) * Math.PI * 2) * radius]); + centroid[0] + Math.cos(sign * (i / 12) * Math.PI * 2) * radius, + centroid[1] + Math.sin(sign * (i / 12) * Math.PI * 2) * radius]); if (nodes.length) { var idx = closestIndex(nodes, loc); diff --git a/test/spec/actions/circularize.js b/test/spec/actions/circularize.js index 1b846c6f3..cb63f9ad6 100644 --- a/test/spec/actions/circularize.js +++ b/test/spec/actions/circularize.js @@ -26,7 +26,7 @@ describe("iD.actions.Circularize", function () { graph = iD.actions.Circularize('-', projection)(graph); - expect(graph.entity('-').nodes.slice(0, 4)).to.eql(['c', 'b', 'a', 'd']); + expect(graph.entity('-').nodes.slice(0, 4).sort()).to.eql(['a', 'b', 'c', 'd']); }); it("deletes unused nodes that are not members of other ways", function () { @@ -40,7 +40,7 @@ describe("iD.actions.Circularize", function () { graph = iD.actions.Circularize('-', projection, 3)(graph); - expect(graph.entity('d')).to.be.undefined; + expect(graph.entity('a')).to.be.undefined; }); it("reconnects unused nodes that are members of other ways", function () { @@ -51,12 +51,48 @@ describe("iD.actions.Circularize", function () { 'd': iD.Node({id: 'd', loc: [0, 2]}), 'e': iD.Node({id: 'e', loc: [1, 1]}), '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'a']}), - '=': iD.Way({id: '=', nodes: ['d']}) + '=': iD.Way({id: '=', nodes: ['a']}) }); graph = iD.actions.Circularize('-', projection, 3)(graph); - expect(graph.entity('d')).to.be.undefined; + expect(graph.entity('a')).to.be.undefined; expect(graph.entity('=').nodes).to.eql(['c']); }); + + function area(id, graph) { + return d3.geom.polygon(_.pluck(graph.childNodes(graph.entity(id)), 'loc')).area(); + } + + it("leaves clockwise ways clockwise", 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', 'd', 'c', 'b', 'a']}) + }); + + expect(area('+', graph)).to.be.gt(0); + + graph = iD.actions.Circularize('+', projection)(graph); + + expect(area('+', graph)).to.be.gt(0); + }); + + it("leaves counter-clockwise ways counter-clockwise", 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', 'a']}) + }); + + expect(area('-', graph)).to.be.lt(0); + + graph = iD.actions.Circularize('-', projection)(graph); + + expect(area('-', graph)).to.be.lt(0); + }); });