bounds.js 5.3 KB

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