| 1 | import {Adder} from "d3-array";
|
|---|
| 2 | import {areaStream, areaRingSum} from "./area.js";
|
|---|
| 3 | import {cartesian, cartesianCross, cartesianNormalizeInPlace, spherical} from "./cartesian.js";
|
|---|
| 4 | import {abs, degrees, epsilon, radians} from "./math.js";
|
|---|
| 5 | import stream from "./stream.js";
|
|---|
| 6 |
|
|---|
| 7 | var lambda0, phi0, lambda1, phi1, // bounds
|
|---|
| 8 | lambda2, // previous lambda-coordinate
|
|---|
| 9 | lambda00, phi00, // first point
|
|---|
| 10 | p0, // previous 3D point
|
|---|
| 11 | deltaSum,
|
|---|
| 12 | ranges,
|
|---|
| 13 | range;
|
|---|
| 14 |
|
|---|
| 15 | var boundsStream = {
|
|---|
| 16 | point: boundsPoint,
|
|---|
| 17 | lineStart: boundsLineStart,
|
|---|
| 18 | lineEnd: boundsLineEnd,
|
|---|
| 19 | polygonStart: function() {
|
|---|
| 20 | boundsStream.point = boundsRingPoint;
|
|---|
| 21 | boundsStream.lineStart = boundsRingStart;
|
|---|
| 22 | boundsStream.lineEnd = boundsRingEnd;
|
|---|
| 23 | deltaSum = new Adder();
|
|---|
| 24 | areaStream.polygonStart();
|
|---|
| 25 | },
|
|---|
| 26 | polygonEnd: function() {
|
|---|
| 27 | areaStream.polygonEnd();
|
|---|
| 28 | boundsStream.point = boundsPoint;
|
|---|
| 29 | boundsStream.lineStart = boundsLineStart;
|
|---|
| 30 | boundsStream.lineEnd = boundsLineEnd;
|
|---|
| 31 | if (areaRingSum < 0) lambda0 = -(lambda1 = 180), phi0 = -(phi1 = 90);
|
|---|
| 32 | else if (deltaSum > epsilon) phi1 = 90;
|
|---|
| 33 | else if (deltaSum < -epsilon) phi0 = -90;
|
|---|
| 34 | range[0] = lambda0, range[1] = lambda1;
|
|---|
| 35 | },
|
|---|
| 36 | sphere: function() {
|
|---|
| 37 | lambda0 = -(lambda1 = 180), phi0 = -(phi1 = 90);
|
|---|
| 38 | }
|
|---|
| 39 | };
|
|---|
| 40 |
|
|---|
| 41 | function boundsPoint(lambda, phi) {
|
|---|
| 42 | ranges.push(range = [lambda0 = lambda, lambda1 = lambda]);
|
|---|
| 43 | if (phi < phi0) phi0 = phi;
|
|---|
| 44 | if (phi > phi1) phi1 = phi;
|
|---|
| 45 | }
|
|---|
| 46 |
|
|---|
| 47 | function linePoint(lambda, phi) {
|
|---|
| 48 | var p = cartesian([lambda * radians, phi * radians]);
|
|---|
| 49 | if (p0) {
|
|---|
| 50 | var normal = cartesianCross(p0, p),
|
|---|
| 51 | equatorial = [normal[1], -normal[0], 0],
|
|---|
| 52 | inflection = cartesianCross(equatorial, normal);
|
|---|
| 53 | cartesianNormalizeInPlace(inflection);
|
|---|
| 54 | inflection = spherical(inflection);
|
|---|
| 55 | var delta = lambda - lambda2,
|
|---|
| 56 | sign = delta > 0 ? 1 : -1,
|
|---|
| 57 | lambdai = inflection[0] * degrees * sign,
|
|---|
| 58 | phii,
|
|---|
| 59 | antimeridian = abs(delta) > 180;
|
|---|
| 60 | if (antimeridian ^ (sign * lambda2 < lambdai && lambdai < sign * lambda)) {
|
|---|
| 61 | phii = inflection[1] * degrees;
|
|---|
| 62 | if (phii > phi1) phi1 = phii;
|
|---|
| 63 | } else if (lambdai = (lambdai + 360) % 360 - 180, antimeridian ^ (sign * lambda2 < lambdai && lambdai < sign * lambda)) {
|
|---|
| 64 | phii = -inflection[1] * degrees;
|
|---|
| 65 | if (phii < phi0) phi0 = phii;
|
|---|
| 66 | } else {
|
|---|
| 67 | if (phi < phi0) phi0 = phi;
|
|---|
| 68 | if (phi > phi1) phi1 = phi;
|
|---|
| 69 | }
|
|---|
| 70 | if (antimeridian) {
|
|---|
| 71 | if (lambda < lambda2) {
|
|---|
| 72 | if (angle(lambda0, lambda) > angle(lambda0, lambda1)) lambda1 = lambda;
|
|---|
| 73 | } else {
|
|---|
| 74 | if (angle(lambda, lambda1) > angle(lambda0, lambda1)) lambda0 = lambda;
|
|---|
| 75 | }
|
|---|
| 76 | } else {
|
|---|
| 77 | if (lambda1 >= lambda0) {
|
|---|
| 78 | if (lambda < lambda0) lambda0 = lambda;
|
|---|
| 79 | if (lambda > lambda1) lambda1 = lambda;
|
|---|
| 80 | } else {
|
|---|
| 81 | if (lambda > lambda2) {
|
|---|
| 82 | if (angle(lambda0, lambda) > angle(lambda0, lambda1)) lambda1 = lambda;
|
|---|
| 83 | } else {
|
|---|
| 84 | if (angle(lambda, lambda1) > angle(lambda0, lambda1)) lambda0 = lambda;
|
|---|
| 85 | }
|
|---|
| 86 | }
|
|---|
| 87 | }
|
|---|
| 88 | } else {
|
|---|
| 89 | ranges.push(range = [lambda0 = lambda, lambda1 = lambda]);
|
|---|
| 90 | }
|
|---|
| 91 | if (phi < phi0) phi0 = phi;
|
|---|
| 92 | if (phi > phi1) phi1 = phi;
|
|---|
| 93 | p0 = p, lambda2 = lambda;
|
|---|
| 94 | }
|
|---|
| 95 |
|
|---|
| 96 | function boundsLineStart() {
|
|---|
| 97 | boundsStream.point = linePoint;
|
|---|
| 98 | }
|
|---|
| 99 |
|
|---|
| 100 | function boundsLineEnd() {
|
|---|
| 101 | range[0] = lambda0, range[1] = lambda1;
|
|---|
| 102 | boundsStream.point = boundsPoint;
|
|---|
| 103 | p0 = null;
|
|---|
| 104 | }
|
|---|
| 105 |
|
|---|
| 106 | function boundsRingPoint(lambda, phi) {
|
|---|
| 107 | if (p0) {
|
|---|
| 108 | var delta = lambda - lambda2;
|
|---|
| 109 | deltaSum.add(abs(delta) > 180 ? delta + (delta > 0 ? 360 : -360) : delta);
|
|---|
| 110 | } else {
|
|---|
| 111 | lambda00 = lambda, phi00 = phi;
|
|---|
| 112 | }
|
|---|
| 113 | areaStream.point(lambda, phi);
|
|---|
| 114 | linePoint(lambda, phi);
|
|---|
| 115 | }
|
|---|
| 116 |
|
|---|
| 117 | function boundsRingStart() {
|
|---|
| 118 | areaStream.lineStart();
|
|---|
| 119 | }
|
|---|
| 120 |
|
|---|
| 121 | function boundsRingEnd() {
|
|---|
| 122 | boundsRingPoint(lambda00, phi00);
|
|---|
| 123 | areaStream.lineEnd();
|
|---|
| 124 | if (abs(deltaSum) > epsilon) lambda0 = -(lambda1 = 180);
|
|---|
| 125 | range[0] = lambda0, range[1] = lambda1;
|
|---|
| 126 | p0 = null;
|
|---|
| 127 | }
|
|---|
| 128 |
|
|---|
| 129 | // Finds the left-right distance between two longitudes.
|
|---|
| 130 | // This is almost the same as (lambda1 - lambda0 + 360°) % 360°, except that we want
|
|---|
| 131 | // the distance between ±180° to be 360°.
|
|---|
| 132 | function angle(lambda0, lambda1) {
|
|---|
| 133 | return (lambda1 -= lambda0) < 0 ? lambda1 + 360 : lambda1;
|
|---|
| 134 | }
|
|---|
| 135 |
|
|---|
| 136 | function rangeCompare(a, b) {
|
|---|
| 137 | return a[0] - b[0];
|
|---|
| 138 | }
|
|---|
| 139 |
|
|---|
| 140 | function rangeContains(range, x) {
|
|---|
| 141 | return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x;
|
|---|
| 142 | }
|
|---|
| 143 |
|
|---|
| 144 | export default function(feature) {
|
|---|
| 145 | var i, n, a, b, merged, deltaMax, delta;
|
|---|
| 146 |
|
|---|
| 147 | phi1 = lambda1 = -(lambda0 = phi0 = Infinity);
|
|---|
| 148 | ranges = [];
|
|---|
| 149 | stream(feature, boundsStream);
|
|---|
| 150 |
|
|---|
| 151 | // First, sort ranges by their minimum longitudes.
|
|---|
| 152 | if (n = ranges.length) {
|
|---|
| 153 | ranges.sort(rangeCompare);
|
|---|
| 154 |
|
|---|
| 155 | // Then, merge any ranges that overlap.
|
|---|
| 156 | for (i = 1, a = ranges[0], merged = [a]; i < n; ++i) {
|
|---|
| 157 | b = ranges[i];
|
|---|
| 158 | if (rangeContains(a, b[0]) || rangeContains(a, b[1])) {
|
|---|
| 159 | if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1];
|
|---|
| 160 | if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0];
|
|---|
| 161 | } else {
|
|---|
| 162 | merged.push(a = b);
|
|---|
| 163 | }
|
|---|
| 164 | }
|
|---|
| 165 |
|
|---|
| 166 | // Finally, find the largest gap between the merged ranges.
|
|---|
| 167 | // The final bounding box will be the inverse of this gap.
|
|---|
| 168 | for (deltaMax = -Infinity, n = merged.length - 1, i = 0, a = merged[n]; i <= n; a = b, ++i) {
|
|---|
| 169 | b = merged[i];
|
|---|
| 170 | if ((delta = angle(a[1], b[0])) > deltaMax) deltaMax = delta, lambda0 = b[0], lambda1 = a[1];
|
|---|
| 171 | }
|
|---|
| 172 | }
|
|---|
| 173 |
|
|---|
| 174 | ranges = range = null;
|
|---|
| 175 |
|
|---|
| 176 | return lambda0 === Infinity || phi0 === Infinity
|
|---|
| 177 | ? [[NaN, NaN], [NaN, NaN]]
|
|---|
| 178 | : [[lambda0, phi0], [lambda1, phi1]];
|
|---|
| 179 | }
|
|---|