123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- 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]];
- }
|