| 1 | import {cartesian, cartesianAddInPlace, cartesianCross, cartesianDot, cartesianScale, spherical} from "../cartesian.js";
|
|---|
| 2 | import {circleStream} from "../circle.js";
|
|---|
| 3 | import {abs, cos, epsilon, pi, radians, sqrt} from "../math.js";
|
|---|
| 4 | import pointEqual from "../pointEqual.js";
|
|---|
| 5 | import clip from "./index.js";
|
|---|
| 6 |
|
|---|
| 7 | export default function(radius) {
|
|---|
| 8 | var cr = cos(radius),
|
|---|
| 9 | delta = 2 * radians,
|
|---|
| 10 | smallRadius = cr > 0,
|
|---|
| 11 | notHemisphere = abs(cr) > epsilon; // TODO optimise for this common case
|
|---|
| 12 |
|
|---|
| 13 | function interpolate(from, to, direction, stream) {
|
|---|
| 14 | circleStream(stream, radius, delta, direction, from, to);
|
|---|
| 15 | }
|
|---|
| 16 |
|
|---|
| 17 | function visible(lambda, phi) {
|
|---|
| 18 | return cos(lambda) * cos(phi) > cr;
|
|---|
| 19 | }
|
|---|
| 20 |
|
|---|
| 21 | // Takes a line and cuts into visible segments. Return values used for polygon
|
|---|
| 22 | // clipping: 0 - there were intersections or the line was empty; 1 - no
|
|---|
| 23 | // intersections 2 - there were intersections, and the first and last segments
|
|---|
| 24 | // should be rejoined.
|
|---|
| 25 | function clipLine(stream) {
|
|---|
| 26 | var point0, // previous point
|
|---|
| 27 | c0, // code for previous point
|
|---|
| 28 | v0, // visibility of previous point
|
|---|
| 29 | v00, // visibility of first point
|
|---|
| 30 | clean; // no intersections
|
|---|
| 31 | return {
|
|---|
| 32 | lineStart: function() {
|
|---|
| 33 | v00 = v0 = false;
|
|---|
| 34 | clean = 1;
|
|---|
| 35 | },
|
|---|
| 36 | point: function(lambda, phi) {
|
|---|
| 37 | var point1 = [lambda, phi],
|
|---|
| 38 | point2,
|
|---|
| 39 | v = visible(lambda, phi),
|
|---|
| 40 | c = smallRadius
|
|---|
| 41 | ? v ? 0 : code(lambda, phi)
|
|---|
| 42 | : v ? code(lambda + (lambda < 0 ? pi : -pi), phi) : 0;
|
|---|
| 43 | if (!point0 && (v00 = v0 = v)) stream.lineStart();
|
|---|
| 44 | if (v !== v0) {
|
|---|
| 45 | point2 = intersect(point0, point1);
|
|---|
| 46 | if (!point2 || pointEqual(point0, point2) || pointEqual(point1, point2))
|
|---|
| 47 | point1[2] = 1;
|
|---|
| 48 | }
|
|---|
| 49 | if (v !== v0) {
|
|---|
| 50 | clean = 0;
|
|---|
| 51 | if (v) {
|
|---|
| 52 | // outside going in
|
|---|
| 53 | stream.lineStart();
|
|---|
| 54 | point2 = intersect(point1, point0);
|
|---|
| 55 | stream.point(point2[0], point2[1]);
|
|---|
| 56 | } else {
|
|---|
| 57 | // inside going out
|
|---|
| 58 | point2 = intersect(point0, point1);
|
|---|
| 59 | stream.point(point2[0], point2[1], 2);
|
|---|
| 60 | stream.lineEnd();
|
|---|
| 61 | }
|
|---|
| 62 | point0 = point2;
|
|---|
| 63 | } else if (notHemisphere && point0 && smallRadius ^ v) {
|
|---|
| 64 | var t;
|
|---|
| 65 | // If the codes for two points are different, or are both zero,
|
|---|
| 66 | // and there this segment intersects with the small circle.
|
|---|
| 67 | if (!(c & c0) && (t = intersect(point1, point0, true))) {
|
|---|
| 68 | clean = 0;
|
|---|
| 69 | if (smallRadius) {
|
|---|
| 70 | stream.lineStart();
|
|---|
| 71 | stream.point(t[0][0], t[0][1]);
|
|---|
| 72 | stream.point(t[1][0], t[1][1]);
|
|---|
| 73 | stream.lineEnd();
|
|---|
| 74 | } else {
|
|---|
| 75 | stream.point(t[1][0], t[1][1]);
|
|---|
| 76 | stream.lineEnd();
|
|---|
| 77 | stream.lineStart();
|
|---|
| 78 | stream.point(t[0][0], t[0][1], 3);
|
|---|
| 79 | }
|
|---|
| 80 | }
|
|---|
| 81 | }
|
|---|
| 82 | if (v && (!point0 || !pointEqual(point0, point1))) {
|
|---|
| 83 | stream.point(point1[0], point1[1]);
|
|---|
| 84 | }
|
|---|
| 85 | point0 = point1, v0 = v, c0 = c;
|
|---|
| 86 | },
|
|---|
| 87 | lineEnd: function() {
|
|---|
| 88 | if (v0) stream.lineEnd();
|
|---|
| 89 | point0 = null;
|
|---|
| 90 | },
|
|---|
| 91 | // Rejoin first and last segments if there were intersections and the first
|
|---|
| 92 | // and last points were visible.
|
|---|
| 93 | clean: function() {
|
|---|
| 94 | return clean | ((v00 && v0) << 1);
|
|---|
| 95 | }
|
|---|
| 96 | };
|
|---|
| 97 | }
|
|---|
| 98 |
|
|---|
| 99 | // Intersects the great circle between a and b with the clip circle.
|
|---|
| 100 | function intersect(a, b, two) {
|
|---|
| 101 | var pa = cartesian(a),
|
|---|
| 102 | pb = cartesian(b);
|
|---|
| 103 |
|
|---|
| 104 | // We have two planes, n1.p = d1 and n2.p = d2.
|
|---|
| 105 | // Find intersection line p(t) = c1 n1 + c2 n2 + t (n1 ⨯ n2).
|
|---|
| 106 | var n1 = [1, 0, 0], // normal
|
|---|
| 107 | n2 = cartesianCross(pa, pb),
|
|---|
| 108 | n2n2 = cartesianDot(n2, n2),
|
|---|
| 109 | n1n2 = n2[0], // cartesianDot(n1, n2),
|
|---|
| 110 | determinant = n2n2 - n1n2 * n1n2;
|
|---|
| 111 |
|
|---|
| 112 | // Two polar points.
|
|---|
| 113 | if (!determinant) return !two && a;
|
|---|
| 114 |
|
|---|
| 115 | var c1 = cr * n2n2 / determinant,
|
|---|
| 116 | c2 = -cr * n1n2 / determinant,
|
|---|
| 117 | n1xn2 = cartesianCross(n1, n2),
|
|---|
| 118 | A = cartesianScale(n1, c1),
|
|---|
| 119 | B = cartesianScale(n2, c2);
|
|---|
| 120 | cartesianAddInPlace(A, B);
|
|---|
| 121 |
|
|---|
| 122 | // Solve |p(t)|^2 = 1.
|
|---|
| 123 | var u = n1xn2,
|
|---|
| 124 | w = cartesianDot(A, u),
|
|---|
| 125 | uu = cartesianDot(u, u),
|
|---|
| 126 | t2 = w * w - uu * (cartesianDot(A, A) - 1);
|
|---|
| 127 |
|
|---|
| 128 | if (t2 < 0) return;
|
|---|
| 129 |
|
|---|
| 130 | var t = sqrt(t2),
|
|---|
| 131 | q = cartesianScale(u, (-w - t) / uu);
|
|---|
| 132 | cartesianAddInPlace(q, A);
|
|---|
| 133 | q = spherical(q);
|
|---|
| 134 |
|
|---|
| 135 | if (!two) return q;
|
|---|
| 136 |
|
|---|
| 137 | // Two intersection points.
|
|---|
| 138 | var lambda0 = a[0],
|
|---|
| 139 | lambda1 = b[0],
|
|---|
| 140 | phi0 = a[1],
|
|---|
| 141 | phi1 = b[1],
|
|---|
| 142 | z;
|
|---|
| 143 |
|
|---|
| 144 | if (lambda1 < lambda0) z = lambda0, lambda0 = lambda1, lambda1 = z;
|
|---|
| 145 |
|
|---|
| 146 | var delta = lambda1 - lambda0,
|
|---|
| 147 | polar = abs(delta - pi) < epsilon,
|
|---|
| 148 | meridian = polar || delta < epsilon;
|
|---|
| 149 |
|
|---|
| 150 | if (!polar && phi1 < phi0) z = phi0, phi0 = phi1, phi1 = z;
|
|---|
| 151 |
|
|---|
| 152 | // Check that the first point is between a and b.
|
|---|
| 153 | if (meridian
|
|---|
| 154 | ? polar
|
|---|
| 155 | ? phi0 + phi1 > 0 ^ q[1] < (abs(q[0] - lambda0) < epsilon ? phi0 : phi1)
|
|---|
| 156 | : phi0 <= q[1] && q[1] <= phi1
|
|---|
| 157 | : delta > pi ^ (lambda0 <= q[0] && q[0] <= lambda1)) {
|
|---|
| 158 | var q1 = cartesianScale(u, (-w + t) / uu);
|
|---|
| 159 | cartesianAddInPlace(q1, A);
|
|---|
| 160 | return [q, spherical(q1)];
|
|---|
| 161 | }
|
|---|
| 162 | }
|
|---|
| 163 |
|
|---|
| 164 | // Generates a 4-bit vector representing the location of a point relative to
|
|---|
| 165 | // the small circle's bounding box.
|
|---|
| 166 | function code(lambda, phi) {
|
|---|
| 167 | var r = smallRadius ? radius : pi - radius,
|
|---|
| 168 | code = 0;
|
|---|
| 169 | if (lambda < -r) code |= 1; // left
|
|---|
| 170 | else if (lambda > r) code |= 2; // right
|
|---|
| 171 | if (phi < -r) code |= 4; // below
|
|---|
| 172 | else if (phi > r) code |= 8; // above
|
|---|
| 173 | return code;
|
|---|
| 174 | }
|
|---|
| 175 |
|
|---|
| 176 | return clip(visible, clipLine, interpolate, smallRadius ? [0, -radius] : [-pi, radius - pi]);
|
|---|
| 177 | }
|
|---|