Move a bunch of commonly used vector and projection math functions into geo

- geoVecAdd
- geoVecSubtract
- geoVecScale
- geoZoomToScale
- geoScaleToZoom
This commit is contained in:
Bryan Housel
2017-12-18 15:05:42 -05:00
parent 18c97d52c8
commit 2e2b037e36
21 changed files with 485 additions and 439 deletions
+44 -44
View File
@@ -11,22 +11,21 @@ import _without from 'lodash-es/without';
import { osmNode } from '../osm';
import {
geoChooseEdge,
geoAngle,
geoChooseEdge,
geoInterp,
geoPathIntersections,
geoPathLength,
geoSphericalDistance
geoSphericalDistance,
geoVecAdd,
geoVecSubtract
} from '../geo';
// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/MoveCommand.java
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as
export function actionMove(moveIds, tryDelta, projection, cache) {
var delta = tryDelta;
function vecAdd(a, b) { return [a[0] + b[0], a[1] + b[1]]; }
function vecSub(a, b) { return [a[0] - b[0], a[1] - b[1]]; }
var _delta = tryDelta;
function setupCache(graph) {
function canMove(nodeId) {
@@ -118,11 +117,11 @@ export function actionMove(moveIds, tryDelta, projection, cache) {
// Place a vertex where the moved vertex used to be, to preserve way shape..
function replaceMovedVertex(nodeId, wayId, graph, delta) {
var way = graph.entity(wayId),
moved = graph.entity(nodeId),
movedIndex = way.nodes.indexOf(nodeId),
len, prevIndex, nextIndex;
function replaceMovedVertex(nodeId, wayId, graph, _delta) {
var way = graph.entity(wayId);
var moved = graph.entity(nodeId);
var movedIndex = way.nodes.indexOf(nodeId);
var len, prevIndex, nextIndex;
if (way.isClosed()) {
len = way.nodes.length - 1;
@@ -134,14 +133,14 @@ export function actionMove(moveIds, tryDelta, projection, cache) {
nextIndex = movedIndex + 1;
}
var prev = graph.hasEntity(way.nodes[prevIndex]),
next = graph.hasEntity(way.nodes[nextIndex]);
var prev = graph.hasEntity(way.nodes[prevIndex]);
var next = graph.hasEntity(way.nodes[nextIndex]);
// Don't add orig vertex at endpoint..
if (!prev || !next) return graph;
var key = wayId + '_' + nodeId,
orig = cache.replacedVertex[key];
var key = wayId + '_' + nodeId;
var orig = cache.replacedVertex[key];
if (!orig) {
orig = osmNode();
cache.replacedVertex[key] = orig;
@@ -149,9 +148,9 @@ export function actionMove(moveIds, tryDelta, projection, cache) {
}
var start, end;
if (delta) {
if (_delta) {
start = projection(cache.startLoc[nodeId]);
end = projection.invert(vecAdd(start, delta));
end = projection.invert(geoVecAdd(start, _delta));
} else {
end = cache.startLoc[nodeId];
}
@@ -184,24 +183,24 @@ export function actionMove(moveIds, tryDelta, projection, cache) {
// Reorder nodes around intersections that have moved..
function unZorroIntersection(intersection, graph) {
var vertex = graph.entity(intersection.nodeId),
way1 = graph.entity(intersection.movedId),
way2 = graph.entity(intersection.unmovedId),
isEP1 = intersection.movedIsEP,
isEP2 = intersection.unmovedIsEP;
var vertex = graph.entity(intersection.nodeId);
var way1 = graph.entity(intersection.movedId);
var way2 = graph.entity(intersection.unmovedId);
var isEP1 = intersection.movedIsEP;
var isEP2 = intersection.unmovedIsEP;
// don't move the vertex if it is the endpoint of both ways.
if (isEP1 && isEP2) return graph;
var nodes1 = _without(graph.childNodes(way1), vertex),
nodes2 = _without(graph.childNodes(way2), vertex);
var nodes1 = _without(graph.childNodes(way1), vertex);
var nodes2 = _without(graph.childNodes(way2), vertex);
if (way1.isClosed() && way1.first() === vertex.id) nodes1.push(nodes1[0]);
if (way2.isClosed() && way2.first() === vertex.id) nodes2.push(nodes2[0]);
var edge1 = !isEP1 && geoChooseEdge(nodes1, projection(vertex.loc), projection),
edge2 = !isEP2 && geoChooseEdge(nodes2, projection(vertex.loc), projection),
loc;
var edge1 = !isEP1 && geoChooseEdge(nodes1, projection(vertex.loc), projection);
var edge2 = !isEP2 && geoChooseEdge(nodes2, projection(vertex.loc), projection);
var loc;
// snap vertex to nearest edge (or some point between them)..
if (!isEP1 && !isEP2) {
@@ -236,7 +235,7 @@ export function actionMove(moveIds, tryDelta, projection, cache) {
function cleanupIntersections(graph) {
_each(cache.intersection, function(obj) {
graph = replaceMovedVertex(obj.nodeId, obj.movedId, graph, delta);
graph = replaceMovedVertex(obj.nodeId, obj.movedId, graph, _delta);
graph = replaceMovedVertex(obj.nodeId, obj.unmovedId, graph, null);
graph = unZorroIntersection(obj, graph);
});
@@ -245,7 +244,7 @@ export function actionMove(moveIds, tryDelta, projection, cache) {
}
// check if moving way endpoint can cross an unmoved way, if so limit delta..
// check if moving way endpoint can cross an unmoved way, if so limit _delta..
function limitDelta(graph) {
_each(cache.intersection, function(obj) {
// Don't limit movement if this is vertex joins 2 endpoints..
@@ -253,27 +252,28 @@ export function actionMove(moveIds, tryDelta, projection, cache) {
// Don't limit movement if this vertex is not an endpoint anyway..
if (!obj.movedIsEP) return;
var node = graph.entity(obj.nodeId),
start = projection(node.loc),
end = vecAdd(start, delta),
movedNodes = graph.childNodes(graph.entity(obj.movedId)),
movedPath = _map(_map(movedNodes, 'loc'),
function(loc) { return vecAdd(projection(loc), delta); }),
unmovedNodes = graph.childNodes(graph.entity(obj.unmovedId)),
unmovedPath = _map(_map(unmovedNodes, 'loc'), projection),
hits = geoPathIntersections(movedPath, unmovedPath);
var node = graph.entity(obj.nodeId);
var start = projection(node.loc);
var end = geoVecAdd(start, _delta);
var movedNodes = graph.childNodes(graph.entity(obj.movedId));
var movedPath = _map(_map(movedNodes, 'loc'), function(loc) {
return geoVecAdd(projection(loc), _delta);
});
var unmovedNodes = graph.childNodes(graph.entity(obj.unmovedId));
var unmovedPath = _map(_map(unmovedNodes, 'loc'), projection);
var hits = geoPathIntersections(movedPath, unmovedPath);
for (var i = 0; i < hits.length; i++) {
if (_isEqual(hits[i], end)) continue;
var edge = geoChooseEdge(unmovedNodes, end, projection);
delta = vecSub(projection(edge.loc), start);
_delta = geoVecSubtract(projection(edge.loc), start);
}
});
}
var action = function(graph) {
if (delta[0] === 0 && delta[1] === 0) return graph;
if (_delta[0] === 0 && _delta[1] === 0) return graph;
setupCache(graph);
@@ -282,9 +282,9 @@ export function actionMove(moveIds, tryDelta, projection, cache) {
}
_each(cache.nodes, function(id) {
var node = graph.entity(id),
start = projection(node.loc),
end = vecAdd(start, delta);
var node = graph.entity(id);
var start = projection(node.loc);
var end = geoVecAdd(start, _delta);
graph = graph.replace(node.move(projection.invert(end)));
});
@@ -297,7 +297,7 @@ export function actionMove(moveIds, tryDelta, projection, cache) {
action.delta = function() {
return delta;
return _delta;
};
+103 -77
View File
@@ -2,94 +2,130 @@ import _every from 'lodash-es/every';
import _some from 'lodash-es/some';
export function geoRoundCoords(c) {
return [Math.floor(c[0]), Math.floor(c[1])];
// constants
var TAU = 2 * Math.PI;
var EQUATORIAL_RADIUS = 6356752.314245179;
var POLAR_RADIUS = 6378137.0;
// 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];
return [
p1[0] + (p2[0] - p1[0]) * t,
p1[1] + (p2[1] - p1[1]) * t
];
}
// 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product.
// 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(o, a, b) {
return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
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], y = a[1] - b[1];
var x = a[0] - b[0];
var y = a[1] - b[1];
return Math.sqrt((x * x) + (y * y));
}
// using WGS84 polar radius (6356752.314245179 m)
// const = 2 * PI * r / 360
export function geoLatToMeters(dLat) {
return dLat * 110946.257617;
return dLat * (TAU * POLAR_RADIUS / 360);
}
// using WGS84 equatorial radius (6378137.0 m)
// const = 2 * PI * r / 360
export function geoLonToMeters(dLon, atLat) {
return Math.abs(atLat) >= 90 ? 0 :
dLon * 111319.490793 * Math.abs(Math.cos(atLat * (Math.PI/180)));
dLon * (TAU * EQUATORIAL_RADIUS / 360) * Math.abs(Math.cos(atLat * (Math.PI / 180)));
}
// using WGS84 polar radius (6356752.314245179 m)
// const = 2 * PI * r / 360
export function geoMetersToLat(m) {
return m / 110946.257617;
return m / (TAU * POLAR_RADIUS / 360);
}
// using WGS84 equatorial radius (6378137.0 m)
// const = 2 * PI * r / 360
export function geoMetersToLon(m, atLat) {
return Math.abs(atLat) >= 90 ? 0 :
m / 111319.490793 / Math.abs(Math.cos(atLat * (Math.PI/180)));
m / (TAU * EQUATORIAL_RADIUS / 360) / Math.abs(Math.cos(atLat * (Math.PI / 180)));
}
export function geoOffsetToMeters(offset) {
var equatRadius = 6356752.314245179,
polarRadius = 6378137.0,
tileSize = 256;
export function geoOffsetToMeters(offset, tileSize) {
tileSize = tileSize || 256;
return [
offset[0] * 2 * Math.PI * equatRadius / tileSize,
-offset[1] * 2 * Math.PI * polarRadius / tileSize
offset[0] * TAU * EQUATORIAL_RADIUS / tileSize,
-offset[1] * TAU * POLAR_RADIUS / tileSize
];
}
export function geoMetersToOffset(meters) {
var equatRadius = 6356752.314245179,
polarRadius = 6378137.0,
tileSize = 256;
export function geoMetersToOffset(meters, tileSize) {
tileSize = tileSize || 256;
return [
meters[0] * tileSize / (2 * Math.PI * equatRadius),
-meters[1] * tileSize / (2 * Math.PI * polarRadius)
meters[0] * tileSize / (TAU * EQUATORIAL_RADIUS),
-meters[1] * tileSize / (TAU * POLAR_RADIUS)
];
}
// Equirectangular approximation of spherical distances on Earth
export function geoSphericalDistance(a, b) {
var x = geoLonToMeters(a[0] - b[0], (a[1] + b[1]) / 2),
y = geoLatToMeters(a[1] - b[1]);
var x = geoLonToMeters(a[0] - b[0], (a[1] + b[1]) / 2);
var y = geoLatToMeters(a[1] - b[1]);
return Math.sqrt((x * x) + (y * y));
}
// 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;
var log2ts = Math.log(tileSize) * Math.LOG2E;
return Math.log(k * TAU) / Math.LN2 - log2ts;
}
export function geoEdgeEqual(a, b) {
return (a[0] === b[0] && a[1] === b[1]) ||
(a[0] === b[1] && a[1] === b[0]);
@@ -122,23 +158,18 @@ 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) {
var dist = geoEuclideanDistance,
points = nodes.map(function(n) { return projection(n.loc); }),
min = Infinity,
idx, loc;
function dot(p, q) {
return p[0] * q[0] + p[1] * q[1];
}
var dist = geoEuclideanDistance;
var points = nodes.map(function(n) { return projection(n.loc); });
var min = Infinity;
var idx;
var loc;
for (var i = 0; i < points.length - 1; i++) {
var o = points[i],
s = [points[i + 1][0] - o[0],
points[i + 1][1] - o[1]],
v = [point[0] - o[0],
point[1] - o[1]],
proj = dot(v, s) / dot(s, s),
p;
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 p;
if (proj < 0) {
p = o;
@@ -169,25 +200,18 @@ export function geoChooseEdge(nodes, point, projection) {
// This uses the vector cross product approach described below:
// http://stackoverflow.com/a/565282/786339
export function geoLineIntersection(a, b) {
function subtractPoints(point1, point2) {
return [point1[0] - point2[0], point1[1] - point2[1]];
}
function crossProduct(point1, point2) {
return point1[0] * point2[1] - point1[1] * point2[0];
}
var p = [a[0][0], a[0][1]],
p2 = [a[1][0], a[1][1]],
q = [b[0][0], b[0][1]],
q2 = [b[1][0], b[1][1]],
r = subtractPoints(p2, p),
s = subtractPoints(q2, q),
uNumerator = crossProduct(subtractPoints(q, p), r),
denominator = crossProduct(r, s);
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 = geoCross(geoVecSubtract(q, p), r);
var denominator = geoCross(r, s);
if (uNumerator && denominator) {
var u = uNumerator / denominator,
t = crossProduct(subtractPoints(q, p), s) / denominator;
var u = uNumerator / denominator;
var t = geoCross(geoVecSubtract(q, p), s) / denominator;
if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) {
return geoInterp(p, p2, t);
@@ -202,10 +226,12 @@ 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] ],
b = [ path2[j], path2[j+1] ],
hit = geoLineIntersection(a, b);
if (hit) intersections.push(hit);
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;
@@ -222,9 +248,9 @@ export function geoPathIntersections(path1, path2) {
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
//
export function geoPointInPolygon(point, polygon) {
var x = point[0],
y = point[1],
inside = false;
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];
@@ -250,8 +276,8 @@ 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] ],
b = [ inner[j], inner[j+1] ];
var a = [ outer[i], outer[i +1 ] ];
var b = [ inner[j], inner[j + 1] ];
if (geoLineIntersection(a, b)) return true;
}
}
+7 -1
View File
@@ -1,12 +1,12 @@
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 { geoRoundCoords } from './geo.js';
export { geoRotate } from './geo.js';
export { geoLatToMeters } from './geo.js';
export { geoLineIntersection } from './geo.js';
@@ -20,5 +20,11 @@ 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 { geoVecAdd } from './geo.js';
export { geoVecFloor } from './geo.js';
export { geoVecSubtract } from './geo.js';
export { geoVecScale } from './geo.js';
export { geoZoomToScale } from './geo.js';
export { geoViewportEdge } from './geo.js';
+7 -7
View File
@@ -23,7 +23,12 @@ import {
modeSelect
} from './index';
import { geoChooseEdge, geoViewportEdge } from '../geo';
import {
geoChooseEdge,
geoVecSubtract,
geoViewportEdge
} from '../geo';
import { osmNode } from '../osm';
import { utilEntitySelector } from '../util';
import { uiFlash } from '../ui';
@@ -46,11 +51,6 @@ export function modeDragNode(context) {
var _lastLoc;
function vecSub(a, b) {
return [a[0] - b[0], a[1] - b[1]];
}
function startNudge(entity, nudge) {
if (_nudgeInterval) window.clearInterval(_nudgeInterval);
_nudgeInterval = window.setInterval(function() {
@@ -135,7 +135,7 @@ export function modeDragNode(context) {
nudge = nudge || [0, 0];
var currPoint = (d3_event && d3_event.point) || context.projection(_lastLoc);
var currMouse = vecSub(currPoint, nudge);
var currMouse = geoVecSubtract(currPoint, nudge);
var loc = context.projection.invert(currMouse);
if (!_nudgeInterval) {
+8 -7
View File
@@ -133,15 +133,16 @@ _extend(osmWay.prototype, {
isConvex: function(resolver) {
if (!this.isClosed() || this.isDegenerate()) return null;
var nodes = _uniq(resolver.childNodes(this)),
coords = _map(nodes, 'loc'),
curr = 0, prev = 0;
var nodes = _uniq(resolver.childNodes(this));
var coords = _map(nodes, 'loc');
var curr = 0;
var prev = 0;
for (var i = 0; i < coords.length; i++) {
var o = coords[(i+1) % coords.length],
a = coords[i],
b = coords[(i+2) % coords.length],
res = geoCross(o, a, b);
var o = coords[(i+1) % coords.length];
var a = coords[i];
var b = coords[(i+2) % coords.length];
var res = geoCross(a, b, o);
curr = (res > 0) ? 1 : (res < 0) ? -1 : 0;
if (curr === 0) {
+58 -56
View File
@@ -2,28 +2,29 @@ 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 } from '../geo';
import { geoEuclideanDistance, geoScaleToZoom } from '../geo';
import { utilPrefixCSSProperty } from '../util';
export function rendererTileLayer(context) {
var tileSize = 256,
geotile = d3_geoTile(),
projection,
cache = {},
tileOrigin,
z,
transformProp = utilPrefixCSSProperty('Transform'),
source;
var tileSize = 256;
var transformProp = utilPrefixCSSProperty('Transform');
var geotile = d3_geoTile();
var _projection;
var _cache = {};
var _tileOrigin;
var _zoom;
var _source;
// blacklist overlay tiles around Null Island..
function nearNullIsland(x, y, z) {
if (z >= 7) {
var center = Math.pow(2, z - 1),
width = Math.pow(2, z - 6),
min = center - (width / 2),
max = center + (width / 2) - 1;
var center = Math.pow(2, z - 1);
var width = Math.pow(2, z - 6);
var min = center - (width / 2);
var max = center + (width / 2) - 1;
return x >= min && x <= max && y >= min && y <= max;
}
return false;
@@ -31,8 +32,8 @@ export function rendererTileLayer(context) {
function tileSizeAtZoom(d, z) {
var epsilon = 0.002;
return ((tileSize * Math.pow(2, z - d[2])) / tileSize) + epsilon;
var EPSILON = 0.002;
return ((tileSize * Math.pow(2, z - d[2])) / tileSize) + EPSILON;
}
@@ -49,7 +50,7 @@ export function rendererTileLayer(context) {
function lookUp(d) {
for (var up = -1; up > -d[2]; up--) {
var tile = atZoom(d, up);
if (cache[source.url(tile)] !== false) {
if (_cache[_source.url(tile)] !== false) {
return tile;
}
}
@@ -57,7 +58,8 @@ export function rendererTileLayer(context) {
function uniqueBy(a, n) {
var o = [], seen = {};
var o = [];
var seen = {};
for (var i = 0; i < a.length; i++) {
if (seen[a[i][n]] === undefined) {
o.push(a[i]);
@@ -69,37 +71,37 @@ export function rendererTileLayer(context) {
function addSource(d) {
d.push(source.url(d));
d.push(_source.url(d));
return d;
}
// Update tiles based on current state of `projection`.
function background(selection) {
z = Math.max(Math.log(projection.scale() * 2 * Math.PI) / Math.log(2) - 8, 0);
_zoom = geoScaleToZoom(_projection.scale(), tileSize);
var pixelOffset;
if (source) {
if (_source) {
pixelOffset = [
source.offset()[0] * Math.pow(2, z),
source.offset()[1] * Math.pow(2, z)
_source.offset()[0] * Math.pow(2, _zoom),
_source.offset()[1] * Math.pow(2, _zoom)
];
} else {
pixelOffset = [0, 0];
}
var translate = [
projection.translate()[0] + pixelOffset[0],
projection.translate()[1] + pixelOffset[1]
_projection.translate()[0] + pixelOffset[0],
_projection.translate()[1] + pixelOffset[1]
];
geotile
.scale(projection.scale() * 2 * Math.PI)
.scale(_projection.scale() * 2 * Math.PI)
.translate(translate);
tileOrigin = [
projection.scale() * Math.PI - translate[0],
projection.scale() * Math.PI - translate[1]
_tileOrigin = [
_projection.scale() * Math.PI - translate[0],
_projection.scale() * Math.PI - translate[1]
];
render(selection);
@@ -107,36 +109,36 @@ export function rendererTileLayer(context) {
// Derive the tiles onscreen, remove those offscreen and position them.
// Important that this part not depend on `projection` because it's
// Important that this part not depend on `_projection` because it's
// rentered when tiles load/error (see #644).
function render(selection) {
if (!source) return;
if (!_source) return;
var requests = [];
var showDebug = context.getDebug('tile') && !source.overlay;
var showDebug = context.getDebug('tile') && !_source.overlay;
if (source.validZoom(z)) {
if (_source.validZoom(_zoom)) {
geotile().forEach(function(d) {
addSource(d);
if (d[3] === '') return;
if (typeof d[3] !== 'string') return; // Workaround for #2295
requests.push(d);
if (cache[d[3]] === false && lookUp(d)) {
if (_cache[d[3]] === false && lookUp(d)) {
requests.push(addSource(lookUp(d)));
}
});
requests = uniqueBy(requests, 3).filter(function(r) {
if (!!source.overlay && nearNullIsland(r[0], r[1], r[2])) {
if (!!_source.overlay && nearNullIsland(r[0], r[1], r[2])) {
return false;
}
// don't re-request tiles which have failed in the past
return cache[r[3]] !== false;
return _cache[r[3]] !== false;
});
}
function load(d) {
cache[d[3]] = true;
_cache[d[3]] = true;
d3_select(this)
.on('error', null)
.on('load', null)
@@ -145,7 +147,7 @@ export function rendererTileLayer(context) {
}
function error(d) {
cache[d[3]] = false;
_cache[d[3]] = false;
d3_select(this)
.on('error', null)
.on('load', null)
@@ -154,19 +156,19 @@ export function rendererTileLayer(context) {
}
function imageTransform(d) {
var _ts = tileSize * Math.pow(2, z - d[2]);
var scale = tileSizeAtZoom(d, z);
var ts = tileSize * Math.pow(2, _zoom - d[2]);
var scale = tileSizeAtZoom(d, _zoom);
return 'translate(' +
((d[0] * _ts) - tileOrigin[0]) + 'px,' +
((d[1] * _ts) - tileOrigin[1]) + 'px) ' +
((d[0] * ts) - _tileOrigin[0]) + 'px,' +
((d[1] * ts) - _tileOrigin[1]) + 'px) ' +
'scale(' + scale + ',' + scale + ')';
}
function tileCenter(d) {
var _ts = tileSize * Math.pow(2, z - d[2]);
var ts = tileSize * Math.pow(2, _zoom - d[2]);
return [
((d[0] * _ts) - tileOrigin[0] + (_ts / 2)),
((d[1] * _ts) - tileOrigin[1] + (_ts / 2))
((d[0] * ts) - _tileOrigin[0] + (ts / 2)),
((d[1] * ts) - _tileOrigin[1] + (ts / 2))
];
}
@@ -178,10 +180,10 @@ export function rendererTileLayer(context) {
// Pick a representative tile near the center of the viewport
// (This is useful for sampling the imagery vintage)
var dims = geotile.size(),
mapCenter = [dims[0] / 2, dims[1] / 2],
minDist = Math.max(dims[0], dims[1]),
nearCenter;
var dims = geotile.size();
var mapCenter = [dims[0] / 2, dims[1] / 2];
var minDist = Math.max(dims[0], dims[1]);
var nearCenter;
requests.forEach(function(d) {
var c = tileCenter(d);
@@ -255,8 +257,8 @@ export function rendererTileLayer(context) {
.selectAll('.tile-label-debug-vintage')
.each(function(d) {
var span = d3_select(this);
var center = context.projection.invert(tileCenter(d));
source.getMetadata(center, d, function(err, result) {
var center = context._projection.invert(tileCenter(d));
_source.getMetadata(center, d, function(err, result) {
span.text((result && result.vintage && result.vintage.range) ||
t('info_panels.background.vintage') + ': ' + t('info_panels.background.unknown')
);
@@ -268,8 +270,8 @@ export function rendererTileLayer(context) {
background.projection = function(_) {
if (!arguments.length) return projection;
projection = _;
if (!arguments.length) return _projection;
_projection = _;
return background;
};
@@ -282,10 +284,10 @@ export function rendererTileLayer(context) {
background.source = function(_) {
if (!arguments.length) return source;
source = _;
cache = {};
geotile.scaleExtent(source.scaleExtent);
if (!arguments.length) return _source;
_source = _;
_cache = {};
geotile.scaleExtent(_source.scaleExtent);
return background;
};
+14 -14
View File
@@ -500,8 +500,8 @@ export default {
}
// update blacklists
var elements = xml.getElementsByTagName('blacklist'),
regexes = [];
var elements = xml.getElementsByTagName('blacklist');
var regexes = [];
for (var i = 0; i < elements.length; i++) {
var regex = elements[i].getAttribute('regex'); // needs unencode?
if (regex) {
@@ -516,8 +516,8 @@ export default {
if (rateLimitError) {
callback(rateLimitError, 'rateLimited');
} else {
var apiStatus = xml.getElementsByTagName('status'),
val = apiStatus[0].getAttribute('api');
var apiStatus = xml.getElementsByTagName('status');
var val = apiStatus[0].getAttribute('api');
callback(undefined, val);
}
@@ -544,14 +544,14 @@ export default {
loadTiles: function(projection, dimensions, callback) {
if (off) return;
var that = this,
s = projection.scale() * 2 * Math.PI,
z = Math.max(Math.log(s) / Math.log(2) - 8, 0),
ts = 256 * Math.pow(2, z - tileZoom),
origin = [
s / 2 - projection.translate()[0],
s / 2 - projection.translate()[1]
];
var that = this;
var s = projection.scale() * 2 * Math.PI;
var z = Math.max(Math.log(s) / Math.log(2) - 8, 0);
var ts = 256 * Math.pow(2, z - tileZoom);
var origin = [
s / 2 - projection.translate()[0],
s / 2 - projection.translate()[1]
];
var tiles = d3_geoTile()
.scaleExtent([tileZoom, tileZoom])
@@ -559,8 +559,8 @@ export default {
.size(dimensions)
.translate(projection.translate())()
.map(function(tile) {
var x = tile[0] * ts - origin[0],
y = tile[1] * ts - origin[1];
var x = tile[0] * ts - origin[0];
var y = tile[1] * ts - origin[1];
return {
id: tile.toString(),
+3 -5
View File
@@ -13,7 +13,8 @@ import {
geoEuclideanDistance,
geoInterp,
geoPolygonIntersectsPolygon,
geoPathLength
geoPathLength,
geoScaleToZoom
} from '../geo';
import { osmEntity } from '../osm';
@@ -27,9 +28,6 @@ import {
} from '../util';
var TAU = 2 * Math.PI;
function ktoz(k) { return Math.log(k * TAU) / Math.LN2 - 8; }
export function svgLabels(projection, context) {
var path = d3_geoPath(projection);
@@ -261,7 +259,7 @@ export function svgLabels(projection, context) {
function drawLabels(selection, graph, entities, filter, dimensions, fullRedraw) {
var wireframe = context.surface().classed('fill-wireframe');
var zoom = ktoz(projection.scale());
var zoom = geoScaleToZoom(projection.scale());
var labelable = [];
var renderNodeAs = {};
+2 -5
View File
@@ -1,12 +1,9 @@
import { dataFeatureIcons } from '../../data';
import { geoScaleToZoom } from '../geo';
import { osmEntity } from '../osm';
import { svgPointTransform, svgTagClasses } from './index';
var TAU = 2 * Math.PI;
function ktoz(k) { return Math.log(k * TAU) / Math.LN2 - 8; }
export function svgPoints(projection, context) {
function markerPath(selection, klass) {
@@ -55,7 +52,7 @@ export function svgPoints(projection, context) {
function drawPoints(selection, graph, entities, filter) {
var wireframe = context.surface().classed('fill-wireframe');
var zoom = ktoz(projection.scale());
var zoom = geoScaleToZoom(projection.scale());
// points with a direction will render as vertices at higher zooms
function renderAsPoint(entity) {
+5 -8
View File
@@ -4,14 +4,11 @@ import _values from 'lodash-es/values';
import { select as d3_select } from 'd3-selection';
import { dataFeatureIcons } from '../../data';
import { geoScaleToZoom } from '../geo';
import { osmEntity } from '../osm';
import { svgPointTransform } from './index';
var TAU = 2 * Math.PI;
function ktoz(k) { return Math.log(k * TAU) / Math.LN2 - 8; }
export function svgVertices(projection, context) {
var radiuses = {
// z16-, z17, z18+, w/icon
@@ -46,7 +43,7 @@ export function svgVertices(projection, context) {
var icons = {};
var directions = {};
var wireframe = context.surface().classed('fill-wireframe');
var zoom = ktoz(projection.scale());
var zoom = geoScaleToZoom(projection.scale());
var z = (zoom < 17 ? 0 : zoom < 18 ? 1 : 2);
@@ -258,7 +255,7 @@ export function svgVertices(projection, context) {
function drawVertices(selection, graph, entities, filter, extent, fullRedraw) {
var wireframe = context.surface().classed('fill-wireframe');
var zoom = ktoz(projection.scale());
var zoom = geoScaleToZoom(projection.scale());
var mode = context.mode();
var isMoving = mode && /^(add|draw|drag|move|rotate)/.test(mode.id);
@@ -318,7 +315,7 @@ export function svgVertices(projection, context) {
// partial redraw - only update the selected items..
drawVertices.drawSelected = function(selection, graph, target, extent) {
var wireframe = context.surface().classed('fill-wireframe');
var zoom = ktoz(projection.scale());
var zoom = geoScaleToZoom(projection.scale());
_prevSelected = _currSelected || {};
_currSelected = getSiblingAndChildVertices(context.selectedIDs(), graph, wireframe, zoom);
@@ -334,7 +331,7 @@ export function svgVertices(projection, context) {
if (target === _currHoverTarget) return; // continue only if something changed
var wireframe = context.surface().classed('fill-wireframe');
var zoom = ktoz(projection.scale());
var zoom = geoScaleToZoom(projection.scale());
_prevHover = _currHover || {};
_currHoverTarget = target;
+2 -2
View File
@@ -3,7 +3,7 @@ import {
select as d3_select
} from 'd3-selection';
import { geoRoundCoords } from '../geo';
import { geoVecFloor } from '../geo';
import { textDirection } from '../util/locale';
import { uiTooltipHtml } from './tooltipHtml';
@@ -81,7 +81,7 @@ export function uiEditMenu(context, operations) {
.attr('class', function (d) { return 'edit-menu-item edit-menu-item-' + d.id; })
.classed('disabled', function (d) { return d.disabled(); })
.attr('transform', function (d, i) {
return 'translate(' + geoRoundCoords([
return 'translate(' + geoVecFloor([
0,
m + i * buttonHeight
]).join(',') + ')';
+3 -2
View File
@@ -26,7 +26,8 @@ import {
import {
geoExtent,
geoRawMercator
geoRawMercator,
geoZoomToScale
} from '../../geo';
import {
@@ -84,7 +85,7 @@ export function uiFieldRestrictions(field, context) {
var z = 24;
projection
.scale(256 * Math.pow(2, z) / (2 * Math.PI));
.scale(geoZoomToScale(z));
var s = projection(vertex.loc);
+59 -60
View File
@@ -13,45 +13,44 @@ import {
import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js';
import { t } from '../util/locale';
import { svgDebug, svgGpx } from '../svg';
import { geoRawMercator } from '../geo';
import {
geoRawMercator,
geoScaleToZoom,
geoVecSubtract,
geoVecScale,
geoZoomToScale,
} from '../geo';
import { rendererTileLayer } from '../renderer';
import { svgDebug, svgGpx } from '../svg';
import { utilSetTransform } from '../util';
import { utilGetDimensions } from '../util/dimensions';
var TAU = 2 * Math.PI;
function ztok(z) { return 256 * Math.pow(2, z) / TAU; }
function ktoz(k) { return Math.log(k * TAU) / Math.LN2 - 8; }
function vecSub(a, b) { return [ a[0] - b[0], a[1] - b[1] ]; }
function vecScale(a, b) { return [ a[0] * b, a[1] * b ]; }
export function uiMapInMap(context) {
function map_in_map(selection) {
var backgroundLayer = rendererTileLayer(context),
overlayLayers = {},
projection = geoRawMercator(),
gpxLayer = svgGpx(projection, context).showLabels(false),
debugLayer = svgDebug(projection, context),
zoom = d3_zoom()
.scaleExtent([ztok(0.5), ztok(24)])
.on('start', zoomStarted)
.on('zoom', zoomed)
.on('end', zoomEnded),
isTransformed = false,
isHidden = true,
skipEvents = false,
gesture = null,
zDiff = 6, // by default, minimap renders at (main zoom - 6)
wrap = d3_select(null),
tiles = d3_select(null),
viewport = d3_select(null),
tStart, // transform at start of gesture
tCurr, // transform at most recent event
timeoutId;
var backgroundLayer = rendererTileLayer(context);
var overlayLayers = {};
var projection = geoRawMercator();
var gpxLayer = svgGpx(projection, context).showLabels(false);
var debugLayer = svgDebug(projection, context);
var zoom = d3_zoom()
.scaleExtent([geoZoomToScale(0.5), geoZoomToScale(24)])
.on('start', zoomStarted)
.on('zoom', zoomed)
.on('end', zoomEnded);
var isTransformed = false;
var isHidden = true;
var skipEvents = false;
var gesture = null;
var zDiff = 6; // by default, minimap renders at (main zoom - 6)
var wrap = d3_select(null);
var tiles = d3_select(null);
var viewport = d3_select(null);
var tStart; // transform at start of gesture
var tCurr; // transform at most recent event
var timeoutId;
function zoomStarted() {
@@ -64,11 +63,11 @@ export function uiMapInMap(context) {
function zoomed() {
if (skipEvents) return;
var x = d3_event.transform.x,
y = d3_event.transform.y,
k = d3_event.transform.k,
isZooming = (k !== tStart.k),
isPanning = (x !== tStart.x || y !== tStart.y);
var x = d3_event.transform.x;
var y = d3_event.transform.y;
var k = d3_event.transform.k;
var isZooming = (k !== tStart.k);
var isPanning = (x !== tStart.x || y !== tStart.y);
if (!isZooming && !isPanning) {
return; // no change
@@ -79,12 +78,12 @@ export function uiMapInMap(context) {
gesture = isZooming ? 'zoom' : 'pan';
}
var tMini = projection.transform(),
tX, tY, scale;
var tMini = projection.transform();
var tX, tY, scale;
if (gesture === 'zoom') {
var dMini = utilGetDimensions(wrap),
cMini = vecScale(dMini, 0.5);
var dMini = utilGetDimensions(wrap);
var cMini = geoVecScale(dMini, 0.5);
scale = k / tMini.k;
tX = (cMini[0] / scale - cMini[0]) * scale;
tY = (cMini[1] / scale - cMini[1]) * scale;
@@ -100,8 +99,8 @@ export function uiMapInMap(context) {
isTransformed = true;
tCurr = d3_zoomIdentity.translate(x, y).scale(k);
var zMain = ktoz(context.projection.scale()),
zMini = ktoz(k);
var zMain = geoScaleToZoom(context.projection.scale());
var zMini = geoScaleToZoom(k);
zDiff = zMain - zMini;
@@ -115,29 +114,29 @@ export function uiMapInMap(context) {
updateProjection();
gesture = null;
var dMini = utilGetDimensions(wrap),
cMini = vecScale(dMini, 0.5);
var dMini = utilGetDimensions(wrap);
var cMini = geoVecScale(dMini, 0.5);
context.map().center(projection.invert(cMini)); // recenter main map..
}
function updateProjection() {
var loc = context.map().center(),
dMini = utilGetDimensions(wrap),
cMini = vecScale(dMini, 0.5),
tMain = context.projection.transform(),
zMain = ktoz(tMain.k),
zMini = Math.max(zMain - zDiff, 0.5),
kMini = ztok(zMini);
var loc = context.map().center();
var dMini = utilGetDimensions(wrap);
var cMini = geoVecScale(dMini, 0.5);
var tMain = context.projection.transform();
var zMain = geoScaleToZoom(tMain.k);
var zMini = Math.max(zMain - zDiff, 0.5);
var kMini = geoZoomToScale(zMini);
projection
.translate([tMain.x, tMain.y])
.scale(kMini);
var point = projection(loc),
mouse = (gesture === 'pan') ? vecSub([tCurr.x, tCurr.y], [tStart.x, tStart.y]) : [0, 0],
xMini = cMini[0] - point[0] + tMain.x + mouse[0],
yMini = cMini[1] - point[1] + tMain.y + mouse[1];
var point = projection(loc);
var mouse = (gesture === 'pan') ? geoVecSubtract([tCurr.x, tCurr.y], [tStart.x, tStart.y]) : [0, 0];
var xMini = cMini[0] - point[0] + tMain.x + mouse[0];
var yMini = cMini[1] - point[1] + tMain.y + mouse[1];
projection
.translate([xMini, yMini])
@@ -152,7 +151,7 @@ export function uiMapInMap(context) {
}
zoom
.scaleExtent([ztok(0.5), ztok(zMain - 3)]);
.scaleExtent([geoZoomToScale(0.5), geoZoomToScale(zMain - 3)]);
skipEvents = true;
wrap.call(zoom.transform, tCurr);
@@ -166,8 +165,8 @@ export function uiMapInMap(context) {
updateProjection();
var dMini = utilGetDimensions(wrap),
zMini = ktoz(projection.scale());
var dMini = utilGetDimensions(wrap);
var zMini = geoScaleToZoom(projection.scale());
// setup tile container
tiles = wrap
@@ -249,8 +248,8 @@ export function uiMapInMap(context) {
// redraw viewport bounding box
if (gesture !== 'pan') {
var getPath = d3_geoPath(projection),
bbox = { type: 'Polygon', coordinates: [context.map().extent().polygon()] };
var getPath = d3_geoPath(projection);
var bbox = { type: 'Polygon', coordinates: [context.map().extent().polygon()] };
viewport = wrap.selectAll('.map-in-map-viewport')
.data([0]);
+2 -2
View File
@@ -3,7 +3,7 @@ import {
select as d3_select
} from 'd3-selection';
import { geoRoundCoords } from '../geo';
import { geoVecFloor } from '../geo';
import { uiTooltipHtml } from './tooltipHtml';
@@ -58,7 +58,7 @@ export function uiRadialMenu(context, operations) {
.attr('class', function(d) { return 'radial-menu-item radial-menu-item-' + d.id; })
.classed('disabled', function(d) { return d.disabled(); })
.attr('transform', function(d, i) {
return 'translate(' + geoRoundCoords([
return 'translate(' + geoVecFloor([
r * Math.sin(a0 + i * a),
r * Math.cos(a0 + i * a)]).join(',') + ')';
});
+162 -125
View File
@@ -1,60 +1,96 @@
describe('iD.geo', function() {
describe('geoRoundCoords', function() {
it('rounds coordinates', function() {
expect(iD.geoRoundCoords([0.1, 1])).to.eql([0, 1]);
expect(iD.geoRoundCoords([0, 1])).to.eql([0, 1]);
expect(iD.geoRoundCoords([0, 1.1])).to.eql([0, 1]);
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],
b = [10, 10];
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],
b = [10, 10];
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('cross product of right hand turn is positive', function() {
var o = [0, 0],
a = [2, 0],
b = [0, 2];
expect(iD.geoCross(o, a, b)).to.eql(4);
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('cross product of left hand turn is negative', function() {
var o = [0, 0],
a = [2, 0],
b = [0, -2];
expect(iD.geoCross(o, 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('cross product of colinear points is zero', function() {
var o = [0, 0],
a = [-2, 0],
b = [2, 0];
expect(iD.geoCross(o, a, b)).to.equal(0);
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],
b = [0, 0];
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],
b = [10, 0];
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],
b = [4, 3];
var a = [0, 0];
var b = [4, 3];
expect(iD.geoEuclideanDistance(a, b)).to.eql(5);
});
});
@@ -64,10 +100,10 @@ describe('iD.geo', function() {
expect(iD.geoLatToMeters(0)).to.eql(0);
});
it('1 degree latitude is approx 111 km', function() {
expect(iD.geoLatToMeters(1)).to.be.within(110E3, 112E3);
expect(iD.geoLatToMeters(1)).to.be.closeTo(111319, 10);
});
it('-1 degree latitude is approx -111 km', function() {
expect(iD.geoLatToMeters(-1)).to.be.within(-112E3, -110E3);
expect(iD.geoLatToMeters(-1)).to.be.closeTo(-111319, 10);
});
});
@@ -76,21 +112,21 @@ describe('iD.geo', function() {
expect(iD.geoLonToMeters(0, 0)).to.eql(0);
});
it('distance of 1 degree longitude varies with latitude', function() {
expect(iD.geoLonToMeters(1, 0)).to.be.within(110E3, 112E3);
expect(iD.geoLonToMeters(1, 15)).to.be.within(107E3, 108E3);
expect(iD.geoLonToMeters(1, 30)).to.be.within(96E3, 97E3);
expect(iD.geoLonToMeters(1, 45)).to.be.within(78E3, 79E3);
expect(iD.geoLonToMeters(1, 60)).to.be.within(55E3, 56E3);
expect(iD.geoLonToMeters(1, 75)).to.be.within(28E3, 29E3);
expect(iD.geoLonToMeters(1, 0)).to.be.closeTo(110946, 10);
expect(iD.geoLonToMeters(1, 15)).to.be.closeTo(107165, 10);
expect(iD.geoLonToMeters(1, 30)).to.be.closeTo(96082, 10);
expect(iD.geoLonToMeters(1, 45)).to.be.closeTo(78450, 10);
expect(iD.geoLonToMeters(1, 60)).to.be.closeTo(55473, 10);
expect(iD.geoLonToMeters(1, 75)).to.be.closeTo(28715, 10);
expect(iD.geoLonToMeters(1, 90)).to.eql(0);
});
it('distance of -1 degree longitude varies with latitude', function() {
expect(iD.geoLonToMeters(-1, 0)).to.be.within(-112E3, -110E3);
expect(iD.geoLonToMeters(-1, -15)).to.be.within(-108E3, -107E3);
expect(iD.geoLonToMeters(-1, -30)).to.be.within(-97E3, -96E3);
expect(iD.geoLonToMeters(-1, -45)).to.be.within(-79E3, -78E3);
expect(iD.geoLonToMeters(-1, -60)).to.be.within(-56E3, -55E3);
expect(iD.geoLonToMeters(-1, -75)).to.be.within(-29E3, -28E3);
expect(iD.geoLonToMeters(-1, -0)).to.be.closeTo(-110946, 10);
expect(iD.geoLonToMeters(-1, -15)).to.be.closeTo(-107165, 10);
expect(iD.geoLonToMeters(-1, -30)).to.be.closeTo(-96082, 10);
expect(iD.geoLonToMeters(-1, -45)).to.be.closeTo(-78450, 10);
expect(iD.geoLonToMeters(-1, -60)).to.be.closeTo(-55473, 10);
expect(iD.geoLonToMeters(-1, -75)).to.be.closeTo(-28715, 10);
expect(iD.geoLonToMeters(-1, -90)).to.eql(0);
});
});
@@ -100,10 +136,10 @@ describe('iD.geo', function() {
expect(iD.geoMetersToLat(0)).to.eql(0);
});
it('111 km is approx 1 degree latitude', function() {
expect(iD.geoMetersToLat(111E3)).to.be.within(0.995, 1.005);
expect(iD.geoMetersToLat(111319)).to.be.closeTo(1, 0.0001);
});
it('-111 km is approx -1 degree latitude', function() {
expect(iD.geoMetersToLat(-111E3)).to.be.within(-1.005, -0.995);
expect(iD.geoMetersToLat(-111319)).to.be.closeTo(-1, 0.0001);
});
});
@@ -112,22 +148,22 @@ describe('iD.geo', function() {
expect(iD.geoMetersToLon(0, 0)).to.eql(0);
});
it('distance of 1 degree longitude varies with latitude', function() {
expect(iD.geoMetersToLon(111320, 0)).to.be.within(0.995, 1.005);
expect(iD.geoMetersToLon(107551, 15)).to.be.within(0.995, 1.005);
expect(iD.geoMetersToLon(96486, 30)).to.be.within(0.995, 1.005);
expect(iD.geoMetersToLon(78847, 45)).to.be.within(0.995, 1.005);
expect(iD.geoMetersToLon(55800, 60)).to.be.within(0.995, 1.005);
expect(iD.geoMetersToLon(28902, 75)).to.be.within(0.995, 1.005);
expect(iD.geoMetersToLon(110946, 0)).to.be.closeTo(1, 1e-4);
expect(iD.geoMetersToLon(107165, 15)).to.be.closeTo(1, 1e-4);
expect(iD.geoMetersToLon(96082, 30)).to.be.closeTo(1, 1e-4);
expect(iD.geoMetersToLon(78450, 45)).to.be.closeTo(1, 1e-4);
expect(iD.geoMetersToLon(55473, 60)).to.be.closeTo(1, 1e-4);
expect(iD.geoMetersToLon(28715, 75)).to.be.closeTo(1, 1e-4);
expect(iD.geoMetersToLon(1, 90)).to.eql(0);
});
it('distance of -1 degree longitude varies with latitude', function() {
expect(iD.geoMetersToLon(-111320, 0)).to.be.within(-1.005, -0.995);
expect(iD.geoMetersToLon(-107551, 15)).to.be.within(-1.005, -0.995);
expect(iD.geoMetersToLon(-96486, 30)).to.be.within(-1.005, -0.995);
expect(iD.geoMetersToLon(-78847, 45)).to.be.within(-1.005, -0.995);
expect(iD.geoMetersToLon(-55800, 60)).to.be.within(-1.005, -0.995);
expect(iD.geoMetersToLon(-28902, 75)).to.be.within(-1.005, -0.995);
expect(iD.geoMetersToLon(-1, 90)).to.eql(0);
expect(iD.geoMetersToLon(-110946, -0)).to.be.closeTo(-1, 1e-4);
expect(iD.geoMetersToLon(-107165, -15)).to.be.closeTo(-1, 1e-4);
expect(iD.geoMetersToLon(-96082, -30)).to.be.closeTo(-1, 1e-4);
expect(iD.geoMetersToLon(-78450, -45)).to.be.closeTo(-1, 1e-4);
expect(iD.geoMetersToLon(-55473, -60)).to.be.closeTo(-1, 1e-4);
expect(iD.geoMetersToLon(-28715, -75)).to.be.closeTo(-1, 1e-4);
expect(iD.geoMetersToLon(-1, -90)).to.eql(0);
});
});
@@ -159,43 +195,61 @@ describe('iD.geo', function() {
describe('geoSphericalDistance', function() {
it('distance between two same points is zero', function() {
var a = [0, 0],
b = [0, 0];
var a = [0, 0];
var b = [0, 0];
expect(iD.geoSphericalDistance(a, b)).to.eql(0);
});
it('a straight 1 degree line at the equator is aproximately 111 km', function() {
var a = [0, 0],
b = [1, 0];
expect(iD.geoSphericalDistance(a, b)).to.be.within(110E3, 112E3);
var a = [0, 0];
var b = [1, 0];
expect(iD.geoSphericalDistance(a, b)).to.be.closeTo(110946, 10);
});
it('a pythagorean triangle is (nearly) right', function() {
var a = [0, 0],
b = [4, 3];
expect(iD.geoSphericalDistance(a, b)).to.be.within(555E3, 556E3);
var a = [0, 0];
var b = [4, 3];
expect(iD.geoSphericalDistance(a, b)).to.be.closeTo(555282, 10);
});
it('east-west distances at high latitude are shorter', function() {
var a = [0, 60],
b = [1, 60];
expect(iD.geoSphericalDistance(a, b)).to.be.within(55E3, 56E3);
var a = [0, 60];
var b = [1, 60];
expect(iD.geoSphericalDistance(a, b)).to.be.closeTo(55473, 10);
});
it('north-south distances at high latitude are not shorter', function() {
var a = [0, 60],
b = [0, 61];
expect(iD.geoSphericalDistance(a, b)).to.be.within(110E3, 112E3);
var a = [0, 60];
var b = [0, 61];
expect(iD.geoSphericalDistance(a, b)).to.be.closeTo(111319, 10);
});
});
describe('geoZoomToScale', function() {
it('converts from zoom to projection scale (tileSize = 256)', function() {
expect(iD.geoZoomToScale(17)).to.be.closeTo(5340353.715440872, 1e-6);
});
it('converts from zoom to projection scale (tileSize = 512)', function() {
expect(iD.geoZoomToScale(17, 512)).to.be.closeTo(10680707.430881744, 1e-6);
});
});
describe('geoScaleToZoom', function() {
it('converts from projection scale to zoom (tileSize = 256)', function() {
expect(iD.geoScaleToZoom(5340353.715440872)).to.be.closeTo(17, 1e-6);
});
it('converts from projection scale to zoom (tileSize = 512)', function() {
expect(iD.geoScaleToZoom(10680707.430881744, 512)).to.be.closeTo(17, 1e-6);
});
});
describe('geoEdgeEqual', function() {
it('returns false for inequal edges', function() {
expect(iD.geoEdgeEqual(['a','b'], ['a','c'])).to.be.false;
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;
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;
expect(iD.geoEdgeEqual(['a', 'b'], ['b', 'a'])).to.be.true;
});
});
@@ -211,10 +265,10 @@ describe('iD.geo', function() {
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);
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);
@@ -222,10 +276,10 @@ describe('iD.geo', function() {
});
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);
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);
@@ -246,7 +300,7 @@ describe('iD.geo', function() {
});
it('returns undefined properties for a degenerate way (single node)', function() {
expect(iD.geoChooseEdge([iD.Node({loc: [0, 0]})], [0, 0], projection)).to.eql({
expect(iD.geoChooseEdge([iD.osmNode({loc: [0, 0]})], [0, 0], projection)).to.eql({
index: undefined,
distance: Infinity,
loc: undefined
@@ -259,14 +313,10 @@ describe('iD.geo', function() {
// c
//
// * = [2, 0]
var a = [0, 0],
b = [5, 0],
c = [2, 1],
nodes = [
iD.Node({loc: a}),
iD.Node({loc: b})
];
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);
@@ -274,14 +324,10 @@ describe('iD.geo', function() {
});
it('returns the starting vertex when the orthogonal projection is < 0', function() {
var a = [0, 0],
b = [5, 0],
c = [-3, 4],
nodes = [
iD.Node({loc: a}),
iD.Node({loc: b})
];
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);
@@ -289,14 +335,10 @@ describe('iD.geo', function() {
});
it('returns the ending vertex when the orthogonal projection is > 1', function() {
var a = [0, 0],
b = [5, 0],
c = [8, 4],
nodes = [
iD.Node({loc: a}),
iD.Node({loc: b})
];
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);
@@ -306,28 +348,28 @@ describe('iD.geo', function() {
describe('geoLineIntersection', function() {
it('returns null if lines are colinear with overlap', function() {
var a = [[0, 0], [10, 0]],
b = [[-5, 0], [5, 0]];
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]],
b = [[-10, 0], [-5, 0]];
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]],
b = [[0, 5], [10, 5]];
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]],
b = [[5, 10], [5, -10]];
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]],
b = [[-5, 10], [-5, -10]];
var a = [[0, 0], [10, 0]];
var b = [[-5, 10], [-5, -10]];
expect(iD.geoLineIntersection(a, b)).to.be.null;
});
});
@@ -339,12 +381,7 @@ describe('iD.geo', function() {
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 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;
});
+1 -4
View File
@@ -1,13 +1,10 @@
describe('iD.svgAreas', function () {
var TAU = 2 * Math.PI;
function ztok(z) { return 256 * Math.pow(2, z) / TAU; }
var context, surface;
var all = function() { return true; };
var none = function() { return false; };
var projection = d3.geoProjection(function(x, y) { return [x, -y]; })
.translate([0, 0])
.scale(ztok(17))
.scale(iD.geoZoomToScale(17))
.clipExtent([[0, 0], [Infinity, Infinity]]);
+1 -4
View File
@@ -1,11 +1,8 @@
describe('iD.svgLayers', function () {
var TAU = 2 * Math.PI;
function ztok(z) { return 256 * Math.pow(2, z) / TAU; }
var context, container;
var projection = d3.geoProjection(function(x, y) { return [x, -y]; })
.translate([0, 0])
.scale(ztok(17))
.scale(iD.geoZoomToScale(17))
.clipExtent([[0, 0], [Infinity, Infinity]]);
beforeEach(function () {
+1 -4
View File
@@ -1,13 +1,10 @@
describe('iD.svgLines', function () {
var TAU = 2 * Math.PI;
function ztok(z) { return 256 * Math.pow(2, z) / TAU; }
var context, surface;
var all = function() { return true; };
var none = function() { return false; };
var projection = d3.geoProjection(function(x, y) { return [x, -y]; })
.translate([0, 0])
.scale(ztok(17))
.scale(iD.geoZoomToScale(17))
.clipExtent([[0, 0], [Infinity, Infinity]]);
+1 -4
View File
@@ -1,13 +1,10 @@
describe('iD.svgMidpoints', function () {
var TAU = 2 * Math.PI;
function ztok(z) { return 256 * Math.pow(2, z) / TAU; }
var context, surface;
var _selectedIDs = [];
var filter = function() { return true; };
var projection = d3.geoProjection(function(x, y) { return [x, -y]; })
.translate([0, 0])
.scale(ztok(17))
.scale(iD.geoZoomToScale(17))
.clipExtent([[0, 0], [Infinity, Infinity]]);
+1 -4
View File
@@ -1,11 +1,8 @@
describe('iD.svgPoints', function () {
var TAU = 2 * Math.PI;
function ztok(z) { return 256 * Math.pow(2, z) / TAU; }
var context, surface;
var projection = d3.geoProjection(function(x, y) { return [x, -y]; })
.translate([0, 0])
.scale(ztok(17))
.scale(iD.geoZoomToScale(17))
.clipExtent([[0, 0], [Infinity, Infinity]]);
beforeEach(function () {
+1 -4
View File
@@ -1,12 +1,9 @@
describe('iD.svgVertices', function () {
var TAU = 2 * Math.PI;
function ztok(z) { return 256 * Math.pow(2, z) / TAU; }
var context;
var surface;
var projection = d3.geoProjection(function(x, y) { return [x, -y]; })
.translate([0, 0])
.scale(ztok(17))
.scale(iD.geoZoomToScale(17))
.clipExtent([[0, 0], [Infinity, Infinity]]);