1 |
|
2 | (function (global, factory) {
|
3 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3-geo')) :
|
4 | typeof 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 |
|
8 | function noop() {}
|
9 |
|
10 | function clipBuffer() {
|
11 | var lines = [],
|
12 | line;
|
13 | return {
|
14 | point: function(x, y, i, t) {
|
15 | var point = [x, y];
|
16 |
|
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 |
|
36 | var abs = Math.abs;
|
37 | var atan = Math.atan;
|
38 | var atan2 = Math.atan2;
|
39 | var cos = Math.cos;
|
40 | var exp = Math.exp;
|
41 | var floor = Math.floor;
|
42 | var log = Math.log;
|
43 | var max = Math.max;
|
44 | var min = Math.min;
|
45 | var pow = Math.pow;
|
46 | var sign = Math.sign || function(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; };
|
47 | var sin = Math.sin;
|
48 | var tan = Math.tan;
|
49 |
|
50 | var epsilon = 1e-6;
|
51 | var epsilon2 = 1e-12;
|
52 | var pi = Math.PI;
|
53 | var halfPi = pi / 2;
|
54 | var quarterPi = pi / 4;
|
55 | var sqrt1_2 = Math.SQRT1_2;
|
56 | var sqrtPi = sqrt(pi);
|
57 | var tau = pi * 2;
|
58 | var degrees = 180 / pi;
|
59 | var radians = pi / 180;
|
60 |
|
61 | function asin(x) {
|
62 | return x > 1 ? halfPi : x < -1 ? -halfPi : Math.asin(x);
|
63 | }
|
64 |
|
65 | function acos(x) {
|
66 | return x > 1 ? 0 : x < -1 ? pi : Math.acos(x);
|
67 | }
|
68 |
|
69 | function sqrt(x) {
|
70 | return x > 0 ? Math.sqrt(x) : 0;
|
71 | }
|
72 |
|
73 | function pointEqual(a, b) {
|
74 | return abs(a[0] - b[0]) < epsilon && abs(a[1] - b[1]) < epsilon;
|
75 | }
|
76 |
|
77 | function Intersection(point, points, other, entry) {
|
78 | this.x = point;
|
79 | this.z = points;
|
80 | this.o = other;
|
81 | this.e = entry;
|
82 | this.v = false;
|
83 | this.n = this.p = null;
|
84 | }
|
85 |
|
86 |
|
87 |
|
88 |
|
89 | function 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 |
|
100 |
|
101 |
|
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 |
|
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 |
|
162 | function 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 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 | function adder() {
|
185 | return new Adder;
|
186 | }
|
187 |
|
188 | function Adder() {
|
189 | this.reset();
|
190 | }
|
191 |
|
192 | Adder.prototype = {
|
193 | constructor: Adder,
|
194 | reset: function() {
|
195 | this.s =
|
196 | this.t = 0;
|
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 |
|
209 | var temp = new Adder;
|
210 |
|
211 | function 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 |
|
218 | function spherical(cartesian) {
|
219 | return [atan2(cartesian[1], cartesian[0]), asin(cartesian[2])];
|
220 | }
|
221 |
|
222 | function sphericalDegrees(cartesian) {
|
223 | var c = spherical(cartesian);
|
224 | return [c[0] * degrees, c[1] * degrees];
|
225 | }
|
226 |
|
227 | function 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 |
|
232 | function cartesianDegrees(spherical) {
|
233 | return cartesian([spherical[0] * radians, spherical[1] * radians]);
|
234 | }
|
235 |
|
236 | function cartesianDot(a, b) {
|
237 | return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
238 | }
|
239 |
|
240 | function 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 |
|
245 | function 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 |
|
250 | function 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 |
|
257 | var sum = adder();
|
258 |
|
259 | function 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 |
|
294 |
|
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 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 | return (angle < -epsilon || angle < epsilon && sum < -epsilon) ^ (winding & 1);
|
320 | }
|
321 |
|
322 | function 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 |
|
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 |
|
423 |
|
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 |
|
433 | function validSegment(segment) {
|
434 | return segment.length > 1;
|
435 | }
|
436 |
|
437 |
|
438 |
|
439 | function 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 |
|
444 | function 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 |
|
453 | function 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 |
|
475 |
|
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 |
|
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 |
|
512 | function 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 |
|
520 | var intersectCoincident = {};
|
521 |
|
522 | function 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 |
|
532 | var clipNone = function(stream) { return stream; };
|
533 |
|
534 |
|
535 | function 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]);
|
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 |
|
593 | function ringRadians(ring) {
|
594 | return ring.map(function(point) {
|
595 | return [point[0] * radians, point[1] * radians];
|
596 | });
|
597 | }
|
598 |
|
599 | function 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 |
|
612 | function clipPolygonSort(a, b) {
|
613 | (a = a.x), (b = b.x);
|
614 | return a.index - b.index || a.t - b.t;
|
615 | }
|
616 |
|
617 | function 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 |
|
663 | function clipPolygonDistance(a, b) {
|
664 | var axb = cartesianCross(a, b);
|
665 | return atan2(sqrt(cartesianDot(axb, axb)), cartesianDot(a, b));
|
666 | }
|
667 |
|
668 | function visible(polygon) {
|
669 | return function(lambda, phi) {
|
670 | return polygonContains(polygon, [lambda, phi]);
|
671 | };
|
672 | }
|
673 |
|
674 | function randsign(i, j) {
|
675 | return sign(sin(100 * i + j));
|
676 | }
|
677 |
|
678 | function 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);
|
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;
|
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 |
|
795 |
|
796 | clean: function() {
|
797 | return clean | ((v00 && v0) << 1);
|
798 | }
|
799 | };
|
800 | };
|
801 | }
|
802 |
|
803 |
|
804 |
|
805 |
|
806 |
|
807 |
|
808 |
|
809 | function 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 |
|
831 | function 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 |
|
840 | function 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 |
|
852 | function subtract(a, b) {
|
853 | return [a[0] - b[0], a[1] - b[1]];
|
854 | }
|
855 |
|
856 |
|
857 | function length(v) {
|
858 | return sqrt(v[0] * v[0] + v[1] * v[1]);
|
859 | }
|
860 |
|
861 |
|
862 | function 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 |
|
867 |
|
868 |
|
869 |
|
870 |
|
871 | function polyhedral(tree, face) {
|
872 |
|
873 | recurse(tree, {transform: null});
|
874 |
|
875 | function recurse(node, parent) {
|
876 | node.edges = faceEdges(node.face);
|
877 |
|
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 |
|
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 |
|
919 |
|
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 |
|
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 |
|
960 | function 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 |
|
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 |
|
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 |
|
996 | function pointEqual$1(a, b) {
|
997 | return a && b && a[0] === b[0] && a[1] === b[1];
|
998 | }
|
999 |
|
1000 |
|
1001 | function 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 |
|
1016 | function 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 |
|
1023 | function hasInverse(node) {
|
1024 | return node.project.invert || node.children && node.children.some(hasInverse);
|
1025 | }
|
1026 |
|
1027 |
|
1028 | var octahedron = [
|
1029 | [0, 90],
|
1030 | [-90, 0], [0, 0], [90, 0], [180, 0],
|
1031 | [0, -90]
|
1032 | ];
|
1033 |
|
1034 | var 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 |
|
1049 | function 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 |
|
1077 |
|
1078 | function collignonRaw(lambda, phi) {
|
1079 | var alpha = sqrt(1 - sin(phi));
|
1080 | return [(2 / sqrtPi) * lambda * alpha, sqrtPi * (1 - alpha)];
|
1081 | }
|
1082 |
|
1083 | collignonRaw.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 |
|
1088 | var kx = 2 / sqrt(3);
|
1089 |
|
1090 | function collignonK(a, b) {
|
1091 | var p = collignonRaw(a, b);
|
1092 | return [p[0] * kx, p[1]];
|
1093 | }
|
1094 |
|
1095 | collignonK.invert = function(x,y) {
|
1096 | return collignonRaw.invert(x / kx, y);
|
1097 | };
|
1098 |
|
1099 | function 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 |
|
1126 | function 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 |
|
1212 | function 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 |
|
1217 | function 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 |
|
1226 | function 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 |
|
1234 | function 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 |
|
1245 | function voronoi(parents, polygons, faceProjection, find) {
|
1246 | parents = parents || [];
|
1247 | polygons = polygons || { features: [] };
|
1248 | find = find || find0;
|
1249 |
|
1250 |
|
1251 |
|
1252 |
|
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 |
|
1265 |
|
1266 |
|
1267 |
|
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 |
|
1284 |
|
1285 | parents.forEach(function(d, i) {
|
1286 | var node = faces[d];
|
1287 | node && (node.children || (node.children = [])).push(faces[i]);
|
1288 | });
|
1289 | }
|
1290 |
|
1291 |
|
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 |
|
1361 | function 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 |
|
1405 |
|
1406 | function 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);
|
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 |
|
1438 | function complexMul(a, b) {
|
1439 | return [a[0] * b[0] - a[1] * b[1], a[1] * b[0] + a[0] * b[1]];
|
1440 | }
|
1441 |
|
1442 | function complexAdd(a, b) {
|
1443 | return [a[0] + b[0], a[1] + b[1]];
|
1444 | }
|
1445 |
|
1446 | function complexSub(a, b) {
|
1447 | return [a[0] - b[0], a[1] - b[1]];
|
1448 | }
|
1449 |
|
1450 | function complexNorm2(a) {
|
1451 | return a[0] * a[0] + a[1] * a[1];
|
1452 | }
|
1453 |
|
1454 | function complexNorm(a) {
|
1455 | return sqrt(complexNorm2(a));
|
1456 | }
|
1457 |
|
1458 | function 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 |
|
1468 | function 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 |
|
1502 |
|
1503 | var w = [-1 / 2, sqrt(3) / 2],
|
1504 | w1 = [1.7666387502854533, 0],
|
1505 | m = 0.3 * 0.3;
|
1506 |
|
1507 |
|
1508 |
|
1509 | function sm_1(z) {
|
1510 | var k = [0, 0];
|
1511 |
|
1512 |
|
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 |
|
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 |
|
1558 |
|
1559 |
|
1560 |
|
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 |
|
1580 | var lagrange1_2 = lagrangeRaw ? lagrangeRaw(0.5) : null;
|
1581 | function 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 |
|
1588 |
|
1589 | function 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 |
|
1599 | function 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 |
|
1619 | function leeRaw(lambda, phi) {
|
1620 |
|
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 |
|
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 |
|
1639 |
|
1640 | var y = complexSub([1, 0], complexMul(rot, z));
|
1641 |
|
1642 |
|
1643 |
|
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 |
|
1681 |
|
1682 |
|
1683 |
|
1684 |
|
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 |
|
1697 | var t = (n - 0.3) / (0.5 - 0.3);
|
1698 | return complexAdd(complexMul(k, [t, 0]), complexMul(h, [1 - t, 0]));
|
1699 | }
|
1700 |
|
1701 | var asin1_3 = asin(1 / 3);
|
1702 | var centers = [
|
1703 | [0, 90],
|
1704 | [-180, -asin1_3 * degrees],
|
1705 | [-60, -asin1_3 * degrees],
|
1706 | [60, -asin1_3 * degrees]
|
1707 | ];
|
1708 | var 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 |
|
1716 | function 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])
|
1768 |
|
1769 | .angle(30)
|
1770 | .scale(118.662)
|
1771 | .translate([480, 195.47]);
|
1772 | }
|
1773 |
|
1774 |
|
1775 |
|
1776 |
|
1777 |
|
1778 |
|
1779 |
|
1780 |
|
1781 |
|
1782 |
|
1783 |
|
1784 |
|
1785 |
|
1786 | function GrayFullerRaw() {
|
1787 | var SQRT_3 = sqrt(3);
|
1788 |
|
1789 |
|
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 |
|
1807 | grayfuller.invert = function(x, y) {
|
1808 |
|
1809 |
|
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 |
|
1838 | function 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 |
|
2023 | function 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 |
|
2051 | function 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 |
|
2104 | var 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
|
2154 | function 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 |
|
2174 | var hypot = Math.hypot;
|
2175 |
|
2176 | var 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 |
|
2197 | function 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 |
|
2361 | function 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 |
|
2390 | function 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 |
|
2428 | function 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 |
|
2490 | var phi1 = atan(sqrt1_2) * degrees;
|
2491 |
|
2492 | var cube = [
|
2493 | [0, phi1], [90, phi1], [180, phi1], [-90, phi1],
|
2494 | [0, -phi1], [90, -phi1], [180, -phi1], [-90, -phi1]
|
2495 | ];
|
2496 |
|
2497 | var 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 |
|
2517 | function 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 |
|
2552 | function 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 |
|
2671 | function 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 |
|
3209 | function 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.
|
3227 | var DEFAULT_PLANAR_PROJECTION_RAW = d3Geo.geoAzimuthalEqualAreaRaw;
|
3228 | var DEFAULT_CUTOFF_LATITUDE = -0.05;
|
3229 |
|
3230 | // Offset used to prevent logarithm of 0.
|
3231 | var CARTESIAN_OFFSET = 1e-10;
|
3232 |
|
3233 | // Projection parameters for the default 960x500 projection area.
|
3234 | var 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.
|
3243 | var 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.
|
3251 | var N_SIDE = 5;
|
3252 | var N_BOTTOM = 50;
|
3253 |
|
3254 |
|
3255 | function 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 |
|
3292 | function 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 |
|
3379 | exports.geoClipPolygon = clipPolygon;
|
3380 | exports.geoIntersectArc = intersect$1;
|
3381 | exports.geoPolyhedral = polyhedral;
|
3382 | exports.geoPolyhedralButterfly = butterfly;
|
3383 | exports.geoPolyhedralCollignon = collignon;
|
3384 | exports.geoPolyhedralWaterman = waterman;
|
3385 | exports.geoPolyhedralVoronoi = voronoi;
|
3386 | exports.geoDodecahedral = dodecahedral;
|
3387 | exports.geoCox = cox;
|
3388 | exports.geoCoxRaw = coxRaw;
|
3389 | exports.geoTetrahedralLee = tetrahedralLee;
|
3390 | exports.geoLeeRaw = leeRaw;
|
3391 | exports.geoGrayFullerRaw = GrayFullerRaw;
|
3392 | exports.geoAirocean = airocean;
|
3393 | exports.geoIcosahedral = icosahedral;
|
3394 | exports.geoImago = imago;
|
3395 | exports.geoImagoBlock = imagoBlock;
|
3396 | exports.geoImagoRaw = imagoRaw;
|
3397 | exports.geoCubic = cubic;
|
3398 | exports.geoCahillKeyes = cahillKeyes;
|
3399 | exports.geoCahillKeyesRaw = cahillKeyesRaw;
|
3400 | exports.geoComplexLog = complexLog;
|
3401 | exports.geoComplexLogRaw = complexLogRaw;
|
3402 |
|
3403 | Object.defineProperty(exports, '__esModule', { value: true });
|
3404 |
|
3405 | })));
|
3406 |
|
\ | No newline at end of file |