diff --git a/modules/actions/circularize.js b/modules/actions/circularize.js index dc66825ac..908edb349 100644 --- a/modules/actions/circularize.js +++ b/modules/actions/circularize.js @@ -10,11 +10,7 @@ import { polygonCentroid as d3_polygonCentroid } from 'd3-polygon'; -import { - geoEuclideanDistance, - geoInterp -} from '../geo'; - +import { geoVecInterp, geoVecLength } from '../geo'; import { osmNode } from '../osm'; @@ -41,8 +37,8 @@ export function actionCircularize(wayId, projection, maxAngle) { keyNodes = nodes.filter(function(n) { return graph.parentWays(n).length !== 1; }), points = nodes.map(function(n) { return projection(n.loc); }), keyPoints = keyNodes.map(function(n) { return projection(n.loc); }), - centroid = (points.length === 2) ? geoInterp(points[0], points[1], 0.5) : d3_polygonCentroid(points), - radius = d3_median(points, function(p) { return geoEuclideanDistance(centroid, p); }), + centroid = (points.length === 2) ? geoVecInterp(points[0], points[1], 0.5) : d3_polygonCentroid(points), + radius = d3_median(points, function(p) { return geoVecLength(centroid, p); }), sign = d3_polygonArea(points) > 0 ? 1 : -1, ids; @@ -82,7 +78,7 @@ export function actionCircularize(wayId, projection, maxAngle) { } // position this key node - var distance = geoEuclideanDistance(centroid, keyPoints[i]); + var distance = geoVecLength(centroid, keyPoints[i]); if (distance === 0) { distance = 1e-4; } keyPoints[i] = [ centroid[0] + (keyPoints[i][0] - centroid[0]) / distance * radius, @@ -91,7 +87,7 @@ export function actionCircularize(wayId, projection, maxAngle) { loc = projection.invert(keyPoints[i]); node = keyNodes[i]; origNode = origNodes[node.id]; - node = node.move(geoInterp(origNode.loc, loc, t)); + node = node.move(geoVecInterp(origNode.loc, loc, t)); graph = graph.replace(node); // figure out the between delta angle we want to match to @@ -122,7 +118,7 @@ export function actionCircularize(wayId, projection, maxAngle) { origNode = origNodes[node.id]; nearNodes[node.id] = angle; - node = node.move(geoInterp(origNode.loc, loc, t)); + node = node.move(geoVecInterp(origNode.loc, loc, t)); graph = graph.replace(node); } @@ -145,7 +141,7 @@ export function actionCircularize(wayId, projection, maxAngle) { } } - node = osmNode({ loc: geoInterp(origNode.loc, loc, t) }); + node = osmNode({ loc: geoVecInterp(origNode.loc, loc, t) }); graph = graph.replace(node); nodes.splice(endNodeIndex + j, 0, node); @@ -220,7 +216,7 @@ export function actionCircularize(wayId, projection, maxAngle) { // move interior nodes to the surface of the convex hull.. for (var j = 1; j < indexRange; j++) { - var point = geoInterp(hull[i], hull[i+1], j / indexRange), + var point = geoVecInterp(hull[i], hull[i+1], j / indexRange), node = nodes[(j + startIndex) % nodes.length].move(projection.invert(point)); graph = graph.replace(node); } diff --git a/modules/actions/move.js b/modules/actions/move.js index baa75fb32..1f400085c 100644 --- a/modules/actions/move.js +++ b/modules/actions/move.js @@ -13,11 +13,11 @@ import { osmNode } from '../osm'; import { geoAngle, geoChooseEdge, - geoInterp, geoPathIntersections, geoPathLength, geoSphericalDistance, geoVecAdd, + geoVecInterp, geoVecSubtract } from '../geo'; @@ -206,7 +206,7 @@ export function actionMove(moveIds, tryDelta, projection, cache) { if (!isEP1 && !isEP2) { var epsilon = 1e-4, maxIter = 10; for (var i = 0; i < maxIter; i++) { - loc = geoInterp(edge1.loc, edge2.loc, 0.5); + loc = geoVecInterp(edge1.loc, edge2.loc, 0.5); edge1 = geoChooseEdge(nodes1, projection(loc), projection); edge2 = geoChooseEdge(nodes2, projection(loc), projection); if (Math.abs(edge1.distance - edge2.distance) < epsilon) break; diff --git a/modules/actions/move_node.js b/modules/actions/move_node.js index 8de29a16a..4288dcbc5 100644 --- a/modules/actions/move_node.js +++ b/modules/actions/move_node.js @@ -1,4 +1,4 @@ -import { geoInterp } from '../geo'; +import { geoVecInterp } from '../geo'; export function actionMoveNode(nodeID, toLoc) { @@ -8,7 +8,7 @@ export function actionMoveNode(nodeID, toLoc) { var node = graph.entity(nodeID); return graph.replace( - node.move(geoInterp(node.loc, toLoc, t)) + node.move(geoVecInterp(node.loc, toLoc, t)) ); }; diff --git a/modules/actions/orthogonalize.js b/modules/actions/orthogonalize.js index 33b4a4c61..b9d64b205 100644 --- a/modules/actions/orthogonalize.js +++ b/modules/actions/orthogonalize.js @@ -2,10 +2,7 @@ import _clone from 'lodash-es/clone'; import _uniq from 'lodash-es/uniq'; import { actionDeleteNode } from './delete_node'; -import { - geoEuclideanDistance, - geoInterp -} from '../geo'; +import { geoVecInterp, geoVecLength } from '../geo'; /* @@ -40,7 +37,7 @@ export function actionOrthogonalize(wayId, projection) { node = graph.entity(nodes[corner.i].id); loc = projection.invert(points[corner.i]); - graph = graph.replace(node.move(geoInterp(node.loc, loc, t))); + graph = graph.replace(node.move(geoVecInterp(node.loc, loc, t))); } else { var best, @@ -69,7 +66,7 @@ export function actionOrthogonalize(wayId, projection) { if (originalPoints[i][0] !== points[i][0] || originalPoints[i][1] !== points[i][1]) { loc = projection.invert(points[i]); node = graph.entity(nodes[i].id); - graph = graph.replace(node.move(geoInterp(node.loc, loc, t))); + graph = graph.replace(node.move(geoVecInterp(node.loc, loc, t))); } } @@ -100,7 +97,7 @@ export function actionOrthogonalize(wayId, projection) { q = subtractPoints(c, b), scale, dotp; - scale = 2 * Math.min(geoEuclideanDistance(p, [0, 0]), geoEuclideanDistance(q, [0, 0])); + scale = 2 * Math.min(geoVecLength(p, [0, 0]), geoVecLength(q, [0, 0])); p = normalizePoint(p, 1.0); q = normalizePoint(q, 1.0); diff --git a/modules/actions/reflect.js b/modules/actions/reflect.js index b27c9eadf..be435c5f9 100644 --- a/modules/actions/reflect.js +++ b/modules/actions/reflect.js @@ -4,10 +4,10 @@ import { } from 'd3-polygon'; import { - geoEuclideanDistance, geoExtent, - geoInterp, - geoRotate + geoRotate, + geoVecInterp, + geoVecLength } from '../geo'; import { utilGetAllNodes } from '../util'; @@ -69,7 +69,7 @@ export function actionReflect(reflectIds, projection) { q2 = [(ssr.poly[1][0] + ssr.poly[2][0]) / 2, (ssr.poly[1][1] + ssr.poly[2][1]) / 2 ], p, q; - var isLong = (geoEuclideanDistance(p1, q1) > geoEuclideanDistance(p2, q2)); + var isLong = (geoVecLength(p1, q1) > geoVecLength(p2, q2)); if ((useLongAxis && isLong) || (!useLongAxis && !isLong)) { p = p1; q = q1; @@ -92,7 +92,7 @@ export function actionReflect(reflectIds, projection) { b * (c[0] - p[0]) - a * (c[1] - p[1]) + p[1] ]; var loc2 = projection.invert(c2); - node = node.move(geoInterp(node.loc, loc2, t)); + node = node.move(geoVecInterp(node.loc, loc2, t)); graph = graph.replace(node); } diff --git a/modules/actions/straighten.js b/modules/actions/straighten.js index e16bdd613..e9456d220 100644 --- a/modules/actions/straighten.js +++ b/modules/actions/straighten.js @@ -1,8 +1,8 @@ import { actionDeleteNode } from './delete_node'; import { - geoEuclideanDistance, - geoInterp + geoVecInterp, + geoVecLength } from '../geo'; @@ -44,7 +44,7 @@ export function actionStraighten(wayId, projection) { ], loc2 = projection.invert(p); - graph = graph.replace(node.move(geoInterp(node.loc, loc2, t))); + graph = graph.replace(node.move(geoVecInterp(node.loc, loc2, t))); } else { // safe to delete @@ -69,7 +69,7 @@ export function actionStraighten(wayId, projection) { points = nodes.map(function(n) { return projection(n.loc); }), startPoint = points[0], endPoint = points[points.length-1], - threshold = 0.2 * geoEuclideanDistance(startPoint, endPoint), + threshold = 0.2 * geoVecLength(startPoint, endPoint), i; if (threshold === 0) { diff --git a/modules/behavior/draw.js b/modules/behavior/draw.js index 513c0b30c..77315e03b 100644 --- a/modules/behavior/draw.js +++ b/modules/behavior/draw.js @@ -14,7 +14,7 @@ import { behaviorTail } from './tail'; import { geoChooseEdge, - geoEuclideanDistance, + geoVecLength, geoViewportEdge } from '../geo'; @@ -81,7 +81,7 @@ export function behaviorDraw(context) { d3_select(window).on('mouseup.draw', function() { var t2 = +new Date(); var p2 = point(); - var dist = geoEuclideanDistance(p1, p2); + var dist = geoVecLength(p1, p2); element.on('mousemove.draw', mousemove); d3_select(window).on('mouseup.draw', null); @@ -157,7 +157,7 @@ export function behaviorDraw(context) { var currSpace = context.mouse(); if (_disableSpace && _lastSpace) { - var dist = geoEuclideanDistance(_lastSpace, currSpace); + var dist = geoVecLength(_lastSpace, currSpace); if (dist > tolerance) { _disableSpace = false; } diff --git a/modules/behavior/select.js b/modules/behavior/select.js index a77044133..98799e625 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -6,7 +6,7 @@ import { select as d3_select } from 'd3-selection'; -import { geoEuclideanDistance } from '../geo'; +import { geoVecLength } from '../geo'; import { modeBrowse, @@ -103,7 +103,7 @@ export function behaviorSelect(context) { if (!p1) return; var p2 = point(); - var dist = geoEuclideanDistance(p1, p2); + var dist = geoVecLength(p1, p2); p1 = null; if (dist > tolerance) { diff --git a/modules/geo/geo.js b/modules/geo/geo.js index 7a4fe121a..0c5a27778 100644 --- a/modules/geo/geo.js +++ b/modules/geo/geo.js @@ -1,6 +1,15 @@ import _every from 'lodash-es/every'; import _some from 'lodash-es/some'; +import { + geoVecAngle, + geoVecCross, + geoVecDot, + geoVecInterp, + geoVecLength, + geoVecSubtract +} from './vector.js'; + // constants var TAU = 2 * Math.PI; @@ -8,66 +17,6 @@ var EQUATORIAL_RADIUS = 6356752.314245179; var POLAR_RADIUS = 6378137.0; -// vector addition -export function geoVecEquals(a, b) { - return (a[0] === b[0]) && (a[1] === b[1]); -} - -// vector addition -export function geoVecAdd(a, b) { - return [ a[0] + b[0], a[1] + b[1] ]; -} - -// vector subtraction -export function geoVecSubtract(a, b) { - return [ a[0] - b[0], a[1] - b[1] ]; -} - -// vector multiplication -export function geoVecScale(a, b) { - return [ a[0] * b, a[1] * b ]; -} - -// vector rounding (was: geoRoundCoordinates) -export function geoVecFloor(a) { - return [ Math.floor(a[0]), Math.floor(a[1]) ]; -} - -// linear interpolation -export function geoInterp(p1, p2, t) { - return [ - p1[0] + (p2[0] - p1[0]) * t, - p1[1] + (p2[1] - p1[1]) * t - ]; -} - - -// dot product -export function geoDot(a, b, origin) { - origin = origin || [0, 0]; - return (a[0] - origin[0]) * (b[0] - origin[0]) + - (a[1] - origin[1]) * (b[1] - origin[1]); -} - - -// 2D cross product of OA and OB vectors, returns magnitude of Z vector -// Returns a positive value, if OAB makes a counter-clockwise turn, -// negative for clockwise turn, and zero if the points are collinear. -export function geoCross(a, b, origin) { - origin = origin || [0, 0]; - return (a[0] - origin[0]) * (b[1] - origin[1]) - - (a[1] - origin[1]) * (b[0] - origin[0]); -} - - -// http://jsperf.com/id-dist-optimization -export function geoEuclideanDistance(a, b) { - var x = a[0] - b[0]; - var y = a[1] - b[1]; - return Math.sqrt((x * x) + (y * y)); -} - - export function geoLatToMeters(dLat) { return dLat * (TAU * POLAR_RADIUS / 360); } @@ -140,9 +89,7 @@ export function geoEdgeEqual(a, b) { // 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) { - a = projection(a.loc); - b = projection(b.loc); - return Math.atan2(b[1] - a[1], b[0] - a[0]); + return geoVecAngle(projection(a.loc), projection(b.loc)); } @@ -163,7 +110,7 @@ export function geoRotate(points, angle, around) { // 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 = geoEuclideanDistance; + 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; @@ -176,7 +123,7 @@ export function geoChooseEdge(nodes, point, projection, skipID) { var o = points[i]; var s = geoVecSubtract(points[i + 1], o); var v = geoVecSubtract(point, o); - var proj = geoDot(v, s) / geoDot(s, s); + var proj = geoVecDot(v, s) / geoVecDot(s, s); var p; if (proj < 0) { @@ -214,15 +161,15 @@ export function geoLineIntersection(a, b) { var q2 = [b[1][0], b[1][1]]; var r = geoVecSubtract(p2, p); var s = geoVecSubtract(q2, q); - var uNumerator = geoCross(geoVecSubtract(q, p), r); - var denominator = geoCross(r, s); + var uNumerator = geoVecCross(geoVecSubtract(q, p), r); + var denominator = geoVecCross(r, s); if (uNumerator && denominator) { var u = uNumerator / denominator; - var t = geoCross(geoVecSubtract(q, p), s) / denominator; + var t = geoVecCross(geoVecSubtract(q, p), s) / denominator; if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) { - return geoInterp(p, p2, t); + return geoVecInterp(p, p2, t); } } @@ -284,7 +231,7 @@ 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 a = [ outer[i], outer[i + 1] ]; var b = [ inner[j], inner[j + 1] ]; if (geoLineIntersection(a, b)) return true; } @@ -305,7 +252,7 @@ export function geoPolygonIntersectsPolygon(outer, inner, checkSegments) { export function geoPathLength(path) { var length = 0; for (var i = 0; i < path.length - 1; i++) { - length += geoEuclideanDistance(path[i], path[i + 1]); + length += geoVecLength(path[i], path[i + 1]); } return length; } diff --git a/modules/geo/index.js b/modules/geo/index.js index 95bba82c5..dca2f31dd 100644 --- a/modules/geo/index.js +++ b/modules/geo/index.js @@ -1,11 +1,7 @@ export { geoAngle } from './geo.js'; export { geoChooseEdge } from './geo.js'; -export { geoCross } from './geo.js'; -export { geoDot } from './geo.js'; export { geoEdgeEqual } from './geo.js'; -export { geoEuclideanDistance } from './geo.js'; export { geoExtent } from './extent.js'; -export { geoInterp } from './geo.js'; export { geoRawMercator } from './raw_mercator.js'; export { geoRotate } from './geo.js'; export { geoLatToMeters } from './geo.js'; @@ -22,10 +18,15 @@ export { geoPolygonContainsPolygon } from './geo.js'; export { geoPolygonIntersectsPolygon } from './geo.js'; export { geoScaleToZoom } from './geo.js'; export { geoSphericalDistance } from './geo.js'; -export { geoVecAdd } from './geo.js'; -export { geoVecEquals } from './geo.js'; -export { geoVecFloor } from './geo.js'; -export { geoVecSubtract } from './geo.js'; -export { geoVecScale } from './geo.js'; +export { geoVecAdd } from './vector.js'; +export { geoVecAngle } from './vector.js'; +export { geoVecCross } from './vector.js'; +export { geoVecDot } from './vector.js'; +export { geoVecEqual } from './vector.js'; +export { geoVecFloor } from './vector.js'; +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/modules/geo/vector.js b/modules/geo/vector.js new file mode 100644 index 000000000..0e7929b3b --- /dev/null +++ b/modules/geo/vector.js @@ -0,0 +1,62 @@ +// vector equals +export function geoVecEqual(a, b) { + return (a[0] === b[0]) && (a[1] === b[1]); +} + +// vector addition +export function geoVecAdd(a, b) { + return [ a[0] + b[0], a[1] + b[1] ]; +} + +// vector subtraction +export function geoVecSubtract(a, b) { + return [ a[0] - b[0], a[1] - b[1] ]; +} + +// vector multiplication +export function geoVecScale(a, b) { + return [ a[0] * b, a[1] * b ]; +} + +// vector rounding (was: geoRoundCoordinates) +export function geoVecFloor(a) { + return [ Math.floor(a[0]), Math.floor(a[1]) ]; +} + +// linear interpolation +export function geoVecInterp(a, b, t) { + return [ + a[0] + (b[0] - a[0]) * t, + a[1] + (b[1] - a[1]) * t + ]; +} + +// http://jsperf.com/id-dist-optimization +export function geoVecLength(a, b) { + var x = a[0] - b[0]; + var y = a[1] - b[1]; + return Math.sqrt((x * x) + (y * y)); +} + +// Return the counterclockwise angle in the range (-pi, pi) +// between the positive X axis and the line intersecting a and b. +export function geoVecAngle(a, b) { + return Math.atan2(b[1] - a[1], b[0] - a[0]); +} + +// dot product +export function geoVecDot(a, b, origin) { + origin = origin || [0, 0]; + return (a[0] - origin[0]) * (b[0] - origin[0]) + + (a[1] - origin[1]) * (b[1] - origin[1]); +} + +// 2D cross product of OA and OB vectors, returns magnitude of Z vector +// Returns a positive value, if OAB makes a counter-clockwise turn, +// negative for clockwise turn, and zero if the points are collinear. +export function geoVecCross(a, b, origin) { + origin = origin || [0, 0]; + return (a[0] - origin[0]) * (b[1] - origin[1]) - + (a[1] - origin[1]) * (b[0] - origin[0]); +} + diff --git a/modules/index.js b/modules/index.js index 981a047db..dcd7f276e 100644 --- a/modules/index.js +++ b/modules/index.js @@ -29,6 +29,10 @@ export { coreDifference as Difference } from './core/difference'; export { coreGraph as Graph } from './core/graph'; export { coreHistory as History } from './core/history'; export { coreTree as Tree } from './core/tree'; +export { geoVecCross as geoCross } from './geo/vector'; +export { geoVecInterp as geoInterp } from './geo/vector'; +export { geoVecFloor as geoRoundCoordinates } from './geo/vector'; +export { geoVecLength as geoEuclideanDistance } from './geo/vector'; export { osmEntity as Entity } from './osm/entity'; export { osmNode as Node } from './osm/node'; export { osmRelation as Relation } from './osm/relation'; diff --git a/modules/modes/drag_node.js b/modules/modes/drag_node.js index d165949d5..3809a352f 100644 --- a/modules/modes/drag_node.js +++ b/modules/modes/drag_node.js @@ -21,7 +21,7 @@ import { import { geoChooseEdge, geoLineIntersection, - geoVecEquals, + geoVecEqual, geoVecSubtract, geoViewportEdge } from '../geo'; @@ -203,8 +203,8 @@ export function modeDragNode(context) { var p = actives[j]; var q = inactives[k]; // skip if segments share an endpoint - if (geoVecEquals(p[1], q[0]) || geoVecEquals(p[0], q[1]) || - geoVecEquals(p[0], q[0]) || geoVecEquals(p[1], q[1]) ) { + if (geoVecEqual(p[1], q[0]) || geoVecEqual(p[0], q[1]) || + geoVecEqual(p[0], q[0]) || geoVecEqual(p[1], q[1]) ) { continue; } else if (geoLineIntersection(p, q)) { return true; diff --git a/modules/modes/rotate.js b/modules/modes/rotate.js index d889a174b..4addaed88 100644 --- a/modules/modes/rotate.js +++ b/modules/modes/rotate.js @@ -13,7 +13,7 @@ import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; import { actionRotate } from '../actions'; import { behaviorEdit } from '../behavior'; -import { geoInterp } from '../geo'; +import { geoVecInterp } from '../geo'; import { modeBrowse, @@ -80,7 +80,7 @@ export function modeRotate(context, entityIDs) { if (points.length === 1) { // degenerate case _pivot = points[0]; } else if (points.length === 2) { - _pivot = geoInterp(points[0], points[1], 0.5); + _pivot = geoVecInterp(points[0], points[1], 0.5); } else { _pivot = d3_polygonCentroid(d3_polygonHull(points)); } diff --git a/modules/osm/way.js b/modules/osm/way.js index c93224146..14e4e410c 100644 --- a/modules/osm/way.js +++ b/modules/osm/way.js @@ -4,7 +4,7 @@ import _uniq from 'lodash-es/uniq'; import { geoArea as d3_geoArea } from 'd3-geo'; -import { geoExtent, geoCross } from '../geo'; +import { geoExtent, geoVecCross } from '../geo'; import { osmEntity } from './entity'; import { osmLanes } from './lanes'; import { osmOneWayTags } from './tags'; @@ -142,7 +142,7 @@ _extend(osmWay.prototype, { var o = coords[(i+1) % coords.length]; var a = coords[i]; var b = coords[(i+2) % coords.length]; - var res = geoCross(a, b, o); + var res = geoVecCross(a, b, o); curr = (res > 0) ? 1 : (res < 0) ? -1 : 0; if (curr === 0) { diff --git a/modules/renderer/tile_layer.js b/modules/renderer/tile_layer.js index 9d143f045..b0fb42aa3 100644 --- a/modules/renderer/tile_layer.js +++ b/modules/renderer/tile_layer.js @@ -2,7 +2,7 @@ import { select as d3_select } from 'd3-selection'; import { t } from '../util/locale'; import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile'; -import { geoEuclideanDistance, geoScaleToZoom } from '../geo'; +import { geoScaleToZoom, geoVecLength } from '../geo'; import { utilPrefixCSSProperty } from '../util'; @@ -187,7 +187,7 @@ export function rendererTileLayer(context) { requests.forEach(function(d) { var c = tileCenter(d); - var dist = geoEuclideanDistance(c, mapCenter); + var dist = geoVecLength(c, mapCenter); if (dist < minDist) { minDist = dist; nearCenter = d; diff --git a/modules/svg/helpers.js b/modules/svg/helpers.js index b4d00264e..40f28d0a6 100644 --- a/modules/svg/helpers.js +++ b/modules/svg/helpers.js @@ -6,7 +6,7 @@ import { geoStream as d3_geoStream } from 'd3-geo'; -import { geoEuclideanDistance } from '../geo'; +import { geoVecLength } from '../geo'; // Touch targets control which other vertices we can drag a vertex onto. @@ -82,7 +82,7 @@ export function svgOneWaySegments(projection, graph, dt) { b = [x, y]; if (a) { - var span = geoEuclideanDistance(a, b) - offset; + var span = geoVecLength(a, b) - offset; if (span >= 0) { var angle = Math.atan2(b[1] - a[1], b[0] - a[0]); diff --git a/modules/svg/labels.js b/modules/svg/labels.js index 15c3d2b08..c79448d75 100644 --- a/modules/svg/labels.js +++ b/modules/svg/labels.js @@ -10,11 +10,11 @@ import { textDirection } from '../util/locale'; import { geoExtent, - geoEuclideanDistance, - geoInterp, geoPolygonIntersectsPolygon, geoPathLength, - geoScaleToZoom + geoScaleToZoom, + geoVecInterp, + geoVecLength } from '../geo'; import { osmEntity } from '../osm'; @@ -489,10 +489,10 @@ export function svgLabels(projection, context) { var b = sub[j + 1]; // split up the text into small collision boxes - var num = Math.max(1, Math.floor(geoEuclideanDistance(a, b) / boxsize / 2)); + var num = Math.max(1, Math.floor(geoVecLength(a, b) / boxsize / 2)); for (var box = 0; box < num; box++) { - var p = geoInterp(a, b, box / num); + var p = geoVecInterp(a, b, box / num); var x0 = p[0] - boxsize - padding; var y0 = p[1] - boxsize - padding; var x1 = p[0] + boxsize + padding; @@ -532,7 +532,7 @@ export function svgLabels(projection, context) { for (var i = 0; i < points.length - 1; i++) { var a = points[i]; var b = points[i + 1]; - var current = geoEuclideanDistance(a, b); + var current = geoVecLength(a, b); var portion; if (!start && sofar + current >= from) { portion = (from - sofar) / current; diff --git a/modules/svg/midpoints.js b/modules/svg/midpoints.js index ed629c6c6..bcf9495b2 100644 --- a/modules/svg/midpoints.js +++ b/modules/svg/midpoints.js @@ -7,9 +7,9 @@ import { import { geoAngle, - geoEuclideanDistance, - geoInterp, - geoLineIntersection + geoLineIntersection, + geoVecInterp, + geoVecLength } from '../geo'; @@ -73,8 +73,8 @@ export function svgMidpoints(projection, context) { if (midpoints[id]) { midpoints[id].parents.push(entity); } else { - if (geoEuclideanDistance(projection(a.loc), projection(b.loc)) > 40) { - var point = geoInterp(a.loc, b.loc, 0.5); + if (geoVecLength(projection(a.loc), projection(b.loc)) > 40) { + var point = geoVecInterp(a.loc, b.loc, 0.5); var loc = null; if (extent.intersects(point)) { @@ -83,8 +83,8 @@ export function svgMidpoints(projection, context) { for (var k = 0; k < 4; k++) { point = geoLineIntersection([a.loc, b.loc], [poly[k], poly[k + 1]]); if (point && - geoEuclideanDistance(projection(a.loc), projection(point)) > 20 && - geoEuclideanDistance(projection(b.loc), projection(point)) > 20) + geoVecLength(projection(a.loc), projection(point)) > 20 && + geoVecLength(projection(b.loc), projection(point)) > 20) { loc = point; break; diff --git a/test/index.html b/test/index.html index 18092851b..ed2cb0c54 100644 --- a/test/index.html +++ b/test/index.html @@ -74,6 +74,7 @@ + diff --git a/test/spec/actions/circularize.js b/test/spec/actions/circularize.js index fb54bbd05..5d7e4c73c 100644 --- a/test/spec/actions/circularize.js +++ b/test/spec/actions/circularize.js @@ -5,7 +5,7 @@ describe('iD.actionCircularize', function () { var points = graph.childNodes(graph.entity(id)) .map(function (n) { return projection(n.loc); }), centroid = d3.polygonCentroid(points), - radius = iD.geoEuclideanDistance(centroid, points[0]), + radius = iD.geoVecLength(centroid, points[0]), estArea = Math.PI * radius * radius, trueArea = Math.abs(d3.polygonArea(points)), pctDiff = (estArea - trueArea) / estArea; @@ -31,10 +31,10 @@ describe('iD.actionCircularize', function () { vector2 = [point2[0] - center[0], point2[1] - center[1]], distance; - distance = iD.geoEuclideanDistance(vector1, [0, 0]); + distance = iD.geoVecLength(vector1, [0, 0]); vector1 = [vector1[0] / distance, vector1[1] / distance]; - distance = iD.geoEuclideanDistance(vector2, [0, 0]); + distance = iD.geoVecLength(vector2, [0, 0]); vector2 = [vector2[0] / distance, vector2[1] / distance]; return 180 / Math.PI * Math.acos(vector1[0] * vector2[0] + vector1[1] * vector2[1]); @@ -106,7 +106,7 @@ describe('iD.actionCircularize', function () { graph = iD.actionCircularize('-', projection)(graph); expect(isCircular('-', graph)).to.be.ok; - expect(iD.geoEuclideanDistance(graph.entity('d').loc, [2, -2])).to.be.lt(0.5); + expect(iD.geoVecLength(graph.entity('d').loc, [2, -2])).to.be.lt(0.5); }); it('creates circle respecting min-angle limit', function() { diff --git a/test/spec/geo/geo.js b/test/spec/geo/geo.js index 585e7a2e7..9f31f1d27 100644 --- a/test/spec/geo/geo.js +++ b/test/spec/geo/geo.js @@ -1,108 +1,5 @@ describe('iD.geo', function() { - describe('geoVecEquals', function() { - it('tests vectors for equality', function() { - expect(iD.geoVecEquals([1, 2], [1, 2])).to.be.true; - expect(iD.geoVecEquals([1, 2], [1, 0])).to.be.false; - expect(iD.geoVecEquals([1, 2], [2, 1])).to.be.false; - }); - }); - - describe('geoVecAdd', function() { - it('adds vectors', function() { - expect(iD.geoVecAdd([1, 2], [3, 4])).to.eql([4, 6]); - expect(iD.geoVecAdd([1, 2], [0, 0])).to.eql([1, 2]); - expect(iD.geoVecAdd([1, 2], [-3, -4])).to.eql([-2, -2]); - }); - }); - - describe('geoVecSubtract', function() { - it('subtracts vectors', function() { - expect(iD.geoVecSubtract([1, 2], [3, 4])).to.eql([-2, -2]); - expect(iD.geoVecSubtract([1, 2], [0, 0])).to.eql([1, 2]); - expect(iD.geoVecSubtract([1, 2], [-3, -4])).to.eql([4, 6]); - }); - }); - - describe('geoVecScale', function() { - it('multiplies vectors', function() { - expect(iD.geoVecScale([1, 2], 0)).to.eql([0, 0]); - expect(iD.geoVecScale([1, 2], 1)).to.eql([1, 2]); - expect(iD.geoVecScale([1, 2], 2)).to.eql([2, 4]); - expect(iD.geoVecScale([1, 2], 0.5)).to.eql([0.5, 1]); - }); - }); - - describe('geoVecFloor (was: geoRoundCoordinates)', function() { - it('rounds vectors', function() { - expect(iD.geoVecFloor([0.1, 1])).to.eql([0, 1]); - expect(iD.geoVecFloor([0, 1])).to.eql([0, 1]); - expect(iD.geoVecFloor([0, 1.1])).to.eql([0, 1]); - }); - }); - - describe('geoInterp', function() { - it('interpolates halfway', function() { - var a = [0, 0]; - var b = [10, 10]; - expect(iD.geoInterp(a, b, 0.5)).to.eql([5, 5]); - }); - it('interpolates to one side', function() { - var a = [0, 0]; - var b = [10, 10]; - expect(iD.geoInterp(a, b, 0)).to.eql([0, 0]); - }); - }); - - describe('geoDot', function() { - it('dot product of right angle is zero', function() { - var a = [1, 0]; - var b = [0, 1]; - expect(iD.geoDot(a, b)).to.eql(0); - }); - it('dot product of same vector multiplies', function() { - var a = [2, 0]; - var b = [2, 0]; - expect(iD.geoDot(a, b)).to.eql(4); - }); - }); - - describe('geoCross', function() { - it('2D cross product of right hand turn is positive', function() { - var a = [2, 0]; - var b = [0, 2]; - expect(iD.geoCross(a, b)).to.eql(4); - }); - it('2D cross product of left hand turn is negative', function() { - var a = [2, 0]; - var b = [0, -2]; - expect(iD.geoCross(a, b)).to.eql(-4); - }); - it('2D cross product of colinear points is zero', function() { - var a = [-2, 0]; - var b = [2, 0]; - expect(iD.geoCross(a, b)).to.equal(0); - }); - }); - - describe('geoEuclideanDistance', function() { - it('distance between two same points is zero', function() { - var a = [0, 0]; - var b = [0, 0]; - expect(iD.geoEuclideanDistance(a, b)).to.eql(0); - }); - it('a straight 10 unit line is 10', function() { - var a = [0, 0]; - var b = [10, 0]; - expect(iD.geoEuclideanDistance(a, b)).to.eql(10); - }); - it('a pythagorean triangle is right', function() { - var a = [0, 0]; - var b = [4, 3]; - expect(iD.geoEuclideanDistance(a, b)).to.eql(5); - }); - }); - describe('geoLatToMeters', function() { it('0 degrees latitude is 0 meters', function() { expect(iD.geoLatToMeters(0)).to.eql(0); diff --git a/test/spec/geo/vector.js b/test/spec/geo/vector.js new file mode 100644 index 000000000..21968bb5a --- /dev/null +++ b/test/spec/geo/vector.js @@ -0,0 +1,115 @@ +describe('iD.geo vector', function() { + + describe('geoVecEqual', function() { + it('tests vectors for equality', function() { + expect(iD.geoVecEqual([1, 2], [1, 2])).to.be.true; + expect(iD.geoVecEqual([1, 2], [1, 0])).to.be.false; + expect(iD.geoVecEqual([1, 2], [2, 1])).to.be.false; + }); + }); + + describe('geoVecAdd', function() { + it('adds vectors', function() { + expect(iD.geoVecAdd([1, 2], [3, 4])).to.eql([4, 6]); + expect(iD.geoVecAdd([1, 2], [0, 0])).to.eql([1, 2]); + expect(iD.geoVecAdd([1, 2], [-3, -4])).to.eql([-2, -2]); + }); + }); + + describe('geoVecSubtract', function() { + it('subtracts vectors', function() { + expect(iD.geoVecSubtract([1, 2], [3, 4])).to.eql([-2, -2]); + expect(iD.geoVecSubtract([1, 2], [0, 0])).to.eql([1, 2]); + expect(iD.geoVecSubtract([1, 2], [-3, -4])).to.eql([4, 6]); + }); + }); + + describe('geoVecScale', function() { + it('multiplies vectors', function() { + expect(iD.geoVecScale([1, 2], 0)).to.eql([0, 0]); + expect(iD.geoVecScale([1, 2], 1)).to.eql([1, 2]); + expect(iD.geoVecScale([1, 2], 2)).to.eql([2, 4]); + expect(iD.geoVecScale([1, 2], 0.5)).to.eql([0.5, 1]); + }); + }); + + describe('geoVecFloor (was: geoRoundCoordinates)', function() { + it('rounds vectors', function() { + expect(iD.geoVecFloor([0.1, 1])).to.eql([0, 1]); + expect(iD.geoVecFloor([0, 1])).to.eql([0, 1]); + expect(iD.geoVecFloor([0, 1.1])).to.eql([0, 1]); + }); + }); + + describe('geoVecInterp', function() { + it('interpolates halfway', function() { + var a = [0, 0]; + var b = [10, 10]; + expect(iD.geoVecInterp(a, b, 0.5)).to.eql([5, 5]); + }); + it('interpolates to one side', function() { + var a = [0, 0]; + var b = [10, 10]; + expect(iD.geoVecInterp(a, b, 0)).to.eql([0, 0]); + }); + }); + + describe('geoVecLength (was: geoEuclideanDistance)', function() { + it('distance between two same points is zero', function() { + var a = [0, 0]; + var b = [0, 0]; + expect(iD.geoVecLength(a, b)).to.eql(0); + }); + it('a straight 10 unit line is 10', function() { + var a = [0, 0]; + var b = [10, 0]; + expect(iD.geoVecLength(a, b)).to.eql(10); + }); + it('a pythagorean triangle is right', function() { + var a = [0, 0]; + var b = [4, 3]; + expect(iD.geoVecLength(a, b)).to.eql(5); + }); + }); + + describe('geoVecAngle', function() { + it('returns angle between a and b', function() { + expect(iD.geoVecAngle([0, 0], [1, 0])).to.be.closeTo(0, 1e-6); + expect(iD.geoVecAngle([0, 0], [0, 1])).to.be.closeTo(Math.PI / 2, 1e-6); + expect(iD.geoVecAngle([0, 0], [-1, 0])).to.be.closeTo(Math.PI, 1e-6); + expect(iD.geoVecAngle([0, 0], [0, -1])).to.be.closeTo(-Math.PI / 2, 1e-6); + }); + }); + + describe('geoVecDot', function() { + it('dot product of right angle is zero', function() { + var a = [1, 0]; + var b = [0, 1]; + expect(iD.geoVecDot(a, b)).to.eql(0); + }); + it('dot product of same vector multiplies', function() { + var a = [2, 0]; + var b = [2, 0]; + expect(iD.geoVecDot(a, b)).to.eql(4); + }); + }); + + describe('geoVecCross', function() { + it('2D cross product of right hand turn is positive', function() { + var a = [2, 0]; + var b = [0, 2]; + expect(iD.geoVecCross(a, b)).to.eql(4); + }); + it('2D cross product of left hand turn is negative', function() { + var a = [2, 0]; + var b = [0, -2]; + expect(iD.geoVecCross(a, b)).to.eql(-4); + }); + it('2D cross product of colinear points is zero', function() { + var a = [-2, 0]; + var b = [2, 0]; + expect(iD.geoVecCross(a, b)).to.equal(0); + }); + }); + +});