| import "../math/abs"; | |
| import "geo"; | |
| import "stream"; | |
| import "area"; | |
| import "cartesian"; | |
| import "spherical"; | |
| d3.geo.bounds = (function() { | |
| var λ0, φ0, λ1, φ1, // bounds | |
| λ_, // previous λ-coordinate | |
| λ__, φ__, // first point | |
| p0, // previous 3D point | |
| dλSum, | |
| ranges, | |
| range; | |
| var bound = { | |
| point: point, | |
| lineStart: lineStart, | |
| lineEnd: lineEnd, | |
| polygonStart: function() { | |
| bound.point = ringPoint; | |
| bound.lineStart = ringStart; | |
| bound.lineEnd = ringEnd; | |
| dλSum = 0; | |
| d3_geo_area.polygonStart(); | |
| }, | |
| polygonEnd: function() { | |
| d3_geo_area.polygonEnd(); | |
| bound.point = point; | |
| bound.lineStart = lineStart; | |
| bound.lineEnd = lineEnd; | |
| if (d3_geo_areaRingSum < 0) λ0 = -(λ1 = 180), φ0 = -(φ1 = 90); | |
| else if (dλSum > ε) φ1 = 90; | |
| else if (dλSum < -ε) φ0 = -90; | |
| range[0] = λ0, range[1] = λ1; | |
| } | |
| }; | |
| function point(λ, φ) { | |
| ranges.push(range = [λ0 = λ, λ1 = λ]); | |
| if (φ < φ0) φ0 = φ; | |
| if (φ > φ1) φ1 = φ; | |
| } | |
| function linePoint(λ, φ) { | |
| var p = d3_geo_cartesian([λ * d3_radians, φ * d3_radians]); | |
| if (p0) { | |
| var normal = d3_geo_cartesianCross(p0, p), | |
| equatorial = [normal[1], -normal[0], 0], | |
| inflection = d3_geo_cartesianCross(equatorial, normal); | |
| d3_geo_cartesianNormalize(inflection); | |
| inflection = d3_geo_spherical(inflection); | |
| var dλ = λ - λ_, | |
| s = dλ > 0 ? 1 : -1, | |
| λi = inflection[0] * d3_degrees * s, | |
| antimeridian = abs(dλ) > 180; | |
| if (antimeridian ^ (s * λ_ < λi && λi < s * λ)) { | |
| var φi = inflection[1] * d3_degrees; | |
| if (φi > φ1) φ1 = φi; | |
| } else if (λi = (λi + 360) % 360 - 180, antimeridian ^ (s * λ_ < λi && λi < s * λ)) { | |
| var φi = -inflection[1] * d3_degrees; | |
| if (φi < φ0) φ0 = φi; | |
| } else { | |
| if (φ < φ0) φ0 = φ; | |
| if (φ > φ1) φ1 = φ; | |
| } | |
| if (antimeridian) { | |
| if (λ < λ_) { | |
| if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ; | |
| } else { | |
| if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ; | |
| } | |
| } else { | |
| if (λ1 >= λ0) { | |
| if (λ < λ0) λ0 = λ; | |
| if (λ > λ1) λ1 = λ; | |
| } else { | |
| if (λ > λ_) { | |
| if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ; | |
| } else { | |
| if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ; | |
| } | |
| } | |
| } | |
| } else { | |
| point(λ, φ); | |
| } | |
| p0 = p, λ_ = λ; | |
| } | |
| function lineStart() { bound.point = linePoint; } | |
| function lineEnd() { | |
| range[0] = λ0, range[1] = λ1; | |
| bound.point = point; | |
| p0 = null; | |
| } | |
| function ringPoint(λ, φ) { | |
| if (p0) { | |
| var dλ = λ - λ_; | |
| dλSum += abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ; | |
| } else λ__ = λ, φ__ = φ; | |
| d3_geo_area.point(λ, φ); | |
| linePoint(λ, φ); | |
| } | |
| function ringStart() { | |
| d3_geo_area.lineStart(); | |
| } | |
| function ringEnd() { | |
| ringPoint(λ__, φ__); | |
| d3_geo_area.lineEnd(); | |
| if (abs(dλSum) > ε) λ0 = -(λ1 = 180); | |
| range[0] = λ0, range[1] = λ1; | |
| p0 = null; | |
| } | |
| // Finds the left-right distance between two longitudes. | |
| // This is almost the same as (λ1 - λ0 + 360°) % 360°, except that we want | |
| // the distance between ±180° to be 360°. | |
| function angle(λ0, λ1) { return (λ1 -= λ0) < 0 ? λ1 + 360 : λ1; } | |
| function compareRanges(a, b) { return a[0] - b[0]; } | |
| function withinRange(x, range) { | |
| return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x; | |
| } | |
| return function(feature) { | |
| φ1 = λ1 = -(λ0 = φ0 = Infinity); | |
| ranges = []; | |
| d3.geo.stream(feature, bound); | |
| var n = ranges.length; | |
| if (n) { | |
| // First, sort ranges by their minimum longitudes. | |
| ranges.sort(compareRanges); | |
| // Then, merge any ranges that overlap. | |
| for (var i = 1, a = ranges[0], b, merged = [a]; i < n; ++i) { | |
| b = ranges[i]; | |
| if (withinRange(b[0], a) || withinRange(b[1], a)) { | |
| 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. | |
| var best = -Infinity, dλ; | |
| for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) { | |
| b = merged[i]; | |
| if ((dλ = angle(a[1], b[0])) > best) best = dλ, λ0 = b[0], λ1 = a[1]; | |
| } | |
| } | |
| ranges = range = null; | |
| return λ0 === Infinity || φ0 === Infinity | |
| ? [[NaN, NaN], [NaN, NaN]] | |
| : [[λ0, φ0], [λ1, φ1]]; | |
| }; | |
| })(); |