diff --git a/modules/geo/geo.js b/modules/geo/geo.js
index 0c5a27778..e0f7aa436 100644
--- a/modules/geo/geo.js
+++ b/modules/geo/geo.js
@@ -39,15 +39,6 @@ export function geoMetersToLon(m, atLat) {
}
-export function geoOffsetToMeters(offset, tileSize) {
- tileSize = tileSize || 256;
- return [
- offset[0] * TAU * EQUATORIAL_RADIUS / tileSize,
- -offset[1] * TAU * POLAR_RADIUS / tileSize
- ];
-}
-
-
export function geoMetersToOffset(meters, tileSize) {
tileSize = tileSize || 256;
return [
@@ -57,6 +48,15 @@ export function geoMetersToOffset(meters, tileSize) {
}
+export function geoOffsetToMeters(offset, tileSize) {
+ tileSize = tileSize || 256;
+ return [
+ offset[0] * TAU * EQUATORIAL_RADIUS / tileSize,
+ -offset[1] * TAU * POLAR_RADIUS / tileSize
+ ];
+}
+
+
// Equirectangular approximation of spherical distances on Earth
export function geoSphericalDistance(a, b) {
var x = geoLonToMeters(a[0] - b[0], (a[1] + b[1]) / 2);
@@ -65,13 +65,6 @@ export function geoSphericalDistance(a, b) {
}
-// zoom to scale
-export function geoZoomToScale(z, tileSize) {
- tileSize = tileSize || 256;
- return tileSize * Math.pow(2, z) / TAU;
-}
-
-
// scale to zoom
export function geoScaleToZoom(k, tileSize) {
tileSize = tileSize || 256;
@@ -80,203 +73,11 @@ export function geoScaleToZoom(k, tileSize) {
}
-export function geoEdgeEqual(a, b) {
- return (a[0] === b[0] && a[1] === b[1]) ||
- (a[0] === b[1] && a[1] === b[0]);
+// zoom to scale
+export function geoZoomToScale(z, tileSize) {
+ tileSize = tileSize || 256;
+ return tileSize * Math.pow(2, z) / TAU;
}
-// Return the counterclockwise angle in the range (-pi, pi)
-// between the positive X axis and the line intersecting a and b.
-export function geoAngle(a, b, projection) {
- return geoVecAngle(projection(a.loc), projection(b.loc));
-}
-
-// 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
-// chosen edge, the chosen `loc` on that edge, and the `distance` to to it.
-export function geoChooseEdge(nodes, point, projection, skipID) {
- var dist = geoVecLength;
- var points = nodes.map(function(n) { return projection(n.loc); });
- var ids = nodes.map(function(n) { return n.id; });
- var min = Infinity;
- var idx;
- var loc;
-
- for (var i = 0; i < points.length - 1; i++) {
- if (ids[i] === skipID || ids[i + 1] === skipID) continue;
-
- var o = points[i];
- var s = geoVecSubtract(points[i + 1], o);
- var v = geoVecSubtract(point, o);
- var proj = geoVecDot(v, s) / geoVecDot(s, s);
- var p;
-
- if (proj < 0) {
- p = o;
- } else if (proj > 1) {
- p = points[i + 1];
- } else {
- p = [o[0] + proj * s[0], o[1] + proj * s[1]];
- }
-
- var d = dist(p, point);
- if (d < min) {
- min = d;
- idx = i + 1;
- loc = projection.invert(p);
- }
- }
-
- if (idx !== undefined) {
- return { index: idx, distance: min, loc: loc };
- } else {
- return null;
- }
-}
-
-
-// Return the intersection point of 2 line segments.
-// From https://github.com/pgkelley4/line-segments-intersect
-// This uses the vector cross product approach described below:
-// http://stackoverflow.com/a/565282/786339
-export function geoLineIntersection(a, b) {
- var p = [a[0][0], a[0][1]];
- var p2 = [a[1][0], a[1][1]];
- var q = [b[0][0], b[0][1]];
- var q2 = [b[1][0], b[1][1]];
- var r = geoVecSubtract(p2, p);
- var s = geoVecSubtract(q2, q);
- var uNumerator = geoVecCross(geoVecSubtract(q, p), r);
- var denominator = geoVecCross(r, s);
-
- if (uNumerator && denominator) {
- var u = uNumerator / denominator;
- var t = geoVecCross(geoVecSubtract(q, p), s) / denominator;
-
- if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) {
- return geoVecInterp(p, p2, t);
- }
- }
-
- return null;
-}
-
-
-export function geoPathIntersections(path1, path2) {
- var intersections = [];
- for (var i = 0; i < path1.length - 1; i++) {
- for (var j = 0; j < path2.length - 1; j++) {
- var a = [ path1[i], path1[i+1] ];
- var b = [ path2[j], path2[j+1] ];
- var hit = geoLineIntersection(a, b);
- if (hit) {
- intersections.push(hit);
- }
- }
- }
- return intersections;
-}
-
-
-// Return whether point is contained in polygon.
-//
-// `point` should be a 2-item array of coordinates.
-// `polygon` should be an array of 2-item arrays of coordinates.
-//
-// From https://github.com/substack/point-in-polygon.
-// ray-casting algorithm based on
-// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
-//
-export function geoPointInPolygon(point, polygon) {
- var x = point[0];
- var y = point[1];
- var inside = false;
-
- for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
- var xi = polygon[i][0], yi = polygon[i][1];
- var xj = polygon[j][0], yj = polygon[j][1];
-
- var intersect = ((yi > y) !== (yj > y)) &&
- (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
- if (intersect) inside = !inside;
- }
-
- return inside;
-}
-
-
-export function geoPolygonContainsPolygon(outer, inner) {
- return _every(inner, function(point) {
- return geoPointInPolygon(point, outer);
- });
-}
-
-
-export function geoPolygonIntersectsPolygon(outer, inner, checkSegments) {
- function testSegments(outer, inner) {
- for (var i = 0; i < outer.length - 1; i++) {
- for (var j = 0; j < inner.length - 1; j++) {
- var a = [ outer[i], outer[i + 1] ];
- var b = [ inner[j], inner[j + 1] ];
- if (geoLineIntersection(a, b)) return true;
- }
- }
- return false;
- }
-
- function testPoints(outer, inner) {
- return _some(inner, function(point) {
- return geoPointInPolygon(point, outer);
- });
- }
-
- return testPoints(outer, inner) || (!!checkSegments && testSegments(outer, inner));
-}
-
-
-export function geoPathLength(path) {
- var length = 0;
- for (var i = 0; i < path.length - 1; i++) {
- length += geoVecLength(path[i], path[i + 1]);
- }
- return length;
-}
-
-
-// If the given point is at the edge of the padded viewport,
-// return a vector that will nudge the viewport in that direction
-export function geoViewportEdge(point, dimensions) {
- var pad = [80, 20, 50, 20]; // top, right, bottom, left
- var x = 0;
- var y = 0;
-
- if (point[0] > dimensions[0] - pad[1])
- x = -10;
- if (point[0] < pad[3])
- x = 10;
- if (point[1] > dimensions[1] - pad[2])
- y = -10;
- if (point[1] < pad[0])
- y = 10;
-
- if (x || y) {
- return [x, y];
- } else {
- return null;
- }
-}
diff --git a/modules/geo/geom.js b/modules/geo/geom.js
new file mode 100644
index 000000000..fef500eb6
--- /dev/null
+++ b/modules/geo/geom.js
@@ -0,0 +1,213 @@
+import _every from 'lodash-es/every';
+import _some from 'lodash-es/some';
+
+import {
+ geoVecAngle,
+ geoVecCross,
+ geoVecDot,
+ geoVecInterp,
+ geoVecLength,
+ geoVecSubtract
+} from './vector.js';
+
+
+// Return the counterclockwise angle in the range (-pi, pi)
+// between the positive X axis and the line intersecting a and b.
+export function geoAngle(a, b, projection) {
+ return geoVecAngle(projection(a.loc), projection(b.loc));
+}
+
+export function geoEdgeEqual(a, b) {
+ return (a[0] === b[0] && a[1] === b[1]) ||
+ (a[0] === b[1] && a[1] === b[0]);
+}
+
+// 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
+// chosen edge, the chosen `loc` on that edge, and the `distance` to to it.
+export function geoChooseEdge(nodes, point, projection, skipID) {
+ var dist = geoVecLength;
+ var points = nodes.map(function(n) { return projection(n.loc); });
+ var ids = nodes.map(function(n) { return n.id; });
+ var min = Infinity;
+ var idx;
+ var loc;
+
+ for (var i = 0; i < points.length - 1; i++) {
+ if (ids[i] === skipID || ids[i + 1] === skipID) continue;
+
+ var o = points[i];
+ var s = geoVecSubtract(points[i + 1], o);
+ var v = geoVecSubtract(point, o);
+ var proj = geoVecDot(v, s) / geoVecDot(s, s);
+ var p;
+
+ if (proj < 0) {
+ p = o;
+ } else if (proj > 1) {
+ p = points[i + 1];
+ } else {
+ p = [o[0] + proj * s[0], o[1] + proj * s[1]];
+ }
+
+ var d = dist(p, point);
+ if (d < min) {
+ min = d;
+ idx = i + 1;
+ loc = projection.invert(p);
+ }
+ }
+
+ if (idx !== undefined) {
+ return { index: idx, distance: min, loc: loc };
+ } else {
+ return null;
+ }
+}
+
+
+// Return the intersection point of 2 line segments.
+// From https://github.com/pgkelley4/line-segments-intersect
+// This uses the vector cross product approach described below:
+// http://stackoverflow.com/a/565282/786339
+export function geoLineIntersection(a, b) {
+ var p = [a[0][0], a[0][1]];
+ var p2 = [a[1][0], a[1][1]];
+ var q = [b[0][0], b[0][1]];
+ var q2 = [b[1][0], b[1][1]];
+ var r = geoVecSubtract(p2, p);
+ var s = geoVecSubtract(q2, q);
+ var uNumerator = geoVecCross(geoVecSubtract(q, p), r);
+ var denominator = geoVecCross(r, s);
+
+ if (uNumerator && denominator) {
+ var u = uNumerator / denominator;
+ var t = geoVecCross(geoVecSubtract(q, p), s) / denominator;
+
+ if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) {
+ return geoVecInterp(p, p2, t);
+ }
+ }
+
+ return null;
+}
+
+
+export function geoPathIntersections(path1, path2) {
+ var intersections = [];
+ for (var i = 0; i < path1.length - 1; i++) {
+ for (var j = 0; j < path2.length - 1; j++) {
+ var a = [ path1[i], path1[i+1] ];
+ var b = [ path2[j], path2[j+1] ];
+ var hit = geoLineIntersection(a, b);
+ if (hit) {
+ intersections.push(hit);
+ }
+ }
+ }
+ return intersections;
+}
+
+
+// Return whether point is contained in polygon.
+//
+// `point` should be a 2-item array of coordinates.
+// `polygon` should be an array of 2-item arrays of coordinates.
+//
+// From https://github.com/substack/point-in-polygon.
+// ray-casting algorithm based on
+// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
+//
+export function geoPointInPolygon(point, polygon) {
+ var x = point[0];
+ var y = point[1];
+ var inside = false;
+
+ for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
+ var xi = polygon[i][0];
+ var yi = polygon[i][1];
+ var xj = polygon[j][0];
+ var yj = polygon[j][1];
+
+ var intersect = ((yi > y) !== (yj > y)) &&
+ (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
+ if (intersect) inside = !inside;
+ }
+
+ return inside;
+}
+
+
+export function geoPolygonContainsPolygon(outer, inner) {
+ return _every(inner, function(point) {
+ return geoPointInPolygon(point, outer);
+ });
+}
+
+
+export function geoPolygonIntersectsPolygon(outer, inner, checkSegments) {
+ function testSegments(outer, inner) {
+ for (var i = 0; i < outer.length - 1; i++) {
+ for (var j = 0; j < inner.length - 1; j++) {
+ var a = [ outer[i], outer[i + 1] ];
+ var b = [ inner[j], inner[j + 1] ];
+ if (geoLineIntersection(a, b)) return true;
+ }
+ }
+ return false;
+ }
+
+ function testPoints(outer, inner) {
+ return _some(inner, function(point) {
+ return geoPointInPolygon(point, outer);
+ });
+ }
+
+ return testPoints(outer, inner) || (!!checkSegments && testSegments(outer, inner));
+}
+
+
+export function geoPathLength(path) {
+ var length = 0;
+ for (var i = 0; i < path.length - 1; i++) {
+ length += geoVecLength(path[i], path[i + 1]);
+ }
+ return length;
+}
+
+
+// If the given point is at the edge of the padded viewport,
+// return a vector that will nudge the viewport in that direction
+export function geoViewportEdge(point, dimensions) {
+ var pad = [80, 20, 50, 20]; // top, right, bottom, left
+ var x = 0;
+ var y = 0;
+
+ if (point[0] > dimensions[0] - pad[1])
+ x = -10;
+ if (point[0] < pad[3])
+ x = 10;
+ if (point[1] > dimensions[1] - pad[2])
+ y = -10;
+ if (point[1] < pad[0])
+ y = 10;
+
+ if (x || y) {
+ return [x, y];
+ } else {
+ return null;
+ }
+}
diff --git a/modules/geo/index.js b/modules/geo/index.js
index dca2f31dd..fe801aff6 100644
--- a/modules/geo/index.js
+++ b/modules/geo/index.js
@@ -1,23 +1,29 @@
-export { geoAngle } from './geo.js';
-export { geoChooseEdge } from './geo.js';
-export { geoEdgeEqual } from './geo.js';
export { geoExtent } from './extent.js';
-export { geoRawMercator } from './raw_mercator.js';
-export { geoRotate } from './geo.js';
+
export { geoLatToMeters } from './geo.js';
-export { geoLineIntersection } from './geo.js';
export { geoLonToMeters } from './geo.js';
export { geoMetersToLat } from './geo.js';
export { geoMetersToLon } from './geo.js';
export { geoMetersToOffset } from './geo.js';
export { geoOffsetToMeters } from './geo.js';
-export { geoPathIntersections } from './geo.js';
-export { geoPathLength } from './geo.js';
-export { geoPointInPolygon } from './geo.js';
-export { geoPolygonContainsPolygon } from './geo.js';
-export { geoPolygonIntersectsPolygon } from './geo.js';
export { geoScaleToZoom } from './geo.js';
export { geoSphericalDistance } from './geo.js';
+export { geoZoomToScale } from './geo.js';
+
+export { geoAngle } from './geom.js';
+export { geoChooseEdge } from './geom.js';
+export { geoEdgeEqual } from './geom.js';
+export { geoRotate } from './geom.js';
+export { geoLineIntersection } from './geom.js';
+export { geoPathIntersections } from './geom.js';
+export { geoPathLength } from './geom.js';
+export { geoPointInPolygon } from './geom.js';
+export { geoPolygonContainsPolygon } from './geom.js';
+export { geoPolygonIntersectsPolygon } from './geom.js';
+export { geoViewportEdge } from './geom.js';
+
+export { geoRawMercator } from './raw_mercator.js';
+
export { geoVecAdd } from './vector.js';
export { geoVecAngle } from './vector.js';
export { geoVecCross } from './vector.js';
@@ -28,5 +34,4 @@ export { geoVecInterp } from './vector.js';
export { geoVecLength } from './vector.js';
export { geoVecSubtract } from './vector.js';
export { geoVecScale } from './vector.js';
-export { geoZoomToScale } from './geo.js';
-export { geoViewportEdge } from './geo.js';
+
diff --git a/test/index.html b/test/index.html
index ed2cb0c54..2357d5ba1 100644
--- a/test/index.html
+++ b/test/index.html
@@ -74,6 +74,7 @@
+
diff --git a/test/spec/geo/geo.js b/test/spec/geo/geo.js
index 9f31f1d27..76c77ca09 100644
--- a/test/spec/geo/geo.js
+++ b/test/spec/geo/geo.js
@@ -1,4 +1,4 @@
-describe('iD.geo', function() {
+describe('iD.geo - geography', function() {
describe('geoLatToMeters', function() {
it('0 degrees latitude is 0 meters', function() {
@@ -144,298 +144,4 @@ 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]];
- var angle = Math.PI;
- var around = [0, 0];
- var 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]];
- var angle = Math.PI;
- var around = [3, 0];
- var 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;
-
- it('returns null for a degenerate way (no nodes)', function() {
- expect(iD.geoChooseEdge([], [0, 0], projection)).to.be.null;
- });
-
- it('returns null for a degenerate way (single node)', function() {
- expect(iD.geoChooseEdge([iD.osmNode({loc: [0, 0]})], [0, 0], projection)).to.be.null;
- });
-
- it('calculates the orthogonal projection of a point onto a segment', function() {
- // a --*--- b
- // |
- // c
- //
- // * = [2, 0]
- var a = [0, 0];
- var b = [5, 0];
- var c = [2, 1];
- var nodes = [ iD.osmNode({loc: a}), iD.osmNode({loc: b}) ];
- var choice = iD.geoChooseEdge(nodes, c, projection);
- expect(choice.index).to.eql(1);
- expect(choice.distance).to.eql(1);
- expect(choice.loc).to.eql([2, 0]);
- });
-
- it('returns the starting vertex when the orthogonal projection is < 0', function() {
- var a = [0, 0];
- var b = [5, 0];
- var c = [-3, 4];
- var nodes = [ iD.osmNode({loc: a}), iD.osmNode({loc: b}) ];
- var choice = iD.geoChooseEdge(nodes, c, projection);
- expect(choice.index).to.eql(1);
- expect(choice.distance).to.eql(5);
- expect(choice.loc).to.eql([0, 0]);
- });
-
- it('returns the ending vertex when the orthogonal projection is > 1', function() {
- var a = [0, 0];
- var b = [5, 0];
- var c = [8, 4];
- var nodes = [ iD.osmNode({loc: a}), iD.osmNode({loc: b}) ];
- var choice = iD.geoChooseEdge(nodes, c, projection);
- expect(choice.index).to.eql(1);
- expect(choice.distance).to.eql(5);
- expect(choice.loc).to.eql([5, 0]);
- });
-
- it('skips the given nodeID at end of way', function() {
- //
- // a --*-- b
- // e |
- // | |
- // d - c
- //
- // * = [2, 0]
- var a = [0, 0];
- var b = [5, 0];
- var c = [5, 5];
- var d = [2, 5];
- var e = [2, 0.1]; // e.g. user is dragging e onto ab
- var nodes = [
- iD.osmNode({id: 'a', loc: a}),
- iD.osmNode({id: 'b', loc: b}),
- iD.osmNode({id: 'c', loc: c}),
- iD.osmNode({id: 'd', loc: d}),
- iD.osmNode({id: 'e', loc: e})
- ];
- var choice = iD.geoChooseEdge(nodes, e, projection, 'e');
- expect(choice.index).to.eql(1);
- expect(choice.distance).to.eql(0.1);
- expect(choice.loc).to.eql([2, 0]);
- });
-
- it('skips the given nodeID in middle of way', function() {
- //
- // a --*-- b
- // d |
- // / \ |
- // e c
- //
- // * = [2, 0]
- var a = [0, 0];
- var b = [5, 0];
- var c = [5, 5];
- var d = [2, 0.1]; // e.g. user is dragging d onto ab
- var e = [0, 5];
- var nodes = [
- iD.osmNode({id: 'a', loc: a}),
- iD.osmNode({id: 'b', loc: b}),
- iD.osmNode({id: 'c', loc: c}),
- iD.osmNode({id: 'd', loc: d}),
- iD.osmNode({id: 'e', loc: e})
- ];
- var choice = iD.geoChooseEdge(nodes, d, projection, 'd');
- expect(choice.index).to.eql(1);
- expect(choice.distance).to.eql(0.1);
- expect(choice.loc).to.eql([2, 0]);
- });
-
- it('returns null if all nodes are skipped', function() {
- var nodes = [
- iD.osmNode({id: 'a', loc: [0, 0]}),
- iD.osmNode({id: 'b', loc: [5, 0]}),
- ];
- var choice = iD.geoChooseEdge(nodes, [2, 2], projection, 'a');
- expect(choice).to.be.null;
- });
- });
-
- describe('geoLineIntersection', function() {
- it('returns null if lines are colinear with overlap', function() {
- var a = [[0, 0], [10, 0]];
- var b = [[-5, 0], [5, 0]];
- expect(iD.geoLineIntersection(a, b)).to.be.null;
- });
- it('returns null if lines are colinear but disjoint', function() {
- var a = [[5, 0], [10, 0]];
- var b = [[-10, 0], [-5, 0]];
- expect(iD.geoLineIntersection(a, b)).to.be.null;
- });
- it('returns null if lines are parallel', function() {
- var a = [[0, 0], [10, 0]];
- var b = [[0, 5], [10, 5]];
- expect(iD.geoLineIntersection(a, b)).to.be.null;
- });
- it('returns the intersection point between 2 lines', function() {
- var a = [[0, 0], [10, 0]];
- var b = [[5, 10], [5, -10]];
- expect(iD.geoLineIntersection(a, b)).to.eql([5, 0]);
- });
- it('returns null if lines are not parallel but not intersecting', function() {
- var a = [[0, 0], [10, 0]];
- var b = [[-5, 10], [-5, -10]];
- expect(iD.geoLineIntersection(a, b)).to.be.null;
- });
- });
-
- describe('geoPointInPolygon', function() {
- it('says a point in a polygon is on a polygon', function() {
- var poly = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
- var point = [0.5, 0.5];
- expect(iD.geoPointInPolygon(point, poly)).to.be.true;
- });
- it('says a point outside of a polygon is outside', function() {
- var poly = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
- var point = [0.5, 1.5];
- expect(iD.geoPointInPolygon(point, poly)).to.be.false;
- });
- });
-
- describe('geoPolygonContainsPolygon', function() {
- it('says a polygon in a polygon is in', function() {
- var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
- var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]];
- expect(iD.geoPolygonContainsPolygon(outer, inner)).to.be.true;
- });
- it('says a polygon outside of a polygon is out', function() {
- var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
- var inner = [[1, 1], [1, 9], [2, 2], [2, 1], [1, 1]];
- expect(iD.geoPolygonContainsPolygon(outer, inner)).to.be.false;
- });
- });
-
- describe('geoPolygonIntersectsPolygon', function() {
- it('returns true when outer polygon fully contains inner', function() {
- var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
- var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]];
- expect(iD.geoPolygonIntersectsPolygon(outer, inner)).to.be.true;
- });
-
- it('returns true when outer polygon partially contains inner (some vertices contained)', function() {
- var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
- var inner = [[-1, -1], [1, 2], [2, 2], [2, 1], [1, 1]];
- expect(iD.geoPolygonIntersectsPolygon(outer, inner)).to.be.true;
- });
-
- it('returns false when outer polygon partially contains inner (no vertices contained - lax test)', function() {
- var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
- var inner = [[1, -1], [1, 4], [2, 4], [2, -1], [1, -1]];
- expect(iD.geoPolygonIntersectsPolygon(outer, inner)).to.be.false;
- });
-
- it('returns true when outer polygon partially contains inner (no vertices contained - strict test)', function() {
- var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
- var inner = [[1, -1], [1, 4], [2, 4], [2, -1], [1, -1]];
- expect(iD.geoPolygonIntersectsPolygon(outer, inner, true)).to.be.true;
- });
-
- it('returns false when outer and inner are fully disjoint', function() {
- var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
- var inner = [[-1, -1], [-1, -2], [-2, -2], [-2, -1], [-1, -1]];
- expect(iD.geoPolygonIntersectsPolygon(outer, inner)).to.be.false;
- });
- });
-
- describe('geoPathLength', function() {
- it('calculates a simple path length', function() {
- var path = [[0, 0], [0, 1], [3, 5]];
- expect(iD.geoPathLength(path)).to.eql(6);
- });
-
- it('does not fail on single-point path', function() {
- var path = [[0, 0]];
- expect(iD.geoPathLength(path)).to.eql(0);
- });
-
- it('estimates zero-length edges', function() {
- var path = [[0, 0], [0, 0]];
- expect(iD.geoPathLength(path)).to.eql(0);
- });
- });
-
- describe('geoViewportEdge', function() {
- var dimensions = [1000, 1000];
- it('returns null if the point is not at the edge', function() {
- expect(iD.geoViewportEdge([500, 500], dimensions)).to.be.null;
- });
- it('nudges top edge', function() {
- expect(iD.geoViewportEdge([500, 5], dimensions)).to.eql([0, 10]);
- });
- it('nudges top-right corner', function() {
- expect(iD.geoViewportEdge([995, 5], dimensions)).to.eql([-10, 10]);
- });
- it('nudges right edge', function() {
- expect(iD.geoViewportEdge([995, 500], dimensions)).to.eql([-10, 0]);
- });
- it('nudges bottom-right corner', function() {
- expect(iD.geoViewportEdge([995, 995], dimensions)).to.eql([-10, -10]);
- });
- it('nudges bottom edge', function() {
- expect(iD.geoViewportEdge([500, 995], dimensions)).to.eql([0, -10]);
- });
- it('nudges bottom-left corner', function() {
- expect(iD.geoViewportEdge([5, 995], dimensions)).to.eql([10, -10]);
- });
- it('nudges left edge', function() {
- expect(iD.geoViewportEdge([5, 500], dimensions)).to.eql([10, 0]);
- });
- it('nudges top-left corner', function() {
- expect(iD.geoViewportEdge([5, 5], dimensions)).to.eql([10, 10]);
- });
- });
-
});
diff --git a/test/spec/geo/geom.js b/test/spec/geo/geom.js
new file mode 100644
index 000000000..11b37c35a
--- /dev/null
+++ b/test/spec/geo/geom.js
@@ -0,0 +1,297 @@
+describe('iD.geo - geometry', function() {
+
+ 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('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('geoRotate', function() {
+ it('rotates points around [0, 0]', function() {
+ var points = [[5, 0], [5, 1]];
+ var angle = Math.PI;
+ var around = [0, 0];
+ var 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]];
+ var angle = Math.PI;
+ var around = [3, 0];
+ var 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;
+
+ it('returns null for a degenerate way (no nodes)', function() {
+ expect(iD.geoChooseEdge([], [0, 0], projection)).to.be.null;
+ });
+
+ it('returns null for a degenerate way (single node)', function() {
+ expect(iD.geoChooseEdge([iD.osmNode({loc: [0, 0]})], [0, 0], projection)).to.be.null;
+ });
+
+ it('calculates the orthogonal projection of a point onto a segment', function() {
+ // a --*--- b
+ // |
+ // c
+ //
+ // * = [2, 0]
+ var a = [0, 0];
+ var b = [5, 0];
+ var c = [2, 1];
+ var nodes = [ iD.osmNode({loc: a}), iD.osmNode({loc: b}) ];
+ var choice = iD.geoChooseEdge(nodes, c, projection);
+ expect(choice.index).to.eql(1);
+ expect(choice.distance).to.eql(1);
+ expect(choice.loc).to.eql([2, 0]);
+ });
+
+ it('returns the starting vertex when the orthogonal projection is < 0', function() {
+ var a = [0, 0];
+ var b = [5, 0];
+ var c = [-3, 4];
+ var nodes = [ iD.osmNode({loc: a}), iD.osmNode({loc: b}) ];
+ var choice = iD.geoChooseEdge(nodes, c, projection);
+ expect(choice.index).to.eql(1);
+ expect(choice.distance).to.eql(5);
+ expect(choice.loc).to.eql([0, 0]);
+ });
+
+ it('returns the ending vertex when the orthogonal projection is > 1', function() {
+ var a = [0, 0];
+ var b = [5, 0];
+ var c = [8, 4];
+ var nodes = [ iD.osmNode({loc: a}), iD.osmNode({loc: b}) ];
+ var choice = iD.geoChooseEdge(nodes, c, projection);
+ expect(choice.index).to.eql(1);
+ expect(choice.distance).to.eql(5);
+ expect(choice.loc).to.eql([5, 0]);
+ });
+
+ it('skips the given nodeID at end of way', function() {
+ //
+ // a --*-- b
+ // e |
+ // | |
+ // d - c
+ //
+ // * = [2, 0]
+ var a = [0, 0];
+ var b = [5, 0];
+ var c = [5, 5];
+ var d = [2, 5];
+ var e = [2, 0.1]; // e.g. user is dragging e onto ab
+ var nodes = [
+ iD.osmNode({id: 'a', loc: a}),
+ iD.osmNode({id: 'b', loc: b}),
+ iD.osmNode({id: 'c', loc: c}),
+ iD.osmNode({id: 'd', loc: d}),
+ iD.osmNode({id: 'e', loc: e})
+ ];
+ var choice = iD.geoChooseEdge(nodes, e, projection, 'e');
+ expect(choice.index).to.eql(1);
+ expect(choice.distance).to.eql(0.1);
+ expect(choice.loc).to.eql([2, 0]);
+ });
+
+ it('skips the given nodeID in middle of way', function() {
+ //
+ // a --*-- b
+ // d |
+ // / \ |
+ // e c
+ //
+ // * = [2, 0]
+ var a = [0, 0];
+ var b = [5, 0];
+ var c = [5, 5];
+ var d = [2, 0.1]; // e.g. user is dragging d onto ab
+ var e = [0, 5];
+ var nodes = [
+ iD.osmNode({id: 'a', loc: a}),
+ iD.osmNode({id: 'b', loc: b}),
+ iD.osmNode({id: 'c', loc: c}),
+ iD.osmNode({id: 'd', loc: d}),
+ iD.osmNode({id: 'e', loc: e})
+ ];
+ var choice = iD.geoChooseEdge(nodes, d, projection, 'd');
+ expect(choice.index).to.eql(1);
+ expect(choice.distance).to.eql(0.1);
+ expect(choice.loc).to.eql([2, 0]);
+ });
+
+ it('returns null if all nodes are skipped', function() {
+ var nodes = [
+ iD.osmNode({id: 'a', loc: [0, 0]}),
+ iD.osmNode({id: 'b', loc: [5, 0]}),
+ ];
+ var choice = iD.geoChooseEdge(nodes, [2, 2], projection, 'a');
+ expect(choice).to.be.null;
+ });
+ });
+
+ describe('geoLineIntersection', function() {
+ it('returns null if lines are colinear with overlap', function() {
+ var a = [[0, 0], [10, 0]];
+ var b = [[-5, 0], [5, 0]];
+ expect(iD.geoLineIntersection(a, b)).to.be.null;
+ });
+ it('returns null if lines are colinear but disjoint', function() {
+ var a = [[5, 0], [10, 0]];
+ var b = [[-10, 0], [-5, 0]];
+ expect(iD.geoLineIntersection(a, b)).to.be.null;
+ });
+ it('returns null if lines are parallel', function() {
+ var a = [[0, 0], [10, 0]];
+ var b = [[0, 5], [10, 5]];
+ expect(iD.geoLineIntersection(a, b)).to.be.null;
+ });
+ it('returns the intersection point between 2 lines', function() {
+ var a = [[0, 0], [10, 0]];
+ var b = [[5, 10], [5, -10]];
+ expect(iD.geoLineIntersection(a, b)).to.eql([5, 0]);
+ });
+ it('returns null if lines are not parallel but not intersecting', function() {
+ var a = [[0, 0], [10, 0]];
+ var b = [[-5, 10], [-5, -10]];
+ expect(iD.geoLineIntersection(a, b)).to.be.null;
+ });
+ });
+
+ describe('geoPointInPolygon', function() {
+ it('says a point in a polygon is on a polygon', function() {
+ var poly = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
+ var point = [0.5, 0.5];
+ expect(iD.geoPointInPolygon(point, poly)).to.be.true;
+ });
+ it('says a point outside of a polygon is outside', function() {
+ var poly = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
+ var point = [0.5, 1.5];
+ expect(iD.geoPointInPolygon(point, poly)).to.be.false;
+ });
+ });
+
+ describe('geoPolygonContainsPolygon', function() {
+ it('says a polygon in a polygon is in', function() {
+ var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
+ var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]];
+ expect(iD.geoPolygonContainsPolygon(outer, inner)).to.be.true;
+ });
+ it('says a polygon outside of a polygon is out', function() {
+ var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
+ var inner = [[1, 1], [1, 9], [2, 2], [2, 1], [1, 1]];
+ expect(iD.geoPolygonContainsPolygon(outer, inner)).to.be.false;
+ });
+ });
+
+ describe('geoPolygonIntersectsPolygon', function() {
+ it('returns true when outer polygon fully contains inner', function() {
+ var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
+ var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]];
+ expect(iD.geoPolygonIntersectsPolygon(outer, inner)).to.be.true;
+ });
+
+ it('returns true when outer polygon partially contains inner (some vertices contained)', function() {
+ var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
+ var inner = [[-1, -1], [1, 2], [2, 2], [2, 1], [1, 1]];
+ expect(iD.geoPolygonIntersectsPolygon(outer, inner)).to.be.true;
+ });
+
+ it('returns false when outer polygon partially contains inner (no vertices contained - lax test)', function() {
+ var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
+ var inner = [[1, -1], [1, 4], [2, 4], [2, -1], [1, -1]];
+ expect(iD.geoPolygonIntersectsPolygon(outer, inner)).to.be.false;
+ });
+
+ it('returns true when outer polygon partially contains inner (no vertices contained - strict test)', function() {
+ var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
+ var inner = [[1, -1], [1, 4], [2, 4], [2, -1], [1, -1]];
+ expect(iD.geoPolygonIntersectsPolygon(outer, inner, true)).to.be.true;
+ });
+
+ it('returns false when outer and inner are fully disjoint', function() {
+ var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
+ var inner = [[-1, -1], [-1, -2], [-2, -2], [-2, -1], [-1, -1]];
+ expect(iD.geoPolygonIntersectsPolygon(outer, inner)).to.be.false;
+ });
+ });
+
+ describe('geoPathLength', function() {
+ it('calculates a simple path length', function() {
+ var path = [[0, 0], [0, 1], [3, 5]];
+ expect(iD.geoPathLength(path)).to.eql(6);
+ });
+
+ it('does not fail on single-point path', function() {
+ var path = [[0, 0]];
+ expect(iD.geoPathLength(path)).to.eql(0);
+ });
+
+ it('estimates zero-length edges', function() {
+ var path = [[0, 0], [0, 0]];
+ expect(iD.geoPathLength(path)).to.eql(0);
+ });
+ });
+
+ describe('geoViewportEdge', function() {
+ var dimensions = [1000, 1000];
+ it('returns null if the point is not at the edge', function() {
+ expect(iD.geoViewportEdge([500, 500], dimensions)).to.be.null;
+ });
+ it('nudges top edge', function() {
+ expect(iD.geoViewportEdge([500, 5], dimensions)).to.eql([0, 10]);
+ });
+ it('nudges top-right corner', function() {
+ expect(iD.geoViewportEdge([995, 5], dimensions)).to.eql([-10, 10]);
+ });
+ it('nudges right edge', function() {
+ expect(iD.geoViewportEdge([995, 500], dimensions)).to.eql([-10, 0]);
+ });
+ it('nudges bottom-right corner', function() {
+ expect(iD.geoViewportEdge([995, 995], dimensions)).to.eql([-10, -10]);
+ });
+ it('nudges bottom edge', function() {
+ expect(iD.geoViewportEdge([500, 995], dimensions)).to.eql([0, -10]);
+ });
+ it('nudges bottom-left corner', function() {
+ expect(iD.geoViewportEdge([5, 995], dimensions)).to.eql([10, -10]);
+ });
+ it('nudges left edge', function() {
+ expect(iD.geoViewportEdge([5, 500], dimensions)).to.eql([10, 0]);
+ });
+ it('nudges top-left corner', function() {
+ expect(iD.geoViewportEdge([5, 5], dimensions)).to.eql([10, 10]);
+ });
+ });
+
+});
diff --git a/test/spec/geo/vector.js b/test/spec/geo/vector.js
index 21968bb5a..7b69aab00 100644
--- a/test/spec/geo/vector.js
+++ b/test/spec/geo/vector.js
@@ -1,4 +1,4 @@
-describe('iD.geo vector', function() {
+describe('iD.geo - vector', function() {
describe('geoVecEqual', function() {
it('tests vectors for equality', function() {