diff --git a/modules/actions/reflect.js b/modules/actions/reflect.js index 3cdbcb62a..056932c58 100644 --- a/modules/actions/reflect.js +++ b/modules/actions/reflect.js @@ -6,7 +6,8 @@ import { import { geoEuclideanDistance, - geoExtent + geoExtent, + geoRotate } from '../geo'; @@ -14,15 +15,6 @@ import { export function actionReflect(wayId, projection) { var useLongAxis = true; - function rotatePolygon(polygon, angle, centroid) { - return polygon.map(function(point) { - var radial = [point[0] - centroid[0], point[1] - centroid[1]]; - return [ - radial[0] * Math.cos(angle) - radial[1] * Math.sin(angle) + centroid[0], - radial[0] * Math.sin(angle) + radial[1] * Math.cos(angle) + centroid[1] - ]; - }); - } // http://gis.stackexchange.com/questions/22895/finding-minimum-area-rectangle-for-given-points // http://gis.stackexchange.com/questions/3739/generalisation-strategies-for-building-outlines/3756#3756 @@ -39,7 +31,7 @@ export function actionReflect(wayId, projection) { for (var i = 0; i < hull.length - 1; i++) { var c2 = hull[i + 1], angle = Math.atan2(c2[1] - c1[1], c2[0] - c1[0]), - poly = rotatePolygon(hull, -angle, centroid), + poly = geoRotate(hull, -angle, centroid), extent = poly.reduce(function(extent, point) { return extent.extend(geoExtent(point)); }, geoExtent()), @@ -54,7 +46,7 @@ export function actionReflect(wayId, projection) { } return { - poly: rotatePolygon(ssrExtent.polygon(), ssrAngle, centroid), + poly: geoRotate(ssrExtent.polygon(), ssrAngle, centroid), angle: ssrAngle }; } diff --git a/modules/actions/rotate.js b/modules/actions/rotate.js index f1c25db0d..8667876f7 100644 --- a/modules/actions/rotate.js +++ b/modules/actions/rotate.js @@ -1,29 +1,19 @@ import _ from 'lodash'; +import { geoRotate } from '../geo'; export function actionRotate(wayId, pivot, angle, projection) { - return function(graph) { + var action = function(graph) { return graph.update(function(graph) { var way = graph.entity(wayId); - _.uniq(way.nodes).forEach(function(id) { - var node = graph.entity(id), - point = projection(node.loc), - radial = [0,0]; - - radial[0] = point[0] - pivot[0]; - radial[1] = point[1] - pivot[1]; - - point = [ - radial[0] * Math.cos(angle) - radial[1] * Math.sin(angle) + pivot[0], - radial[0] * Math.sin(angle) + radial[1] * Math.cos(angle) + pivot[1] - ]; + point = geoRotate([projection(node.loc)], angle, pivot)[0]; graph = graph.replace(node.move(projection.invert(point))); - }); - }); }; + + return action; } diff --git a/modules/geo/geo.js b/modules/geo/geo.js index 218124f95..096c0a18e 100644 --- a/modules/geo/geo.js +++ b/modules/geo/geo.js @@ -104,6 +104,18 @@ export function geoAngle(a, b, projection) { } +// Rotate all points counterclockwise around a pivot point by given angle +export function geoRotate(points, angle, around) { + return points.map(function(point) { + var radial = [point[0] - around[0], point[1] - around[1]]; + return [ + radial[0] * Math.cos(angle) - radial[1] * Math.sin(angle) + around[0], + radial[0] * Math.sin(angle) + radial[1] * Math.cos(angle) + around[1] + ]; + }); +} + + // Choose the edge with the minimal distance from `point` to its orthogonal // projection onto that edge, if such a projection exists, or the distance to // the closest vertex on that edge. Returns an object with the `index` of the diff --git a/modules/geo/index.js b/modules/geo/index.js index cea5b48cd..5e37b16fe 100644 --- a/modules/geo/index.js +++ b/modules/geo/index.js @@ -7,6 +7,7 @@ export { geoExtent } from './extent.js'; export { geoInterp } from './geo.js'; export { geoRawMercator } from './raw_mercator.js'; export { geoRoundCoords } from './geo.js'; +export { geoRotate } from './geo.js'; export { geoLatToMeters } from './geo.js'; export { geoLineIntersection } from './geo.js'; export { geoLonToMeters } from './geo.js'; diff --git a/test/spec/geo/geo.js b/test/spec/geo/geo.js index 96332a8c2..6b2e0cb85 100644 --- a/test/spec/geo/geo.js +++ b/test/spec/geo/geo.js @@ -185,6 +185,54 @@ describe('iD.geo', function() { }); }); + describe('geoEdgeEqual', function() { + it('returns false for inequal edges', function() { + expect(iD.geoEdgeEqual(['a','b'], ['a','c'])).to.be.false; + }); + + it('returns true for equal edges along same direction', function() { + expect(iD.geoEdgeEqual(['a','b'], ['a','b'])).to.be.true; + }); + + it('returns true for equal edges along opposite direction', function() { + expect(iD.geoEdgeEqual(['a','b'], ['b','a'])).to.be.true; + }); + }); + + describe('geoAngle', function() { + it('returns angle between a and b', function() { + var projection = function (_) { return _; }; + expect(iD.geoAngle({loc:[0, 0]}, {loc:[1, 0]}, projection)).to.be.closeTo(0, 1e-6); + expect(iD.geoAngle({loc:[0, 0]}, {loc:[0, 1]}, projection)).to.be.closeTo(Math.PI / 2, 1e-6); + expect(iD.geoAngle({loc:[0, 0]}, {loc:[-1, 0]}, projection)).to.be.closeTo(Math.PI, 1e-6); + expect(iD.geoAngle({loc:[0, 0]}, {loc:[0, -1]}, projection)).to.be.closeTo(-Math.PI / 2, 1e-6); + }); + }); + + describe('geoRotate', function() { + it('rotates points around [0, 0]', function() { + var points = [[5, 0], [5, 1]], + angle = Math.PI, + around = [0, 0], + result = iD.geoRotate(points, angle, around); + expect(result[0][0]).to.be.closeTo(-5, 1e-6); + expect(result[0][1]).to.be.closeTo(0, 1e-6); + expect(result[1][0]).to.be.closeTo(-5, 1e-6); + expect(result[1][1]).to.be.closeTo(-1, 1e-6); + }); + + it('rotates points around [3, 0]', function() { + var points = [[5, 0], [5, 1]], + angle = Math.PI, + around = [3, 0], + result = iD.geoRotate(points, angle, around); + expect(result[0][0]).to.be.closeTo(1, 1e-6); + expect(result[0][1]).to.be.closeTo(0, 1e-6); + expect(result[1][0]).to.be.closeTo(1, 1e-6); + expect(result[1][1]).to.be.closeTo(-1, 1e-6); + }); + }); + describe('geoChooseEdge', function() { var projection = function (l) { return l; }; projection.invert = projection;