UNPKG

91.4 kBJavaScriptView Raw
1// https://github.com/d3/d3-geo-polygon v1.9.0 Copyright 2019 Mike Bostock
2(function (global, factory) {
3typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3-geo')) :
4typeof define === 'function' && define.amd ? define(['exports', 'd3-array', 'd3-geo'], factory) :
5(factory((global.d3 = global.d3 || {}),global.d3,global.d3));
6}(this, (function (exports,d3Array,d3Geo) { 'use strict';
7
8function noop() {}
9
10function clipBuffer() {
11 var lines = [],
12 line;
13 return {
14 point: function(x, y, i, t) {
15 var point = [x, y];
16 // when called by clipPolygon, store index and t
17 if (arguments.length > 2) { point.index = i; point.t = t; }
18 line.push(point);
19 },
20 lineStart: function() {
21 lines.push(line = []);
22 },
23 lineEnd: noop,
24 rejoin: function() {
25 if (lines.length > 1) lines.push(lines.pop().concat(lines.shift()));
26 },
27 result: function() {
28 var result = lines;
29 lines = [];
30 line = null;
31 return result;
32 }
33 };
34}
35
36var abs = Math.abs;
37var atan = Math.atan;
38var atan2 = Math.atan2;
39var cos = Math.cos;
40var exp = Math.exp;
41var floor = Math.floor;
42var log = Math.log;
43var max = Math.max;
44var min = Math.min;
45var pow = Math.pow;
46var sign = Math.sign || function(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; };
47var sin = Math.sin;
48var tan = Math.tan;
49
50var epsilon = 1e-6;
51var epsilon2 = 1e-12;
52var pi = Math.PI;
53var halfPi = pi / 2;
54var quarterPi = pi / 4;
55var sqrt1_2 = Math.SQRT1_2;
56var sqrtPi = sqrt(pi);
57var tau = pi * 2;
58var degrees = 180 / pi;
59var radians = pi / 180;
60
61function asin(x) {
62 return x > 1 ? halfPi : x < -1 ? -halfPi : Math.asin(x);
63}
64
65function acos(x) {
66 return x > 1 ? 0 : x < -1 ? pi : Math.acos(x);
67}
68
69function sqrt(x) {
70 return x > 0 ? Math.sqrt(x) : 0;
71}
72
73function pointEqual(a, b) {
74 return abs(a[0] - b[0]) < epsilon && abs(a[1] - b[1]) < epsilon;
75}
76
77function Intersection(point, points, other, entry) {
78 this.x = point;
79 this.z = points;
80 this.o = other; // another intersection
81 this.e = entry; // is an entry?
82 this.v = false; // visited
83 this.n = this.p = null; // next & previous
84}
85
86// A generalized polygon clipping algorithm: given a polygon that has been cut
87// into its visible line segments, and rejoins the segments by interpolating
88// along the clip edge.
89function clipRejoin(segments, compareIntersection, startInside, interpolate, stream) {
90 var subject = [],
91 clip = [],
92 i,
93 n;
94
95 segments.forEach(function(segment) {
96 if ((n = segment.length - 1) <= 0) return;
97 var n, p0 = segment[0], p1 = segment[n], x;
98
99 // If the first and last points of a segment are coincident, then treat as a
100 // closed ring. TODO if all rings are closed, then the winding order of the
101 // exterior ring should be checked.
102 if (pointEqual(p0, p1)) {
103 stream.lineStart();
104 for (i = 0; i < n; ++i) stream.point((p0 = segment[i])[0], p0[1]);
105 stream.lineEnd();
106 return;
107 }
108
109 subject.push(x = new Intersection(p0, segment, null, true));
110 clip.push(x.o = new Intersection(p0, null, x, false));
111 subject.push(x = new Intersection(p1, segment, null, false));
112 clip.push(x.o = new Intersection(p1, null, x, true));
113 });
114
115 if (!subject.length) return;
116
117 clip.sort(compareIntersection);
118 link(subject);
119 link(clip);
120
121 for (i = 0, n = clip.length; i < n; ++i) {
122 clip[i].e = startInside = !startInside;
123 }
124
125 var start = subject[0],
126 points,
127 point;
128
129 while (1) {
130 // Find first unvisited intersection.
131 var current = start,
132 isSubject = true;
133 while (current.v) if ((current = current.n) === start) return;
134 points = current.z;
135 stream.lineStart();
136 do {
137 current.v = current.o.v = true;
138 if (current.e) {
139 if (isSubject) {
140 for (i = 0, n = points.length; i < n; ++i) stream.point((point = points[i])[0], point[1]);
141 } else {
142 interpolate(current.x, current.n.x, 1, stream);
143 }
144 current = current.n;
145 } else {
146 if (isSubject) {
147 points = current.p.z;
148 for (i = points.length - 1; i >= 0; --i) stream.point((point = points[i])[0], point[1]);
149 } else {
150 interpolate(current.x, current.p.x, -1, stream);
151 }
152 current = current.p;
153 }
154 current = current.o;
155 points = current.z;
156 isSubject = !isSubject;
157 } while (!current.v);
158 stream.lineEnd();
159 }
160}
161
162function link(array) {
163 if (!(n = array.length)) return;
164 var n,
165 i = 0,
166 a = array[0],
167 b;
168 while (++i < n) {
169 a.n = b = array[i];
170 b.p = a;
171 a = b;
172 }
173 a.n = b = array[0];
174 b.p = a;
175}
176
177// Adds floating point numbers with twice the normal precision.
178// Reference: J. R. Shewchuk, Adaptive Precision Floating-Point Arithmetic and
179// Fast Robust Geometric Predicates, Discrete & Computational Geometry 18(3)
180// 305–363 (1997).
181// Code adapted from GeographicLib by Charles F. F. Karney,
182// http://geographiclib.sourceforge.net/
183
184function adder() {
185 return new Adder;
186}
187
188function Adder() {
189 this.reset();
190}
191
192Adder.prototype = {
193 constructor: Adder,
194 reset: function() {
195 this.s = // rounded value
196 this.t = 0; // exact error
197 },
198 add: function(y) {
199 add(temp, y, this.t);
200 add(this, temp.s, this.s);
201 if (this.s) this.t += temp.t;
202 else this.s = temp.t;
203 },
204 valueOf: function() {
205 return this.s;
206 }
207};
208
209var temp = new Adder;
210
211function add(adder, a, b) {
212 var x = adder.s = a + b,
213 bv = x - a,
214 av = x - bv;
215 adder.t = (a - av) + (b - bv);
216}
217
218function spherical(cartesian) {
219 return [atan2(cartesian[1], cartesian[0]), asin(cartesian[2])];
220}
221
222function sphericalDegrees(cartesian) {
223 var c = spherical(cartesian);
224 return [c[0] * degrees, c[1] * degrees];
225}
226
227function cartesian(spherical) {
228 var lambda = spherical[0], phi = spherical[1], cosPhi = cos(phi);
229 return [cosPhi * cos(lambda), cosPhi * sin(lambda), sin(phi)];
230}
231
232function cartesianDegrees(spherical) {
233 return cartesian([spherical[0] * radians, spherical[1] * radians]);
234}
235
236function cartesianDot(a, b) {
237 return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
238}
239
240function cartesianCross(a, b) {
241 return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
242}
243
244// TODO return d
245function cartesianNormalizeInPlace(d) {
246 var l = sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
247 d[0] /= l, d[1] /= l, d[2] /= l;
248}
249
250function cartesianEqual(a, b) {
251 var dx = b[0] - a[0],
252 dy = b[1] - a[1],
253 dz = b[2] - a[2];
254 return dx * dx + dy * dy + dz * dz < epsilon2 * epsilon2;
255}
256
257var sum = adder();
258
259function polygonContains(polygon, point) {
260 var lambda = point[0],
261 phi = point[1],
262 normal = [sin(lambda), -cos(lambda), 0],
263 angle = 0,
264 winding = 0;
265
266 sum.reset();
267
268 for (var i = 0, n = polygon.length; i < n; ++i) {
269 if (!(m = (ring = polygon[i]).length)) continue;
270 var ring,
271 m,
272 point0 = ring[m - 1],
273 lambda0 = point0[0],
274 phi0 = point0[1] / 2 + quarterPi,
275 sinPhi0 = sin(phi0),
276 cosPhi0 = cos(phi0);
277
278 for (var j = 0; j < m; ++j, lambda0 = lambda1, sinPhi0 = sinPhi1, cosPhi0 = cosPhi1, point0 = point1) {
279 var point1 = ring[j],
280 lambda1 = point1[0],
281 phi1 = point1[1] / 2 + quarterPi,
282 sinPhi1 = sin(phi1),
283 cosPhi1 = cos(phi1),
284 delta = lambda1 - lambda0,
285 sign$$1 = delta >= 0 ? 1 : -1,
286 absDelta = sign$$1 * delta,
287 antimeridian = absDelta > pi,
288 k = sinPhi0 * sinPhi1;
289
290 sum.add(atan2(k * sign$$1 * sin(absDelta), cosPhi0 * cosPhi1 + k * cos(absDelta)));
291 angle += antimeridian ? delta + sign$$1 * tau : delta;
292
293 // Are the longitudes either side of the point’s meridian (lambda),
294 // and are the latitudes smaller than the parallel (phi)?
295 if (antimeridian ^ lambda0 >= lambda ^ lambda1 >= lambda) {
296 var arc = cartesianCross(cartesian(point0), cartesian(point1));
297 cartesianNormalizeInPlace(arc);
298 var intersection = cartesianCross(normal, arc);
299 cartesianNormalizeInPlace(intersection);
300 var phiArc = (antimeridian ^ delta >= 0 ? -1 : 1) * asin(intersection[2]);
301 if (phi > phiArc || phi === phiArc && (arc[0] || arc[1])) {
302 winding += antimeridian ^ delta >= 0 ? 1 : -1;
303 }
304 }
305 }
306 }
307
308 // First, determine whether the South pole is inside or outside:
309 //
310 // It is inside if:
311 // * the polygon winds around it in a clockwise direction.
312 // * the polygon does not (cumulatively) wind around it, but has a negative
313 // (counter-clockwise) area.
314 //
315 // Second, count the (signed) number of times a segment crosses a lambda
316 // from the point to the South pole. If it is zero, then the point is the
317 // same side as the South pole.
318
319 return (angle < -epsilon || angle < epsilon && sum < -epsilon) ^ (winding & 1);
320}
321
322function clip(pointVisible, clipLine, interpolate, start, sort) {
323 if (typeof sort === "undefined") sort = compareIntersection;
324
325 return function(sink) {
326 var line = clipLine(sink),
327 ringBuffer = clipBuffer(),
328 ringSink = clipLine(ringBuffer),
329 polygonStarted = false,
330 polygon,
331 segments,
332 ring;
333
334 var clip = {
335 point: point,
336 lineStart: lineStart,
337 lineEnd: lineEnd,
338 polygonStart: function() {
339 clip.point = pointRing;
340 clip.lineStart = ringStart;
341 clip.lineEnd = ringEnd;
342 segments = [];
343 polygon = [];
344 },
345 polygonEnd: function() {
346 clip.point = point;
347 clip.lineStart = lineStart;
348 clip.lineEnd = lineEnd;
349 segments = d3Array.merge(segments);
350 var startInside = polygonContains(polygon, start);
351 if (segments.length) {
352 if (!polygonStarted) sink.polygonStart(), polygonStarted = true;
353 clipRejoin(segments, sort, startInside, interpolate, sink);
354 } else if (startInside) {
355 if (!polygonStarted) sink.polygonStart(), polygonStarted = true;
356 interpolate(null, null, 1, sink);
357 }
358 if (polygonStarted) sink.polygonEnd(), polygonStarted = false;
359 segments = polygon = null;
360 },
361 sphere: function() {
362 interpolate(null, null, 1, sink);
363 }
364 };
365
366 function point(lambda, phi) {
367 if (pointVisible(lambda, phi)) sink.point(lambda, phi);
368 }
369
370 function pointLine(lambda, phi) {
371 line.point(lambda, phi);
372 }
373
374 function lineStart() {
375 clip.point = pointLine;
376 line.lineStart();
377 }
378
379 function lineEnd() {
380 clip.point = point;
381 line.lineEnd();
382 }
383
384 function pointRing(lambda, phi, close) {
385 ring.push([lambda, phi]);
386 ringSink.point(lambda, phi, close);
387 }
388
389 function ringStart() {
390 ringSink.lineStart();
391 ring = [];
392 }
393
394 function ringEnd() {
395 pointRing(ring[0][0], ring[0][1], true);
396 ringSink.lineEnd();
397
398 var clean = ringSink.clean(),
399 ringSegments = ringBuffer.result(),
400 i, n = ringSegments.length, m,
401 segment,
402 point;
403
404 ring.pop();
405 polygon.push(ring);
406 ring = null;
407
408 if (!n) return;
409
410 // No intersections.
411 if (clean & 1) {
412 segment = ringSegments[0];
413 if ((m = segment.length - 1) > 0) {
414 if (!polygonStarted) sink.polygonStart(), polygonStarted = true;
415 sink.lineStart();
416 for (i = 0; i < m; ++i) sink.point((point = segment[i])[0], point[1]);
417 sink.lineEnd();
418 }
419 return;
420 }
421
422 // Rejoin connected segments.
423 // TODO reuse ringBuffer.rejoin()?
424 if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
425
426 segments.push(ringSegments.filter(validSegment));
427 }
428
429 return clip;
430 };
431}
432
433function validSegment(segment) {
434 return segment.length > 1;
435}
436
437// Intersections are sorted along the clip edge. For both antimeridian cutting
438// and circle clipping, the same comparison is used.
439function compareIntersection(a, b) {
440 return ((a = a.x)[0] < 0 ? a[1] - halfPi - epsilon : halfPi - a[1])
441 - ((b = b.x)[0] < 0 ? b[1] - halfPi - epsilon : halfPi - b[1]);
442}
443
444function intersectSegment(from, to) {
445 this.from = from, this.to = to;
446 this.normal = cartesianCross(from, to);
447 this.fromNormal = cartesianCross(this.normal, from);
448 this.toNormal = cartesianCross(this.normal, to);
449 this.l = acos(cartesianDot(from, to));
450}
451
452// >> here a and b are segments processed by intersectSegment
453function intersect(a, b) {
454 if (cartesianEqual(a.from, b.from) || cartesianEqual(a.from, b.to))
455 return a.from;
456 if (cartesianEqual(a.to, b.from) || cartesianEqual(a.to, b.to))
457 return a.to;
458
459 var lc = (a.l + b.l < pi) ? cos(a.l + b.l) - epsilon : -1;
460 if (cartesianDot(a.from, b.from) < lc
461 || cartesianDot(a.from, b.to) < lc
462 || cartesianDot(a.to, b.from) < lc
463 || cartesianDot(a.to, b.to) < lc)
464 return;
465
466 var axb = cartesianCross(a.normal, b.normal);
467 cartesianNormalizeInPlace(axb);
468
469 var a0 = cartesianDot(axb, a.fromNormal),
470 a1 = cartesianDot(axb, a.toNormal),
471 b0 = cartesianDot(axb, b.fromNormal),
472 b1 = cartesianDot(axb, b.toNormal);
473
474 // check if the candidate lies on both segments
475 // or is almost equal to one of the four points
476 if (
477 (a0 > 0 && a1 < 0 && b0 > 0 && b1 < 0) ||
478 (a0 >= 0 &&
479 a1 <= 0 &&
480 b0 >= 0 &&
481 b1 <= 0 &&
482 (cartesianEqual(axb, a.from) ||
483 cartesianEqual(axb, a.to) ||
484 cartesianEqual(axb, b.from) ||
485 cartesianEqual(axb, b.to)))
486 )
487 return axb;
488
489 // same test for the antipode
490 axb[0] = -axb[0];
491 axb[1] = -axb[1];
492 axb[2] = -axb[2];
493 a0 = -a0;
494 a1 = -a1;
495 b0 = -b0;
496 b1 = -b1;
497
498 if (
499 (a0 > 0 && a1 < 0 && b0 > 0 && b1 < 0) ||
500 (a0 >= 0 &&
501 a1 <= 0 &&
502 b0 >= 0 &&
503 b1 <= 0 &&
504 (cartesianEqual(axb, a.from) ||
505 cartesianEqual(axb, a.to) ||
506 cartesianEqual(axb, b.from) ||
507 cartesianEqual(axb, b.to)))
508 )
509 return axb;
510}
511
512function intersectPointOnLine(p, a) {
513 var a0 = cartesianDot(p, a.fromNormal),
514 a1 = cartesianDot(p, a.toNormal);
515 p = cartesianDot(p, a.normal);
516
517 return abs(p) < epsilon2 && (a0 > -epsilon2 && a1 < epsilon2 || a0 < epsilon2 && a1 > -epsilon2);
518}
519
520var intersectCoincident = {};
521
522function intersect$1(a, b) {
523 var ca = a.map(p => cartesian(p.map(d => d * radians))),
524 cb = b.map(p => cartesian(p.map(d => d * radians)));
525 var i = intersect(
526 new intersectSegment(ca[0], ca[1]),
527 new intersectSegment(cb[0], cb[1])
528 );
529 return !i ? i : spherical(i).map(d => d * degrees);
530}
531
532var clipNone = function(stream) { return stream; };
533
534// clipPolygon
535function clipPolygon(geometry) {
536 function clipGeometry(geometry) {
537 var polygons;
538
539 if (geometry.type === "MultiPolygon") {
540 polygons = geometry.coordinates;
541 } else if (geometry.type === "Polygon") {
542 polygons = [geometry.coordinates];
543 } else {
544 return clipNone;
545 }
546
547 var clips = polygons.map(function(polygon) {
548 polygon = polygon.map(ringRadians);
549 var pointVisible = visible(polygon),
550 segments = ringSegments(polygon[0]); // todo holes?
551 return clip(
552 pointVisible,
553 clipLine(segments, pointVisible),
554 interpolate(segments, polygon),
555 polygon[0][0],
556 clipPolygonSort
557 );
558 });
559
560 var clipPolygon = function(stream) {
561 var clipstream = clips.map(clip$$1 => clip$$1(stream));
562 return {
563 point: function(lambda, phi) {
564 clipstream.forEach(clip$$1 => clip$$1.point(lambda, phi));
565 },
566 lineStart: function() {
567 clipstream.forEach(clip$$1 => clip$$1.lineStart());
568 },
569 lineEnd: function() {
570 clipstream.forEach(clip$$1 => clip$$1.lineEnd());
571 },
572 polygonStart: function() {
573 clipstream.forEach(clip$$1 => clip$$1.polygonStart());
574 },
575 polygonEnd: function() {
576 clipstream.forEach(clip$$1 => clip$$1.polygonEnd());
577 },
578 sphere: function() {
579 clipstream.forEach(clip$$1 => clip$$1.sphere());
580 }
581 };
582 };
583
584 clipPolygon.polygon = function(_) {
585 return _ ? ((geometry = _), clipGeometry(geometry)) : geometry;
586 };
587 return clipPolygon;
588 }
589
590 return clipGeometry(geometry);
591}
592
593function ringRadians(ring) {
594 return ring.map(function(point) {
595 return [point[0] * radians, point[1] * radians];
596 });
597}
598
599function ringSegments(ring) {
600 var c,
601 c0,
602 segments = [];
603 ring.forEach(function(point, i) {
604 c = cartesian(point);
605 if (i) segments.push(new intersectSegment(c0, c));
606 c0 = c;
607 return point;
608 });
609 return segments;
610}
611
612function clipPolygonSort(a, b) {
613 (a = a.x), (b = b.x);
614 return a.index - b.index || a.t - b.t;
615}
616
617function interpolate(segments, polygon) {
618 return function(from, to, direction, stream) {
619 if (from == null) {
620 stream.polygonStart();
621 polygon.forEach(function(ring) {
622 stream.lineStart();
623 ring.forEach(function(point) {
624 stream.point(point[0], point[1]);
625 });
626 stream.lineEnd();
627 });
628 stream.polygonEnd();
629 } else if (
630 from.index !== to.index &&
631 from.index != null &&
632 to.index != null
633 ) {
634 for (
635 var i = from.index;
636 i !== to.index;
637 i = (i + direction + segments.length) % segments.length
638 ) {
639 var segment = segments[i],
640 point = spherical(direction > 0 ? segment.to : segment.from);
641 stream.point(point[0], point[1]);
642 }
643 } else if (
644 from.index === to.index &&
645 from.t > to.t &&
646 from.index != null &&
647 to.index != null
648 ) {
649 for (
650 i = 0;
651 i < segments.length;
652 i++
653 ) {
654 segment = segments[(from.index + i * direction + segments.length)%segments.length],
655 point = spherical(direction > 0 ? segment.to : segment.from);
656 stream.point(point[0], point[1]);
657 }
658 }
659 };
660}
661
662// Geodesic coordinates for two 3D points.
663function clipPolygonDistance(a, b) {
664 var axb = cartesianCross(a, b);
665 return atan2(sqrt(cartesianDot(axb, axb)), cartesianDot(a, b));
666}
667
668function visible(polygon) {
669 return function(lambda, phi) {
670 return polygonContains(polygon, [lambda, phi]);
671 };
672}
673
674function randsign(i, j) {
675 return sign(sin(100 * i + j));
676}
677
678function clipLine(segments, pointVisible) {
679 return function(stream) {
680 var point0, lambda00, phi00, v00, v0, clean, line, lines = [];
681 return {
682 lineStart: function() {
683 point0 = null;
684 clean = 1;
685 line = [];
686 },
687 lineEnd: function() {
688 if (v0) lines.push(line);
689 lines.forEach(function(line) {
690 stream.lineStart();
691 line.forEach(function(point) {
692 stream.point(...point); // can have 4 dimensions
693 });
694 stream.lineEnd();
695 });
696 lines = [];
697 },
698 point: function(lambda, phi, close) {
699 if (cos(lambda) == -1) lambda -= sign(sin(lambda)) * 1e-5; // move away from -180/180 https://github.com/d3/d3-geo/pull/108#issuecomment-323798937
700 if (close) (lambda = lambda00), (phi = phi00);
701 var point = cartesian([lambda, phi]),
702 v = v0,
703 intersection,
704 i,
705 j,
706 s,
707 t;
708 if (point0) {
709 var segment = new intersectSegment(point0, point),
710 intersections = [];
711 for (i = 0, j = 100; i < segments.length && j > 0; ++i) {
712 s = segments[i];
713 intersection = intersect(segment, s);
714 if (intersection) {
715 if (
716 intersection === intersectCoincident ||
717 cartesianEqual(intersection, point0) ||
718 cartesianEqual(intersection, point) ||
719 cartesianEqual(intersection, s.from) ||
720 cartesianEqual(intersection, s.to)
721 ) {
722 t = 1e-4;
723 lambda =
724 ((lambda + 3 * pi + randsign(i, j) * t) % (2 * pi)) - pi;
725 phi = min(
726 pi / 2 - 1e-4,
727 max(1e-4 - pi / 2, phi + randsign(i, j) * t)
728 );
729 segment = new intersectSegment(
730 point0,
731 (point = cartesian([lambda, phi]))
732 );
733 (i = -1), --j;
734 intersections.length = 0;
735 continue;
736 }
737 var sph = spherical(intersection);
738 intersection.distance = clipPolygonDistance(point0, intersection);
739 intersection.index = i;
740 intersection.t = clipPolygonDistance(s.from, intersection);
741 (intersection[0] = sph[0]),
742 (intersection[1] = sph[1]),
743 intersection.pop();
744 intersections.push(intersection);
745 }
746 }
747 if (intersections.length) {
748 clean = 0;
749 intersections.sort(function(a, b) {
750 return a.distance - b.distance;
751 });
752 for (i = 0; i < intersections.length; ++i) {
753 intersection = intersections[i];
754 v = !v;
755 if (v) {
756 line = [];
757 line.push([
758 intersection[0],
759 intersection[1],
760 intersection.index,
761 intersection.t
762 ]);
763 } else {
764 line.push([
765 intersection[0],
766 intersection[1],
767 intersection.index,
768 intersection.t
769 ]);
770 lines.push(line);
771 }
772 }
773 }
774 if (v) line.push([lambda, phi]);
775 } else {
776 for (i = 0, j = 100; i < segments.length && j > 0; ++i) {
777 s = segments[i];
778 if (intersectPointOnLine(point, s)) {
779 t = 1e-4;
780 lambda = ((lambda + 3 * pi + randsign(i, j) * t) % (2 * pi)) - pi;
781 phi = min(
782 pi / 2 - 1e-4,
783 max(1e-4 - pi / 2, phi + randsign(i, j) * t)
784 );
785 point = cartesian([lambda, phi]);
786 (i = -1), --j;
787 }
788 }
789 v00 = v = pointVisible((lambda00 = lambda), (phi00 = phi));
790 if (v) line = [], line.push([lambda, phi]);
791 }
792 (point0 = point), (v0 = v);
793 },
794 // Rejoin first and last segments if there were intersections and the first
795 // and last points were visible.
796 clean: function() {
797 return clean | ((v00 && v0) << 1);
798 }
799 };
800 };
801}
802
803// Note: 6-element arrays are used to denote the 3x3 affine transform matrix:
804// [a, b, c,
805// d, e, f,
806// 0, 0, 1] - this redundant row is left out.
807
808// Transform matrix for [a0, a1] -> [b0, b1].
809function matrix(a, b) {
810 var u = subtract(a[1], a[0]),
811 v = subtract(b[1], b[0]),
812 phi = angle(u, v),
813 s = length(u) / length(v);
814
815 return multiply([
816 1, 0, a[0][0],
817 0, 1, a[0][1]
818 ], multiply([
819 s, 0, 0,
820 0, s, 0
821 ], multiply([
822 cos(phi), sin(phi), 0,
823 -sin(phi), cos(phi), 0
824 ], [
825 1, 0, -b[0][0],
826 0, 1, -b[0][1]
827 ])));
828}
829
830// Inverts a transform matrix.
831function inverse(m) {
832 var k = 1 / (m[0] * m[4] - m[1] * m[3]);
833 return [
834 k * m[4], -k * m[1], k * (m[1] * m[5] - m[2] * m[4]),
835 -k * m[3], k * m[0], k * (m[2] * m[3] - m[0] * m[5])
836 ];
837}
838
839// Multiplies two 3x2 matrices.
840function multiply(a, b) {
841 return [
842 a[0] * b[0] + a[1] * b[3],
843 a[0] * b[1] + a[1] * b[4],
844 a[0] * b[2] + a[1] * b[5] + a[2],
845 a[3] * b[0] + a[4] * b[3],
846 a[3] * b[1] + a[4] * b[4],
847 a[3] * b[2] + a[4] * b[5] + a[5]
848 ];
849}
850
851// Subtracts 2D vectors.
852function subtract(a, b) {
853 return [a[0] - b[0], a[1] - b[1]];
854}
855
856// Magnitude of a 2D vector.
857function length(v) {
858 return sqrt(v[0] * v[0] + v[1] * v[1]);
859}
860
861// Angle between two 2D vectors.
862function angle(a, b) {
863 return atan2(a[0] * b[1] - a[1] * b[0], a[0] * b[0] + a[1] * b[1]);
864}
865
866// Creates a polyhedral projection.
867// * tree: a spanning tree of polygon faces. Nodes are automatically
868// augmented with a transform matrix.
869// * face: a function that returns the appropriate node for a given {lambda, phi}
870// point (radians).
871function polyhedral(tree, face) {
872
873 recurse(tree, {transform: null});
874
875 function recurse(node, parent) {
876 node.edges = faceEdges(node.face);
877 // Find shared edge.
878 if (parent.face) {
879 var shared = node.shared = sharedEdge(node.face, parent.face),
880 m = matrix(shared.map(parent.project), shared.map(node.project));
881 node.transform = parent.transform ? multiply(parent.transform, m) : m;
882 // Replace shared edge in parent edges array.
883 var edges = parent.edges;
884 for (var i = 0, n = edges.length; i < n; ++i) {
885 if (pointEqual$1(shared[0], edges[i][1]) && pointEqual$1(shared[1], edges[i][0])) edges[i] = node;
886 if (pointEqual$1(shared[0], edges[i][0]) && pointEqual$1(shared[1], edges[i][1])) edges[i] = node;
887 }
888 edges = node.edges;
889 for (i = 0, n = edges.length; i < n; ++i) {
890 if (pointEqual$1(shared[0], edges[i][0]) && pointEqual$1(shared[1], edges[i][1])) edges[i] = parent;
891 if (pointEqual$1(shared[0], edges[i][1]) && pointEqual$1(shared[1], edges[i][0])) edges[i] = parent;
892 }
893 } else {
894 node.transform = parent.transform;
895 }
896 if (node.children) {
897 node.children.forEach(function(child) {
898 recurse(child, node);
899 });
900 }
901 return node;
902 }
903
904 function forward(lambda, phi) {
905 var node = face(lambda, phi),
906 point = node.project([lambda * degrees, phi * degrees]),
907 t;
908 if (t = node.transform) {
909 return [
910 t[0] * point[0] + t[1] * point[1] + t[2],
911 -(t[3] * point[0] + t[4] * point[1] + t[5])
912 ];
913 }
914 point[1] = -point[1];
915 return point;
916 }
917
918 // Naive inverse! A faster solution would use bounding boxes, or even a
919 // polygonal quadtree.
920 if (hasInverse(tree)) forward.invert = function(x, y) {
921 var coordinates = faceInvert(tree, [x, -y]);
922 return coordinates && (coordinates[0] *= radians, coordinates[1] *= radians, coordinates);
923 };
924
925 function faceInvert(node, coordinates) {
926 var invert = node.project.invert,
927 t = node.transform,
928 point = coordinates;
929 if (t) {
930 t = inverse(t);
931 point = [
932 t[0] * point[0] + t[1] * point[1] + t[2],
933 (t[3] * point[0] + t[4] * point[1] + t[5])
934 ];
935 }
936 if (invert && node === faceDegrees(p = invert(point))) return p;
937 var p,
938 children = node.children;
939 for (var i = 0, n = children && children.length; i < n; ++i) {
940 if (p = faceInvert(children[i], coordinates)) return p;
941 }
942 }
943
944 function faceDegrees(coordinates) {
945 return face(coordinates[0] * radians, coordinates[1] * radians);
946 }
947
948 var proj = d3Geo.geoProjection(forward);
949
950 // run around the mesh of faces and stream all vertices to create the clipping polygon
951 var polygon = [];
952 outline({point: function(lambda, phi) { polygon.push([lambda, phi]); }}, tree);
953 polygon.push(polygon[0]);
954 proj.preclip(clipPolygon({ type: "Polygon", coordinates: [ polygon ] }));
955 proj.tree = function() { return tree; };
956
957 return proj;
958}
959
960function outline(stream, node, parent) {
961 var point,
962 edges = node.edges,
963 n = edges.length,
964 edge,
965 multiPoint = {type: "MultiPoint", coordinates: node.face},
966 notPoles = node.face.filter(function(d) { return abs(d[1]) !== 90; }),
967 b = d3Geo.geoBounds({type: "MultiPoint", coordinates: notPoles}),
968 inside = false,
969 j = -1,
970 dx = b[1][0] - b[0][0];
971 // TODO
972 node.centroid = dx === 180 || dx === 360
973 ? [(b[0][0] + b[1][0]) / 2, (b[0][1] + b[1][1]) / 2]
974 : d3Geo.geoCentroid(multiPoint);
975 // First find the shared edge…
976 if (parent) while (++j < n) {
977 if (edges[j] === parent) break;
978 }
979 ++j;
980 for (var i = 0; i < n; ++i) {
981 edge = edges[(i + j) % n];
982 if (Array.isArray(edge)) {
983 if (!inside) {
984 stream.point((point = d3Geo.geoInterpolate(edge[0], node.centroid)(epsilon))[0], point[1]);
985 inside = true;
986 }
987 stream.point((point = d3Geo.geoInterpolate(edge[1], node.centroid)(epsilon))[0], point[1]);
988 } else {
989 inside = false;
990 if (edge !== parent) outline(stream, edge, node);
991 }
992 }
993}
994
995// Tests equality of two spherical points.
996function pointEqual$1(a, b) {
997 return a && b && a[0] === b[0] && a[1] === b[1];
998}
999
1000// Finds a shared edge given two clockwise polygons.
1001function sharedEdge(a, b) {
1002 var x, y, n = a.length, found = null;
1003 for (var i = 0; i < n; ++i) {
1004 x = a[i];
1005 for (var j = b.length; --j >= 0;) {
1006 y = b[j];
1007 if (x[0] === y[0] && x[1] === y[1]) {
1008 if (found) return [found, x];
1009 found = x;
1010 }
1011 }
1012 }
1013}
1014
1015// Converts an array of n face vertices to an array of n + 1 edges.
1016function faceEdges(face) {
1017 var n = face.length,
1018 edges = [];
1019 for (var a = face[n - 1], i = 0; i < n; ++i) edges.push([a, a = face[i]]);
1020 return edges;
1021}
1022
1023function hasInverse(node) {
1024 return node.project.invert || node.children && node.children.some(hasInverse);
1025}
1026
1027// TODO generate on-the-fly to avoid external modification.
1028var octahedron = [
1029 [0, 90],
1030 [-90, 0], [0, 0], [90, 0], [180, 0],
1031 [0, -90]
1032];
1033
1034var octahedron$1 = [
1035 [0, 2, 1],
1036 [0, 3, 2],
1037 [5, 1, 2],
1038 [5, 2, 3],
1039 [0, 1, 4],
1040 [0, 4, 3],
1041 [5, 4, 1],
1042 [5, 3, 4]
1043].map(function(face) {
1044 return face.map(function(i) {
1045 return octahedron[i];
1046 });
1047});
1048
1049function butterfly(faceProjection) {
1050
1051 faceProjection = faceProjection || function(face) {
1052 var c = d3Geo.geoCentroid({type: "MultiPoint", coordinates: face});
1053 return d3Geo.geoGnomonic().scale(1).translate([0, 0]).rotate([-c[0], -c[1]]);
1054 };
1055
1056 var faces = octahedron$1.map(function(face) {
1057 return {face: face, project: faceProjection(face)};
1058 });
1059
1060 [-1, 0, 0, 1, 0, 1, 4, 5].forEach(function(d, i) {
1061 var node = faces[d];
1062 node && (node.children || (node.children = [])).push(faces[i]);
1063 });
1064
1065 return polyhedral(faces[0], function(lambda, phi) {
1066 return faces[lambda < -pi / 2 ? phi < 0 ? 6 : 4
1067 : lambda < 0 ? phi < 0 ? 2 : 0
1068 : lambda < pi / 2 ? phi < 0 ? 3 : 1
1069 : phi < 0 ? 7 : 5];
1070 })
1071 .angle(-30)
1072 .scale(101.858)
1073 .center([0, 45]);
1074}
1075
1076// code duplicated from d3-geo-projection
1077
1078function collignonRaw(lambda, phi) {
1079 var alpha = sqrt(1 - sin(phi));
1080 return [(2 / sqrtPi) * lambda * alpha, sqrtPi * (1 - alpha)];
1081}
1082
1083collignonRaw.invert = function(x, y) {
1084 var lambda = (lambda = y / sqrtPi - 1) * lambda;
1085 return [lambda > 0 ? x * sqrt(pi / lambda) / 2 : 0, asin(1 - lambda)];
1086};
1087
1088var kx = 2 / sqrt(3);
1089
1090function collignonK(a, b) {
1091 var p = collignonRaw(a, b);
1092 return [p[0] * kx, p[1]];
1093}
1094
1095collignonK.invert = function(x,y) {
1096 return collignonRaw.invert(x / kx, y);
1097};
1098
1099function collignon(faceProjection) {
1100
1101 faceProjection = faceProjection || function(face) {
1102 var c = d3Geo.geoCentroid({type: "MultiPoint", coordinates: face});
1103 return d3Geo.geoProjection(collignonK).translate([0, 0]).scale(1).rotate(c[1] > 0 ? [-c[0], 0] : [180 - c[0], 180]);
1104 };
1105
1106 var faces = octahedron$1.map(function(face) {
1107 return {face: face, project: faceProjection(face)};
1108 });
1109
1110 [-1, 0, 0, 1, 0, 1, 4, 5].forEach(function(d, i) {
1111 var node = faces[d];
1112 node && (node.children || (node.children = [])).push(faces[i]);
1113 });
1114
1115 return polyhedral(faces[0], function(lambda, phi) {
1116 return faces[lambda < -pi / 2 ? phi < 0 ? 6 : 4
1117 : lambda < 0 ? phi < 0 ? 2 : 0
1118 : lambda < pi / 2 ? phi < 0 ? 3 : 1
1119 : phi < 0 ? 7 : 5];
1120 })
1121 .angle(-30)
1122 .scale(121.906)
1123 .center([0, 48.5904]);
1124}
1125
1126function waterman(faceProjection) {
1127
1128 faceProjection = faceProjection || function(face) {
1129 var c = face.length === 6 ? d3Geo.geoCentroid({type: "MultiPoint", coordinates: face}) : face[0];
1130 return d3Geo.geoGnomonic().scale(1).translate([0, 0]).rotate([-c[0], -c[1]]);
1131 };
1132
1133 var w5 = octahedron$1.map(function(face) {
1134 var xyz = face.map(cartesian$1),
1135 n = xyz.length,
1136 a = xyz[n - 1],
1137 b,
1138 hexagon = [];
1139 for (var i = 0; i < n; ++i) {
1140 b = xyz[i];
1141 hexagon.push(spherical$1([
1142 a[0] * 0.9486832980505138 + b[0] * 0.31622776601683794,
1143 a[1] * 0.9486832980505138 + b[1] * 0.31622776601683794,
1144 a[2] * 0.9486832980505138 + b[2] * 0.31622776601683794
1145 ]), spherical$1([
1146 b[0] * 0.9486832980505138 + a[0] * 0.31622776601683794,
1147 b[1] * 0.9486832980505138 + a[1] * 0.31622776601683794,
1148 b[2] * 0.9486832980505138 + a[2] * 0.31622776601683794
1149 ]));
1150 a = b;
1151 }
1152 return hexagon;
1153 });
1154
1155 var cornerNormals = [];
1156
1157 var parents = [-1, 0, 0, 1, 0, 1, 4, 5];
1158
1159 w5.forEach(function(hexagon, j) {
1160 var face = octahedron$1[j],
1161 n = face.length,
1162 normals = cornerNormals[j] = [];
1163 for (var i = 0; i < n; ++i) {
1164 w5.push([
1165 face[i],
1166 hexagon[(i * 2 + 2) % (2 * n)],
1167 hexagon[(i * 2 + 1) % (2 * n)]
1168 ]);
1169 parents.push(j);
1170 normals.push(cross(
1171 cartesian$1(hexagon[(i * 2 + 2) % (2 * n)]),
1172 cartesian$1(hexagon[(i * 2 + 1) % (2 * n)])
1173 ));
1174 }
1175 });
1176
1177 var faces = w5.map(function(face) {
1178 return {
1179 project: faceProjection(face),
1180 face: face
1181 };
1182 });
1183
1184 parents.forEach(function(d, i) {
1185 var parent = faces[d];
1186 parent && (parent.children || (parent.children = [])).push(faces[i]);
1187 });
1188
1189 function face(lambda, phi) {
1190 var cosphi = cos(phi),
1191 p = [cosphi * cos(lambda), cosphi * sin(lambda), sin(phi)];
1192
1193 var hexagon = lambda < -pi / 2 ? phi < 0 ? 6 : 4
1194 : lambda < 0 ? phi < 0 ? 2 : 0
1195 : lambda < pi / 2 ? phi < 0 ? 3 : 1
1196 : phi < 0 ? 7 : 5;
1197
1198 var n = cornerNormals[hexagon];
1199
1200 return faces[dot(n[0], p) < 0 ? 8 + 3 * hexagon
1201 : dot(n[1], p) < 0 ? 8 + 3 * hexagon + 1
1202 : dot(n[2], p) < 0 ? 8 + 3 * hexagon + 2
1203 : hexagon];
1204 }
1205
1206 return polyhedral(faces[0], face)
1207 .angle(-30)
1208 .scale(110.625)
1209 .center([0, 45]);
1210}
1211
1212function dot(a, b) {
1213 for (var i = 0, n = a.length, s = 0; i < n; ++i) s += a[i] * b[i];
1214 return s;
1215}
1216
1217function cross(a, b) {
1218 return [
1219 a[1] * b[2] - a[2] * b[1],
1220 a[2] * b[0] - a[0] * b[2],
1221 a[0] * b[1] - a[1] * b[0]
1222 ];
1223}
1224
1225// Converts 3D Cartesian to spherical coordinates (degrees).
1226function spherical$1(cartesian) {
1227 return [
1228 atan2(cartesian[1], cartesian[0]) * degrees,
1229 asin(max(-1, min(1, cartesian[2]))) * degrees
1230 ];
1231}
1232
1233// Converts spherical coordinates (degrees) to 3D Cartesian.
1234function cartesian$1(coordinates) {
1235 var lambda = coordinates[0] * radians,
1236 phi = coordinates[1] * radians,
1237 cosphi = cos(phi);
1238 return [
1239 cosphi * cos(lambda),
1240 cosphi * sin(lambda),
1241 sin(phi)
1242 ];
1243}
1244
1245function voronoi(parents, polygons, faceProjection, find) {
1246 parents = parents || [];
1247 polygons = polygons || { features: [] };
1248 find = find || find0;
1249
1250 // it is possible to pass a specific projection on each face
1251 // by default is is a gnomonic projection centered on the face's centroid
1252 // scale 1 by convention
1253 faceProjection =
1254 faceProjection ||
1255 function(face) {
1256 return d3Geo.geoGnomonic()
1257 .scale(1)
1258 .translate([0, 0])
1259 .rotate([-face.site[0], -face.site[1]]);
1260 };
1261
1262 var faces = [];
1263 function build_tree() {
1264 // the faces from the polyhedron each yield
1265 // - face: its vertices
1266 // - site: its voronoi site (default: centroid)
1267 // - project: local projection on this face
1268 faces = polygons.features.map(function(feature, i) {
1269 var polygon = feature.geometry.coordinates[0];
1270 var face = polygon.slice(0, -1);
1271 face.site =
1272 feature.properties && feature.properties.sitecoordinates
1273 ? feature.properties.sitecoordinates
1274 : d3Geo.geoCentroid(feature.geometry);
1275 return {
1276 face: face,
1277 site: face.site,
1278 id: i,
1279 project: faceProjection(face)
1280 };
1281 });
1282
1283 // Build a tree of the faces, starting with face 0 (North Pole)
1284 // which has no parent (-1)
1285 parents.forEach(function(d, i) {
1286 var node = faces[d];
1287 node && (node.children || (node.children = [])).push(faces[i]);
1288 });
1289 }
1290
1291 // a basic function to find the polygon that contains the point
1292 function find0(lambda, phi) {
1293 var d0 = Infinity;
1294 var found = -1;
1295 for (var i = 0; i < faces.length; i++) {
1296 var d = d3Geo.geoDistance(faces[i].site, [lambda, phi]);
1297 if (d < d0) {
1298 d0 = d;
1299 found = i;
1300 }
1301 }
1302 return found;
1303 }
1304
1305 function faceFind(lambda, phi) {
1306 return faces[find(lambda * degrees, phi * degrees)];
1307 }
1308
1309 var p = d3Geo.geoGnomonic();
1310
1311 function reset() {
1312 var rotate = p.rotate(),
1313 translate = p.translate(),
1314 center = p.center(),
1315 scale = p.scale(),
1316 angle = p.angle();
1317
1318 if (faces.length) {
1319 p = polyhedral(faces[0], faceFind);
1320 }
1321
1322 p.parents = function(_) {
1323 if (!arguments.length) return parents;
1324 parents = _;
1325 build_tree();
1326 return reset();
1327 };
1328
1329 p.polygons = function(_) {
1330 if (!arguments.length) return polygons;
1331 polygons = _;
1332 build_tree();
1333 return reset();
1334 };
1335
1336 p.faceProjection = function(_) {
1337 if (!arguments.length) return faceProjection;
1338 faceProjection = _;
1339 build_tree();
1340 return reset();
1341 };
1342
1343 p.faceFind = function(_) {
1344 if (!arguments.length) return find;
1345 find = _;
1346 return reset();
1347 };
1348
1349 return p
1350 .rotate(rotate)
1351 .translate(translate)
1352 .center(center)
1353 .scale(scale)
1354 .angle(angle);
1355 }
1356
1357 build_tree();
1358 return reset();
1359}
1360
1361function dodecahedral() {
1362 var A0 = asin(1/sqrt(3)) * degrees,
1363 A1 = acos((sqrt(5) - 1) / sqrt(3) / 2) * degrees,
1364 A2 = 90 - A1,
1365 A3 = acos(-(1 + sqrt(5)) / sqrt(3) / 2) * degrees;
1366
1367 var dodecahedron = [
1368 [[45,A0],[0,A1],[180,A1],[135,A0],[90,A2]],
1369 [[45,A0],[A2,0],[-A2,0],[-45,A0],[0,A1]],
1370 [[45,A0],[90,A2],[90,-A2],[45,-A0],[A2,0]],
1371 [[0,A1],[-45,A0],[-90,A2],[-135,A0],[180,A1]],
1372 [[A2,0],[45,-A0],[0,-A1],[-45,-A0],[-A2,0]],
1373 [[90,A2],[135,A0],[A3,0],[135,-A0],[90,-A2]],
1374 [[45,-A0],[90,-A2],[135,-A0],[180,-A1],[0,-A1]],
1375 [[135,A0],[180,A1],[-135,A0],[-A3,0],[A3,0]],
1376 [[-45,A0],[-A2,0],[-45,-A0],[-90,-A2],[-90,A2]],
1377 [[-45,-A0],[0,-A1],[180,-A1],[-135,-A0],[-90,-A2]],
1378 [[135,-A0],[A3,0],[-A3,0],[-135,-A0],[180,-A1]],
1379 [[-135,A0],[-90,A2],[-90,-A2],[-135,-A0],[-A3,0]]
1380 ];
1381
1382
1383 var polygons = {
1384 type: "FeatureCollection",
1385 features: dodecahedron.map(function(face) {
1386 face.push(face[0]);
1387 return {
1388 geometry: {
1389 type: "Polygon",
1390 coordinates: [ face ]
1391 }
1392 };
1393 })
1394 };
1395
1396 return voronoi()
1397 .parents([-1,0,4,8,1,2,2,3,1,8,6,3])
1398 .angle(72 * 1.5)
1399 .polygons(polygons)
1400 .scale(99.8)
1401 .rotate([-8,0,-32]);
1402}
1403
1404// code duplicated from d3-geo-projection
1405
1406function lagrangeRaw(n) {
1407
1408 function forward(lambda, phi) {
1409 if (abs(abs(phi) - halfPi) < epsilon) return [0, phi < 0 ? -2 : 2];
1410 var sinPhi = sin(phi),
1411 v = pow((1 + sinPhi) / (1 - sinPhi), n / 2),
1412 c = 0.5 * (v + 1 / v) + cos(lambda *= n);
1413 return [
1414 2 * sin(lambda) / c,
1415 (v - 1 / v) / c
1416 ];
1417 }
1418
1419 forward.invert = function(x, y) {
1420 var y0 = abs(y);
1421 if (abs(y0 - 2) < epsilon) return x ? null : [0, sign(y) * halfPi];
1422 if (y0 > 2) return null;
1423
1424 x /= 2, y /= 2;
1425 var x2 = x * x,
1426 y2 = y * y,
1427 t = 2 * y / (1 + x2 + y2); // tanh(nPhi)
1428 t = pow((1 + t) / (1 - t), 1 / n);
1429 return [
1430 atan2(2 * x, 1 - x2 - y2) / n,
1431 asin((t - 1) / (t + 1))
1432 ];
1433 };
1434
1435 return forward;
1436}
1437
1438function complexMul(a, b) {
1439 return [a[0] * b[0] - a[1] * b[1], a[1] * b[0] + a[0] * b[1]];
1440}
1441
1442function complexAdd(a, b) {
1443 return [a[0] + b[0], a[1] + b[1]];
1444}
1445
1446function complexSub(a, b) {
1447 return [a[0] - b[0], a[1] - b[1]];
1448}
1449
1450function complexNorm2(a) {
1451 return a[0] * a[0] + a[1] * a[1];
1452}
1453
1454function complexNorm(a) {
1455 return sqrt(complexNorm2(a));
1456}
1457
1458function complexLogHypot(a, b) {
1459 var _a = abs(a),
1460 _b = abs(b);
1461 if (a === 0) return log(_b);
1462 if (b === 0) return log(_a);
1463 if (_a < 3000 && _b < 3000) return log(a * a + b * b) * 0.5;
1464 return log(a / cos(atan2(b, a)));
1465}
1466
1467// adapted from https://github.com/infusion/Complex.js
1468function complexPow(a, n) {
1469 var b = a[1],
1470 arg,
1471 loh;
1472 a = a[0];
1473 if (a === 0 && b === 0) return [0, 0];
1474
1475 if (typeof n === "number") n = [n, 0];
1476
1477 if (!n[1]) {
1478 if (b === 0 && a >= 0) {
1479 return [pow(a, n[0]), 0];
1480 } else if (a === 0) {
1481 switch ((n[1] % 4 + 4) % 4) {
1482 case 0:
1483 return [pow(b, n[0]), 0];
1484 case 1:
1485 return [0, pow(b, n[0])];
1486 case 2:
1487 return [-pow(b, n[0]), 0];
1488 case 3:
1489 return [0, -pow(b, n[0])];
1490 }
1491 }
1492 }
1493
1494 arg = atan2(b, a);
1495 loh = complexLogHypot(a, b);
1496 a = exp(n[0] * loh - n[1] * arg);
1497 b = n[1] * loh + n[0] * arg;
1498 return [a * cos(b), a * sin(b)];
1499}
1500
1501// w1 = gamma(1/n) * gamma(1 - 2/n) / n / gamma(1 - 1/n)
1502// https://bl.ocks.org/Fil/852557838117687bbd985e4b38ff77d4
1503var w = [-1 / 2, sqrt(3) / 2],
1504 w1 = [1.7666387502854533, 0],
1505 m = 0.3 * 0.3;
1506
1507// Approximate \int _0 ^sm(z) dt / (1 - t^3)^(2/3)
1508// sm maps a triangle to a disc, sm^-1 does the opposite
1509function sm_1(z) {
1510 var k = [0, 0];
1511
1512 // rotate to have s ~= 1
1513 var rot = complexPow(
1514 w,
1515 d3Array.scan(
1516 [0, 1, 2].map(function(i) {
1517 return -complexMul(z, complexPow(w, [i, 0]))[0];
1518 })
1519 )
1520 );
1521
1522 var y = complexMul(rot, z);
1523 y = [1 - y[0], -y[1]];
1524
1525 // McIlroy formula 5 p6 and table for F3 page 16
1526 var F0 = [
1527 1.44224957030741,
1528 0.240374928384568,
1529 0.0686785509670194,
1530 0.0178055502507087,
1531 0.00228276285265497,
1532 -1.48379585422573e-3,
1533 -1.64287728109203e-3,
1534 -1.02583417082273e-3,
1535 -4.83607537673571e-4,
1536 -1.67030822094781e-4,
1537 -2.45024395166263e-5,
1538 2.14092375450951e-5,
1539 2.55897270486771e-5,
1540 1.73086854400834e-5,
1541 8.72756299984649e-6,
1542 3.18304486798473e-6,
1543 4.79323894565283e-7,
1544 -4.58968389565456e-7,
1545 -5.62970586787826e-7,
1546 -3.92135372833465e-7
1547 ];
1548
1549 var F = [0, 0];
1550 for (var i = F0.length; i--; ) F = complexAdd([F0[i], 0], complexMul(F, y));
1551
1552 k = complexMul(
1553 complexAdd(w1, complexMul([-F[0], -F[1]], complexPow(y, 1 - 2 / 3))),
1554 complexMul(rot, rot)
1555 );
1556
1557 // when we are close to [0,0] we switch to another approximation:
1558 // https://www.wolframalpha.com/input/?i=(-2%2F3+choose+k)++*+(-1)%5Ek++%2F+(k%2B1)+with+k%3D0,1,2,3,4
1559 // the difference is _very_ tiny but necessary
1560 // if we want projection(0,0) === [0,0]
1561 var n = complexNorm2(z);
1562 if (n < m) {
1563 var H0 = [
1564 1,
1565 1 / 3,
1566 5 / 27,
1567 10 / 81,
1568 22 / 243 //…
1569 ];
1570 var z3 = complexPow(z, [3, 0]);
1571 var h = [0, 0];
1572 for (i = H0.length; i--; ) h = complexAdd([H0[i], 0], complexMul(h, z3));
1573 h = complexMul(h, z);
1574 k = complexAdd(complexMul(k, [n / m, 0]), complexMul(h, [1 - n / m, 0]));
1575 }
1576
1577 return k;
1578}
1579
1580var lagrange1_2 = lagrangeRaw ? lagrangeRaw(0.5) : null;
1581function coxRaw(lambda, phi) {
1582 var s = lagrange1_2(lambda, phi);
1583 var t = sm_1([s[1] / 2, s[0] / 2]);
1584 return [t[1], t[0]];
1585}
1586
1587// the Sphere should go *exactly* to the vertices of the triangles
1588// because they are singular points
1589function sphere() {
1590 var c = 2 * asin(1 / sqrt(5)) * degrees;
1591 return {
1592 type: "Polygon",
1593 coordinates: [
1594 [[0, 90], [-180, -c + epsilon], [0, -90], [180, -c + epsilon], [0, 90]]
1595 ]
1596 };
1597}
1598
1599function cox() {
1600 var p = d3Geo.geoProjection(coxRaw);
1601
1602 var stream_ = p.stream;
1603 p.stream = function(stream) {
1604 var rotate = p.rotate(),
1605 rotateStream = stream_(stream),
1606 sphereStream = (p.rotate([0, 0]), stream_(stream));
1607 p.rotate(rotate);
1608 rotateStream.sphere = function() {
1609 d3Geo.geoStream(sphere(), sphereStream);
1610 };
1611 return rotateStream;
1612 };
1613
1614 return p
1615 .scale(188.305)
1616 .translate([480, 333.167]);
1617}
1618
1619function leeRaw(lambda, phi) {
1620 // return d3.geoGnomonicRaw(...arguments);
1621 var w = [-1 / 2, sqrt(3) / 2],
1622 k = [0, 0],
1623 h = [0, 0],
1624 i,
1625 z = complexMul(d3Geo.geoStereographicRaw(lambda, phi), [sqrt(2), 0]);
1626
1627 // rotate to have s ~= 1
1628 var sector = d3Array.scan(
1629 [0, 1, 2].map(function(i) {
1630 return -complexMul(z, complexPow(w, [i, 0]))[0];
1631 })
1632 );
1633 var rot = complexPow(w, [sector, 0]);
1634
1635 var n = complexNorm(z);
1636
1637 if (n > 0.3) {
1638 // if |z| > 0.5, use the approx based on y = (1-z)
1639 // McIlroy formula 6 p6 and table for G page 16
1640 var y = complexSub([1, 0], complexMul(rot, z));
1641
1642 // w1 = gamma(1/3) * gamma(1/2) / 3 / gamma(5/6);
1643 // https://bl.ocks.org/Fil/1aeff1cfda7188e9fbf037d8e466c95c
1644 var w1 = 1.4021821053254548;
1645
1646 var G0 = [
1647 1.15470053837925,
1648 0.192450089729875,
1649 0.0481125224324687,
1650 0.010309826235529,
1651 3.34114739114366e-4,
1652 -1.50351632601465e-3,
1653 -1.2304417796231e-3,
1654 -6.75190201960282e-4,
1655 -2.84084537293856e-4,
1656 -8.21205120500051e-5,
1657 -1.59257630018706e-6,
1658 1.91691805888369e-5,
1659 1.73095888028726e-5,
1660 1.03865580818367e-5,
1661 4.70614523937179e-6,
1662 1.4413500104181e-6,
1663 1.92757960170179e-8,
1664 -3.82869799649063e-7,
1665 -3.57526015225576e-7,
1666 -2.2175964844211e-7
1667 ];
1668
1669 var G = [0, 0];
1670 for (i = G0.length; i--; ) {
1671 G = complexAdd([G0[i], 0], complexMul(G, y));
1672 }
1673
1674 k = complexSub([w1, 0], complexMul(complexPow(y, 1 / 2), G));
1675 k = complexMul(k, rot);
1676 k = complexMul(k, rot);
1677 }
1678
1679 if (n < 0.5) {
1680 // if |z| < 0.3
1681 // https://www.wolframalpha.com/input/?i=series+of+((1-z%5E3))+%5E+(-1%2F2)+at+z%3D0 (and ask for "more terms")
1682 // 1 + z^3/2 + (3 z^6)/8 + (5 z^9)/16 + (35 z^12)/128 + (63 z^15)/256 + (231 z^18)/1024 + O(z^21)
1683 // https://www.wolframalpha.com/input/?i=integral+of+1+%2B+z%5E3%2F2+%2B+(3+z%5E6)%2F8+%2B+(5+z%5E9)%2F16+%2B+(35+z%5E12)%2F128+%2B+(63+z%5E15)%2F256+%2B+(231+z%5E18)%2F1024
1684 // (231 z^19)/19456 + (63 z^16)/4096 + (35 z^13)/1664 + z^10/32 + (3 z^7)/56 + z^4/8 + z + constant
1685 var H0 = [1, 1 / 8, 3 / 56, 1 / 32, 35 / 1664, 63 / 4096, 231 / 19456];
1686 var z3 = complexPow(z, [3, 0]);
1687 for (i = H0.length; i--; ) {
1688 h = complexAdd([H0[i], 0], complexMul(h, z3));
1689 }
1690 h = complexMul(h, z);
1691 }
1692
1693 if (n < 0.3) return h;
1694 if (n > 0.5) return k;
1695
1696 // in between 0.3 and 0.5, interpolate
1697 var t = (n - 0.3) / (0.5 - 0.3);
1698 return complexAdd(complexMul(k, [t, 0]), complexMul(h, [1 - t, 0]));
1699}
1700
1701var asin1_3 = asin(1 / 3);
1702var centers = [
1703 [0, 90],
1704 [-180, -asin1_3 * degrees],
1705 [-60, -asin1_3 * degrees],
1706 [60, -asin1_3 * degrees]
1707];
1708var tetrahedron = [[1, 2, 3], [0, 2, 1], [0, 3, 2], [0, 1, 3]].map(function(
1709 face
1710) {
1711 return face.map(function(i) {
1712 return centers[i];
1713 });
1714});
1715
1716function tetrahedralLee() {
1717 var faceProjection = function(face) {
1718 var c = d3Geo.geoCentroid({ type: "MultiPoint", coordinates: face }),
1719 rotate = [-c[0], -c[1], 30];
1720 if (abs(c[1]) == 90) {
1721 rotate = [0, -c[1], -30];
1722 }
1723 return d3Geo.geoProjection(leeRaw)
1724 .scale(1)
1725 .translate([0, 0])
1726 .rotate(rotate);
1727 };
1728
1729 var faces = tetrahedron.map(function(face) {
1730 return { face: face, project: faceProjection(face) };
1731 });
1732
1733 [-1, 0, 0, 0].forEach(function(d, i) {
1734 var node = faces[d];
1735 node && (node.children || (node.children = [])).push(faces[i]);
1736 });
1737
1738 var p = polyhedral(
1739 faces[0],
1740 function(lambda, phi) {
1741 lambda *= degrees;
1742 phi *= degrees;
1743 for (var i = 0; i < faces.length; i++) {
1744 if (
1745 d3Geo.geoContains(
1746 {
1747 type: "Polygon",
1748 coordinates: [
1749 [
1750 tetrahedron[i][0],
1751 tetrahedron[i][1],
1752 tetrahedron[i][2],
1753 tetrahedron[i][0]
1754 ]
1755 ]
1756 },
1757 [lambda, phi]
1758 )
1759 ) {
1760 return faces[i];
1761 }
1762 }
1763 }
1764 );
1765
1766 return p
1767 .rotate([30, 180]) // North Pole aspect, needs clipPolygon
1768 // .rotate([-30, 0]) // South Pole aspect
1769 .angle(30)
1770 .scale(118.662)
1771 .translate([480, 195.47]);
1772}
1773
1774/*
1775 * Buckminster Fuller’s spherical triangle transformation procedure
1776 *
1777 * Based on Robert W. Gray’s formulae published in “Exact Transformation Equations
1778 * For Fuller's World Map,” _Cartographica_, 32(3): 17-25 (1995).
1779 *
1780 * Implemented for D3.js by Philippe Rivière, 2018 (https://visionscarto.net/)
1781 *
1782 * To the extent possible under law, Philippe Rivière has waived all copyright
1783 * and related or neighboring rights to this implementation. (Public Domain.)
1784 */
1785
1786function GrayFullerRaw() {
1787 var SQRT_3 = sqrt(3);
1788
1789 // Gray’s constants
1790 var Z = sqrt(5 + 2 * sqrt(5)) / sqrt(15),
1791 el = sqrt(8) / sqrt(5 + sqrt(5)),
1792 dve = sqrt(3 + sqrt(5)) / sqrt(5 + sqrt(5));
1793
1794 var grayfuller = function(lambda, phi) {
1795 var cosPhi = cos(phi),
1796 s = Z / (cosPhi * cos(lambda)),
1797 x = cosPhi * sin(lambda) * s,
1798 y = sin(phi) * s,
1799 a1p = atan2(2 * y / SQRT_3 + el / 3 - el / 2, dve),
1800 a2p = atan2(x - y / SQRT_3 + el / 3 - el / 2, dve),
1801 a3p = atan2(el / 3 - x - y / SQRT_3 - el / 2, dve);
1802
1803 return [SQRT_3 * (a2p - a3p), 2 * a1p - a2p - a3p];
1804 };
1805
1806 // Inverse approximation
1807 grayfuller.invert = function(x, y) {
1808 // if the point is out of the triangle, return
1809 // something meaningless (but far away enough)
1810 if (x * x + y * y > 5) return [0, 3];
1811
1812 var R = 2.9309936378128416,
1813 p = d3Geo.geoGnomonicRaw.invert(x / R, y / R);
1814
1815 var j = 0;
1816 do {
1817 var f = grayfuller(p[0], p[1]),
1818 dx = x - f[0],
1819 dy = y - f[1];
1820 p[0] += 0.2 * dx;
1821 p[1] += 0.2 * dy;
1822 } while (j++ < 30 && abs(dx) + abs(dy) > epsilon);
1823
1824 return p;
1825 };
1826
1827 return grayfuller;
1828}
1829
1830/*
1831 * Buckminster Fuller’s AirOcean arrangement of the icosahedron
1832 *
1833 * Implemented for D3.js by Jason Davies (2013),
1834 * Enrico Spinielli (2017) and Philippe Rivière (2017, 2018)
1835 *
1836 */
1837
1838function airoceanRaw(faceProjection) {
1839 var theta = atan(0.5) * degrees;
1840
1841 // construction inspired by
1842 // https://en.wikipedia.org/wiki/Regular_icosahedron#Spherical_coordinates
1843 var vertices = [[0, 90], [0, -90]].concat(
1844 d3Array.range(10).map(function(i) {
1845 var phi = (i * 36 + 180) % 360 - 180;
1846 return [phi, i & 1 ? theta : -theta];
1847 })
1848 );
1849
1850 // icosahedron
1851 var polyhedron = [
1852 [0, 3, 11],
1853 [0, 5, 3],
1854 [0, 7, 5],
1855 [0, 9, 7],
1856 [0, 11, 9], // North
1857 [2, 11, 3],
1858 [3, 4, 2],
1859 [4, 3, 5],
1860 [5, 6, 4],
1861 [6, 5, 7],
1862 [7, 8, 6],
1863 [8, 7, 9],
1864 [9, 10, 8],
1865 [10, 9, 11],
1866 [11, 2, 10], // Equator
1867 [1, 2, 4],
1868 [1, 4, 6],
1869 [1, 6, 8],
1870 [1, 8, 10],
1871 [1, 10, 2] // South
1872 ].map(function(face) {
1873 return face.map(function(i) {
1874 return vertices[i];
1875 });
1876 });
1877
1878 // add centroid
1879 polyhedron.forEach(function(face) {
1880 face.centroid = d3Geo.geoCentroid({ type: "MultiPoint", coordinates: face });
1881 });
1882
1883 // split the relevant faces:
1884 // * face[15] in the centroid: this will become face[15], face[20] and face[21]
1885 // * face[14] in the middle of the side: this will become face[14] and face[22]
1886 (function() {
1887 var face, tmp, mid, centroid;
1888
1889 // Split face[15] in 3 faces at centroid.
1890 face = polyhedron[15];
1891 centroid = face.centroid;
1892 tmp = face.slice();
1893 face[0] = centroid; // (new) face[15]
1894
1895 face = [tmp[0], centroid, tmp[2]];
1896 face.centroid = centroid;
1897 polyhedron.push(face); // face[20]
1898
1899 face = [tmp[0], tmp[1], centroid];
1900 face.centroid = centroid;
1901 polyhedron.push(face); // face[21]
1902
1903 // Split face 14 at the edge.
1904 face = polyhedron[14];
1905 centroid = face.centroid;
1906 tmp = face.slice();
1907
1908 // compute planar midpoint
1909 var proj = d3Geo.geoGnomonic()
1910 .scale(1)
1911 .translate([0, 0])
1912 .rotate([-centroid[0], -centroid[1]]);
1913 var a = proj(face[1]),
1914 b = proj(face[2]);
1915 mid = proj.invert([(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]);
1916 face[1] = mid; // (new) face[14]
1917
1918 // build the new half face
1919 face = [tmp[0], tmp[1], mid];
1920 face.centroid = centroid; // use original face[14] centroid
1921 polyhedron.push(face); // face[22]
1922
1923 // cut face 19 to connect to 22
1924 face = polyhedron[19];
1925 centroid = face.centroid;
1926 tmp = face.slice();
1927 face[1] = mid;
1928
1929 // build the new half face
1930 face = [mid, tmp[0], tmp[1]];
1931 face.centroid = centroid;
1932 polyhedron.push(face); // face[23]
1933 })();
1934
1935 var airocean = function(faceProjection) {
1936 faceProjection =
1937 faceProjection ||
1938 function(face) {
1939 // for half-triangles this is definitely not centroid({type: "MultiPoint", coordinates: face});
1940 var c = face.centroid;
1941 return d3Geo.geoGnomonic()
1942 .scale(1)
1943 .translate([0, 0])
1944 .rotate([-c[0], -c[1]]);
1945 };
1946
1947 var faces = polyhedron.map(function(face, i) {
1948 var polygon = face.slice();
1949 polygon.push(polygon[0]);
1950
1951 return {
1952 face: face,
1953 site: face.centroid,
1954 id: i,
1955 contains: function(lambda, phi) {
1956 return d3Geo.geoContains({ type: "Polygon", coordinates: [polygon] }, [
1957 lambda * degrees,
1958 phi * degrees
1959 ]);
1960 },
1961 project: faceProjection(face)
1962 };
1963 });
1964
1965 // Connect each face to a parent face.
1966 var parents = [
1967 // N
1968 -1, // 0
1969 0, // 1
1970 1, // 2
1971 11, // 3
1972 13, // 4
1973
1974 // Eq
1975 6, // 5
1976 7, // 6
1977 1, // 7
1978 7, // 8
1979 8, // 9
1980
1981 9, // 10
1982 10, // 11
1983 11, // 12
1984 12, // 13
1985 13, // 14
1986
1987 // S
1988 6, // 15
1989 8, // 16
1990 10, // 17
1991 17, // 18
1992 21, // 19
1993 16, // 20
1994 15, // 21
1995 19, // 22
1996 19 // 23
1997 ];
1998
1999 parents.forEach(function(d, i) {
2000 var node = faces[d];
2001 node && (node.children || (node.children = [])).push(faces[i]);
2002 });
2003
2004 function face(lambda, phi) {
2005 for (var i = 0; i < faces.length; i++) {
2006 if (faces[i].contains(lambda, phi)) return faces[i];
2007 }
2008 }
2009
2010 // Polyhedral projection
2011 var proj = polyhedral(
2012 faces[0], // the root face
2013 face // a function that returns a face given coords
2014 );
2015
2016 proj.faces = faces;
2017 return proj;
2018 };
2019
2020 return airocean(faceProjection);
2021}
2022
2023function airocean () {
2024 var p = airoceanRaw(function(face) {
2025 var c = face.centroid;
2026
2027 face.direction =
2028 Math.abs(c[1] - 52.62) < 1 || Math.abs(c[1] + 10.81) < 1 ? 0 : 60;
2029 return d3Geo.geoProjection(GrayFullerRaw())
2030 .scale(1)
2031 .translate([0, 0])
2032 .rotate([-c[0], -c[1], face.direction || 0]);
2033 });
2034
2035 return p
2036 .rotate([-83.65929, 25.44458, -87.45184])
2037 .angle(-60)
2038 .scale(45.4631)
2039 .center([126, 0]);
2040}
2041
2042/*
2043 * Icosahedral map
2044 *
2045 * Implemented for D3.js by Jason Davies (2013),
2046 * Enrico Spinielli (2017) and Philippe Rivière (2017, 2018)
2047 *
2048 */
2049
2050
2051function icosahedral() {
2052 var theta = atan(0.5) * degrees;
2053
2054 // construction inspired by
2055 // https://en.wikipedia.org/wiki/Regular_icosahedron#Spherical_coordinates
2056 var vertices = [[0, 90], [0, -90]].concat(
2057 [0,1,2,3,4,5,6,7,8,9].map(function(i) {
2058 var phi = (i * 36 + 180) % 360 - 180;
2059 return [phi, i & 1 ? theta : -theta];
2060 })
2061 );
2062
2063 // icosahedron
2064 var polyhedron = [
2065 [0, 3, 11],
2066 [0, 5, 3],
2067 [0, 7, 5],
2068 [0, 9, 7],
2069 [0, 11, 9], // North
2070 [2, 11, 3],
2071 [3, 4, 2],
2072 [4, 3, 5],
2073 [5, 6, 4],
2074 [6, 5, 7],
2075 [7, 8, 6],
2076 [8, 7, 9],
2077 [9, 10, 8],
2078 [10, 9, 11],
2079 [11, 2, 10], // Equator
2080 [1, 2, 4],
2081 [1, 4, 6],
2082 [1, 6, 8],
2083 [1, 8, 10],
2084 [1, 10, 2] // South
2085 ].map(function(face) {
2086 return face.map(function(i) {
2087 return vertices[i];
2088 });
2089 });
2090
2091 var polygons = {
2092 type: "FeatureCollection",
2093 features: polyhedron.map(function(face) {
2094 face.push(face[0]);
2095 return {
2096 geometry: {
2097 type: "Polygon",
2098 coordinates: [ face ]
2099 }
2100 };
2101 })
2102 };
2103
2104var parents = [
2105 // N
2106 -1, // 0
2107 7, // 1
2108 9, // 2
2109 11, // 3
2110 13, // 4
2111
2112 // Eq
2113 0, // 5
2114 5, // 6
2115 6, // 7
2116 7, // 8
2117 8, // 9
2118
2119 9, // 10
2120 10, // 11
2121 11, // 12
2122 12, // 13
2123 13, // 14
2124
2125 // S
2126 6, // 15
2127 8, // 16
2128 10, // 17
2129 12, // 18
2130 14, // 19
2131 ];
2132
2133 return voronoi()
2134 .parents(parents)
2135 .angle(0)
2136 .polygons(polygons)
2137 .rotate([108,0])
2138 .scale(131.777)
2139 .center([162, 0]);
2140 }
2141
2142
2143/*
2144 // Jarke J. van Wijk, "Unfolding the Earth: Myriahedral Projections",
2145 // The Cartographic Journal Vol. 45 No. 1 pp. 32–42 February 2008, fig. 8
2146 // https://bl.ocks.org/espinielli/475f5fde42a5513ab7eba3f53033ea9e
2147 d3.geoIcosahedral().parents([-1,0,1,11,3,0,7,1,7,8,9,10,11,12,13,6,8,10,19,15])
2148 .angle(-60)
2149 .rotate([-83.65929, 25.44458, -87.45184])
2150*/
2151
2152// Newton-Raphson
2153// Solve f(x) = y, start from x
2154function solve(f, y, x) {
2155 var steps = 100, delta, f0, f1;
2156 x = x === undefined ? 0 : +x;
2157 y = +y;
2158 do {
2159 f0 = f(x);
2160 f1 = f(x + epsilon);
2161 if (f0 === f1) f1 = f0 + epsilon;
2162 x -= delta = (-1 * epsilon * (f0 - y)) / (f0 - f1);
2163 } while (steps-- > 0 && abs(delta) > epsilon);
2164 return steps < 0 ? NaN : x;
2165}
2166
2167/*
2168 * Imago projection, by Justin Kunimune
2169 *
2170 * Inspired by Hajime Narukawa’s AuthaGraph
2171 *
2172 */
2173
2174var hypot = Math.hypot;
2175
2176var ASIN_ONE_THD = asin(1 / 3),
2177 centrums = [
2178 [halfPi, 0, 0, -halfPi, 0, sqrt(3)],
2179 [-ASIN_ONE_THD, 0, pi, halfPi, 0, -sqrt(3)],
2180 [-ASIN_ONE_THD, (2 * pi) / 3, pi, (5 * pi) / 6, 3, 0],
2181 [-ASIN_ONE_THD, (-2 * pi) / 3, pi, pi / 6, -3, 0]
2182 ],
2183 TETRAHEDRON_WIDE_VERTEX = {
2184 sphereSym: 3,
2185 planarSym: 6,
2186 width: 6,
2187 height: 2 * sqrt(3),
2188 centrums,
2189 rotateOOB: function(x, y, xCen, yCen) {
2190 if (abs(x) > this.width / 2) return [2 * xCen - x, -y];
2191 else return [-x, this.height * sign(y) - y];
2192 },
2193 inBounds: () => true
2194 },
2195 configuration = TETRAHEDRON_WIDE_VERTEX;
2196
2197function imagoRaw(k) {
2198 function faceProject(lon, lat) {
2199 const tht = atan(((lon - asin(sin(lon) / sqrt(3))) / pi) * sqrt(12)),
2200 p = (halfPi - lat) / atan(sqrt(2) / cos(lon));
2201
2202 return [(pow(p, k) * sqrt(3)) / cos(tht), tht];
2203 }
2204
2205 function faceInverse(r, th) {
2206 const l = solve(
2207 l => atan(((l - asin(sin(l) / sqrt(3))) / pi) * sqrt(12)),
2208 th,
2209 th / 2
2210 ),
2211 R = r / (sqrt(3) / cos(th));
2212 return [halfPi - pow(R, 1 / k) * atan(sqrt(2) / cos(l)), l];
2213 }
2214
2215 function obliquifySphc(latF, lonF, pole) {
2216 if (pole == null)
2217 // null pole indicates that this procedure should be bypassed
2218 return [latF, lonF];
2219
2220 const lat0 = pole[0],
2221 lon0 = pole[1],
2222 tht0 = pole[2];
2223
2224 let lat1, lon1;
2225 if (lat0 == halfPi) lat1 = latF;
2226 else
2227 lat1 = asin(
2228 sin(lat0) * sin(latF) + cos(lat0) * cos(latF) * cos(lon0 - lonF)
2229 ); // relative latitude
2230
2231 if (lat0 == halfPi)
2232 // accounts for all the 0/0 errors at the poles
2233 lon1 = lonF - lon0;
2234 else if (lat0 == -halfPi) lon1 = lon0 - lonF - pi;
2235 else {
2236 lon1 =
2237 acos(
2238 (cos(lat0) * sin(latF) - sin(lat0) * cos(latF) * cos(lon0 - lonF)) /
2239 cos(lat1)
2240 ) - pi; // relative longitude
2241 if (isNaN(lon1)) {
2242 if (
2243 (cos(lon0 - lonF) >= 0 && latF < lat0) ||
2244 (cos(lon0 - lonF) < 0 && latF < -lat0)
2245 )
2246 lon1 = 0;
2247 else lon1 = -pi;
2248 } else if (sin(lonF - lon0) > 0)
2249 // it's a plus-or-minus arccos.
2250 lon1 = -lon1;
2251 }
2252 lon1 = lon1 - tht0;
2253
2254 return [lat1, lon1];
2255 }
2256
2257 function obliquifyPlnr(coords, pole) {
2258 if (pole == null)
2259 //this indicates that you just shouldn't do this calculation
2260 return coords;
2261
2262 let lat1 = coords[0],
2263 lon1 = coords[1];
2264 const lat0 = pole[0],
2265 lon0 = pole[1],
2266 tht0 = pole[2];
2267
2268 lon1 += tht0;
2269 let latf = asin(sin(lat0) * sin(lat1) - cos(lat0) * cos(lon1) * cos(lat1)),
2270 lonf,
2271 innerFunc = sin(lat1) / cos(lat0) / cos(latf) - tan(lat0) * tan(latf);
2272 if (lat0 == halfPi)
2273 // accounts for special case when lat0 = pi/2
2274 lonf = lon1 + lon0;
2275 else if (lat0 == -halfPi)
2276 // accounts for special case when lat0 = -pi/2
2277 lonf = -lon1 + lon0 + pi;
2278 else if (abs(innerFunc) > 1) {
2279 // accounts for special case when cos(lat1) -> 0
2280 if ((lon1 == 0 && lat1 < -lat0) || (lon1 != 0 && lat1 < lat0))
2281 lonf = lon0 + pi;
2282 else lonf = lon0;
2283 } else if (sin(lon1) > 0) lonf = lon0 + acos(innerFunc);
2284 else lonf = lon0 - acos(innerFunc);
2285
2286 let thtf = pole[2];
2287
2288 return [latf, lonf, thtf];
2289 }
2290
2291 function forward(lon, lat) {
2292 const width = configuration.width,
2293 height = configuration.height;
2294 const numSym = configuration.sphereSym; //we're about to be using this variable a lot
2295 let latR = -Infinity;
2296 let lonR = -Infinity;
2297 let centrum = null;
2298 for (const testCentrum of centrums) {
2299 //iterate through the centrums to see which goes here
2300 const relCoords = obliquifySphc(lat, lon, testCentrum);
2301 if (relCoords[0] > latR) {
2302 latR = relCoords[0];
2303 lonR = relCoords[1];
2304 centrum = testCentrum;
2305 }
2306 }
2307
2308 const lonR0 =
2309 floor((lonR + pi / numSym) / ((2 * pi) / numSym)) * ((2 * pi) / numSym);
2310
2311 const rth = faceProject(lonR - lonR0, latR);
2312 const r = rth[0];
2313 const th = rth[1] + centrum[3] + (lonR0 * numSym) / configuration.planarSym;
2314 const x0 = centrum[4];
2315 const y0 = centrum[5];
2316
2317 let output = [r * cos(th) + x0, r * sin(th) + y0];
2318 if (abs(output[0]) > width / 2 || abs(output[1]) > height / 2) {
2319 output = configuration.rotateOOB(output[0], output[1], x0, y0);
2320 }
2321 return output;
2322 }
2323
2324 function invert(x, y) {
2325 if (isNaN(x) || isNaN(y)) return null;
2326
2327 const numSym = configuration.planarSym;
2328
2329 let rM = +Infinity;
2330 let centrum = null; //iterate to see which centrum we get
2331 for (const testCentrum of centrums) {
2332 const rR = hypot(x - testCentrum[4], y - testCentrum[5]);
2333 if (rR < rM) {
2334 //pick the centrum that minimises r
2335 rM = rR;
2336 centrum = testCentrum;
2337 }
2338 }
2339 const th0 = centrum[3],
2340 x0 = centrum[4],
2341 y0 = centrum[5],
2342 r = hypot(x - x0, y - y0),
2343 th = atan2(y - y0, x - x0) - th0,
2344 thBase =
2345 floor((th + pi / numSym) / ((2 * pi) / numSym)) * ((2 * pi) / numSym);
2346
2347 let relCoords = faceInverse(r, th - thBase);
2348
2349 if (relCoords == null) return null;
2350
2351 relCoords[1] = (thBase * numSym) / configuration.sphereSym + relCoords[1];
2352 let absCoords = obliquifyPlnr(relCoords, centrum);
2353 return [absCoords[1], absCoords[0]];
2354 }
2355
2356 forward.invert = invert;
2357
2358 return forward;
2359}
2360
2361function imagoBlock() {
2362 var k = 0.68,
2363 m = d3Geo.geoProjectionMutator(imagoRaw),
2364 p = m(k);
2365
2366 p.k = function(_) {
2367 return arguments.length ? m((k = +_)) : k;
2368 };
2369
2370 var a = -atan(1 / sqrt(2)) * degrees,
2371 border = [
2372 [-180 + epsilon, a + epsilon],
2373 [0, 90],
2374 [180 - epsilon, a + epsilon],
2375 [180 - epsilon, a - epsilon],
2376 [-180 + epsilon, a - epsilon],
2377 [-180 + epsilon, a + epsilon]
2378 ];
2379
2380 return p
2381 .preclip(clipPolygon({
2382 type: "Polygon",
2383 coordinates: [border]
2384 }))
2385 .scale(144.04)
2386 .rotate([18, -12.5, 3.5])
2387 .center([0, 35.2644]);
2388}
2389
2390function imagoWideRaw(k, shift) {
2391 var imago = imagoRaw(k);
2392 const height = configuration.height;
2393
2394 function forward(lon, lat) {
2395 const p = imago(lon, lat),
2396 q = [p[1], -p[0]];
2397
2398 if (q[1] > 0) {
2399 q[0] = height - q[0];
2400 q[1] *= -1;
2401 }
2402
2403 q[0] += shift;
2404 if (q[0] < 0) q[0] += height * 2;
2405
2406 return q;
2407 }
2408
2409 function invert(x, y) {
2410 x = (x - shift) / height;
2411
2412 if (x > 1.5) {
2413 x -= 2;
2414 }
2415
2416 if (x > 0.5) {
2417 x = 1 - x;
2418 y *= -1;
2419 }
2420
2421 return imago.invert(-y, x * height);
2422 }
2423
2424 forward.invert = invert;
2425 return forward;
2426}
2427
2428function imago() {
2429 var k = 0.59,
2430 shift = 1.16,
2431 m = d3Geo.geoProjectionMutator(imagoWideRaw),
2432 p = m(k, shift);
2433
2434 p.shift = function(_) {
2435 return arguments.length ? clipped(m(k, (shift = +_))) : shift;
2436 };
2437 p.k = function(_) {
2438 return arguments.length ? clipped(m((k = +_), shift)) : k;
2439 };
2440
2441 function clipped(p) {
2442 const N = 100 + 2 * epsilon,
2443 border = [],
2444 e = 3e-3;
2445
2446 const scale = p.scale(),
2447 center = p.center(),
2448 translate = p.translate(),
2449 rotate = p.rotate();
2450 p.scale(1)
2451 .center([0, 90])
2452 .rotate([0, 0])
2453 .translate([shift, 0]);
2454 for (let i = N - epsilon; i > 0; i--) {
2455 border.unshift(
2456 p.invert([
2457 1.5 * configuration.height - e,
2458 ((configuration.width / 2) * i) / N
2459 ])
2460 );
2461 border.push(
2462 p.invert([
2463 -0.5 * configuration.height + e,
2464 ((configuration.width / 2) * i) / N
2465 ])
2466 );
2467 }
2468 border.push(border[0]);
2469
2470 return p
2471 .scale(scale)
2472 .center(center)
2473 .translate(translate)
2474 .rotate(rotate)
2475 .preclip(
2476 clipPolygon({
2477 type: "Polygon",
2478 coordinates: [border]
2479 })
2480 );
2481 }
2482
2483 return clipped(p)
2484 .rotate([18, -12.5, 3.5])
2485 .scale(138.42)
2486 .translate([480, 250])
2487 .center([-139.405, 40.5844]);
2488}
2489
2490var phi1 = atan(sqrt1_2) * degrees;
2491
2492var cube = [
2493 [0, phi1], [90, phi1], [180, phi1], [-90, phi1],
2494 [0, -phi1], [90, -phi1], [180, -phi1], [-90, -phi1]
2495];
2496
2497var cube$1 = [
2498 [0, 3, 2, 1], // N
2499 [0, 1, 5, 4],
2500 [1, 2, 6, 5],
2501 [2, 3, 7, 6],
2502 [3, 0, 4, 7],
2503 [4, 5, 6, 7] // S
2504].map(function(face) {
2505 return face.map(function(i) {
2506 return cube[i];
2507 });
2508});
2509
2510/*
2511 * Cubic map
2512 *
2513 * Implemented for D3.js by Enrico Spinielli (2017) and Philippe Rivière (2017, 2018)
2514 *
2515 */
2516
2517function cubic() {
2518 var polygons = {
2519 type: "FeatureCollection",
2520 features: cube$1.map(function(face) {
2521 face = face.slice();
2522 face.push(face[0]);
2523 return {
2524 geometry: {
2525 type: "Polygon",
2526 coordinates: [face]
2527 }
2528 };
2529 })
2530 };
2531
2532 var parents = [-1, 0, 1, 5, 3, 2];
2533
2534 return voronoi()
2535 .polygons(polygons)
2536 .parents(parents)
2537 .angle(0)
2538 .scale(96.8737)
2539 .center([135, -45])
2540 .rotate([120,0]);
2541}
2542
2543/*
2544 * Cahill-Keyes projection
2545 *
2546 * Implemented in Perl by Mary Jo Graça (2011)
2547 *
2548 * Ported to D3.js by Enrico Spinielli (2013)
2549 *
2550 */
2551
2552function cahillKeyes(faceProjection) {
2553 faceProjection =
2554 faceProjection ||
2555 function() {
2556 return cahillKeyesProjection().scale(1);
2557 };
2558
2559 var octahedron = [[0, 90], [-90, 0], [0, 0], [90, 0], [180, 0], [0, -90]];
2560
2561 octahedron = [
2562 [0, 2, 1],
2563 [0, 3, 2],
2564 [5, 1, 2],
2565 [5, 2, 3],
2566 [0, 1, 4],
2567 [0, 4, 3],
2568 [5, 4, 1],
2569 [5, 3, 4]
2570 ].map(function(face) {
2571 return face.map(function(i) {
2572 return octahedron[i];
2573 });
2574 });
2575
2576 var ck = octahedron.map(function(face) {
2577 var xyz = face.map(cartesianDegrees),
2578 n = xyz.length,
2579 a = xyz[n - 1],
2580 b,
2581 theta = 17 * radians,
2582 cosTheta = cos(theta),
2583 sinTheta = sin(theta),
2584 hexagon = [];
2585 for (var i = 0; i < n; ++i) {
2586 b = xyz[i];
2587 hexagon.push(
2588 sphericalDegrees([
2589 a[0] * cosTheta + b[0] * sinTheta,
2590 a[1] * cosTheta + b[1] * sinTheta,
2591 a[2] * cosTheta + b[2] * sinTheta
2592 ]),
2593 sphericalDegrees([
2594 b[0] * cosTheta + a[0] * sinTheta,
2595 b[1] * cosTheta + a[1] * sinTheta,
2596 b[2] * cosTheta + a[2] * sinTheta
2597 ])
2598 );
2599 a = b;
2600 }
2601 return hexagon;
2602 });
2603
2604 var cornerNormals = [];
2605
2606 var parents = [-1, 3, 0, 2, 0, 1, 4, 5];
2607
2608 ck.forEach(function(hexagon, j) {
2609 var face = octahedron[j],
2610 n = face.length,
2611 normals = (cornerNormals[j] = []);
2612 for (var i = 0; i < n; ++i) {
2613 ck.push([
2614 face[i],
2615 hexagon[(i * 2 + 2) % (2 * n)],
2616 hexagon[(i * 2 + 1) % (2 * n)]
2617 ]);
2618 parents.push(j);
2619 normals.push(
2620 cartesianCross(
2621 cartesianDegrees(hexagon[(i * 2 + 2) % (2 * n)]),
2622 cartesianDegrees(hexagon[(i * 2 + 1) % (2 * n)])
2623 )
2624 );
2625 }
2626 });
2627
2628 var faces = ck.map(function(face) {
2629 return {
2630 project: faceProjection(face),
2631 face: face
2632 };
2633 });
2634
2635 parents.forEach(function(d, i) {
2636 var parent = faces[d];
2637 parent && (parent.children || (parent.children = [])).push(faces[i]);
2638 });
2639 return polyhedral(faces[0], face, 0, true)
2640 .scale(0.023975)
2641 .rotate([20, 0])
2642 .center([0,-17]);
2643
2644 function face(lambda, phi) {
2645 var cosPhi = cos(phi),
2646 p = [cosPhi * cos(lambda), cosPhi * sin(lambda), sin(phi)];
2647
2648 var hexagon =
2649 lambda < -pi / 2
2650 ? phi < 0 ? 6 : 4
2651 : lambda < 0
2652 ? phi < 0 ? 2 : 0
2653 : lambda < pi / 2 ? (phi < 0 ? 3 : 1) : phi < 0 ? 7 : 5;
2654
2655 var n = cornerNormals[hexagon];
2656
2657 return faces[
2658 cartesianDot(n[0], p) < 0
2659 ? 8 + 3 * hexagon
2660 : cartesianDot(n[1], p) < 0
2661 ? 8 + 3 * hexagon + 1
2662 : cartesianDot(n[2], p) < 0 ? 8 + 3 * hexagon + 2 : hexagon
2663 ];
2664 }
2665}
2666
2667// all names of reference points, A, B, D, ... , G, P75
2668// or zones, A-L, are detailed fully in Gene Keyes'
2669// web site http://www.genekeyes.com/CKOG-OOo/7-CKOG-illus-&-coastline.html
2670
2671function cahillKeyesRaw(mg) {
2672 var CK = {
2673 lengthMG: mg // magic scaling length
2674 };
2675
2676 preliminaries();
2677
2678 function preliminaries() {
2679 var pointN, lengthMB, lengthMN, lengthNG, pointU;
2680 var m = 29, // meridian
2681 p = 15, // parallel
2682 p73a,
2683 lF,
2684 lT,
2685 lM,
2686 l,
2687 pointV,
2688 k = sqrt(3);
2689
2690 CK.lengthMA = 940 / 10000 * CK.lengthMG;
2691 CK.lengthParallel0to73At0 = CK.lengthMG / 100;
2692 CK.lengthParallel73to90At0 =
2693 (CK.lengthMG - CK.lengthMA - CK.lengthParallel0to73At0 * 73) / (90 - 73);
2694 CK.sin60 = k / 2; // √3/2
2695 CK.cos60 = 0.5;
2696 CK.pointM = [0, 0];
2697 CK.pointG = [CK.lengthMG, 0];
2698 pointN = [CK.lengthMG, CK.lengthMG * tan(30 * radians)];
2699 CK.pointA = [CK.lengthMA, 0];
2700 CK.pointB = lineIntersection(CK.pointM, 30, CK.pointA, 45);
2701 CK.lengthAG = distance(CK.pointA, CK.pointG);
2702 CK.lengthAB = distance(CK.pointA, CK.pointB);
2703 lengthMB = distance(CK.pointM, CK.pointB);
2704 lengthMN = distance(CK.pointM, pointN);
2705 lengthNG = distance(pointN, CK.pointG);
2706 CK.pointD = interpolate(lengthMB, lengthMN, pointN, CK.pointM);
2707 CK.pointF = [CK.lengthMG, lengthNG - lengthMB];
2708 CK.pointE = [
2709 pointN[0] - CK.lengthMA * sin(30 * radians),
2710 pointN[1] - CK.lengthMA * cos(30 * radians)
2711 ];
2712 CK.lengthGF = distance(CK.pointG, CK.pointF);
2713 CK.lengthBD = distance(CK.pointB, CK.pointD);
2714 CK.lengthBDE = CK.lengthBD + CK.lengthAB; // lengthAB = lengthDE
2715 CK.lengthGFE = CK.lengthGF + CK.lengthAB; // lengthAB = lengthFE
2716 CK.deltaMEq = CK.lengthGFE / 45;
2717 CK.lengthAP75 = (90 - 75) * CK.lengthParallel73to90At0;
2718 CK.lengthAP73 = CK.lengthMG - CK.lengthMA - CK.lengthParallel0to73At0 * 73;
2719 pointU = [
2720 CK.pointA[0] + CK.lengthAP73 * cos(30 * radians),
2721 CK.pointA[1] + CK.lengthAP73 * sin(30 * radians)
2722 ];
2723 CK.pointT = lineIntersection(pointU, -60, CK.pointB, 30);
2724
2725 p73a = parallel73(m);
2726 lF = p73a.lengthParallel73;
2727 lT = lengthTorridSegment(m);
2728 lM = lengthMiddleSegment(m);
2729 l = p * (lT + lM + lF) / 73;
2730 pointV = [0, 0];
2731 CK.pointC = [0, 0];
2732 CK.radius = 0;
2733
2734 l = l - lT;
2735 pointV = interpolate(l, lM, jointT(m), jointF(m));
2736 CK.pointC[1] =
2737 (pointV[0] * pointV[0] +
2738 pointV[1] * pointV[1] -
2739 CK.pointD[0] * CK.pointD[0] -
2740 CK.pointD[1] * CK.pointD[1]) /
2741 (2 * (k * pointV[0] + pointV[1] - k * CK.pointD[0] - CK.pointD[1]));
2742 CK.pointC[0] = k * CK.pointC[1];
2743 CK.radius = distance(CK.pointC, CK.pointD);
2744
2745 return CK;
2746 }
2747
2748 //**** helper functions ****//
2749
2750 // distance between two 2D coordinates
2751
2752 function distance(p1, p2) {
2753 var deltaX = p1[0] - p2[0],
2754 deltaY = p1[1] - p2[1];
2755 return sqrt(deltaX * deltaX + deltaY * deltaY);
2756 }
2757
2758 // return 2D point at position length/totallength of the line
2759 // defined by two 2D points, start and end.
2760
2761 function interpolate(length, totalLength, start, end) {
2762 var xy = [
2763 start[0] + (end[0] - start[0]) * length / totalLength,
2764 start[1] + (end[1] - start[1]) * length / totalLength
2765 ];
2766 return xy;
2767 }
2768
2769 // return the 2D point intersection between two lines defined
2770 // by one 2D point and a slope each.
2771
2772 function lineIntersection(point1, slope1, point2, slope2) {
2773 // s1/s2 = slope in degrees
2774 var m1 = tan(slope1 * radians),
2775 m2 = tan(slope2 * radians),
2776 p = [0, 0];
2777 p[0] =
2778 (m1 * point1[0] - m2 * point2[0] - point1[1] + point2[1]) / (m1 - m2);
2779 p[1] = m1 * (p[0] - point1[0]) + point1[1];
2780 return p;
2781 }
2782
2783 // return the 2D point intercepting a circumference centered
2784 // at cc and of radius rn and a line defined by 2 points, p1 and p2:
2785 // First element of the returned array is a flag to state whether there is
2786 // an intersection, a value of zero (0) means NO INTERSECTION.
2787 // The following array is the 2D point of the intersection.
2788 // Equations from "Intersection of a Line and a Sphere (or circle)/Line Segment"
2789 // at http://paulbourke.net/geometry/circlesphere/
2790 function circleLineIntersection(cc, r, p1, p2) {
2791 var x1 = p1[0],
2792 y1 = p1[1],
2793 x2 = p2[0],
2794 y2 = p2[1],
2795 xc = cc[0],
2796 yc = cc[1],
2797 a = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1),
2798 b = 2 * ((x2 - x1) * (x1 - xc) + (y2 - y1) * (y1 - yc)),
2799 c =
2800 xc * xc + yc * yc + x1 * x1 + y1 * y1 - 2 * (xc * x1 + yc * y1) - r * r,
2801 d = b * b - 4 * a * c,
2802 u1 = 0,
2803 u2 = 0,
2804 x = 0,
2805 y = 0;
2806 if (a === 0) {
2807 return [0, [0, 0]];
2808 } else if (d < 0) {
2809 return [0, [0, 0]];
2810 }
2811 u1 = (-b + sqrt(d)) / (2 * a);
2812 u2 = (-b - sqrt(d)) / (2 * a);
2813 if (0 <= u1 && u1 <= 1) {
2814 x = x1 + u1 * (x2 - x1);
2815 y = y1 + u1 * (y2 - y1);
2816 return [1, [x, y]];
2817 } else if (0 <= u2 && u2 <= 1) {
2818 x = x1 + u2 * (x2 - x1);
2819 y = y1 + u2 * (y2 - y1);
2820 return [1, [x, y]];
2821 } else {
2822 return [0, [0, 0]];
2823 }
2824 }
2825
2826 // counterclockwise rotate 2D vector, xy, by angle (in degrees)
2827 // [original CKOG uses clockwise rotation]
2828
2829 function rotate(xy, angle) {
2830 var xynew = [0, 0];
2831
2832 if (angle === -60) {
2833 xynew[0] = xy[0] * CK.cos60 + xy[1] * CK.sin60;
2834 xynew[1] = -xy[0] * CK.sin60 + xy[1] * CK.cos60;
2835 } else if (angle === -120) {
2836 xynew[0] = -xy[0] * CK.cos60 + xy[1] * CK.sin60;
2837 xynew[1] = -xy[0] * CK.sin60 - xy[1] * CK.cos60;
2838 } else {
2839 // !!!!! This should not happen for this projection!!!!
2840 // the general algorith: cos(angle) * xy + sin(angle) * perpendicular(xy)
2841 // return cos(angle * radians) * xy + sin(angle * radians) * perpendicular(xy);
2842 //console.log("rotate: angle " + angle + " different than -60 or -120!");
2843 // counterclockwise
2844 xynew[0] = xy[0] * cos(angle * radians) - xy[1] * sin(angle * radians);
2845 xynew[1] = xy[0] * sin(angle * radians) + xy[1] * cos(angle * radians);
2846 }
2847
2848 return xynew;
2849 }
2850
2851 // truncate towards zero like int() in Perl
2852 function truncate(n) {
2853 return Math[n > 0 ? "floor" : "ceil"](n);
2854 }
2855
2856 function equator(m) {
2857 var l = CK.deltaMEq * m,
2858 jointE = [0, 0];
2859 if (l <= CK.lengthGF) {
2860 jointE = [CK.pointG[0], l];
2861 } else {
2862 l = l - CK.lengthGF;
2863 jointE = interpolate(l, CK.lengthAB, CK.pointF, CK.pointE);
2864 }
2865 return jointE;
2866 }
2867
2868 function jointE(m) {
2869 return equator(m);
2870 }
2871
2872 function jointT(m) {
2873 return lineIntersection(CK.pointM, 2 * m / 3, jointE(m), m / 3);
2874 }
2875
2876 function jointF(m) {
2877 if (m === 0) {
2878 return [CK.pointA + CK.lengthAB, 0];
2879 }
2880 var xy = lineIntersection(CK.pointA, m, CK.pointM, 2 * m / 3);
2881 return xy;
2882 }
2883
2884 function lengthTorridSegment(m) {
2885 return distance(jointE(m), jointT(m));
2886 }
2887
2888 function lengthMiddleSegment(m) {
2889 return distance(jointT(m), jointF(m));
2890 }
2891
2892 function parallel73(m) {
2893 var p73 = [0, 0],
2894 jF = jointF(m),
2895 lF = 0,
2896 xy = [0, 0];
2897 if (m <= 30) {
2898 p73[0] = CK.pointA[0] + CK.lengthAP73 * cos(m * radians);
2899 p73[1] = CK.pointA[1] + CK.lengthAP73 * sin(m * radians);
2900 lF = distance(jF, p73);
2901 } else {
2902 p73 = lineIntersection(CK.pointT, -60, jF, m);
2903 lF = distance(jF, p73);
2904 if (m > 44) {
2905 xy = lineIntersection(CK.pointT, -60, jF, 2 / 3 * m);
2906 if (xy[0] > p73[0]) {
2907 p73 = xy;
2908 lF = -distance(jF, p73);
2909 }
2910 }
2911 }
2912 return {
2913 parallel73: p73,
2914 lengthParallel73: lF
2915 };
2916 }
2917
2918 function parallel75(m) {
2919 return [
2920 CK.pointA[0] + CK.lengthAP75 * cos(m * radians),
2921 CK.pointA[1] + CK.lengthAP75 * sin(m * radians)
2922 ];
2923 }
2924
2925 // special functions to transform lon/lat to x/y
2926 function ll2mp(lon, lat) {
2927 var south = [0, 6, 7, 8, 5],
2928 o = truncate((lon + 180) / 90 + 1),
2929 p, // parallel
2930 m = (lon + 720) % 90 - 45, // meridian
2931 s = sign(m);
2932
2933 m = abs(m);
2934 if (o === 5) o = 1;
2935 if (lat < 0) o = south[o];
2936 p = abs(lat);
2937 return [m, p, s, o];
2938 }
2939
2940 function zoneA(m, p) {
2941 return [CK.pointA[0] + (90 - p) * 104, 0];
2942 }
2943
2944 function zoneB(m, p) {
2945 return [CK.pointG[0] - p * 100, 0];
2946 }
2947
2948 function zoneC(m, p) {
2949 var l = 104 * (90 - p);
2950 return [
2951 CK.pointA[0] + l * cos(m * radians),
2952 CK.pointA[1] + l * sin(m * radians)
2953 ];
2954 }
2955
2956 function zoneD(m /*, p */) {
2957 // p = p; // just keep it for symmetry in signature
2958 return equator(m);
2959 }
2960
2961 function zoneE(m, p) {
2962 var l = 1560 + (75 - p) * 100;
2963 return [
2964 CK.pointA[0] + l * cos(m * radians),
2965 CK.pointA[1] + l * sin(m * radians)
2966 ];
2967 }
2968
2969 function zoneF(m, p) {
2970 return interpolate(p, 15, CK.pointE, CK.pointD);
2971 }
2972
2973 function zoneG(m, p) {
2974 var l = p - 15;
2975 return interpolate(l, 58, CK.pointD, CK.pointT);
2976 }
2977
2978 function zoneH(m, p) {
2979 var p75 = parallel75(45),
2980 p73a = parallel73(m),
2981 p73 = p73a.parallel73,
2982 lF = distance(CK.pointT, CK.pointB),
2983 lF75 = distance(CK.pointB, p75),
2984 l = (75 - p) * (lF75 + lF) / 2,
2985 xy = [0, 0];
2986 if (l <= lF75) {
2987 xy = interpolate(l, lF75, p75, CK.pointB);
2988 } else {
2989 l = l - lF75;
2990 xy = interpolate(l, lF, CK.pointB, p73);
2991 }
2992 return xy;
2993 }
2994
2995 function zoneI(m, p) {
2996 var p73a = parallel73(m),
2997 lT = lengthTorridSegment(m),
2998 lM = lengthMiddleSegment(m),
2999 l = p * (lT + lM + p73a.lengthParallel73) / 73,
3000 xy;
3001 if (l <= lT) {
3002 xy = interpolate(l, lT, jointE(m), jointT(m));
3003 } else if (l <= lT + lM) {
3004 l = l - lT;
3005 xy = interpolate(l, lM, jointT(m), jointF(m));
3006 } else {
3007 l = l - lT - lM;
3008 xy = interpolate(l, p73a.lengthParallel73, jointF(m), p73a.parallel73);
3009 }
3010 return xy;
3011 }
3012
3013 function zoneJ(m, p) {
3014 var p75 = parallel75(m),
3015 lF75 = distance(jointF(m), p75),
3016 p73a = parallel73(m),
3017 p73 = p73a.parallel73,
3018 lF = p73a.lengthParallel73,
3019 l = (75 - p) * (lF75 - lF) / 2,
3020 xy = [0, 0];
3021
3022 if (l <= lF75) {
3023 xy = interpolate(l, lF75, p75, jointF(m));
3024 } else {
3025 l = l - lF75;
3026 xy = interpolate(l, -lF, jointF(m), p73);
3027 }
3028 return xy;
3029 }
3030
3031 function zoneK(m, p, l15) {
3032 var l = p * l15 / 15,
3033 lT = lengthTorridSegment(m),
3034 lM = lengthMiddleSegment(m),
3035 xy = [0, 0];
3036 if (l <= lT) {
3037 // point is in torrid segment
3038 xy = interpolate(l, lT, jointE(m), jointT(m));
3039 } else {
3040 // point is in middle segment
3041 l = l - lT;
3042 xy = interpolate(l, lM, jointT(m), jointF(m));
3043 }
3044 return xy;
3045 }
3046
3047 function zoneL(m, p, l15) {
3048 var p73a = parallel73(m),
3049 p73 = p73a.parallel73,
3050 lT = lengthTorridSegment(m),
3051 lM = lengthMiddleSegment(m),
3052 xy,
3053 lF = p73a.lengthParallel73,
3054 l = l15 + (p - 15) * (lT + lM + lF - l15) / 58;
3055 if (l <= lT) {
3056 //on torrid segment
3057 xy = interpolate(l, lT, jointE(m), jointF(m));
3058 } else if (l <= lT + lM) {
3059 //on middle segment
3060 l = l - lT;
3061 xy = interpolate(l, lM, jointT(m), jointF(m));
3062 } else {
3063 //on frigid segment
3064 l = l - lT - lM;
3065 xy = interpolate(l, lF, jointF(m), p73);
3066 }
3067 return xy;
3068 }
3069
3070 // convert half-octant meridian,parallel to x,y coordinates.
3071 // arguments are meridian, parallel
3072
3073 function mp2xy(m, p) {
3074 var xy = [0, 0],
3075 lT,
3076 p15a,
3077 p15,
3078 flag15,
3079 l15;
3080
3081 if (m === 0) {
3082 // zones (a) and (b)
3083 if (p >= 75) {
3084 xy = zoneA(m, p);
3085 } else {
3086 xy = zoneB(m, p);
3087 }
3088 } else if (p >= 75) {
3089 xy = zoneC(m, p);
3090 } else if (p === 0) {
3091 xy = zoneD(m, p);
3092 } else if (p >= 73 && m <= 30) {
3093 xy = zoneE(m, p);
3094 } else if (m === 45) {
3095 if (p <= 15) {
3096 xy = zoneF(m, p);
3097 } else if (p <= 73) {
3098 xy = zoneG(m, p);
3099 } else {
3100 xy = zoneH(m, p);
3101 }
3102 } else {
3103 if (m <= 29) {
3104 xy = zoneI(m, p);
3105 } else {
3106 // supple zones (j), (k) and (l)
3107 if (p >= 73) {
3108 xy = zoneJ(m, p);
3109 } else {
3110 //zones (k) and (l)
3111 p15a = circleLineIntersection(
3112 CK.pointC,
3113 CK.radius,
3114 jointT(m),
3115 jointF(m)
3116 );
3117 flag15 = p15a[0];
3118 p15 = p15a[1];
3119 lT = lengthTorridSegment(m);
3120 if (flag15 === 1) {
3121 // intersection is in middle segment
3122 l15 = lT + distance(jointT(m), p15);
3123 } else {
3124 // intersection is in torrid segment
3125 p15a = circleLineIntersection(
3126 CK.pointC,
3127 CK.radius,
3128 jointE(m),
3129 jointT(m)
3130 );
3131 flag15 = p15a[0];
3132 p15 = p15a[1];
3133 l15 = lT - distance(jointT(m), p15);
3134 }
3135 if (p <= 15) {
3136 xy = zoneK(m, p, l15);
3137 } else {
3138 //zone (l)
3139 xy = zoneL(m, p, l15);
3140 }
3141 }
3142 }
3143 }
3144 return xy;
3145 }
3146
3147 // from half-octant to megamap (single rotated octant)
3148
3149 function mj2g(xy, octant) {
3150 var xynew = [0, 0];
3151
3152 if (octant === 0) {
3153 xynew = rotate(xy, -60);
3154 } else if (octant === 1) {
3155 xynew = rotate(xy, -120);
3156 xynew[0] -= CK.lengthMG;
3157 } else if (octant === 2) {
3158 xynew = rotate(xy, -60);
3159 xynew[0] -= CK.lengthMG;
3160 } else if (octant === 3) {
3161 xynew = rotate(xy, -120);
3162 xynew[0] += CK.lengthMG;
3163 } else if (octant === 4) {
3164 xynew = rotate(xy, -60);
3165 xynew[0] += CK.lengthMG;
3166 } else if (octant === 5) {
3167 xynew = rotate([2 * CK.lengthMG - xy[0], xy[1]], -60);
3168 xynew[0] += CK.lengthMG;
3169 } else if (octant === 6) {
3170 xynew = rotate([2 * CK.lengthMG - xy[0], xy[1]], -120);
3171 xynew[0] -= CK.lengthMG;
3172 } else if (octant === 7) {
3173 xynew = rotate([2 * CK.lengthMG - xy[0], xy[1]], -60);
3174 xynew[0] -= CK.lengthMG;
3175 } else if (octant === 8) {
3176 xynew = rotate([2 * CK.lengthMG - xy[0], xy[1]], -120);
3177 xynew[0] += CK.lengthMG;
3178 } else {
3179 // TODO trap this some way.
3180 // ERROR!
3181 // print "Error converting to M-map coordinates; there is no Octant octant!\n";
3182 //console.log("mj2g: something weird happened!");
3183 return xynew;
3184 }
3185
3186 return xynew;
3187 }
3188
3189 // general CK map projection
3190
3191 function forward(lambda, phi) {
3192 // lambda, phi are in radians.
3193 var lon = lambda * degrees,
3194 lat = phi * degrees,
3195 res = ll2mp(lon, lat),
3196 m = res[0], // 0 ≤ m ≤ 45
3197 p = res[1], // 0 ≤ p ≤ 90
3198 s = res[2], // -1 / 1 = side of m
3199 o = res[3], // octant
3200 xy = mp2xy(m, p),
3201 mm = mj2g([xy[0], s * xy[1]], o);
3202
3203 return mm;
3204 }
3205
3206 return forward;
3207}
3208
3209function cahillKeyesProjection() {
3210 var mg = 10000,
3211 m = d3Geo.geoProjectionMutator(cahillKeyesRaw);
3212 return m(mg);
3213}
3214
3215/*
3216 * Complex logarithm projection
3217 *
3218 * Based on the following papers by Joachim Böttger et al.:
3219 * - Detail‐In‐Context Visualization for Satellite Imagery (2008) (https://doi.org/10.1111/j.1467-8659.2008.01156.x)
3220 * - Complex Logarithmic Views for Small Details in Large Contexts (2006) (https://doi.org/10.1109/TVCG.2006.126)
3221 *
3222 * Implemented for d3 by Matthias Albrecht and Jochen Görtler (2019)
3223 *
3224 */
3225
3226// Default planar projection and cutoff latitude, see below for an explanation of these settings.
3227var DEFAULT_PLANAR_PROJECTION_RAW = d3Geo.geoAzimuthalEqualAreaRaw;
3228var DEFAULT_CUTOFF_LATITUDE = -0.05;
3229
3230// Offset used to prevent logarithm of 0.
3231var CARTESIAN_OFFSET = 1e-10;
3232
3233// Projection parameters for the default 960x500 projection area.
3234var DEFAULT_PROJECTION_PARAMS = {
3235 angle: 90,
3236 center: [0, 5.022570623227068],
3237 scale: 79.92959180396787,
3238 translate: [479.9999905630355, 250.35977064160338]
3239};
3240
3241// Vertices of the clipping polygon in spherical coordinates.
3242// It contains the whole world except a small strip along longitude 0/180 crossing the south pole.
3243var CLIP_POLY_SPHERICAL = [
3244 [-180, -1e-4],
3245 [180, -1e-4],
3246 [1e-4, DEFAULT_CUTOFF_LATITUDE],
3247 [-1e-4, DEFAULT_CUTOFF_LATITUDE]
3248];
3249
3250// Clipping polygon precision.
3251var N_SIDE = 5;
3252var N_BOTTOM = 50;
3253
3254
3255function complexLogRaw(planarProjectionRaw = DEFAULT_PLANAR_PROJECTION_RAW) {
3256 function forward(lambda, phi) {
3257 // Project on plane.
3258 // Interpret projected point on complex plane.
3259 var aziComp = planarProjectionRaw(lambda, phi);
3260
3261 // Rotate by -90 degrees in complex plane so the following complex log projection will be horizontally centered
3262 aziComp = complexMul(aziComp, [cos(-pi / 2), sin(-pi / 2)]);
3263
3264 // Small offset to prevent logarithm of 0.
3265 if (aziComp[0] == 0 && aziComp[1] == 0) {
3266 aziComp[0] += CARTESIAN_OFFSET;
3267 aziComp[1] += CARTESIAN_OFFSET;
3268 }
3269
3270 // Apply complex logarithm.
3271 var logComp = [complexLogHypot(aziComp[0], aziComp[1]), atan2(aziComp[1], aziComp[0])];
3272
3273 return logComp;
3274 }
3275
3276 function invert(x, y) {
3277 // Inverse complex logarithm (complex exponential function).
3278 var invLogComp = [exp(x) * cos(y), exp(x) * sin(y)];
3279
3280 // Undo rotation.
3281 invLogComp = complexMul(invLogComp, [cos(pi / 2), sin(pi / 2)]);
3282
3283 // Invert azimuthal equal area.
3284 return planarProjectionRaw.invert(invLogComp[0], invLogComp[1]);
3285 }
3286
3287 forward.invert = invert;
3288 return forward;
3289}
3290
3291
3292function complexLog(planarProjectionRaw = DEFAULT_PLANAR_PROJECTION_RAW, cutoffLatitude = DEFAULT_CUTOFF_LATITUDE) {
3293 var mutator = d3Geo.geoProjectionMutator(complexLogRaw);
3294 var projection = mutator(planarProjectionRaw);
3295
3296 // Projection used to project onto the complex plane.
3297 projection.planarProjectionRaw = function(_) {
3298 return arguments.length ? clipped(mutator(planarProjectionRaw = _)) : planarProjectionRaw;
3299 };
3300
3301 // Latitude relative to the projection center at which to cutoff/clip the projection, lower values result in more detail around the projection center.
3302 // Value must be < 0 because complex log projects the origin to infinity.
3303 projection.cutoffLatitude = function(_) {
3304 return arguments.length ? (cutoffLatitude = _, clipped(mutator(planarProjectionRaw))) : cutoffLatitude;
3305 };
3306
3307 function clipped(projection) {
3308 var angle = projection.angle();
3309 var scale = projection.scale();
3310 var center = projection.center();
3311 var translate = projection.translate();
3312 var rotate = projection.rotate();
3313
3314 projection
3315 .angle(DEFAULT_PROJECTION_PARAMS.angle)
3316 .scale(1)
3317 .center([0, 0])
3318 .rotate([0, 0])
3319 .translate([0, 0])
3320 .preclip();
3321
3322 // These are corner vertices of a rectangle in the projected complex log view.
3323 var topLeft = projection(CLIP_POLY_SPHERICAL[0]);
3324 var topRight = projection(CLIP_POLY_SPHERICAL[1]);
3325 var bottomRight = projection([CLIP_POLY_SPHERICAL[2][0], cutoffLatitude]);
3326 var bottomLeft = projection([CLIP_POLY_SPHERICAL[3][0], cutoffLatitude]);
3327 var width = abs(topRight[0] - topLeft[0]);
3328 var height = abs(bottomRight[1] - topRight[1]);
3329
3330 // Prevent overlapping polygons that result from paths that go from one side to the other,
3331 // so cut along 180°/-180° degree line (left and right in complex log projected view).
3332 // This means cutting against a rectangular shaped polygon in the projected view.
3333 // The following generator produces a polygon that is shaped like this:
3334 //
3335 // Winding order: ==>
3336 //
3337 // ******************|
3338 // | |
3339 // | |
3340 // | |
3341 // | |
3342 // | |
3343 // |------------------
3344 //
3345 // N_SIDE determines how many vertices to insert along the sides (marked as | above).
3346 // N_BOTTOM determines how many vertices to insert along the bottom (marked as - above).
3347 //
3348 // The resulting polygon vertices are back-projected to spherical coordinates.
3349 var polygon = {
3350 type: "Polygon",
3351 coordinates: [
3352 [
3353 topLeft,
3354 ...Array.from({length: N_SIDE}, (_, t) => [bottomRight[0], bottomRight[1] - height * (N_SIDE- t) / N_SIDE]),
3355 ...Array.from({length: N_BOTTOM}, (_, t) => [bottomRight[0] - width * t / N_BOTTOM, bottomRight[1]]),
3356 ...Array.from({length: N_SIDE}, (_, t) => [bottomLeft[0], bottomLeft[1] - height * t / N_SIDE]),
3357 topLeft
3358 ].map(point => projection.invert(point))
3359 ]
3360 };
3361
3362 return projection
3363 .angle(angle)
3364 .scale(scale)
3365 .center(center)
3366 .translate(translate)
3367 .rotate(rotate)
3368 .preclip(clipPolygon(polygon));
3369 }
3370
3371 // The following values are for the default 960x500 projection area
3372 return clipped(projection)
3373 .angle(DEFAULT_PROJECTION_PARAMS.angle)
3374 .center(DEFAULT_PROJECTION_PARAMS.center)
3375 .scale(DEFAULT_PROJECTION_PARAMS.scale)
3376 .translate(DEFAULT_PROJECTION_PARAMS.translate);
3377}
3378
3379exports.geoClipPolygon = clipPolygon;
3380exports.geoIntersectArc = intersect$1;
3381exports.geoPolyhedral = polyhedral;
3382exports.geoPolyhedralButterfly = butterfly;
3383exports.geoPolyhedralCollignon = collignon;
3384exports.geoPolyhedralWaterman = waterman;
3385exports.geoPolyhedralVoronoi = voronoi;
3386exports.geoDodecahedral = dodecahedral;
3387exports.geoCox = cox;
3388exports.geoCoxRaw = coxRaw;
3389exports.geoTetrahedralLee = tetrahedralLee;
3390exports.geoLeeRaw = leeRaw;
3391exports.geoGrayFullerRaw = GrayFullerRaw;
3392exports.geoAirocean = airocean;
3393exports.geoIcosahedral = icosahedral;
3394exports.geoImago = imago;
3395exports.geoImagoBlock = imagoBlock;
3396exports.geoImagoRaw = imagoRaw;
3397exports.geoCubic = cubic;
3398exports.geoCahillKeyes = cahillKeyes;
3399exports.geoCahillKeyesRaw = cahillKeyesRaw;
3400exports.geoComplexLog = complexLog;
3401exports.geoComplexLogRaw = complexLogRaw;
3402
3403Object.defineProperty(exports, '__esModule', { value: true });
3404
3405})));
3406
\No newline at end of file