You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
5.3 KiB
176 lines
5.3 KiB
import adder from "./adder"; |
|
import {areaStream, areaRingSum} from "./area"; |
|
import {cartesian, cartesianCross, cartesianNormalizeInPlace, spherical} from "./cartesian"; |
|
import {abs, degrees, epsilon, radians} from "./math"; |
|
import stream from "./stream"; |
|
|
|
var lambda0, phi0, lambda1, phi1, // bounds |
|
lambda2, // previous lambda-coordinate |
|
lambda00, phi00, // first point |
|
p0, // previous 3D point |
|
deltaSum = adder(), |
|
ranges, |
|
range; |
|
|
|
var boundsStream = { |
|
point: boundsPoint, |
|
lineStart: boundsLineStart, |
|
lineEnd: boundsLineEnd, |
|
polygonStart: function() { |
|
boundsStream.point = boundsRingPoint; |
|
boundsStream.lineStart = boundsRingStart; |
|
boundsStream.lineEnd = boundsRingEnd; |
|
deltaSum.reset(); |
|
areaStream.polygonStart(); |
|
}, |
|
polygonEnd: function() { |
|
areaStream.polygonEnd(); |
|
boundsStream.point = boundsPoint; |
|
boundsStream.lineStart = boundsLineStart; |
|
boundsStream.lineEnd = boundsLineEnd; |
|
if (areaRingSum < 0) lambda0 = -(lambda1 = 180), phi0 = -(phi1 = 90); |
|
else if (deltaSum > epsilon) phi1 = 90; |
|
else if (deltaSum < -epsilon) phi0 = -90; |
|
range[0] = lambda0, range[1] = lambda1; |
|
} |
|
}; |
|
|
|
function boundsPoint(lambda, phi) { |
|
ranges.push(range = [lambda0 = lambda, lambda1 = lambda]); |
|
if (phi < phi0) phi0 = phi; |
|
if (phi > phi1) phi1 = phi; |
|
} |
|
|
|
function linePoint(lambda, phi) { |
|
var p = cartesian([lambda * radians, phi * radians]); |
|
if (p0) { |
|
var normal = cartesianCross(p0, p), |
|
equatorial = [normal[1], -normal[0], 0], |
|
inflection = cartesianCross(equatorial, normal); |
|
cartesianNormalizeInPlace(inflection); |
|
inflection = spherical(inflection); |
|
var delta = lambda - lambda2, |
|
sign = delta > 0 ? 1 : -1, |
|
lambdai = inflection[0] * degrees * sign, |
|
phii, |
|
antimeridian = abs(delta) > 180; |
|
if (antimeridian ^ (sign * lambda2 < lambdai && lambdai < sign * lambda)) { |
|
phii = inflection[1] * degrees; |
|
if (phii > phi1) phi1 = phii; |
|
} else if (lambdai = (lambdai + 360) % 360 - 180, antimeridian ^ (sign * lambda2 < lambdai && lambdai < sign * lambda)) { |
|
phii = -inflection[1] * degrees; |
|
if (phii < phi0) phi0 = phii; |
|
} else { |
|
if (phi < phi0) phi0 = phi; |
|
if (phi > phi1) phi1 = phi; |
|
} |
|
if (antimeridian) { |
|
if (lambda < lambda2) { |
|
if (angle(lambda0, lambda) > angle(lambda0, lambda1)) lambda1 = lambda; |
|
} else { |
|
if (angle(lambda, lambda1) > angle(lambda0, lambda1)) lambda0 = lambda; |
|
} |
|
} else { |
|
if (lambda1 >= lambda0) { |
|
if (lambda < lambda0) lambda0 = lambda; |
|
if (lambda > lambda1) lambda1 = lambda; |
|
} else { |
|
if (lambda > lambda2) { |
|
if (angle(lambda0, lambda) > angle(lambda0, lambda1)) lambda1 = lambda; |
|
} else { |
|
if (angle(lambda, lambda1) > angle(lambda0, lambda1)) lambda0 = lambda; |
|
} |
|
} |
|
} |
|
} else { |
|
ranges.push(range = [lambda0 = lambda, lambda1 = lambda]); |
|
} |
|
if (phi < phi0) phi0 = phi; |
|
if (phi > phi1) phi1 = phi; |
|
p0 = p, lambda2 = lambda; |
|
} |
|
|
|
function boundsLineStart() { |
|
boundsStream.point = linePoint; |
|
} |
|
|
|
function boundsLineEnd() { |
|
range[0] = lambda0, range[1] = lambda1; |
|
boundsStream.point = boundsPoint; |
|
p0 = null; |
|
} |
|
|
|
function boundsRingPoint(lambda, phi) { |
|
if (p0) { |
|
var delta = lambda - lambda2; |
|
deltaSum.add(abs(delta) > 180 ? delta + (delta > 0 ? 360 : -360) : delta); |
|
} else { |
|
lambda00 = lambda, phi00 = phi; |
|
} |
|
areaStream.point(lambda, phi); |
|
linePoint(lambda, phi); |
|
} |
|
|
|
function boundsRingStart() { |
|
areaStream.lineStart(); |
|
} |
|
|
|
function boundsRingEnd() { |
|
boundsRingPoint(lambda00, phi00); |
|
areaStream.lineEnd(); |
|
if (abs(deltaSum) > epsilon) lambda0 = -(lambda1 = 180); |
|
range[0] = lambda0, range[1] = lambda1; |
|
p0 = null; |
|
} |
|
|
|
// Finds the left-right distance between two longitudes. |
|
// This is almost the same as (lambda1 - lambda0 + 360°) % 360°, except that we want |
|
// the distance between ±180° to be 360°. |
|
function angle(lambda0, lambda1) { |
|
return (lambda1 -= lambda0) < 0 ? lambda1 + 360 : lambda1; |
|
} |
|
|
|
function rangeCompare(a, b) { |
|
return a[0] - b[0]; |
|
} |
|
|
|
function rangeContains(range, x) { |
|
return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x; |
|
} |
|
|
|
export default function(feature) { |
|
var i, n, a, b, merged, deltaMax, delta; |
|
|
|
phi1 = lambda1 = -(lambda0 = phi0 = Infinity); |
|
ranges = []; |
|
stream(feature, boundsStream); |
|
|
|
// First, sort ranges by their minimum longitudes. |
|
if (n = ranges.length) { |
|
ranges.sort(rangeCompare); |
|
|
|
// Then, merge any ranges that overlap. |
|
for (i = 1, a = ranges[0], merged = [a]; i < n; ++i) { |
|
b = ranges[i]; |
|
if (rangeContains(a, b[0]) || rangeContains(a, b[1])) { |
|
if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1]; |
|
if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0]; |
|
} else { |
|
merged.push(a = b); |
|
} |
|
} |
|
|
|
// Finally, find the largest gap between the merged ranges. |
|
// The final bounding box will be the inverse of this gap. |
|
for (deltaMax = -Infinity, n = merged.length - 1, i = 0, a = merged[n]; i <= n; a = b, ++i) { |
|
b = merged[i]; |
|
if ((delta = angle(a[1], b[0])) > deltaMax) deltaMax = delta, lambda0 = b[0], lambda1 = a[1]; |
|
} |
|
} |
|
|
|
ranges = range = null; |
|
|
|
return lambda0 === Infinity || phi0 === Infinity |
|
? [[NaN, NaN], [NaN, NaN]] |
|
: [[lambda0, phi0], [lambda1, phi1]]; |
|
}
|
|
|