mapshaper 0.7.41 → 0.7.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mapshaper.js CHANGED
@@ -78,7 +78,7 @@
78
78
  return obj === Object(obj); // via underscore
79
79
  }
80
80
 
81
- function clamp$2(val, min, max) {
81
+ function clamp$3(val, min, max) {
82
82
  return val < min ? min : (val > max ? max : val);
83
83
  }
84
84
 
@@ -751,7 +751,7 @@
751
751
  function findValueByRank(arr, rank) {
752
752
  if (!arr.length || rank < 1 || rank > arr.length) error$1("[findValueByRank()] invalid input");
753
753
 
754
- rank = clamp$2(rank | 0, 1, arr.length);
754
+ rank = clamp$3(rank | 0, 1, arr.length);
755
755
  var k = rank - 1, // conv. rank to array index
756
756
  n = arr.length,
757
757
  l = 0,
@@ -1125,7 +1125,7 @@
1125
1125
  // self-import and the resulting Rollup circular-dependency warning.
1126
1126
  var utils = {
1127
1127
  addThousandsSep, addslashes, arrayToIndex,
1128
- clamp: clamp$2, cleanNumericString, contains, copyElements, countValues, createBuffer,
1128
+ clamp: clamp$3, cleanNumericString, contains, copyElements, countValues, createBuffer,
1129
1129
  defaults, difference,
1130
1130
  endsWith, every, expandoBuffer, extend: extend$1, extendBuffer,
1131
1131
  find: find$1, findMedian, findQuantile, findRankByValue, findStringPrefix,
@@ -2083,12 +2083,12 @@
2083
2083
  // TODO: remove this constant, use actual data from dataset CRS,
2084
2084
  // also consider using ellipsoidal formulas where greater accuracy might be important.
2085
2085
  var R$3 = WGS84.SEMIMAJOR_AXIS;
2086
- var D2R$1 = Math.PI / 180;
2087
- var R2D$1 = 180 / Math.PI;
2086
+ var D2R$6 = Math.PI / 180;
2087
+ var R2D$5 = 180 / Math.PI;
2088
2088
 
2089
2089
  // Equirectangular projection
2090
2090
  function degreesToMeters(deg) {
2091
- return deg * D2R$1 * R$3;
2091
+ return deg * D2R$6 * R$3;
2092
2092
  }
2093
2093
 
2094
2094
  function distance3D(ax, ay, az, bx, by, bz) {
@@ -2104,7 +2104,7 @@
2104
2104
  return dx * dx + dy * dy;
2105
2105
  }
2106
2106
 
2107
- function distance2D(ax, ay, bx, by) {
2107
+ function distance2D$1(ax, ay, bx, by) {
2108
2108
  var dx = ax - bx,
2109
2109
  dy = ay - by;
2110
2110
  return Math.sqrt(dx * dx + dy * dy);
@@ -2235,16 +2235,16 @@
2235
2235
 
2236
2236
  function xyzToLngLat(x, y, z, p) {
2237
2237
  var d = distance3D(0, 0, 0, x, y, z); // normalize
2238
- var lat = Math.asin(z / d) / D2R$1;
2239
- var lng = Math.atan2(y / d, x / d) / D2R$1;
2238
+ var lat = Math.asin(z / d) / D2R$6;
2239
+ var lng = Math.atan2(y / d, x / d) / D2R$6;
2240
2240
  p[0] = lng;
2241
2241
  p[1] = lat;
2242
2242
  }
2243
2243
 
2244
2244
  function lngLatToXYZ(lng, lat, p) {
2245
2245
  var cosLat;
2246
- lng *= D2R$1;
2247
- lat *= D2R$1;
2246
+ lng *= D2R$6;
2247
+ lat *= D2R$6;
2248
2248
  cosLat = Math.cos(lat);
2249
2249
  p[0] = Math.cos(lng) * cosLat * R$3;
2250
2250
  p[1] = Math.sin(lng) * cosLat * R$3;
@@ -2282,7 +2282,7 @@
2282
2282
  }
2283
2283
 
2284
2284
  function cosine(ax, ay, bx, by, cx, cy) {
2285
- var den = distance2D(ax, ay, bx, by) * distance2D(bx, by, cx, cy),
2285
+ var den = distance2D$1(ax, ay, bx, by) * distance2D$1(bx, by, cx, cy),
2286
2286
  cos = 0;
2287
2287
  if (den > 0) {
2288
2288
  cos = ((ax - bx) * (cx - bx) + (ay - by) * (cy - by)) / den;
@@ -2364,9 +2364,9 @@
2364
2364
 
2365
2365
  var Geom = /*#__PURE__*/Object.freeze({
2366
2366
  __proto__: null,
2367
- D2R: D2R$1,
2367
+ D2R: D2R$6,
2368
2368
  R: R$3,
2369
- R2D: R2D$1,
2369
+ R2D: R2D$5,
2370
2370
  bearing: bearing,
2371
2371
  bearing2D: bearing2D,
2372
2372
  containsBounds: containsBounds,
@@ -2374,7 +2374,7 @@
2374
2374
  cosine: cosine,
2375
2375
  cosine3D: cosine3D,
2376
2376
  degreesToMeters: degreesToMeters,
2377
- distance2D: distance2D,
2377
+ distance2D: distance2D$1,
2378
2378
  distance3D: distance3D,
2379
2379
  distanceSq: distanceSq,
2380
2380
  distanceSq3D: distanceSq3D,
@@ -2525,7 +2525,7 @@
2525
2525
  if (spherical && arcs.isPlanar()) {
2526
2526
  error("Expected lat-long coordinates");
2527
2527
  }
2528
- calcLen = spherical ? greatCircleDistance : distance2D;
2528
+ calcLen = spherical ? greatCircleDistance : distance2D$1;
2529
2529
  len = 0;
2530
2530
  for (var i=0, n=path.length; i<n; i++) {
2531
2531
  arcs.forEachArcSegment(path[i], addSegLen);
@@ -3311,8 +3311,8 @@
3311
3311
  // angle and seg length ratio thresholds were found by comparing
3312
3312
  // fast and robust outputs on sample data
3313
3313
  if (innerAngle(ax, ay, bx, by, cx, cy, dx, dy) < 0.1) return true;
3314
- var len1 = distance2D(ax, ay, bx, by);
3315
- var len2 = distance2D(cx, cy, dx, dy);
3314
+ var len1 = distance2D$1(ax, ay, bx, by);
3315
+ var len2 = distance2D$1(cx, cy, dx, dy);
3316
3316
  var ratio = (len1 < len2 ? len1 / len2 : len2 / len1) || 0;
3317
3317
  if (ratio < 0.001) return true;
3318
3318
  return false;
@@ -3405,7 +3405,7 @@
3405
3405
  }
3406
3406
 
3407
3407
  function snapIfCloser(p, minDist, x, y, x2, y2) {
3408
- var dist = distance2D(x, y, x2, y2);
3408
+ var dist = distance2D$1(x, y, x2, y2);
3409
3409
  if (dist < minDist) {
3410
3410
  minDist = dist;
3411
3411
  p[0] = x2;
@@ -6287,7 +6287,1807 @@
6287
6287
  probablyDecimalDegreeBounds: probablyDecimalDegreeBounds
6288
6288
  });
6289
6289
 
6290
+ /*
6291
+ * Polyhedral unfolding code adapted from d3-geo-polygon.
6292
+ *
6293
+ * Copyright 2017-2024 Mike Bostock
6294
+ * ISC License: https://github.com/d3/d3-geo-polygon/blob/main/LICENSE
6295
+ */
6296
+
6297
+ var D2R$5 = Math.PI / 180;
6298
+ var R2D$4 = 180 / Math.PI;
6299
+ var EPS$1 = 1e-12;
6300
+
6301
+ // Build a forward-only spherical polyhedral projection.
6302
+ //
6303
+ // config.faces contains spherical polygons in degrees. config.parents defines
6304
+ // a spanning tree of attached faces. faceProjector(face) returns a local
6305
+ // projection accepting radians and returning planar coordinates.
6306
+ function createPolyhedralProjection(config) {
6307
+ var faces = config.faces.map(function(coords, i) {
6308
+ return createFace(coords, i, config.faceSites && config.faceSites[i]);
6309
+ });
6310
+ var parents = config.parents;
6311
+ var attachedPairs = new Set();
6312
+ var faceProjector = config.faceProjector;
6313
+
6314
+ faces.forEach(function(face) {
6315
+ face.project = faceProjector(face);
6316
+ });
6317
+
6318
+ faces.forEach(function(face) {
6319
+ initFaceTransform(face.id);
6320
+ });
6321
+
6322
+ function initFaceTransform(faceId) {
6323
+ var parentId = parents[faceId];
6324
+ var face = faces[faceId];
6325
+ if (face.transform) return;
6326
+ if (parentId < 0) {
6327
+ face.transform = identityMatrix();
6328
+ return;
6329
+ }
6330
+ initFaceTransform(parentId);
6331
+ var parent = faces[parentId];
6332
+ var shared = findSharedEdge(face.coords, parent.coords);
6333
+ if (!shared) {
6334
+ throw new Error('Invalid polyhedral face tree');
6335
+ }
6336
+ var childEdge = shared.map(function(p) {
6337
+ return face.project(p[0] * D2R$5, p[1] * D2R$5);
6338
+ });
6339
+ var parentEdge = shared.map(function(p) {
6340
+ return parent.project(p[0] * D2R$5, p[1] * D2R$5);
6341
+ });
6342
+ face.transform = multiplyMatrices(
6343
+ parent.transform,
6344
+ getEdgeTransform(parentEdge, childEdge)
6345
+ );
6346
+ attachedPairs.add(pairKey(faceId, parentId));
6347
+ }
6348
+
6349
+ var rootEdge = findLongestEdge(faces[0].coords);
6350
+ var rootPlanarEdge = rootEdge.map(function(p) {
6351
+ return faces[0].project(p[0] * D2R$5, p[1] * D2R$5);
6352
+ });
6353
+ var sphericalEdgeLength = angularDistance(rootEdge[0], rootEdge[1]);
6354
+ var planarEdgeLength = distance2D(rootPlanarEdge[0], rootPlanarEdge[1]);
6355
+ var scale = sphericalEdgeLength / planarEdgeLength;
6356
+ var planarAngle = (config.angle || 0) * D2R$5;
6357
+ var edgeIndex = indexEdges(faces, attachedPairs);
6358
+ var outline = buildOutline(faces, attachedPairs).map(function(ring) {
6359
+ return ring.map(transformOutputPoint);
6360
+ });
6361
+ var bounds = getBounds(outline);
6362
+ var centerX = (bounds[0] + bounds[2]) / 2;
6363
+ var centerY = (bounds[1] + bounds[3]) / 2;
6364
+
6365
+ // Centering is calculated after scale and planar rotation.
6366
+ outline.forEach(function(ring) {
6367
+ ring.forEach(function(p) {
6368
+ p[0] -= centerX;
6369
+ p[1] -= centerY;
6370
+ });
6371
+ });
6372
+
6373
+ return {
6374
+ faces: faces,
6375
+ outline: outline,
6376
+ forward: forward,
6377
+ findFace: findFace,
6378
+ findTransitionRegion: findTransitionRegion,
6379
+ getTopology: getTopology
6380
+ };
6381
+
6382
+ function forward(lam, phi) {
6383
+ var rotated = rotateRadians(
6384
+ lam,
6385
+ phi,
6386
+ config.rotation[0] * D2R$5,
6387
+ config.rotation[1] * D2R$5,
6388
+ config.rotation[2] * D2R$5
6389
+ );
6390
+ var face = findFaceRotated(rotated[0], rotated[1]);
6391
+ if (!face) return null;
6392
+ var p = face.project(rotated[0], rotated[1]);
6393
+ p = applyMatrix(face.transform, p);
6394
+ p = transformOutputPoint(p);
6395
+ p[0] -= centerX;
6396
+ p[1] -= centerY;
6397
+ return p;
6398
+ }
6399
+
6400
+ function findFace(lam, phi) {
6401
+ var rotated = rotateRadians(
6402
+ lam,
6403
+ phi,
6404
+ config.rotation[0] * D2R$5,
6405
+ config.rotation[1] * D2R$5,
6406
+ config.rotation[2] * D2R$5
6407
+ );
6408
+ var face = findFaceRotated(rotated[0], rotated[1]);
6409
+ return face ? face.id : -1;
6410
+ }
6411
+
6412
+ function findTransitionRegion(lam, phi) {
6413
+ var rotated = rotateRadians(
6414
+ lam,
6415
+ phi,
6416
+ config.rotation[0] * D2R$5,
6417
+ config.rotation[1] * D2R$5,
6418
+ config.rotation[2] * D2R$5
6419
+ );
6420
+ var face = findFaceRotated(rotated[0], rotated[1]);
6421
+ var region;
6422
+ if (!face) return '-1';
6423
+ region = face.project.findRegion ?
6424
+ face.project.findRegion(rotated[0], rotated[1]) :
6425
+ 0;
6426
+ return face.id + ':' + region;
6427
+ }
6428
+
6429
+ function findFaceRotated(lam, phi) {
6430
+ var faceId;
6431
+ if (config.findFace) {
6432
+ faceId = config.findFace(lam, phi);
6433
+ return faceId >= 0 ? faces[faceId] : null;
6434
+ }
6435
+ var p = radiansToVector(lam, phi);
6436
+ for (var i = 0; i < faces.length; i++) {
6437
+ if (faceContainsVector(faces[i], p)) return faces[i];
6438
+ }
6439
+ return null;
6440
+ }
6441
+
6442
+ function transformOutputPoint(p) {
6443
+ var x = p[0] * scale;
6444
+ // Local unfolding uses screen-oriented y coordinates to match the D3
6445
+ // Airocean face layout; return conventional projected coordinates (y up).
6446
+ var y = -p[1] * scale;
6447
+ if (planarAngle) {
6448
+ return [
6449
+ x * Math.cos(planarAngle) - y * Math.sin(planarAngle),
6450
+ x * Math.sin(planarAngle) + y * Math.cos(planarAngle)
6451
+ ];
6452
+ }
6453
+ return [x, y];
6454
+ }
6455
+
6456
+ function getTopology(lon0) {
6457
+ var seams = edgeIndex.map(function(edge) {
6458
+ var endpoints = edge.points.map(function(p) {
6459
+ var q = rotateRadians(
6460
+ p[0] * D2R$5,
6461
+ p[1] * D2R$5,
6462
+ config.rotation[0] * D2R$5,
6463
+ config.rotation[1] * D2R$5,
6464
+ config.rotation[2] * D2R$5,
6465
+ true
6466
+ );
6467
+ return [normalizeLongitude$1(q[0] * R2D$4 + lon0), q[1] * R2D$4];
6468
+ });
6469
+ var paths = splitPathAtAntimeridian$1(
6470
+ interpolateGreatCircle(endpoints[0], endpoints[1], 0.05)
6471
+ );
6472
+ paths.forEach(function(path) {
6473
+ // The mask must be wider than the tiny chord error between samples.
6474
+ path.mask_width = 4e-5;
6475
+ });
6476
+ return {
6477
+ type: edge.attached ? 'attached' : 'cut',
6478
+ faces: edge.faces,
6479
+ paths: paths
6480
+ };
6481
+ });
6482
+ var regionPairs = new Map();
6483
+ edgeIndex.forEach(function(edge) {
6484
+ if (edge.faces.length == 2) {
6485
+ regionPairs.set(pairKey(edge.faces[0], edge.faces[1]), edge.attached);
6486
+ }
6487
+ });
6488
+ return {
6489
+ regions: faces.map(function(face) {
6490
+ return {id: face.id};
6491
+ }),
6492
+ seams: seams,
6493
+ findRegion: function(lon, lat) {
6494
+ var lam = normalizeLongitude$1(lon - lon0) * D2R$5;
6495
+ return findFace(lam, lat * D2R$5);
6496
+ },
6497
+ findTransitionRegion: function(lon, lat) {
6498
+ var lam = normalizeLongitude$1(lon - lon0) * D2R$5;
6499
+ return findTransitionRegion(lam, lat * D2R$5);
6500
+ },
6501
+ regionsAreAttached: function(a, b) {
6502
+ return regionPairs.get(pairKey(a, b)) === true;
6503
+ },
6504
+ outline: outline.map(function(ring) {
6505
+ return ring.map(function(p) {
6506
+ return p.concat();
6507
+ });
6508
+ })
6509
+ };
6510
+ }
6511
+ }
6512
+
6513
+ function rotateSphericalRadians(lam, phi, rotation, invert) {
6514
+ return rotateRadians(
6515
+ lam,
6516
+ phi,
6517
+ (rotation[0] || 0) * D2R$5,
6518
+ (rotation[1] || 0) * D2R$5,
6519
+ (rotation[2] || 0) * D2R$5,
6520
+ invert
6521
+ );
6522
+ }
6523
+
6524
+ function createFace(coords, id, site) {
6525
+ var vectors = coords.map(function(p) {
6526
+ return degreesToVector$2(p[0], p[1]);
6527
+ });
6528
+ var inside = normalizeVector$1(vectors.reduce(function(sum, p) {
6529
+ sum[0] += p[0];
6530
+ sum[1] += p[1];
6531
+ sum[2] += p[2];
6532
+ return sum;
6533
+ }, [0, 0, 0]));
6534
+ var edges = [];
6535
+ for (var i = 0; i < vectors.length; i++) {
6536
+ var a = vectors[i];
6537
+ var b = vectors[(i + 1) % vectors.length];
6538
+ var normal = cross$1(a, b);
6539
+ edges.push({
6540
+ normal: normal,
6541
+ sign: dot$1(normal, inside) < 0 ? -1 : 1
6542
+ });
6543
+ }
6544
+ return {
6545
+ id: id,
6546
+ coords: coords,
6547
+ vectors: vectors,
6548
+ edges: edges,
6549
+ centroid: site || vectorToDegrees$2(inside)
6550
+ };
6551
+ }
6552
+
6553
+ function faceContainsVector(face, p) {
6554
+ for (var i = 0; i < face.edges.length; i++) {
6555
+ var edge = face.edges[i];
6556
+ if (dot$1(edge.normal, p) * edge.sign < -EPS$1) return false;
6557
+ }
6558
+ return true;
6559
+ }
6560
+
6561
+ function indexEdges(faces, attachedPairs) {
6562
+ var index = new Map();
6563
+ faces.forEach(function(face) {
6564
+ for (var i = 0; i < face.coords.length; i++) {
6565
+ var a = face.coords[i];
6566
+ var b = face.coords[(i + 1) % face.coords.length];
6567
+ var key = edgeKey(a, b);
6568
+ var edge = index.get(key);
6569
+ if (!edge) {
6570
+ edge = {points: [a, b], faces: []};
6571
+ index.set(key, edge);
6572
+ }
6573
+ edge.faces.push(face.id);
6574
+ }
6575
+ });
6576
+ return Array.from(index.values()).map(function(edge) {
6577
+ edge.attached = edge.faces.length == 2 &&
6578
+ attachedPairs.has(pairKey(edge.faces[0], edge.faces[1]));
6579
+ return edge;
6580
+ });
6581
+ }
6582
+
6583
+ function buildOutline(faces, attachedPairs) {
6584
+ var edges = [];
6585
+ faces.forEach(function(face) {
6586
+ for (var i = 0; i < face.coords.length; i++) {
6587
+ var a = face.coords[i];
6588
+ var b = face.coords[(i + 1) % face.coords.length];
6589
+ var adjacent = findAdjacentFace(faces, face.id, a, b);
6590
+ if (adjacent >= 0 && attachedPairs.has(pairKey(face.id, adjacent))) continue;
6591
+ edges.push([
6592
+ applyMatrix(face.transform, face.project(a[0] * D2R$5, a[1] * D2R$5)),
6593
+ applyMatrix(face.transform, face.project(b[0] * D2R$5, b[1] * D2R$5))
6594
+ ]);
6595
+ }
6596
+ });
6597
+ return connectEdges(edges);
6598
+ }
6599
+
6600
+ function connectEdges(edges) {
6601
+ var unused = edges.concat();
6602
+ var rings = [];
6603
+ while (unused.length) {
6604
+ var edge = unused.pop();
6605
+ var ring = [edge[0], edge[1]];
6606
+ while (!pointsAlmostEqual(ring[0], ring[ring.length - 1])) {
6607
+ var last = ring[ring.length - 1];
6608
+ var found = -1;
6609
+ var next;
6610
+ for (var i = 0; i < unused.length; i++) {
6611
+ if (pointsAlmostEqual(last, unused[i][0])) {
6612
+ found = i;
6613
+ next = unused[i][1];
6614
+ break;
6615
+ }
6616
+ if (pointsAlmostEqual(last, unused[i][1])) {
6617
+ found = i;
6618
+ next = unused[i][0];
6619
+ break;
6620
+ }
6621
+ }
6622
+ if (found < 0) break;
6623
+ unused.splice(found, 1);
6624
+ ring.push(next);
6625
+ }
6626
+ if (ring.length > 3 && pointsAlmostEqual(ring[0], ring[ring.length - 1])) {
6627
+ ring[ring.length - 1] = ring[0].concat();
6628
+ rings.push(ring);
6629
+ }
6630
+ }
6631
+ return rings;
6632
+ }
6633
+
6634
+ function findAdjacentFace(faces, faceId, a, b) {
6635
+ var key = edgeKey(a, b);
6636
+ for (var i = 0; i < faces.length; i++) {
6637
+ if (i == faceId) continue;
6638
+ var coords = faces[i].coords;
6639
+ for (var j = 0; j < coords.length; j++) {
6640
+ if (edgeKey(coords[j], coords[(j + 1) % coords.length]) == key) return i;
6641
+ }
6642
+ }
6643
+ return -1;
6644
+ }
6645
+
6646
+ function findSharedEdge(a, b) {
6647
+ for (var i = 0; i < a.length; i++) {
6648
+ var p = a[i];
6649
+ for (var j = 0; j < b.length; j++) {
6650
+ var q = a[(i + 1) % a.length];
6651
+ var c = b[j];
6652
+ var d = b[(j + 1) % b.length];
6653
+ if (sameSphericalPoint(p, c) && sameSphericalPoint(q, d) ||
6654
+ sameSphericalPoint(p, d) && sameSphericalPoint(q, c)) {
6655
+ return [p, q];
6656
+ }
6657
+ }
6658
+ }
6659
+ return null;
6660
+ }
6661
+
6662
+ function findLongestEdge(coords) {
6663
+ var longest = null;
6664
+ var max = -Infinity;
6665
+ for (var i = 0; i < coords.length; i++) {
6666
+ var edge = [coords[i], coords[(i + 1) % coords.length]];
6667
+ var length = angularDistance(edge[0], edge[1]);
6668
+ if (length > max) {
6669
+ longest = edge;
6670
+ max = length;
6671
+ }
6672
+ }
6673
+ return longest;
6674
+ }
6675
+
6676
+ function getEdgeTransform(dest, src) {
6677
+ var ux = dest[1][0] - dest[0][0];
6678
+ var uy = dest[1][1] - dest[0][1];
6679
+ var vx = src[1][0] - src[0][0];
6680
+ var vy = src[1][1] - src[0][1];
6681
+ var scale = Math.sqrt((ux * ux + uy * uy) / (vx * vx + vy * vy));
6682
+ var angle = Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
6683
+ var cos = Math.cos(angle) * scale;
6684
+ var sin = Math.sin(angle) * scale;
6685
+ return [
6686
+ cos,
6687
+ sin,
6688
+ dest[0][0] - cos * src[0][0] - sin * src[0][1],
6689
+ -sin,
6690
+ cos,
6691
+ dest[0][1] + sin * src[0][0] - cos * src[0][1]
6692
+ ];
6693
+ }
6694
+
6695
+ function identityMatrix() {
6696
+ return [1, 0, 0, 0, 1, 0];
6697
+ }
6698
+
6699
+ function multiplyMatrices(a, b) {
6700
+ return [
6701
+ a[0] * b[0] + a[1] * b[3],
6702
+ a[0] * b[1] + a[1] * b[4],
6703
+ a[0] * b[2] + a[1] * b[5] + a[2],
6704
+ a[3] * b[0] + a[4] * b[3],
6705
+ a[3] * b[1] + a[4] * b[4],
6706
+ a[3] * b[2] + a[4] * b[5] + a[5]
6707
+ ];
6708
+ }
6709
+
6710
+ function applyMatrix(m, p) {
6711
+ return [
6712
+ m[0] * p[0] + m[1] * p[1] + m[2],
6713
+ m[3] * p[0] + m[4] * p[1] + m[5]
6714
+ ];
6715
+ }
6716
+
6717
+ function rotateRadians(lam, phi, deltaLam, deltaPhi, deltaGamma, invert) {
6718
+ if (invert) {
6719
+ var inv = rotatePhiGamma$1(lam, phi, deltaPhi, deltaGamma, true);
6720
+ return [normalizeRadians$1(inv[0] - deltaLam), inv[1]];
6721
+ }
6722
+ lam = normalizeRadians$1(lam + deltaLam);
6723
+ return rotatePhiGamma$1(lam, phi, deltaPhi, deltaGamma, false);
6724
+ }
6725
+
6726
+ // Based on d3-geo spherical rotation (ISC license).
6727
+ function rotatePhiGamma$1(lam, phi, deltaPhi, deltaGamma, invert) {
6728
+ var cosDeltaPhi = Math.cos(deltaPhi);
6729
+ var sinDeltaPhi = Math.sin(deltaPhi);
6730
+ var cosDeltaGamma = Math.cos(deltaGamma);
6731
+ var sinDeltaGamma = Math.sin(deltaGamma);
6732
+ var cosPhi = Math.cos(phi);
6733
+ var x = Math.cos(lam) * cosPhi;
6734
+ var y = Math.sin(lam) * cosPhi;
6735
+ var z = Math.sin(phi);
6736
+ var k;
6737
+ if (invert) {
6738
+ k = z * cosDeltaGamma - y * sinDeltaGamma;
6739
+ return [
6740
+ Math.atan2(y * cosDeltaGamma + z * sinDeltaGamma,
6741
+ x * cosDeltaPhi + k * sinDeltaPhi),
6742
+ Math.asin(clamp$2(k * cosDeltaPhi - x * sinDeltaPhi, -1, 1))
6743
+ ];
6744
+ }
6745
+ k = z * cosDeltaPhi + x * sinDeltaPhi;
6746
+ return [
6747
+ Math.atan2(y * cosDeltaGamma - k * sinDeltaGamma,
6748
+ x * cosDeltaPhi - z * sinDeltaPhi),
6749
+ Math.asin(clamp$2(k * cosDeltaGamma + y * sinDeltaGamma, -1, 1))
6750
+ ];
6751
+ }
6752
+
6753
+ function interpolateGreatCircle(a, b, interval) {
6754
+ var av = degreesToVector$2(a[0], a[1]);
6755
+ var bv = degreesToVector$2(b[0], b[1]);
6756
+ var angle = Math.acos(clamp$2(dot$1(av, bv), -1, 1));
6757
+ var n = Math.max(1, Math.ceil(angle * R2D$4 / interval));
6758
+ var sinAngle = Math.sin(angle);
6759
+ var points = [];
6760
+ for (var i = 0; i <= n; i++) {
6761
+ var t = i / n;
6762
+ var p;
6763
+ if (sinAngle < EPS$1) {
6764
+ p = av;
6765
+ } else {
6766
+ var ka = Math.sin((1 - t) * angle) / sinAngle;
6767
+ var kb = Math.sin(t * angle) / sinAngle;
6768
+ p = [
6769
+ av[0] * ka + bv[0] * kb,
6770
+ av[1] * ka + bv[1] * kb,
6771
+ av[2] * ka + bv[2] * kb
6772
+ ];
6773
+ }
6774
+ points.push(vectorToDegrees$2(normalizeVector$1(p)));
6775
+ }
6776
+ return points;
6777
+ }
6778
+
6779
+ function splitPathAtAntimeridian$1(path) {
6780
+ var paths = [];
6781
+ var part = [path[0]];
6782
+ for (var i = 1; i < path.length; i++) {
6783
+ var a = path[i - 1];
6784
+ var b = path[i];
6785
+ if (Math.abs(a[0] - b[0]) > 180) {
6786
+ var bLon = b[0] + (b[0] < a[0] ? 360 : -360);
6787
+ var edgeLon = a[0] < 0 ? -180 : 180;
6788
+ var t = (edgeLon - a[0]) / (bLon - a[0]);
6789
+ var lat = a[1] + (b[1] - a[1]) * t;
6790
+ part.push([edgeLon, lat]);
6791
+ paths.push(part);
6792
+ part = [[-edgeLon, lat], b];
6793
+ } else {
6794
+ part.push(b);
6795
+ }
6796
+ }
6797
+ if (part.length > 1) paths.push(part);
6798
+ return paths;
6799
+ }
6800
+
6801
+ function getBounds(rings) {
6802
+ var bounds = [Infinity, Infinity, -Infinity, -Infinity];
6803
+ rings.forEach(function(ring) {
6804
+ ring.forEach(function(p) {
6805
+ bounds[0] = Math.min(bounds[0], p[0]);
6806
+ bounds[1] = Math.min(bounds[1], p[1]);
6807
+ bounds[2] = Math.max(bounds[2], p[0]);
6808
+ bounds[3] = Math.max(bounds[3], p[1]);
6809
+ });
6810
+ });
6811
+ return bounds;
6812
+ }
6813
+
6814
+ function angularDistance(a, b) {
6815
+ return Math.acos(clamp$2(dot$1(degreesToVector$2(a[0], a[1]),
6816
+ degreesToVector$2(b[0], b[1])), -1, 1));
6817
+ }
6818
+
6819
+ function degreesToVector$2(lon, lat) {
6820
+ return radiansToVector(lon * D2R$5, lat * D2R$5);
6821
+ }
6822
+
6823
+ function radiansToVector(lam, phi) {
6824
+ var cosPhi = Math.cos(phi);
6825
+ return [Math.cos(lam) * cosPhi, Math.sin(lam) * cosPhi, Math.sin(phi)];
6826
+ }
6827
+
6828
+ function vectorToDegrees$2(p) {
6829
+ return [
6830
+ Math.atan2(p[1], p[0]) * R2D$4,
6831
+ Math.asin(clamp$2(p[2], -1, 1)) * R2D$4
6832
+ ];
6833
+ }
6834
+
6835
+ function normalizeVector$1(p) {
6836
+ var k = 1 / Math.sqrt(dot$1(p, p));
6837
+ return [p[0] * k, p[1] * k, p[2] * k];
6838
+ }
6839
+
6840
+ function cross$1(a, b) {
6841
+ return [
6842
+ a[1] * b[2] - a[2] * b[1],
6843
+ a[2] * b[0] - a[0] * b[2],
6844
+ a[0] * b[1] - a[1] * b[0]
6845
+ ];
6846
+ }
6847
+
6848
+ function dot$1(a, b) {
6849
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
6850
+ }
6851
+
6852
+ function distance2D(a, b) {
6853
+ var dx = b[0] - a[0];
6854
+ var dy = b[1] - a[1];
6855
+ return Math.sqrt(dx * dx + dy * dy);
6856
+ }
6857
+
6858
+ function sameSphericalPoint(a, b) {
6859
+ return Math.abs(a[0] - b[0]) < EPS$1 && Math.abs(a[1] - b[1]) < EPS$1;
6860
+ }
6861
+
6862
+ function pointsAlmostEqual(a, b) {
6863
+ return Math.abs(a[0] - b[0]) < 1e-9 && Math.abs(a[1] - b[1]) < 1e-9;
6864
+ }
6865
+
6866
+ function pointKey$1(p) {
6867
+ return p[0].toFixed(12) + ',' + p[1].toFixed(12);
6868
+ }
6869
+
6870
+ function edgeKey(a, b) {
6871
+ var ka = pointKey$1(a);
6872
+ var kb = pointKey$1(b);
6873
+ return ka < kb ? ka + '|' + kb : kb + '|' + ka;
6874
+ }
6875
+
6876
+ function pairKey(a, b) {
6877
+ return a < b ? a + '~' + b : b + '~' + a;
6878
+ }
6879
+
6880
+ function normalizeRadians$1(lam) {
6881
+ while (lam > Math.PI) lam -= Math.PI * 2;
6882
+ while (lam < -Math.PI) lam += Math.PI * 2;
6883
+ return lam;
6884
+ }
6885
+
6886
+ function normalizeLongitude$1(lon) {
6887
+ while (lon > 180) lon -= 360;
6888
+ while (lon < -180) lon += 360;
6889
+ return lon;
6890
+ }
6891
+
6892
+ function clamp$2(val, min, max) {
6893
+ return Math.max(min, Math.min(max, val));
6894
+ }
6895
+
6896
+ /*
6897
+ * Buckminster Fuller's Airocean arrangement of the icosahedron.
6898
+ *
6899
+ * Face layout adapted from d3-geo-polygon (ISC license):
6900
+ * https://github.com/d3/d3-geo-polygon/blob/main/src/airocean.js
6901
+ *
6902
+ * The Gray-Fuller raw transform below is based on Robert W. Gray's published
6903
+ * equations and Philippe Rivière's public-domain clean-room implementation:
6904
+ * https://github.com/d3/d3-geo-polygon/blob/main/src/grayfuller.js
6905
+ */
6906
+
6907
+
6908
+ var D2R$4 = Math.PI / 180;
6909
+ var R2D$3 = 180 / Math.PI;
6910
+ var SQRT3 = Math.sqrt(3);
6911
+ var GRAY_Z = Math.sqrt(5 + 2 * Math.sqrt(5)) / Math.sqrt(15);
6912
+ var GRAY_EL = Math.sqrt(8) / Math.sqrt(5 + Math.sqrt(5));
6913
+ var GRAY_DVE = Math.sqrt(3 + Math.sqrt(5)) / Math.sqrt(5 + Math.sqrt(5));
6914
+ var ROTATION = [-83.65929, 25.44458, -87.45184];
6915
+ var PARENTS = [
6916
+ -1, 0, 1, 11, 13,
6917
+ 6, 7, 1, 7, 8,
6918
+ 9, 10, 11, 12, 13,
6919
+ 6, 8, 10, 17, 21,
6920
+ 16, 15, 19, 19
6921
+ ];
6922
+ var engines$1 = {};
6923
+
6924
+ function registerDymaxionProjections(mproj) {
6925
+ // Some unit tests import source modules directly in an ESM context where
6926
+ // optional CommonJS dependencies are intentionally unavailable.
6927
+ if (!mproj) return;
6928
+ register('dymaxion', 'Dymaxion (Gray-Fuller facets)', 'fuller');
6929
+ register('dymaxion2', 'Dymaxion (Gnomonic facets)', 'gnomonic');
6930
+
6931
+ function register(id, name, method) {
6932
+ if (mproj.internal.pj_list[id]) return;
6933
+ mproj.pj_add(function(P) {
6934
+ initDymaxion(P, method);
6935
+ }, id, name);
6936
+ }
6937
+ }
6938
+
6939
+ function getDymaxionEngine(method) {
6940
+ method = method || 'fuller';
6941
+ if (!engines$1[method]) {
6942
+ var data = createAiroceanFaces();
6943
+ engines$1[method] = createPolyhedralProjection({
6944
+ faces: data.faces,
6945
+ faceSites: data.sites,
6946
+ parents: PARENTS,
6947
+ rotation: ROTATION,
6948
+ angle: -60,
6949
+ faceProjector: method == 'gnomonic' ?
6950
+ createGnomonicFaceProjector :
6951
+ createGrayFullerFaceProjector
6952
+ });
6953
+ }
6954
+ return engines$1[method];
6955
+ }
6956
+
6957
+ function initDymaxion(P, method) {
6958
+ var engine = getDymaxionEngine(method);
6959
+ P.es = 0;
6960
+ P.inv = null;
6961
+ P.fwd = function(lp, xy) {
6962
+ var p = engine.forward(lp.lam, lp.phi);
6963
+ if (!p) {
6964
+ xy.x = xy.y = Infinity;
6965
+ } else {
6966
+ xy.x = p[0];
6967
+ xy.y = p[1];
6968
+ }
6969
+ };
6970
+ P.__projection_topology = engine.getTopology(P.lam0 * R2D$3);
6971
+ P.__projected_outline = engine.outline;
6972
+ }
6973
+
6974
+ function createAiroceanFaces() {
6975
+ var theta = Math.atan(0.5) * R2D$3;
6976
+ var vertices = [[0, 90], [0, -90]];
6977
+ var faces;
6978
+ var sites;
6979
+ for (var i = 0; i < 10; i++) {
6980
+ vertices.push([(i * 36 + 180) % 360 - 180, i & 1 ? theta : -theta]);
6981
+ }
6982
+ faces = [
6983
+ [0, 3, 11],
6984
+ [0, 5, 3],
6985
+ [0, 7, 5],
6986
+ [0, 9, 7],
6987
+ [0, 11, 9],
6988
+ [2, 11, 3],
6989
+ [3, 4, 2],
6990
+ [4, 3, 5],
6991
+ [5, 6, 4],
6992
+ [6, 5, 7],
6993
+ [7, 8, 6],
6994
+ [8, 7, 9],
6995
+ [9, 10, 8],
6996
+ [10, 9, 11],
6997
+ [11, 2, 10],
6998
+ [1, 2, 4],
6999
+ [1, 4, 6],
7000
+ [1, 6, 8],
7001
+ [1, 8, 10],
7002
+ [1, 10, 2]
7003
+ ].map(function(ids) {
7004
+ return ids.map(function(id) {
7005
+ return vertices[id];
7006
+ });
7007
+ });
7008
+ sites = faces.map(getSphericalCentroid);
7009
+ splitAiroceanFaces(faces, sites);
7010
+ return {faces: faces, sites: sites};
7011
+ }
7012
+
7013
+ function splitAiroceanFaces(faces, sites) {
7014
+ var face = faces[15];
7015
+ var site = sites[15];
7016
+ var original = face.concat();
7017
+ face[0] = site;
7018
+ faces.push([original[0], site, original[2]]);
7019
+ sites.push(site);
7020
+ faces.push([original[0], original[1], site]);
7021
+ sites.push(site);
7022
+
7023
+ face = faces[14];
7024
+ site = sites[14];
7025
+ original = face.concat();
7026
+ var mid = getGreatCircleMidpoint(face[1], face[2]);
7027
+ face[1] = mid;
7028
+ faces.push([original[0], original[1], mid]);
7029
+ sites.push(site);
7030
+
7031
+ face = faces[19];
7032
+ site = sites[19];
7033
+ original = face.concat();
7034
+ face[1] = mid;
7035
+ faces.push([mid, original[0], original[1]]);
7036
+ sites.push(site);
7037
+ }
7038
+
7039
+ function createGrayFullerFaceProjector(face) {
7040
+ var c = face.centroid;
7041
+ var direction = Math.abs(c[1] - 52.62) < 1 ||
7042
+ Math.abs(c[1] + 10.81) < 1 ? 0 : 60;
7043
+ var rotation = [-c[0], -c[1], direction];
7044
+ return function(lam, phi) {
7045
+ var p = rotateSphericalRadians(lam, phi, rotation);
7046
+ var q = grayFullerRaw(p[0], p[1]);
7047
+ // D3's face projections use screen-oriented local y coordinates; retaining
7048
+ // that convention reproduces the published Airocean unfolding matrices.
7049
+ return [q[0], -q[1]];
7050
+ };
7051
+ }
7052
+
7053
+ function createGnomonicFaceProjector(face) {
7054
+ var c = face.centroid;
7055
+ var rotation = [-c[0], -c[1], 0];
7056
+ return function(lam, phi) {
7057
+ var p = rotateSphericalRadians(lam, phi, rotation);
7058
+ var cosPhi = Math.cos(p[1]);
7059
+ var k = cosPhi * Math.cos(p[0]);
7060
+ return [
7061
+ cosPhi * Math.sin(p[0]) / k,
7062
+ -Math.sin(p[1]) / k
7063
+ ];
7064
+ };
7065
+ }
7066
+
7067
+ function grayFullerRaw(lam, phi) {
7068
+ var cosPhi = Math.cos(phi);
7069
+ var s = GRAY_Z / (cosPhi * Math.cos(lam));
7070
+ var x = cosPhi * Math.sin(lam) * s;
7071
+ var y = Math.sin(phi) * s;
7072
+ var a1p = Math.atan2(2 * y / SQRT3 + GRAY_EL / 3 - GRAY_EL / 2, GRAY_DVE);
7073
+ var a2p = Math.atan2(x - y / SQRT3 + GRAY_EL / 3 - GRAY_EL / 2, GRAY_DVE);
7074
+ var a3p = Math.atan2(GRAY_EL / 3 - x - y / SQRT3 - GRAY_EL / 2, GRAY_DVE);
7075
+ return [SQRT3 * (a2p - a3p), 2 * a1p - a2p - a3p];
7076
+ }
7077
+
7078
+ function getSphericalCentroid(coords) {
7079
+ var sum = [0, 0, 0];
7080
+ coords.forEach(function(p) {
7081
+ var v = degreesToVector$1(p);
7082
+ sum[0] += v[0];
7083
+ sum[1] += v[1];
7084
+ sum[2] += v[2];
7085
+ });
7086
+ return vectorToDegrees$1(normalize(sum));
7087
+ }
7088
+
7089
+ function getGreatCircleMidpoint(a, b) {
7090
+ var av = degreesToVector$1(a);
7091
+ var bv = degreesToVector$1(b);
7092
+ return vectorToDegrees$1(normalize([
7093
+ av[0] + bv[0],
7094
+ av[1] + bv[1],
7095
+ av[2] + bv[2]
7096
+ ]));
7097
+ }
7098
+
7099
+ function degreesToVector$1(p) {
7100
+ var lam = p[0] * D2R$4;
7101
+ var phi = p[1] * D2R$4;
7102
+ var cosPhi = Math.cos(phi);
7103
+ return [Math.cos(lam) * cosPhi, Math.sin(lam) * cosPhi, Math.sin(phi)];
7104
+ }
7105
+
7106
+ function vectorToDegrees$1(p) {
7107
+ return [
7108
+ Math.atan2(p[1], p[0]) * R2D$3,
7109
+ Math.asin(Math.max(-1, Math.min(1, p[2]))) * R2D$3
7110
+ ];
7111
+ }
7112
+
7113
+ function normalize(p) {
7114
+ var k = 1 / Math.sqrt(p[0] * p[0] + p[1] * p[1] + p[2] * p[2]);
7115
+ return [p[0] * k, p[1] * k, p[2] * k];
7116
+ }
7117
+
7118
+ var Dymaxion = /*#__PURE__*/Object.freeze({
7119
+ __proto__: null,
7120
+ getDymaxionEngine: getDymaxionEngine,
7121
+ registerDymaxionProjections: registerDymaxionProjections
7122
+ });
7123
+
7124
+ /*
7125
+ * Vertex-oriented great-circle "slice-and-dice" projection.
7126
+ *
7127
+ * Based on van Leeuwen and Strebe (2006), with the vector formulation
7128
+ * described by Hall et al. (2020) and implemented independently by DGGAL.
7129
+ * https://doi.org/10.1559/152304006779500687
7130
+ * https://doi.org/10.3390/ijgi9050315
7131
+ */
7132
+
7133
+
7134
+ function createGnomonicProjector(center) {
7135
+ var rotation = [-center[0], -center[1], 0];
7136
+ return function(lam, phi) {
7137
+ var p = rotateSphericalRadians(lam, phi, rotation);
7138
+ var cosPhi = Math.cos(p[1]);
7139
+ var k = cosPhi * Math.cos(p[0]);
7140
+ return [
7141
+ cosPhi * Math.sin(p[0]) / k,
7142
+ -Math.sin(p[1]) / k
7143
+ ];
7144
+ };
7145
+ }
7146
+
7147
+ /*
7148
+ * Cahill-Keyes 12-zone forward transform.
7149
+ *
7150
+ * Implemented in Perl by Mary Jo Graça (2011), ported to D3.js by
7151
+ * Enrico Spinielli (2013), and adapted here from d3-geo-polygon.
7152
+ *
7153
+ * Copyright 2017-2024 Mike Bostock
7154
+ * ISC License: https://github.com/d3/d3-geo-polygon/blob/main/LICENSE
7155
+ */
7156
+
7157
+ var D2R$3 = Math.PI / 180;
7158
+ var R2D$2 = 180 / Math.PI;
7159
+
7160
+ function createCahillKeyesRaw(mg) {
7161
+ return createCahillKeyesTransform(mg, false);
7162
+ }
7163
+
7164
+ function createCahillKeyesTransform(mg, faceOnly) {
7165
+ var CK = {lengthMG: mg};
7166
+ init();
7167
+
7168
+ return function(lambda, phi) {
7169
+ if (faceOnly) {
7170
+ var lon = lambda * R2D$2;
7171
+ var side = lon < 0 ? -1 : lon > 0 ? 1 : 0;
7172
+ var local = mp2xy(Math.abs(lon), Math.abs(phi * R2D$2));
7173
+ return [local[0], side * local[1]];
7174
+ }
7175
+ var res = ll2mp(lambda * R2D$2, phi * R2D$2);
7176
+ var xy = mp2xy(res[0], res[1]);
7177
+ var p = [xy[0], res[2] * xy[1]];
7178
+ return mj2g(p, res[3]);
7179
+ };
7180
+
7181
+ function init() {
7182
+ var k = Math.sqrt(3);
7183
+ var pointN, lengthMB, lengthMN, lengthNG, pointU;
7184
+ var p73a, lF, lT, lM, l, pointV;
7185
+ CK.lengthMA = 0.094 * CK.lengthMG;
7186
+ CK.lengthParallel0to73At0 = CK.lengthMG / 100;
7187
+ CK.lengthParallel73to90At0 =
7188
+ (CK.lengthMG - CK.lengthMA - CK.lengthParallel0to73At0 * 73) / 17;
7189
+ CK.sin60 = k / 2;
7190
+ CK.cos60 = 0.5;
7191
+ CK.pointM = [0, 0];
7192
+ CK.pointG = [CK.lengthMG, 0];
7193
+ pointN = [CK.lengthMG, CK.lengthMG * Math.tan(30 * D2R$3)];
7194
+ CK.pointA = [CK.lengthMA, 0];
7195
+ CK.pointB = lineIntersection(CK.pointM, 30, CK.pointA, 45);
7196
+ CK.lengthAG = distance(CK.pointA, CK.pointG);
7197
+ CK.lengthAB = distance(CK.pointA, CK.pointB);
7198
+ lengthMB = distance(CK.pointM, CK.pointB);
7199
+ lengthMN = distance(CK.pointM, pointN);
7200
+ lengthNG = distance(pointN, CK.pointG);
7201
+ CK.pointD = interpolate(lengthMB, lengthMN, pointN, CK.pointM);
7202
+ CK.pointF = [CK.lengthMG, lengthNG - lengthMB];
7203
+ CK.pointE = [
7204
+ pointN[0] - CK.lengthMA * Math.sin(30 * D2R$3),
7205
+ pointN[1] - CK.lengthMA * Math.cos(30 * D2R$3)
7206
+ ];
7207
+ CK.lengthGF = distance(CK.pointG, CK.pointF);
7208
+ CK.lengthBD = distance(CK.pointB, CK.pointD);
7209
+ CK.lengthGFE = CK.lengthGF + CK.lengthAB;
7210
+ CK.deltaMEq = CK.lengthGFE / 45;
7211
+ CK.lengthAP75 = 15 * CK.lengthParallel73to90At0;
7212
+ CK.lengthAP73 = CK.lengthMG - CK.lengthMA -
7213
+ CK.lengthParallel0to73At0 * 73;
7214
+ pointU = [
7215
+ CK.pointA[0] + CK.lengthAP73 * Math.cos(30 * D2R$3),
7216
+ CK.pointA[1] + CK.lengthAP73 * Math.sin(30 * D2R$3)
7217
+ ];
7218
+ CK.pointT = lineIntersection(pointU, -60, CK.pointB, 30);
7219
+
7220
+ p73a = parallel73(29);
7221
+ lF = p73a.lengthParallel73;
7222
+ lT = lengthTorridSegment(29);
7223
+ lM = lengthMiddleSegment(29);
7224
+ l = 15 * (lT + lM + lF) / 73 - lT;
7225
+ pointV = interpolate(l, lM, jointT(29), jointF(29));
7226
+ CK.pointC = [0, 0];
7227
+ CK.pointC[1] = (
7228
+ pointV[0] * pointV[0] + pointV[1] * pointV[1] -
7229
+ CK.pointD[0] * CK.pointD[0] - CK.pointD[1] * CK.pointD[1]
7230
+ ) / (
7231
+ 2 * (k * pointV[0] + pointV[1] -
7232
+ k * CK.pointD[0] - CK.pointD[1])
7233
+ );
7234
+ CK.pointC[0] = k * CK.pointC[1];
7235
+ CK.radius = distance(CK.pointC, CK.pointD);
7236
+ }
7237
+
7238
+ function ll2mp(lon, lat) {
7239
+ var south = [0, 6, 7, 8, 5];
7240
+ var octant = truncate((lon + 180) / 90 + 1);
7241
+ var meridian = (lon + 720) % 90 - 45;
7242
+ var side = meridian < 0 ? -1 : meridian > 0 ? 1 : 0;
7243
+ meridian = Math.abs(meridian);
7244
+ if (octant === 5) octant = 1;
7245
+ if (lat < 0) octant = south[octant];
7246
+ return [meridian, Math.abs(lat), side, octant];
7247
+ }
7248
+
7249
+ function mp2xy(m, p) {
7250
+ if (m === 0) return p >= 75 ? zoneA(m, p) : zoneB(m, p);
7251
+ if (p >= 75) return zoneC(m, p);
7252
+ if (p === 0) return zoneD(m);
7253
+ if (p >= 73 && m <= 30) return zoneE(m, p);
7254
+ if (m === 45) {
7255
+ return p <= 15 ? zoneF(m, p) :
7256
+ p <= 73 ? zoneG(m, p) : zoneH(m, p);
7257
+ }
7258
+ if (m <= 29) return zoneI(m, p);
7259
+ if (p >= 73) return zoneJ(m, p);
7260
+ var lT = lengthTorridSegment(m);
7261
+ var hit = circleLineIntersection(
7262
+ CK.pointC, CK.radius, jointT(m), jointF(m)
7263
+ );
7264
+ var l15;
7265
+ if (hit[0]) {
7266
+ l15 = lT + distance(jointT(m), hit[1]);
7267
+ } else {
7268
+ hit = circleLineIntersection(
7269
+ CK.pointC, CK.radius, jointE(m), jointT(m)
7270
+ );
7271
+ l15 = lT - distance(jointT(m), hit[1]);
7272
+ }
7273
+ return p <= 15 ? zoneK(m, p, l15) : zoneL(m, p, l15);
7274
+ }
7275
+
7276
+ function zoneA(m, p) {
7277
+ return [CK.pointA[0] + (90 - p) * 104, 0];
7278
+ }
7279
+
7280
+ function zoneB(m, p) {
7281
+ return [CK.pointG[0] - p * 100, 0];
7282
+ }
7283
+
7284
+ function zoneC(m, p) {
7285
+ return radialPoint(CK.pointA, 104 * (90 - p), m);
7286
+ }
7287
+
7288
+ function zoneD(m) {
7289
+ return equator(m);
7290
+ }
7291
+
7292
+ function zoneE(m, p) {
7293
+ return radialPoint(CK.pointA, 1560 + (75 - p) * 100, m);
7294
+ }
7295
+
7296
+ function zoneF(m, p) {
7297
+ return interpolate(p, 15, CK.pointE, CK.pointD);
7298
+ }
7299
+
7300
+ function zoneG(m, p) {
7301
+ return interpolate(p - 15, 58, CK.pointD, CK.pointT);
7302
+ }
7303
+
7304
+ function zoneH(m, p) {
7305
+ var p75 = parallel75(45);
7306
+ var p73 = parallel73(m).parallel73;
7307
+ var lF = distance(CK.pointT, CK.pointB);
7308
+ var lF75 = distance(CK.pointB, p75);
7309
+ var l = (75 - p) * (lF75 + lF) / 2;
7310
+ return l <= lF75 ?
7311
+ interpolate(l, lF75, p75, CK.pointB) :
7312
+ interpolate(l - lF75, lF, CK.pointB, p73);
7313
+ }
7314
+
7315
+ function zoneI(m, p) {
7316
+ var p73a = parallel73(m);
7317
+ var lT = lengthTorridSegment(m);
7318
+ var lM = lengthMiddleSegment(m);
7319
+ var l = p * (lT + lM + p73a.lengthParallel73) / 73;
7320
+ if (l <= lT) return interpolate(l, lT, jointE(m), jointT(m));
7321
+ if (l <= lT + lM) {
7322
+ return interpolate(l - lT, lM, jointT(m), jointF(m));
7323
+ }
7324
+ return interpolate(
7325
+ l - lT - lM,
7326
+ p73a.lengthParallel73,
7327
+ jointF(m),
7328
+ p73a.parallel73
7329
+ );
7330
+ }
7331
+
7332
+ function zoneJ(m, p) {
7333
+ var p75 = parallel75(m);
7334
+ var p73a = parallel73(m);
7335
+ var lF75 = distance(jointF(m), p75);
7336
+ var l = (75 - p) * (lF75 - p73a.lengthParallel73) / 2;
7337
+ return l <= lF75 ?
7338
+ interpolate(l, lF75, p75, jointF(m)) :
7339
+ interpolate(
7340
+ l - lF75,
7341
+ -p73a.lengthParallel73,
7342
+ jointF(m),
7343
+ p73a.parallel73
7344
+ );
7345
+ }
7346
+
7347
+ function zoneK(m, p, l15) {
7348
+ var l = p * l15 / 15;
7349
+ var lT = lengthTorridSegment(m);
7350
+ var lM = lengthMiddleSegment(m);
7351
+ return l <= lT ?
7352
+ interpolate(l, lT, jointE(m), jointT(m)) :
7353
+ interpolate(l - lT, lM, jointT(m), jointF(m));
7354
+ }
7355
+
7356
+ function zoneL(m, p, l15) {
7357
+ var p73a = parallel73(m);
7358
+ var lT = lengthTorridSegment(m);
7359
+ var lM = lengthMiddleSegment(m);
7360
+ var lF = p73a.lengthParallel73;
7361
+ var l = l15 + (p - 15) * (lT + lM + lF - l15) / 58;
7362
+ if (l <= lT) return interpolate(l, lT, jointE(m), jointF(m));
7363
+ if (l <= lT + lM) {
7364
+ return interpolate(l - lT, lM, jointT(m), jointF(m));
7365
+ }
7366
+ return interpolate(
7367
+ l - lT - lM, lF, jointF(m), p73a.parallel73
7368
+ );
7369
+ }
7370
+
7371
+ function equator(m) {
7372
+ var l = CK.deltaMEq * m;
7373
+ return l <= CK.lengthGF ?
7374
+ [CK.pointG[0], l] :
7375
+ interpolate(l - CK.lengthGF, CK.lengthAB, CK.pointF, CK.pointE);
7376
+ }
7377
+
7378
+ function jointE(m) {
7379
+ return equator(m);
7380
+ }
7381
+
7382
+ function jointT(m) {
7383
+ return lineIntersection(CK.pointM, 2 * m / 3, jointE(m), m / 3);
7384
+ }
7385
+
7386
+ function jointF(m) {
7387
+ if (m === 0) return [CK.pointA[0] + CK.lengthAB, 0];
7388
+ return lineIntersection(CK.pointA, m, CK.pointM, 2 * m / 3);
7389
+ }
7390
+
7391
+ function lengthTorridSegment(m) {
7392
+ return distance(jointE(m), jointT(m));
7393
+ }
7394
+
7395
+ function lengthMiddleSegment(m) {
7396
+ return distance(jointT(m), jointF(m));
7397
+ }
7398
+
7399
+ function parallel73(m) {
7400
+ var p73, lF, xy;
7401
+ var jF = jointF(m);
7402
+ if (m <= 30) {
7403
+ p73 = radialPoint(CK.pointA, CK.lengthAP73, m);
7404
+ lF = distance(jF, p73);
7405
+ } else {
7406
+ p73 = lineIntersection(CK.pointT, -60, jF, m);
7407
+ lF = distance(jF, p73);
7408
+ if (m > 44) {
7409
+ xy = lineIntersection(CK.pointT, -60, jF, 2 * m / 3);
7410
+ if (xy[0] > p73[0]) {
7411
+ p73 = xy;
7412
+ lF = -distance(jF, p73);
7413
+ }
7414
+ }
7415
+ }
7416
+ return {parallel73: p73, lengthParallel73: lF};
7417
+ }
7418
+
7419
+ function parallel75(m) {
7420
+ return radialPoint(CK.pointA, CK.lengthAP75, m);
7421
+ }
7422
+
7423
+ function mj2g(xy, octant) {
7424
+ var out;
7425
+ if (octant === 0) {
7426
+ out = rotate(xy, -60);
7427
+ } else if (octant === 1) {
7428
+ out = rotate(xy, -120);
7429
+ out[0] -= CK.lengthMG;
7430
+ } else if (octant === 2) {
7431
+ out = rotate(xy, -60);
7432
+ out[0] -= CK.lengthMG;
7433
+ } else if (octant === 3) {
7434
+ out = rotate(xy, -120);
7435
+ out[0] += CK.lengthMG;
7436
+ } else if (octant === 4) {
7437
+ out = rotate(xy, -60);
7438
+ out[0] += CK.lengthMG;
7439
+ } else if (octant === 5) {
7440
+ out = rotate([2 * CK.lengthMG - xy[0], xy[1]], -60);
7441
+ out[0] += CK.lengthMG;
7442
+ } else if (octant === 6) {
7443
+ out = rotate([2 * CK.lengthMG - xy[0], xy[1]], -120);
7444
+ out[0] -= CK.lengthMG;
7445
+ } else if (octant === 7) {
7446
+ out = rotate([2 * CK.lengthMG - xy[0], xy[1]], -60);
7447
+ out[0] -= CK.lengthMG;
7448
+ } else if (octant === 8) {
7449
+ out = rotate([2 * CK.lengthMG - xy[0], xy[1]], -120);
7450
+ out[0] += CK.lengthMG;
7451
+ }
7452
+ return out;
7453
+ }
7454
+
7455
+ function radialPoint(origin, length, angle) {
7456
+ return [
7457
+ origin[0] + length * Math.cos(angle * D2R$3),
7458
+ origin[1] + length * Math.sin(angle * D2R$3)
7459
+ ];
7460
+ }
7461
+
7462
+ function rotate(xy, angle) {
7463
+ if (angle === -60) {
7464
+ return [
7465
+ xy[0] * CK.cos60 + xy[1] * CK.sin60,
7466
+ -xy[0] * CK.sin60 + xy[1] * CK.cos60
7467
+ ];
7468
+ }
7469
+ if (angle === -120) {
7470
+ return [
7471
+ -xy[0] * CK.cos60 + xy[1] * CK.sin60,
7472
+ -xy[0] * CK.sin60 - xy[1] * CK.cos60
7473
+ ];
7474
+ }
7475
+ var cos = Math.cos(angle * D2R$3);
7476
+ var sin = Math.sin(angle * D2R$3);
7477
+ return [xy[0] * cos - xy[1] * sin, xy[0] * sin + xy[1] * cos];
7478
+ }
7479
+ }
7480
+
7481
+ function createCahillKeyesFaceRaw(mg) {
7482
+ return createCahillKeyesTransform(mg, true);
7483
+ }
7484
+
7485
+ function distance(a, b) {
7486
+ return Math.hypot(a[0] - b[0], a[1] - b[1]);
7487
+ }
7488
+
7489
+ function interpolate(length, total, start, end) {
7490
+ return [
7491
+ start[0] + (end[0] - start[0]) * length / total,
7492
+ start[1] + (end[1] - start[1]) * length / total
7493
+ ];
7494
+ }
7495
+
7496
+ function lineIntersection(p1, slope1, p2, slope2) {
7497
+ var m1 = Math.tan(slope1 * D2R$3);
7498
+ var m2 = Math.tan(slope2 * D2R$3);
7499
+ var x = (m1 * p1[0] - m2 * p2[0] - p1[1] + p2[1]) / (m1 - m2);
7500
+ return [x, m1 * (x - p1[0]) + p1[1]];
7501
+ }
7502
+
7503
+ function circleLineIntersection(center, radius, p1, p2) {
7504
+ var dx = p2[0] - p1[0];
7505
+ var dy = p2[1] - p1[1];
7506
+ var fx = p1[0] - center[0];
7507
+ var fy = p1[1] - center[1];
7508
+ var a = dx * dx + dy * dy;
7509
+ var b = 2 * (dx * fx + dy * fy);
7510
+ var c = fx * fx + fy * fy - radius * radius;
7511
+ var d = b * b - 4 * a * c;
7512
+ if (a === 0 || d < 0) return [0, [0, 0]];
7513
+ var root = Math.sqrt(d);
7514
+ var u1 = (-b + root) / (2 * a);
7515
+ var u2 = (-b - root) / (2 * a);
7516
+ var u = u1 >= 0 && u1 <= 1 ? u1 :
7517
+ u2 >= 0 && u2 <= 1 ? u2 : null;
7518
+ return u === null ? [0, [0, 0]] :
7519
+ [1, [p1[0] + u * dx, p1[1] + u * dy]];
7520
+ }
7521
+
7522
+ function truncate(n) {
7523
+ return n > 0 ? Math.floor(n) : Math.ceil(n);
7524
+ }
7525
+
7526
+ var D2R$2 = Math.PI / 180;
7527
+ var EPS = 1e-12;
7528
+
7529
+ // Create a smooth facet projection from any radial azimuthal projection.
7530
+ // The azimuthal coordinates are warped back to the gnomonic polygon along
7531
+ // the boundary, so adjacent facets retain identical straight edges.
7532
+ function createRadialFacetProjector(coords, options) {
7533
+ options = options || {};
7534
+ var center = options.planarCenter || getSphericalCenter(coords);
7535
+ var gnomonic = createGnomonicProjector(center);
7536
+ var planar = coords.map(function(p) {
7537
+ return gnomonic(p[0] * D2R$2, p[1] * D2R$2);
7538
+ });
7539
+ var edges = createNormalizedEdges(planar);
7540
+ var radial = createRadialFunction(
7541
+ options.radial || 'equal-area',
7542
+ options.radial2,
7543
+ options.radialBlend
7544
+ );
7545
+ var boundaryStrength = options.boundaryStrength || 1;
7546
+ var radialScale = getRadialScale(planar, radial) *
7547
+ (options.radialScale || 1);
7548
+
7549
+ project.radial = options.radial || 'equal-area';
7550
+ project.boundaryStrength = boundaryStrength;
7551
+ return project;
7552
+
7553
+ function project(lam, phi) {
7554
+ var p = gnomonic(lam, phi);
7555
+ var r = Math.hypot(p[0], p[1]);
7556
+ if (r < EPS) return [0, 0];
7557
+ var c = Math.atan(r);
7558
+ var azimuthalRadius = radial(c) * radialScale;
7559
+ var boundaryWeight = getBoundaryWeight(p, edges, boundaryStrength);
7560
+ var scale = 1 + boundaryWeight * (azimuthalRadius / r - 1);
7561
+ return [p[0] * scale, p[1] * scale];
7562
+ }
7563
+ }
7564
+
7565
+ function createRadialFunction(radial1, radial2, blend) {
7566
+ var f1 = getRadialFunction(radial1);
7567
+ if (!radial2) return f1;
7568
+ var f2 = getRadialFunction(radial2);
7569
+ var k = Math.max(0, Math.min(1, blend == null ? 0.5 : blend));
7570
+ return function(c) {
7571
+ return f1(c) + (f2(c) - f1(c)) * k;
7572
+ };
7573
+ }
7574
+
7575
+ function getRadialFunction(id) {
7576
+ if (typeof id == 'function') return id;
7577
+ id = normalizeRadialProjectionName(id);
7578
+ if (id == 'aeqd') return function(c) {
7579
+ return c;
7580
+ };
7581
+ if (id == 'stere') return function(c) {
7582
+ return 2 * Math.tan(c / 2);
7583
+ };
7584
+ if (id == 'ortho') return function(c) {
7585
+ return Math.sin(c);
7586
+ };
7587
+ if (id == 'gnom') return function(c) {
7588
+ return Math.tan(c);
7589
+ };
7590
+ // Lambert azimuthal equal-area
7591
+ return function(c) {
7592
+ return 2 * Math.sin(c / 2);
7593
+ };
7594
+ }
7595
+
7596
+ function normalizeRadialProjectionName(id) {
7597
+ id = String(id || '').toLowerCase().replace(/_/g, '-');
7598
+ if (id == 'laea' || id == 'lambert' || id == 'equal-area') return 'laea';
7599
+ if (id == 'aeqd' || id == 'equidistant') return 'aeqd';
7600
+ if (id == 'stere' || id == 'stereographic') return 'stere';
7601
+ if (id == 'ortho' || id == 'orthographic') return 'ortho';
7602
+ if (id == 'gnom' || id == 'gnomonic') return 'gnom';
7603
+ throw new Error('Unsupported facet projection: ' + id);
7604
+ }
7605
+
7606
+ function getRadialScale(planar, radial) {
7607
+ var sum = 0;
7608
+ planar.forEach(function(p) {
7609
+ var r = Math.hypot(p[0], p[1]);
7610
+ sum += r / radial(Math.atan(r));
7611
+ });
7612
+ return sum / planar.length;
7613
+ }
7614
+
7615
+ function createNormalizedEdges(planar) {
7616
+ var center = [0, 0];
7617
+ return planar.map(function(a, i) {
7618
+ var b = planar[(i + 1) % planar.length];
7619
+ var dx = b[0] - a[0];
7620
+ var dy = b[1] - a[1];
7621
+ var centerCross = dx * (center[1] - a[1]) -
7622
+ dy * (center[0] - a[0]);
7623
+ return {
7624
+ a: a,
7625
+ dx: dx,
7626
+ dy: dy,
7627
+ denominator: centerCross
7628
+ };
7629
+ });
7630
+ }
7631
+
7632
+ function getBoundaryWeight(p, edges, strength) {
7633
+ var product = 1;
7634
+ for (var i = 0; i < edges.length; i++) {
7635
+ var edge = edges[i];
7636
+ var cross = edge.dx * (p[1] - edge.a[1]) -
7637
+ edge.dy * (p[0] - edge.a[0]);
7638
+ product *= Math.max(0, cross / edge.denominator);
7639
+ }
7640
+ product = Math.max(0, Math.min(1, product));
7641
+ return 1 - Math.pow(1 - product, strength);
7642
+ }
7643
+
7644
+ function getSphericalCenter(coords) {
7645
+ var sum = coords.reduce(function(memo, p) {
7646
+ var lam = p[0] * D2R$2;
7647
+ var phi = p[1] * D2R$2;
7648
+ var cosPhi = Math.cos(phi);
7649
+ memo[0] += Math.cos(lam) * cosPhi;
7650
+ memo[1] += Math.sin(lam) * cosPhi;
7651
+ memo[2] += Math.sin(phi);
7652
+ return memo;
7653
+ }, [0, 0, 0]);
7654
+ return [
7655
+ Math.atan2(sum[1], sum[0]) / D2R$2,
7656
+ Math.atan2(sum[2], Math.hypot(sum[0], sum[1])) / D2R$2
7657
+ ];
7658
+ }
7659
+
7660
+ /*
7661
+ * Octahedral butterfly projections.
7662
+ *
7663
+ * Face layouts are adapted from d3-geo-polygon (ISC license):
7664
+ * https://github.com/d3/d3-geo-polygon
7665
+ *
7666
+ * Copyright 2017-2024 Mike Bostock
7667
+ * ISC License: https://github.com/d3/d3-geo-polygon/blob/main/LICENSE
7668
+ */
7669
+
7670
+
7671
+ var D2R$1 = Math.PI / 180;
7672
+ var R2D$1 = 180 / Math.PI;
7673
+ var RADIAL_BOUNDARY_STRENGTH = 1;
7674
+ var OCTAHEDRON = createOctahedron();
7675
+ var BUTTERFLY_PARENTS = [-1, 0, 0, 1, 0, 1, 4, 5];
7676
+ var DEFAULT_LON0 = {
7677
+ butterfly: 157.5,
7678
+ butterfly2: -20,
7679
+ cahill_keyes: -20
7680
+ };
7681
+ var engines = {};
7682
+
7683
+ function registerButterflyProjections(mproj) {
7684
+ if (!mproj) return;
7685
+ register('butterfly', 'Butterfly projection (Pacific aspect)', 'butterfly');
7686
+ register('butterfly2', 'Butterfly projection (Atlantic aspect)', 'butterfly2');
7687
+ register('cahill_keyes', 'Cahill-Keyes butterfly', 'cahill_keyes');
7688
+
7689
+ function register(id, name, method) {
7690
+ if (mproj.internal.pj_list[id]) return;
7691
+ mproj.pj_add(function(P) {
7692
+ initButterfly(P, method);
7693
+ }, id, name);
7694
+ }
7695
+ }
7696
+
7697
+ function getButterflyEngine(method) {
7698
+ var key = method == 'butterfly2' ? 'butterfly' : method;
7699
+ if (!engines[key]) {
7700
+ if (key == 'cahill_keyes') {
7701
+ engines[key] = createCahillKeyesEngine();
7702
+ } else if (key == 'butterfly') {
7703
+ engines[key] = createButterflyEngine();
7704
+ } else {
7705
+ throw new Error('Unknown butterfly projection: ' + method);
7706
+ }
7707
+ }
7708
+ return engines[key];
7709
+ }
7710
+
7711
+ function initButterfly(P, method) {
7712
+ var engine = getButterflyEngine(method);
7713
+ if (!P.params.lon_0) {
7714
+ P.lam0 = DEFAULT_LON0[method] * D2R$1;
7715
+ }
7716
+ P.es = 0;
7717
+ P.inv = null;
7718
+ P.fwd = function(lp, xy) {
7719
+ var p = engine.forward(lp.lam, lp.phi);
7720
+ if (!p) {
7721
+ xy.x = xy.y = Infinity;
7722
+ } else {
7723
+ xy.x = p[0];
7724
+ xy.y = p[1];
7725
+ }
7726
+ };
7727
+ P.__projection_topology = engine.getTopology(P.lam0 * R2D$1);
7728
+ if (engine.removeOutlineExtremeConnectors) {
7729
+ P.__remove_outline_extreme_connectors = true;
7730
+ }
7731
+ if (engine.useProjectedOutline !== false) {
7732
+ P.__projected_outline = engine.outline;
7733
+ }
7734
+ }
7735
+
7736
+ function createButterflyEngine() {
7737
+ var faceCenters = [-45, 45, -45, 45, -135, 135, -135, 135];
7738
+ // Reuse the Cahill-Keyes polar incisions and 12-zone octant transform,
7739
+ // then unfold the faces with the butterfly attachment tree.
7740
+ var data = createTruncatedOctahedronFaces(
7741
+ Math.cos(17 * D2R$1),
7742
+ Math.sin(17 * D2R$1),
7743
+ BUTTERFLY_PARENTS
7744
+ );
7745
+ var faceRaw = createCahillKeyesFaceRaw(10000);
7746
+ var engine = createPolyhedralProjection({
7747
+ faces: data.faces,
7748
+ parents: data.parents,
7749
+ rotation: [0, 0, 0],
7750
+ // Face-local CK coordinates point opposite the root face.
7751
+ angle: 150,
7752
+ findFace: createTruncatedFaceFinder(data.cornerNormals),
7753
+ faceProjector: function(face) {
7754
+ var baseId = face.id < 8 ? face.id : data.parents[face.id];
7755
+ var center = faceCenters[baseId] * D2R$1;
7756
+ var raw = function(lam, phi) {
7757
+ return faceRaw(normalizeRadians(lam - center), phi);
7758
+ };
7759
+ return createNormalizedFacetProjector(face.coords, raw);
7760
+ }
7761
+ });
7762
+ engine.facetProjection = 'cahill_keyes';
7763
+ return engine;
7764
+ }
7765
+
7766
+ function createNormalizedFacetProjector(coords, raw) {
7767
+ // Remove the global placement and scale of a raw facet while preserving its
7768
+ // internal shape. The polyhedral engine then controls how facets unfold.
7769
+ var points = coords.map(function(p) {
7770
+ return raw(p[0] * D2R$1, p[1] * D2R$1);
7771
+ });
7772
+ var edge = 0;
7773
+ var maxLengthSq = -1;
7774
+ for (var i = 0; i < points.length; i++) {
7775
+ var a = points[i];
7776
+ var b = points[(i + 1) % points.length];
7777
+ var dx = b[0] - a[0];
7778
+ var dy = b[1] - a[1];
7779
+ var lengthSq = dx * dx + dy * dy;
7780
+ if (lengthSq > maxLengthSq) {
7781
+ maxLengthSq = lengthSq;
7782
+ edge = i;
7783
+ }
7784
+ }
7785
+ var origin = points[edge];
7786
+ var end = points[(edge + 1) % points.length];
7787
+ var ux = end[0] - origin[0];
7788
+ var uy = end[1] - origin[1];
7789
+ var center = points.reduce(function(sum, p) {
7790
+ sum[0] += p[0] / points.length;
7791
+ sum[1] += p[1] / points.length;
7792
+ return sum;
7793
+ }, [0, 0]);
7794
+ var side = ux * (center[1] - origin[1]) -
7795
+ uy * (center[0] - origin[0]) < 0 ? -1 : 1;
7796
+ return function(lam, phi) {
7797
+ var p = raw(lam, phi);
7798
+ var x = p[0] - origin[0];
7799
+ var y = p[1] - origin[1];
7800
+ return [
7801
+ (x * ux + y * uy) / maxLengthSq,
7802
+ side * (ux * y - uy * x) / maxLengthSq
7803
+ ];
7804
+ };
7805
+ }
7806
+
7807
+ function createRadialButterflyEngine(options) {
7808
+ options = normalizeRadialFacetOptions(options);
7809
+ var data = createRadialButterflyFaces();
7810
+ var radialOptions = {
7811
+ radial: options.projection,
7812
+ radial2: options.projection2,
7813
+ radialBlend: options.blend,
7814
+ boundaryStrength: RADIAL_BOUNDARY_STRENGTH
7815
+ };
7816
+ var squareProjectors = createSquareProjectors(data.faces, radialOptions);
7817
+ var engine = createPolyhedralProjection({
7818
+ faces: data.faces,
7819
+ parents: data.parents,
7820
+ rotation: [0, 0, 0],
7821
+ angle: -30,
7822
+ findFace: createTruncatedFaceFinder(data.cornerNormals),
7823
+ faceProjector: function(face) {
7824
+ if (face.id < 8) {
7825
+ return createRadialFacetProjector(face.coords, radialOptions);
7826
+ }
7827
+ return squareProjectors.get(pointKey(face.coords[0]));
7828
+ }
7829
+ });
7830
+ engine.radialFacet = {
7831
+ projection: options.projection,
7832
+ projection2: options.projection2,
7833
+ blend: options.blend,
7834
+ boundaryStrength: RADIAL_BOUNDARY_STRENGTH
7835
+ };
7836
+ return engine;
7837
+ }
7838
+
7839
+ function normalizeRadialFacetOptions(options) {
7840
+ options = options || {};
7841
+ var projection2 = options.projection2 ?
7842
+ normalizeRadialProjectionName(options.projection2) :
7843
+ null;
7844
+ var blend = projection2 ?
7845
+ options.blend == null ? 0.5 : Number(options.blend) :
7846
+ 0;
7847
+ if (!Number.isFinite(blend) || blend < 0 || blend > 1) {
7848
+ throw new Error('Facet projection blend must be between 0 and 1');
7849
+ }
7850
+ return {
7851
+ projection: normalizeRadialProjectionName(options.projection || 'laea'),
7852
+ projection2: projection2,
7853
+ blend: blend
7854
+ };
7855
+ }
7856
+
7857
+ function createCahillKeyesEngine() {
7858
+ var data = createTruncatedOctahedronFaces(
7859
+ Math.cos(17 * D2R$1),
7860
+ Math.sin(17 * D2R$1),
7861
+ [-1, 3, 0, 2, 0, 1, 4, 5]
7862
+ );
7863
+ var raw = createCahillKeyesRaw(10000);
7864
+ var engine = createPolyhedralProjection({
7865
+ faces: data.faces,
7866
+ parents: data.parents,
7867
+ rotation: [0, 0, 0],
7868
+ angle: 0,
7869
+ findFace: createTruncatedFaceFinder(data.cornerNormals),
7870
+ faceProjector: function() {
7871
+ return function(lam, phi) {
7872
+ var p = raw(lam, phi);
7873
+ return [p[0], -p[1]];
7874
+ };
7875
+ }
7876
+ });
7877
+ // The 12-zone transform positions all octants directly in its M-profile.
7878
+ // Its face-tree boundary retraces several edges, so derive graticule
7879
+ // footprints through the normal spherical clipping path instead.
7880
+ engine.useProjectedOutline = false;
7881
+ engine.removeOutlineExtremeConnectors = true;
7882
+ return engine;
7883
+ }
7884
+
7885
+ function createRadialButterflyFaces() {
7886
+ return createTruncatedOctahedronFaces(
7887
+ Math.sqrt(0.9),
7888
+ Math.sqrt(0.1),
7889
+ BUTTERFLY_PARENTS
7890
+ );
7891
+ }
7892
+
7893
+ function createTruncatedOctahedronFaces(edgeWeight, vertexWeight, baseParents) {
7894
+ var cornerNormals = [];
7895
+ var faces = OCTAHEDRON.map(function(face) {
7896
+ var vectors = face.map(degreesToVector);
7897
+ var hexagon = [];
7898
+ var a = vectors[vectors.length - 1];
7899
+ vectors.forEach(function(b) {
7900
+ hexagon.push(
7901
+ vectorToDegrees(normalizeVector(addScaledVectors(
7902
+ a, edgeWeight, b, vertexWeight
7903
+ ))),
7904
+ vectorToDegrees(normalizeVector(addScaledVectors(
7905
+ b, edgeWeight, a, vertexWeight
7906
+ )))
7907
+ );
7908
+ a = b;
7909
+ });
7910
+ return hexagon;
7911
+ });
7912
+ canonicalizeVertices(faces);
7913
+ faces.forEach(function(hexagon) {
7914
+ var normals = [];
7915
+ for (var i = 0; i < 3; i++) {
7916
+ normals.push(crossVectors(
7917
+ degreesToVector(hexagon[(i * 2 + 2) % 6]),
7918
+ degreesToVector(hexagon[(i * 2 + 1) % 6])
7919
+ ));
7920
+ }
7921
+ cornerNormals.push(normals);
7922
+ });
7923
+ var parents = baseParents.concat();
7924
+ faces.slice().forEach(function(hexagon, j) {
7925
+ var face = OCTAHEDRON[j];
7926
+ for (var i = 0; i < 3; i++) {
7927
+ faces.push([
7928
+ face[i],
7929
+ hexagon[(i * 2 + 2) % 6],
7930
+ hexagon[(i * 2 + 1) % 6]
7931
+ ]);
7932
+ parents.push(j);
7933
+ }
7934
+ });
7935
+ return {faces: faces, parents: parents, cornerNormals: cornerNormals};
7936
+ }
7937
+
7938
+ function createTruncatedFaceFinder(cornerNormals) {
7939
+ return function(lam, phi) {
7940
+ var cosPhi = Math.cos(phi);
7941
+ var p = [cosPhi * Math.cos(lam), cosPhi * Math.sin(lam), Math.sin(phi)];
7942
+ var face = lam < -Math.PI / 2 ?
7943
+ phi < 0 ? 6 : 4 :
7944
+ lam < 0 ?
7945
+ phi < 0 ? 2 : 0 :
7946
+ lam < Math.PI / 2 ?
7947
+ phi < 0 ? 3 : 1 :
7948
+ phi < 0 ? 7 : 5;
7949
+ var normals = cornerNormals[face];
7950
+ if (dotVectors(normals[0], p) < 0) return 8 + 3 * face;
7951
+ if (dotVectors(normals[1], p) < 0) return 8 + 3 * face + 1;
7952
+ if (dotVectors(normals[2], p) < 0) return 8 + 3 * face + 2;
7953
+ return face;
7954
+ };
7955
+ }
7956
+
7957
+ function canonicalizeVertices(faces) {
7958
+ var index = new Map();
7959
+ faces.forEach(function(face) {
7960
+ face.forEach(function(p, i) {
7961
+ var v = degreesToVector(p);
7962
+ var key = v.map(function(x) {
7963
+ return x.toFixed(12);
7964
+ }).join(',');
7965
+ if (index.has(key)) {
7966
+ face[i] = index.get(key);
7967
+ } else {
7968
+ index.set(key, p);
7969
+ }
7970
+ });
7971
+ });
7972
+ }
7973
+
7974
+ function createSquareProjectors(faces, radialOptions) {
7975
+ var verticesByCenter = new Map();
7976
+ var projectors = new Map();
7977
+ faces.slice(8).forEach(function(face) {
7978
+ var key = pointKey(face[0]);
7979
+ var vertices = verticesByCenter.get(key);
7980
+ if (!vertices) {
7981
+ vertices = new Map();
7982
+ verticesByCenter.set(key, vertices);
7983
+ }
7984
+ vertices.set(pointKey(face[1]), face[1]);
7985
+ vertices.set(pointKey(face[2]), face[2]);
7986
+ });
7987
+ verticesByCenter.forEach(function(vertices, key) {
7988
+ var center = key.split(',').map(Number);
7989
+ var gnomonic = createGnomonicProjector(center);
7990
+ var coords = Array.from(vertices.values()).sort(function(a, b) {
7991
+ return planarAngle(a, gnomonic) - planarAngle(b, gnomonic);
7992
+ });
7993
+ projectors.set(key, createRadialFacetProjector(coords, {
7994
+ planarCenter: center,
7995
+ radial: radialOptions.radial,
7996
+ radial2: radialOptions.radial2,
7997
+ radialBlend: radialOptions.radialBlend,
7998
+ boundaryStrength: radialOptions.boundaryStrength
7999
+ }));
8000
+ });
8001
+ return projectors;
8002
+ }
8003
+
8004
+ function planarAngle(p, project) {
8005
+ var q = project(p[0] * D2R$1, p[1] * D2R$1);
8006
+ return Math.atan2(q[1], q[0]);
8007
+ }
8008
+
8009
+ function createOctahedron() {
8010
+ var vertices = [
8011
+ [0, 90],
8012
+ [-90, 0],
8013
+ [0, 0],
8014
+ [90, 0],
8015
+ [180, 0],
8016
+ [0, -90]
8017
+ ];
8018
+ return [
8019
+ [0, 2, 1],
8020
+ [0, 3, 2],
8021
+ [5, 1, 2],
8022
+ [5, 2, 3],
8023
+ [0, 1, 4],
8024
+ [0, 4, 3],
8025
+ [5, 4, 1],
8026
+ [5, 3, 4]
8027
+ ].map(function(ids) {
8028
+ return ids.map(function(id) {
8029
+ return vertices[id];
8030
+ });
8031
+ });
8032
+ }
8033
+
8034
+ function pointKey(p) {
8035
+ return p[0] + ',' + p[1];
8036
+ }
8037
+
8038
+ function degreesToVector(p) {
8039
+ var lam = p[0] * D2R$1;
8040
+ var phi = p[1] * D2R$1;
8041
+ var cosPhi = Math.cos(phi);
8042
+ return [Math.cos(lam) * cosPhi, Math.sin(lam) * cosPhi, Math.sin(phi)];
8043
+ }
8044
+
8045
+ function vectorToDegrees(p) {
8046
+ return [
8047
+ Math.atan2(p[1], p[0]) * R2D$1,
8048
+ Math.asin(Math.max(-1, Math.min(1, p[2]))) * R2D$1
8049
+ ];
8050
+ }
8051
+
8052
+ function addScaledVectors(a, ka, b, kb) {
8053
+ return [
8054
+ a[0] * ka + b[0] * kb,
8055
+ a[1] * ka + b[1] * kb,
8056
+ a[2] * ka + b[2] * kb
8057
+ ];
8058
+ }
8059
+
8060
+ function crossVectors(a, b) {
8061
+ return [
8062
+ a[1] * b[2] - a[2] * b[1],
8063
+ a[2] * b[0] - a[0] * b[2],
8064
+ a[0] * b[1] - a[1] * b[0]
8065
+ ];
8066
+ }
8067
+
8068
+ function dotVectors(a, b) {
8069
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
8070
+ }
8071
+
8072
+ function normalizeVector(p) {
8073
+ var k = 1 / Math.sqrt(p[0] * p[0] + p[1] * p[1] + p[2] * p[2]);
8074
+ return [p[0] * k, p[1] * k, p[2] * k];
8075
+ }
8076
+
8077
+ function normalizeRadians(lam) {
8078
+ return (lam + Math.PI * 3) % (Math.PI * 2) - Math.PI;
8079
+ }
8080
+
8081
+ var ButterflyProjections = /*#__PURE__*/Object.freeze({
8082
+ __proto__: null,
8083
+ createRadialButterflyEngine: createRadialButterflyEngine,
8084
+ getButterflyEngine: getButterflyEngine,
8085
+ registerButterflyProjections: registerButterflyProjections
8086
+ });
8087
+
6290
8088
  var mproj$1 = require$1('mproj');
8089
+ registerDymaxionProjections(mproj$1);
8090
+ registerButterflyProjections(mproj$1);
6291
8091
 
6292
8092
  var asyncLoader = null;
6293
8093
 
@@ -6456,9 +8256,18 @@
6456
8256
  if (!defn) {
6457
8257
  stop$1("Unknown projection definition:", str);
6458
8258
  }
8259
+ if (utils.isString(defn)) {
8260
+ defn = addDefaultProjectionParams(defn);
8261
+ }
6459
8262
  return defn;
6460
8263
  }
6461
8264
 
8265
+ function addDefaultProjectionParams(defn) {
8266
+ var oceanic = /(^| )\+proj=(igh_o|imoll_o)(?= |$)/.test(defn);
8267
+ var hasLon0 = /(^| )\+lon_0=/.test(defn);
8268
+ return oceanic && !hasLon0 ? defn + ' +lon_0=-160' : defn;
8269
+ }
8270
+
6462
8271
  function looksLikeInitString(str) {
6463
8272
  return /^(esri|epsg|nad83|nad27):[0-9]+$/i.test(String(str));
6464
8273
  }
@@ -32622,6 +34431,10 @@ ${svg}
32622
34431
  describe: 'create a polygon to match the outline of the graticule',
32623
34432
  type: 'flag'
32624
34433
  })
34434
+ .option('outline', {
34435
+ describe: 'create a polyline matching the outline of the graticule',
34436
+ type: 'flag'
34437
+ })
32625
34438
  .option('name', nameOpt)
32626
34439
  .option('target', targetOpt);
32627
34440
 
@@ -41371,7 +43184,7 @@ ${svg}
41371
43184
 
41372
43185
  function getPlanarFilter(interval) {
41373
43186
  return function(a, b) {
41374
- return distance2D(a[0], a[1], b[0], b[1]) <= interval;
43187
+ return distance2D$1(a[0], a[1], b[0], b[1]) <= interval;
41375
43188
  };
41376
43189
  }
41377
43190
 
@@ -41804,7 +43617,7 @@ ${svg}
41804
43617
  }
41805
43618
 
41806
43619
  function probeIsExposed(index, skipA, skipB, vx, vy, p) {
41807
- var r = distance2D(vx, vy, p[0], p[1]);
43620
+ var r = distance2D$1(vx, vy, p[0], p[1]);
41808
43621
  if (!(r > 0)) return false;
41809
43622
  var r2 = r * r * 0.98 * 0.98;
41810
43623
  var px = p[0], py = p[1];
@@ -43386,7 +45199,7 @@ ${svg}
43386
45199
 
43387
45200
  function stopIfBufferReachesPole(shape, dist) {
43388
45201
  var maxAbsLat = 0;
43389
- var angularDist = dist / R$3 * R2D$1;
45202
+ var angularDist = dist / R$3 * R2D$5;
43390
45203
  (shape || []).forEach(function(path) {
43391
45204
  latLngPathIter.init(path);
43392
45205
  while (latLngPathIter.hasNext()) {
@@ -43877,11 +45690,11 @@ ${svg}
43877
45690
  if (a[0] === b[0] && a[1] === b[1]) continue;
43878
45691
  bearing = bearingDegrees2D(a[0], a[1], b[0], b[1]);
43879
45692
  probe = getOffsetPoint(a[0], a[1], bearing - 90, dist);
43880
- rUnits = distance2D(a[0], a[1], probe[0], probe[1]);
45693
+ rUnits = distance2D$1(a[0], a[1], probe[0], probe[1]);
43881
45694
  // perpendicular stab lines every ~0.35r along the band, so cuts
43882
45695
  // affecting only part of a long segment cannot slip between them
43883
45696
  m = Math.max(1, Math.min(60,
43884
- Math.ceil(distance2D(a[0], a[1], b[0], b[1]) / (rUnits * 0.35))));
45697
+ Math.ceil(distance2D$1(a[0], a[1], b[0], b[1]) / (rUnits * 0.35))));
43885
45698
  for (ti = 0; ti < m; ti++) {
43886
45699
  t = (ti + 0.5) / m;
43887
45700
  base = [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
@@ -43915,14 +45728,14 @@ ${svg}
43915
45728
  function findAtRiskSegments(verts, dist) {
43916
45729
  var n = verts.length;
43917
45730
  var probe0 = getOffsetPoint(verts[0][0], verts[0][1], 0, dist);
43918
- var rUnits = distance2D(verts[0][0], verts[0][1], probe0[0], probe0[1]);
45731
+ var rUnits = distance2D$1(verts[0][0], verts[0][1], probe0[0], probe0[1]);
43919
45732
  var cumLen = new Float64Array(n);
43920
45733
  var riskVert = new Uint8Array(n);
43921
45734
  var any = false;
43922
45735
  var i, j, k;
43923
45736
  for (i = 1; i < n; i++) {
43924
45737
  cumLen[i] = cumLen[i - 1] +
43925
- distance2D(verts[i - 1][0], verts[i - 1][1], verts[i][0], verts[i][1]);
45738
+ distance2D$1(verts[i - 1][0], verts[i - 1][1], verts[i][0], verts[i][1]);
43926
45739
  }
43927
45740
  // the ring's start radial and cap-closing radial can cut nearby bands
43928
45741
  riskVert[0] = riskVert[n - 1] = 1;
@@ -43994,7 +45807,7 @@ ${svg}
43994
45807
  j = ids[k];
43995
45808
  if (j <= i) continue;
43996
45809
  var pathDist = cumLen[j] - cumLen[i];
43997
- var d = distance2D(verts[i][0], verts[i][1], verts[j][0], verts[j][1]);
45810
+ var d = distance2D$1(verts[i][0], verts[i][1], verts[j][0], verts[j][1]);
43998
45811
  // 0.8 flags vertex pairs whose intervening path bends by
43999
45812
  // ~130 degrees or more (a circle's chord/arc ratio is 2/pi,
44000
45813
  // so even gentle loops qualify)
@@ -44046,7 +45859,7 @@ ${svg}
44046
45859
  // at the near end is seeded by an upward ray over those same edges.
44047
45860
  function getRingStabber(rings, dist) {
44048
45861
  var probe0 = getOffsetPoint(rings[0][0][0], rings[0][0][1], 0, dist);
44049
- var slabW = distance2D(rings[0][0][0], rings[0][0][1], probe0[0], probe0[1]);
45862
+ var slabW = distance2D$1(rings[0][0][0], rings[0][0][1], probe0[0], probe0[1]);
44050
45863
  var ex = [], ey = [], fx = [], fy = []; // edge data
44051
45864
  var slabs = new Map();
44052
45865
  rings.forEach(function(ring) {
@@ -44146,8 +45959,8 @@ ${svg}
44146
45959
  var eps = dist * 1e-4;
44147
45960
  var aIn = getOffsetPoint(a[0], a[1], bearing, eps);
44148
45961
  var bIn = getOffsetPoint(b[0], b[1], bearing - 180, eps);
44149
- if (distance2D(a[0], a[1], b[0], b[1]) <
44150
- distance2D(a[0], a[1], aIn[0], aIn[1]) * 3) {
45962
+ if (distance2D$1(a[0], a[1], b[0], b[1]) <
45963
+ distance2D$1(a[0], a[1], aIn[0], aIn[1]) * 3) {
44151
45964
  return; // segment too short to inset
44152
45965
  }
44153
45966
  rings.push([
@@ -44544,7 +46357,7 @@ ${svg}
44544
46357
  }
44545
46358
 
44546
46359
  function shallowAngleJoin(a, b, cx, cy, dist) {
44547
- var gap = distance2D(a[0], a[1], b[0], b[1]);
46360
+ var gap = distance2D$1(a[0], a[1], b[0], b[1]);
44548
46361
  var radius = getJoinExtensionDistance(cx, cy, a, b, dist);
44549
46362
  if (gap > radius * 1e-3) return null;
44550
46363
  return [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
@@ -45503,12 +47316,12 @@ ${svg}
45503
47316
  }
45504
47317
 
45505
47318
  function projectBufferPoint(x, y, spherical) {
45506
- return spherical ? [x * D2R$1 * R$3, Math.log(Math.tan(Math.PI / 4 + y * D2R$1 / 2)) * R$3] :
47319
+ return spherical ? [x * D2R$6 * R$3, Math.log(Math.tan(Math.PI / 4 + y * D2R$6 / 2)) * R$3] :
45507
47320
  [x, y];
45508
47321
  }
45509
47322
 
45510
47323
  function unprojectBufferPoint(x, y, spherical) {
45511
- return spherical ? [x / (D2R$1 * R$3), (2 * Math.atan(Math.exp(y / R$3)) - Math.PI / 2) / (D2R$1)] :
47324
+ return spherical ? [x / (D2R$6 * R$3), (2 * Math.atan(Math.exp(y / R$3)) - Math.PI / 2) / (D2R$6)] :
45512
47325
  [x, y];
45513
47326
  }
45514
47327
 
@@ -50179,7 +51992,7 @@ ${svg}
50179
51992
  var maxAbsLat = Math.max(Math.abs(bounds.ymin), Math.abs(bounds.ymax));
50180
51993
  var maxPositiveDistance = getMaxPositiveBufferDistance(lyr, dataset, opts);
50181
51994
  if (!(maxPositiveDistance > 0)) return false;
50182
- return maxAbsLat + maxPositiveDistance / R$3 * R2D$1 >= 90 - POLAR_BUFFER_MARGIN_DEGREES;
51995
+ return maxAbsLat + maxPositiveDistance / R$3 * R2D$5 >= 90 - POLAR_BUFFER_MARGIN_DEGREES;
50183
51996
  }
50184
51997
 
50185
51998
  function getMaxPositiveBufferDistance(lyr, dataset, opts) {
@@ -51431,7 +53244,7 @@ ${svg}
51431
53244
  }
51432
53245
 
51433
53246
  function getCoordinateDistance(distance, arcs) {
51434
- return arcs.isPlanar() ? distance : distance / R$3 * R2D$1;
53247
+ return arcs.isPlanar() ? distance : distance / R$3 * R2D$5;
51435
53248
  }
51436
53249
 
51437
53250
  // @shapeIndex: chunk-bounds index of the source shape (see buildShapeSegmentIndex)
@@ -52099,7 +53912,8 @@ ${svg}
52099
53912
  // TODO: consider projections that may or may not be aligned,
52100
53913
  // depending on parameters
52101
53914
  if (inList(P, 'cassini,gnom,bertin1953,chamb,ob_tran,tpeqd,healpix,rhealpix,' +
52102
- 'ocea,omerc,tmerc,etmerc,nicol')) {
53915
+ 'ocea,omerc,tmerc,etmerc,nicol,dymaxion,dymaxion2,butterfly,butterfly2,' +
53916
+ 'cahill_keyes')) {
52103
53917
  return false;
52104
53918
  }
52105
53919
  if (isAzimuthal(P)) {
@@ -52151,16 +53965,16 @@ ${svg}
52151
53965
  }
52152
53966
 
52153
53967
  function getRotationFunction2(rotation, inv) {
52154
- var a = (rotation[0] || 0) * D2R$1,
52155
- b = (rotation[1] || 0) * D2R$1,
52156
- c = (rotation[2] || 0) * D2R$1;
53968
+ var a = (rotation[0] || 0) * D2R$6,
53969
+ b = (rotation[1] || 0) * D2R$6,
53970
+ c = (rotation[2] || 0) * D2R$6;
52157
53971
  return function(p) {
52158
- p[0] *= D2R$1;
52159
- p[1] *= D2R$1;
53972
+ p[0] *= D2R$6;
53973
+ p[1] *= D2R$6;
52160
53974
  var rotate = inv ? rotatePointInv : rotatePoint;
52161
53975
  rotate(p, a, b, c);
52162
- p[0] *= R2D$1;
52163
- p[1] *= R2D$1;
53976
+ p[0] *= R2D$5;
53977
+ p[1] *= R2D$5;
52164
53978
  return p;
52165
53979
  };
52166
53980
  }
@@ -52573,6 +54387,128 @@ ${svg}
52573
54387
  polygonsToLines: polygonsToLines
52574
54388
  });
52575
54389
 
54390
+ // Geographic topology used to prepare paths for projections whose forward
54391
+ // transform is piecewise. A "cut" seam opens a visible gap; an "attached"
54392
+ // seam (reserved for polyhedral projections) marks a transform boundary that
54393
+ // must gain vertices without disconnecting the path.
54394
+ var landTopology = {
54395
+ regions: [
54396
+ region('northwest', [-180, 0, -40, 90], -100),
54397
+ region('northeast', [-40, 0, 180, 90], 30),
54398
+ region('southwest', [-180, -90, -100, 0], -160),
54399
+ region('south_central_west', [-100, -90, -20, 0], -60),
54400
+ region('south_central_east', [-20, -90, 80, 0], 20),
54401
+ region('southeast', [80, -90, 180, 0], 140)
54402
+ ],
54403
+ seams: [
54404
+ cutSeam([[-40, 0], [-40, 91]]),
54405
+ cutSeam([[-100, -91], [-100, 0]]),
54406
+ cutSeam([[-20, -91], [-20, 0]]),
54407
+ cutSeam([[80, -91], [80, 0]])
54408
+ ]
54409
+ };
54410
+
54411
+ var oceanTopology = {
54412
+ regions: [
54413
+ region('northwest', [-180, 0, -90, 90], -140),
54414
+ region('north_central', [-90, 0, 60, 90], -10),
54415
+ region('northeast', [60, 0, 180, 90], 130),
54416
+ region('southwest', [-180, -90, -60, 0], -110),
54417
+ region('south_central', [-60, -90, 90, 0], 20),
54418
+ region('southeast', [90, -90, 180, 0], 150)
54419
+ ],
54420
+ seams: [
54421
+ cutSeam([[-90, 0], [-90, 91]]),
54422
+ cutSeam([[60, 0], [60, 91]]),
54423
+ cutSeam([[-60, -91], [-60, 0]]),
54424
+ cutSeam([[90, -91], [90, 0]])
54425
+ ]
54426
+ };
54427
+
54428
+ var projectionTopologies = {
54429
+ igh: landTopology,
54430
+ imoll: landTopology,
54431
+ igh_o: oceanTopology,
54432
+ imoll_o: oceanTopology
54433
+ };
54434
+
54435
+ function getProjectionTopology(P) {
54436
+ if (P && P.__projection_topology) {
54437
+ return P.__projection_topology;
54438
+ }
54439
+ var defn = P && projectionTopologies[getCrsSlug(P)];
54440
+ if (!defn) return null;
54441
+ var lon0 = P.lam0 * 180 / Math.PI;
54442
+ return {
54443
+ regions: defn.regions.map(function(o) {
54444
+ return {
54445
+ id: o.id,
54446
+ boundary: rotatePath(o.boundary, lon0),
54447
+ transform: {
54448
+ lon_0: normalizeLongitude(o.transform.lon_0 + lon0)
54449
+ }
54450
+ };
54451
+ }),
54452
+ seams: defn.seams.map(function(o) {
54453
+ return {
54454
+ type: o.type,
54455
+ coordinates: rotatePath(o.coordinates, lon0)
54456
+ };
54457
+ })
54458
+ };
54459
+ }
54460
+
54461
+ function isInterruptedProjection(P) {
54462
+ if (P && P.__projection_topology) {
54463
+ return P.__projection_topology.seams.some(function(o) {
54464
+ return o.type == 'cut';
54465
+ });
54466
+ }
54467
+ var defn = P && projectionTopologies[getCrsSlug(P)];
54468
+ return !!defn && defn.seams.some(function(o) {
54469
+ return o.type == 'cut';
54470
+ });
54471
+ }
54472
+
54473
+ function region(id, bbox, lon0) {
54474
+ return {
54475
+ id: id,
54476
+ boundary: [
54477
+ [bbox[0], bbox[1]],
54478
+ [bbox[0], bbox[3]],
54479
+ [bbox[2], bbox[3]],
54480
+ [bbox[2], bbox[1]],
54481
+ [bbox[0], bbox[1]]
54482
+ ],
54483
+ transform: {lon_0: lon0}
54484
+ };
54485
+ }
54486
+
54487
+ function cutSeam(coordinates) {
54488
+ return {
54489
+ type: 'cut',
54490
+ coordinates: coordinates
54491
+ };
54492
+ }
54493
+
54494
+ function rotatePath(path, degrees) {
54495
+ return path.map(function(p) {
54496
+ return [normalizeLongitude(p[0] + degrees), p[1]];
54497
+ });
54498
+ }
54499
+
54500
+ function normalizeLongitude(lon) {
54501
+ while (lon > 180) lon -= 360;
54502
+ while (lon < -180) lon += 360;
54503
+ return lon;
54504
+ }
54505
+
54506
+ var ProjectionTopology = /*#__PURE__*/Object.freeze({
54507
+ __proto__: null,
54508
+ getProjectionTopology: getProjectionTopology,
54509
+ isInterruptedProjection: isInterruptedProjection
54510
+ });
54511
+
52576
54512
  function getClippingDataset(src, dest, opts) {
52577
54513
  return getUnprojectedBoundingPolygon(src, dest, opts);
52578
54514
  }
@@ -52601,6 +54537,9 @@ ${svg}
52601
54537
 
52602
54538
  // Return projected polygon extent of both clipped and unclipped projections
52603
54539
  function getPolygonDataset$1(src, dest, opts) {
54540
+ if (dest.__projected_outline) {
54541
+ return getCustomProjectedOutline(dest, 'polygon');
54542
+ }
52604
54543
  // use clipping area if projection is clipped
52605
54544
  var dataset = getUnprojectedBoundingPolygon(src, dest, opts);
52606
54545
  if (!dataset) {
@@ -52608,20 +54547,124 @@ ${svg}
52608
54547
  dataset = getBoundingRectangle(dest, {clip_bbox: [-180,-90,180,90]});
52609
54548
  }
52610
54549
  projectDataset(dataset, src, dest, {no_clip: false, quiet: true});
54550
+ if (isInterruptedProjection(dest)) {
54551
+ removeTinyFootprintRings(dataset);
54552
+ }
52611
54553
  return dataset;
52612
54554
  }
52613
54555
 
52614
54556
  // Return projected outline of clipped projections
52615
54557
  function getOutlineDataset(src, dest, opts) {
54558
+ if (dest.__projected_outline) {
54559
+ return getCustomProjectedOutline(dest, 'polyline');
54560
+ }
52616
54561
  var dataset = getUnprojectedBoundingPolygon(src, dest, opts);
54562
+ if (!dataset && isInterruptedProjection(dest)) {
54563
+ dataset = getBoundingRectangle(dest, {clip_bbox: [-180, -90, 180, 90]});
54564
+ }
52617
54565
  if (dataset) {
52618
54566
  // project, with cutting & cleanup
52619
54567
  projectDataset(dataset, src, dest, {no_clip: false, quiet: true});
54568
+ if (isInterruptedProjection(dest)) {
54569
+ removeTinyFootprintRings(dataset);
54570
+ }
52620
54571
  dataset.layers[0].geometry_type = 'polyline';
54572
+ if (dest.__remove_outline_extreme_connectors) {
54573
+ dataset = removeOutlineExtremeConnectors(dataset);
54574
+ }
52621
54575
  }
52622
54576
  return dataset || null;
52623
54577
  }
52624
54578
 
54579
+ function getCustomProjectedOutline(P, geometryType) {
54580
+ var rings = P.__projected_outline.map(function(ring) {
54581
+ return ring.map(function(p) {
54582
+ return [
54583
+ P.fr_meter * (P.a * p[0] + P.x0),
54584
+ P.fr_meter * (P.a * p[1] + P.y0)
54585
+ ];
54586
+ });
54587
+ });
54588
+ var dataset = importGeoJSON({
54589
+ type: rings.length == 1 ? 'Polygon' : 'MultiPolygon',
54590
+ coordinates: rings.length == 1 ? rings : rings.map(function(ring) {
54591
+ return [ring];
54592
+ })
54593
+ });
54594
+ dataset.layers[0].geometry_type = geometryType;
54595
+ dataset.info.crs = P;
54596
+ return dataset;
54597
+ }
54598
+
54599
+ // Narrow pre-projection gutters can leave zero-area rings after a rotated
54600
+ // world boundary is cleaned. Remove only rings that are negligible relative
54601
+ // to the main footprint; legitimate disconnected lobes are retained.
54602
+ function removeTinyFootprintRings(dataset) {
54603
+ var lyr = dataset.layers[0];
54604
+ var arcs = dataset.arcs;
54605
+ var maxArea = 0;
54606
+ lyr.shapes.forEach(function(shp) {
54607
+ (shp || []).forEach(function(path) {
54608
+ maxArea = Math.max(maxArea, Math.abs(geom.getPathArea(path, arcs)));
54609
+ });
54610
+ });
54611
+ if (maxArea === 0) return;
54612
+ editShapes(lyr.shapes, function(path) {
54613
+ if (Math.abs(geom.getPathArea(path, arcs)) <= maxArea * 1e-12) {
54614
+ return null;
54615
+ }
54616
+ });
54617
+ dissolveArcs(dataset);
54618
+ }
54619
+
54620
+ // Polygon clipping closes open interrupted-projection boundaries to form valid
54621
+ // rings. In Cahill-Keyes, some closures connect unrelated polar facet images.
54622
+ // Remove only long, nearly horizontal segments at the map's vertical extent.
54623
+ function removeOutlineExtremeConnectors(dataset) {
54624
+ var bounds = getDatasetBounds(dataset);
54625
+ var width = bounds.width();
54626
+ var height = bounds.height();
54627
+ var edgeTolerance = height * 1e-3;
54628
+ var flatTolerance = height * 1e-4;
54629
+ var arcs = dataset.arcs;
54630
+ var lyr = dataset.layers[0];
54631
+ var paths = [];
54632
+ var removed = 0;
54633
+ lyr.shapes.forEach(function(shp) {
54634
+ (shp || []).forEach(function(path) {
54635
+ var points = [];
54636
+ var iter = arcs.getShapeIter(path);
54637
+ while (iter.hasNext()) {
54638
+ points.push([iter.x, iter.y]);
54639
+ }
54640
+ var part = [points[0]];
54641
+ for (var i = 1; i < points.length; i++) {
54642
+ var a = points[i - 1];
54643
+ var b = points[i];
54644
+ var atTop = Math.min(a[1], b[1]) >= bounds.ymax - edgeTolerance;
54645
+ var atBottom = Math.max(a[1], b[1]) <= bounds.ymin + edgeTolerance;
54646
+ var isFlat = Math.abs(a[1] - b[1]) <= flatTolerance;
54647
+ var isWide = Math.abs(a[0] - b[0]) >= width * 0.02;
54648
+ if ((atTop || atBottom) && isFlat && isWide) {
54649
+ if (part.length > 1) paths.push(part);
54650
+ part = [b];
54651
+ removed++;
54652
+ } else {
54653
+ part.push(b);
54654
+ }
54655
+ }
54656
+ if (part.length > 1) paths.push(part);
54657
+ });
54658
+ });
54659
+ if (removed === 0) return dataset;
54660
+ var cleaned = importGeoJSON({
54661
+ type: paths.length == 1 ? 'LineString' : 'MultiLineString',
54662
+ coordinates: paths.length == 1 ? paths[0] : paths
54663
+ });
54664
+ cleaned.info = dataset.info;
54665
+ return cleaned;
54666
+ }
54667
+
52625
54668
  function getBoundingRectangle(dest, opts) {
52626
54669
  var bbox = opts.clip_bbox || getDefaultClipBBox(dest);
52627
54670
  var rotation = getRotationParams(dest);
@@ -52735,6 +54778,7 @@ ${svg}
52735
54778
  // rotated normal-aspect projections can generally have a thin slice removed
52736
54779
  // from the rotated antimeridian, instead of clipping them
52737
54780
  var cut = insertPreProjectionCuts(dataset, src, dest);
54781
+ cut = insertProjectionTopologyCuts(dataset, dest) || cut;
52738
54782
  var clipped = false;
52739
54783
  var clipData;
52740
54784
  // experimental -- we can probably get away with just clamping some CRSs that
@@ -52795,6 +54839,84 @@ ${svg}
52795
54839
  return false;
52796
54840
  }
52797
54841
 
54842
+ function insertProjectionTopologyCuts(dataset, dest) {
54843
+ var topology = getProjectionTopology(dest);
54844
+ var pathLayers = dataset.layers.filter(layerHasPaths);
54845
+ var cutPaths, geojson, clip;
54846
+ if (!topology || pathLayers.length === 0) return false;
54847
+ insertProjectionTopologyVertices(dataset, topology);
54848
+ cutPaths = topology.seams
54849
+ .filter(function(o) { return o.type == 'cut'; })
54850
+ .reduce(function(memo, o) {
54851
+ if (o.paths) return memo.concat(o.paths);
54852
+ return memo.concat(getCutSeamPaths(o.coordinates));
54853
+ }, []);
54854
+ if (cutPaths.length === 0) return false;
54855
+ geojson = getCutMaskGeoJSON(cutPaths);
54856
+ if (geojson.features.length === 0) return false;
54857
+ clip = importGeoJSON(geojson);
54858
+ if (topology.findRegion) {
54859
+ // Polyhedral cut masks meet and overlap at facet vertices. Union them before
54860
+ // clipping so overlapping source polygons are not interpreted as holes.
54861
+ addIntersectionCuts(clip, {});
54862
+ clip.layers[0] = dissolvePolygonLayer2(clip.layers[0], clip, {quiet: true});
54863
+ }
54864
+ clipLayersInPlace(pathLayers, clip, dataset, 'erase', getInternalClipOpts());
54865
+ return true;
54866
+ }
54867
+
54868
+ // Add a vertex wherever a path changes projection regions. This preserves a
54869
+ // bend at attached polyhedral edges without opening a gap; cut edges are
54870
+ // subsequently disconnected by the narrow seam masks below.
54871
+ function insertProjectionTopologyVertices(dataset, topology) {
54872
+ if (!dataset.arcs || !topology.findRegion) return;
54873
+ var findRegion = topology.findTransitionRegion || topology.findRegion;
54874
+ editArcs(dataset.arcs, function(append, x, y, prevX, prevY, i) {
54875
+ if (i > 0) {
54876
+ findRegionTransitions(prevX, prevY, x, y, findRegion).forEach(append);
54877
+ }
54878
+ append([x, y]);
54879
+ });
54880
+ }
54881
+
54882
+ function findRegionTransitions(ax, ay, bx, by, findRegion) {
54883
+ var interval = 0.5;
54884
+ var n = Math.max(1, Math.ceil(Math.max(Math.abs(bx - ax), Math.abs(by - ay)) / interval));
54885
+ var points = [];
54886
+ var t0 = 0;
54887
+ var region0 = findRegion(ax, ay);
54888
+ for (var i = 1; i <= n; i++) {
54889
+ var t1 = i / n;
54890
+ var x1 = ax + (bx - ax) * t1;
54891
+ var y1 = ay + (by - ay) * t1;
54892
+ var region1 = findRegion(x1, y1);
54893
+ if (region0 != region1 && region0 >= 0 && region1 >= 0) {
54894
+ var t = findRegionBoundary(ax, ay, bx, by, t0, region0, t1, findRegion);
54895
+ var p = [ax + (bx - ax) * t, ay + (by - ay) * t];
54896
+ if (!points.length || Math.abs(t - points[points.length - 1].t) > 1e-10) {
54897
+ p.t = t;
54898
+ points.push(p);
54899
+ }
54900
+ }
54901
+ t0 = t1;
54902
+ region0 = region1;
54903
+ }
54904
+ return points;
54905
+ }
54906
+
54907
+ function findRegionBoundary(ax, ay, bx, by, ta, regionA, tb, findRegion) {
54908
+ for (var i = 0; i < 45; i++) {
54909
+ var tm = (ta + tb) / 2;
54910
+ var regionM = findRegion(ax + (bx - ax) * tm, ay + (by - ay) * tm);
54911
+ if (regionM == regionA) {
54912
+ ta = tm;
54913
+ } else {
54914
+ tb = tm;
54915
+ }
54916
+ }
54917
+ return (ta + tb) / 2;
54918
+ }
54919
+
52798
54920
  function clampDataset(dataset, bbox) {
52799
54921
  transformPoints(dataset, function(x, y) {
52800
54922
  return [utils.clamp(x, bbox[0], bbox[2]), utils.clamp(y, bbox[1], bbox[3])];
@@ -52830,6 +54952,92 @@ ${svg}
52830
54952
  clipLayersInPlace(pathLayers, clip, dataset, 'erase', getInternalClipOpts());
52831
54953
  }
52832
54954
 
54955
+ // Duplicate a seam on the opposite edge of the standard longitude range.
54956
+ // This is needed when a rotated seam coincides with the antimeridian.
54957
+ function getCutSeamPaths(coords) {
54958
+ var paths = [coords];
54959
+ var onEdge = coords.every(function(p) {
54960
+ return Math.abs(Math.abs(p[0]) - 180) < 1e-12;
54961
+ });
54962
+ if (onEdge) {
54963
+ paths.push(coords.map(function(p) {
54964
+ return [p[0] == 180 ? -180 : 180, p[1]];
54965
+ }));
54966
+ }
54967
+ return paths;
54968
+ }
54969
+
54970
+ // Convert seam paths to narrow polygon masks. Densification is required so
54971
+ // their projected edges follow the projection instead of becoming straight
54972
+ // chords between seam endpoints.
54973
+ function getCutMaskGeoJSON(paths) {
54974
+ var features = paths.map(function(coords) {
54975
+ var e = coords.mask_width || 1e-8;
54976
+ var ring = getCutMaskRing(densifyPathByInterval(coords, 0.5), e);
54977
+ return {
54978
+ type: 'Feature',
54979
+ properties: {},
54980
+ geometry: {
54981
+ type: 'Polygon',
54982
+ coordinates: [ring]
54983
+ }
54984
+ };
54985
+ }).filter(function(feature) {
54986
+ return feature.geometry.coordinates[0].length > 3;
54987
+ });
54988
+ // Give finite seams a small end cap. Without this, a path passing exactly
54989
+ // through a polyhedral vertex can touch only the boundary of several masks
54990
+ // and remain connected across an interruption.
54991
+ paths.forEach(function(coords) {
54992
+ var endpoints = [coords[0], coords[coords.length - 1]];
54993
+ endpoints.forEach(function(p) {
54994
+ var e2 = Math.max(1e-7, (coords.mask_width || 0) * 2);
54995
+ var xmin = Math.max(-180, p[0] - e2);
54996
+ var xmax = Math.min(180, p[0] + e2);
54997
+ var ymin = Math.max(-90, p[1] - e2);
54998
+ var ymax = Math.min(90, p[1] + e2);
54999
+ features.push({
55000
+ type: 'Feature',
55001
+ properties: {},
55002
+ geometry: {
55003
+ type: 'Polygon',
55004
+ coordinates: [[
55005
+ [xmin, ymin],
55006
+ [xmax, ymin],
55007
+ [xmax, ymax],
55008
+ [xmin, ymax],
55009
+ [xmin, ymin]
55010
+ ]]
55011
+ }
55012
+ });
55013
+ });
55014
+ });
55015
+ return {
55016
+ type: 'FeatureCollection',
55017
+ features: features
55018
+ };
55019
+ }
55020
+
55021
+ function getCutMaskRing(coords, width) {
55022
+ var left = [];
55023
+ var right = [];
55024
+ for (var i = 0; i < coords.length; i++) {
55025
+ var a = coords[i > 0 ? i - 1 : i];
55026
+ var b = coords[i < coords.length - 1 ? i + 1 : i];
55027
+ var dx = b[0] - a[0];
55028
+ var dy = b[1] - a[1];
55029
+ var len = Math.sqrt(dx * dx + dy * dy);
55030
+ if (len === 0) continue;
55031
+ var nx = -dy / len * width;
55032
+ var ny = dx / len * width;
55033
+ left.push([utils.clamp(coords[i][0] + nx, -180, 180), coords[i][1] + ny]);
55034
+ right.push([utils.clamp(coords[i][0] - nx, -180, 180), coords[i][1] - ny]);
55035
+ }
55036
+ var ring = left.concat(right.reverse());
55037
+ if (ring.length > 0) ring.push(ring[0].concat());
55038
+ return ring;
55039
+ }
55040
+
52833
55041
  function getInternalClipOpts() {
52834
55042
  return {no_cleanup: true, no_warn: true};
52835
55043
  }
@@ -53699,9 +55907,18 @@ ${svg}
53699
55907
  }
53700
55908
 
53701
55909
  function projectDataset(dataset, src, dest, opts) {
55910
+ if (isProjectedCRS(src) && !isInvertibleCRS(src)) {
55911
+ stop$1('Unable to project from a coordinate system that has no inverse transform');
55912
+ }
55913
+ if (isInterruptedProjection(dest) && dataset.layers.some(layerHasRaster)) {
55914
+ stop$1('Raster reprojection is not currently supported for interrupted projections');
55915
+ }
53702
55916
  var proj = getProjTransform2(src, dest); // v2 returns null points instead of throwing an error
53703
55917
  var badArcs = 0;
53704
55918
  var badPoints = 0;
55919
+ var healAntimeridian = isLatLngCRS(src) &&
55920
+ isInterruptedProjection(dest) &&
55921
+ Math.abs(dest.lam0) > 1e-12;
53705
55922
  var clipped = preProjectionClip(dataset, src, dest, opts);
53706
55923
  dataset.layers.forEach(function(lyr) {
53707
55924
  if (layerHasPoints(lyr)) {
@@ -53721,7 +55938,7 @@ ${svg}
53721
55938
  if (clipped) {
53722
55939
  // TODO: could more selective in cleaning clipped layers
53723
55940
  // (probably only needed when clipped area crosses the antimeridian or includes a pole)
53724
- cleanProjectedPathLayers(dataset);
55941
+ cleanProjectedPathLayers(dataset, {heal_antimeridian: healAntimeridian});
53725
55942
  }
53726
55943
 
53727
55944
  if (badArcs > 0 && !opts.quiet) {
@@ -53749,9 +55966,21 @@ ${svg}
53749
55966
  // * Removes line intersections
53750
55967
  // * TODO: what if a layer contains polygons with desired overlaps? should
53751
55968
  // we ignore overlaps between different features?
53752
- function cleanProjectedPathLayers(dataset) {
55969
+ function cleanProjectedPathLayers(dataset, opts) {
55970
+ opts = opts || {};
53753
55971
  // TODO: only clean affected polygons (cleaning all polygons can be slow)
53754
55972
  var polygonLayers = dataset.layers.filter(lyr => lyr.geometry_type == 'polygon');
55973
+ if (opts.heal_antimeridian && dataset.arcs) {
55974
+ // A rotated projection moves the source +/-180 degree boundary into the
55975
+ // map interior. Mathematically identical boundary vertices can diverge by
55976
+ // a few floating-point units during projection, preventing topology from
55977
+ // joining polygon parts that were split at the source antimeridian.
55978
+ var arcs = dataset.arcs;
55979
+ var snapDist = getHighPrecisionSnapInterval(arcs.getBounds().toArray());
55980
+ if (snapCoordsByInterval(arcs, snapDist) > 0) {
55981
+ arcs.dedupCoords();
55982
+ }
55983
+ }
53755
55984
  // clean options: force a topology update (by default, this only happens when
53756
55985
  // vertices change during cleaning, but reprojection can require a topology update
53757
55986
  // even if clean does not change vertices)
@@ -57212,7 +59441,7 @@ ${svg}
57212
59441
  function getSplitLineFunction(crs, dashLen, gapLen, opts) {
57213
59442
  var planar = !!opts.planar;
57214
59443
  var interpolate = getInterpolationFunction(planar ? null : crs);
57215
- var distance = isLatLngCRS(crs) ? greatCircleDistance : distance2D;
59444
+ var distance = isLatLngCRS(crs) ? greatCircleDistance : distance2D$1;
57216
59445
  var inDash, parts2, interval, scale;
57217
59446
  function addPart(coords) {
57218
59447
  if (inDash) parts2.push(coords);
@@ -58083,7 +60312,7 @@ ${svg}
58083
60312
  // Isometric ("Mercator-stretched") latitude in degrees-in; returns +/-Infinity at
58084
60313
  // the poles.
58085
60314
  function isometricLatitude(latDeg) {
58086
- return Math.log(Math.tan(Math.PI / 4 + latDeg * D2R$1 / 2));
60315
+ return Math.log(Math.tan(Math.PI / 4 + latDeg * D2R$6 / 2));
58087
60316
  }
58088
60317
 
58089
60318
  // Rhumb-line (loxodrome) interpolation, fraction k of the way from A to B.
@@ -58108,9 +60337,9 @@ ${svg}
58108
60337
  // Rhumb-line distance in meters, using the raw longitude delta (so a full-parallel
58109
60338
  // sweep returns the parallel's length rather than 0).
58110
60339
  function rhumbDistance(a, b) {
58111
- var phi1 = a[1] * D2R$1, phi2 = b[1] * D2R$1;
60340
+ var phi1 = a[1] * D2R$6, phi2 = b[1] * D2R$6;
58112
60341
  var dPhi = phi2 - phi1;
58113
- var dLambda = (b[0] - a[0]) * D2R$1; // raw
60342
+ var dLambda = (b[0] - a[0]) * D2R$6; // raw
58114
60343
  var dPsi = Math.log(Math.tan(Math.PI / 4 + phi2 / 2) / Math.tan(Math.PI / 4 + phi1 / 2));
58115
60344
  var q = Math.abs(dPsi) > 1e-12 ? dPhi / dPsi : Math.cos(phi1);
58116
60345
  return Math.sqrt(dPhi * dPhi + q * q * dLambda * dLambda) * R$3;
@@ -61765,22 +63994,31 @@ ${svg}
61765
63994
  }
61766
63995
 
61767
63996
  cmd.graticule = function(dataset, opts) {
61768
- var name = opts.name || opts.polygon && 'polygon' || 'graticule';
63997
+ if (opts.polygon && opts.outline) {
63998
+ stop$1('The polygon and outline options cannot be used together');
63999
+ }
64000
+ var boundary = opts.polygon || opts.outline;
64001
+ var name = opts.name || opts.outline && 'outline' || opts.polygon && 'polygon' || 'graticule';
61769
64002
  var graticule, destInfo;
61770
64003
  if (dataset && !isLatLngDataset(dataset)) {
61771
64004
  // project graticule to match dataset
61772
64005
  destInfo = getDatasetCrsInfo(dataset);
61773
64006
  if (!destInfo.crs) stop$1("Coordinate system is unknown, unable to create a graticule");
61774
- graticule = opts.polygon ?
61775
- createProjectedPolygon(destInfo.crs, opts) :
61776
- createProjectedGraticule(destInfo.crs, opts);
64007
+ graticule = opts.outline ?
64008
+ createProjectedOutline(destInfo.crs, opts) :
64009
+ opts.polygon ?
64010
+ createProjectedPolygon(destInfo.crs, opts) :
64011
+ createProjectedGraticule(destInfo.crs, opts);
61777
64012
  setDatasetCrsInfo(graticule, destInfo);
61778
64013
  } else {
61779
- graticule = opts.polygon ?
64014
+ graticule = boundary ?
61780
64015
  createUnprojectedPolygon(opts) :
61781
64016
  createUnprojectedGraticule(opts);
61782
64017
  setDatasetCrsInfo(graticule, getCrsInfo('wgs84'));
61783
64018
  }
64019
+ if (opts.outline) {
64020
+ graticule.layers[0].geometry_type = 'polyline';
64021
+ }
61784
64022
  graticule.layers[0].name = name;
61785
64023
  return graticule;
61786
64024
  };
@@ -61795,6 +64033,12 @@ ${svg}
61795
64033
  return getPolygonDataset$1(src, dest, opts);
61796
64034
  }
61797
64035
 
64036
+ function createProjectedOutline(dest, opts) {
64037
+ var src = parseCrsString$1('wgs84');
64038
+ return getOutlineDataset(src, dest, opts) ||
64039
+ getPolygonDataset$1(src, dest, opts);
64040
+ }
64041
+
61798
64042
  function createUnprojectedGraticule(opts) {
61799
64043
  var src = parseCrsString$1('wgs84');
61800
64044
  var graticule = importGeoJSON(createGraticule(src, false, opts));
@@ -61876,12 +64120,15 @@ ${svg}
61876
64120
  // extended: meridian extends to pole
61877
64121
  function createMeridian(x, extended) {
61878
64122
  var y0 = ystep <= 15 ? ystep : 0;
64123
+ // An exact pole can have several images in a polyhedral projection.
64124
+ // The separate outline supplies the missing, negligible end interval.
64125
+ var poleInset = outlined ? 1e-4 : 0;
61879
64126
  createMeridianPart(x, -90 + y0, 90 - y0);
61880
64127
  if (extended && y0 > 0) {
61881
64128
  // adding extensions as separate parts, so if the polar coordinates
61882
64129
  // fail to project, at least the rest of the meridian line will remain
61883
- createMeridianPart(x, -90, -90 + y0);
61884
- createMeridianPart(x, 90 - y0, 90);
64130
+ createMeridianPart(x, -90 + poleInset, -90 + y0);
64131
+ createMeridianPart(x, 90 - y0, 90 - poleInset);
61885
64132
  }
61886
64133
  }
61887
64134
 
@@ -61891,7 +64138,14 @@ ${svg}
61891
64138
  }
61892
64139
 
61893
64140
  function createParallel(y) {
61894
- var coords = densifyPathByInterval([[-180, y], [180, y]], precision);
64141
+ // Avoid joining distinct images of the antimeridian (and the degenerate
64142
+ // polar parallels) when a projected outline is generated separately.
64143
+ var edgeInset = outlined ? 1e-4 : 0;
64144
+ if (outlined && Math.abs(y) == 90) return;
64145
+ var coords = densifyPathByInterval([
64146
+ [-180 + edgeInset, y],
64147
+ [180 - edgeInset, y]
64148
+ ], precision);
61895
64149
  parallels.push(graticuleFeature(coords, {type: 'parallel', value: y}));
61896
64150
  }
61897
64151
  }
@@ -62490,7 +64744,7 @@ ${svg}
62490
64744
  if (halfLen > 0 === false) {
62491
64745
  return [a, b];
62492
64746
  }
62493
- var segLen = distance2D(a, b, c, d);
64747
+ var segLen = distance2D$1(a, b, c, d);
62494
64748
  var k;
62495
64749
  if (partialLen + segLen >= halfLen) {
62496
64750
  k = (halfLen - partialLen) / segLen;
@@ -64411,7 +66665,7 @@ ${svg}
64411
66665
 
64412
66666
  // Source: https://diego.assencio.com/?index=8d6ca3d82151bad815f78addf9b5c1c6
64413
66667
  function twoCircleIntersection(c1, r1, c2, r2) {
64414
- var d = distance2D(c1[0], c1[1], c2[0], c2[1]);
66668
+ var d = distance2D$1(c1[0], c1[1], c2[0], c2[1]);
64415
66669
  if (d >= r1 + r2) return 0;
64416
66670
  var r1sq = r1 * r1,
64417
66671
  r2sq = r2 * r2,
@@ -66406,7 +68660,7 @@ ${svg}
66406
68660
  if (totalSegs < 1) return 0;
66407
68661
  var MAX_SAMPLES = 100000;
66408
68662
  var stride = Math.ceil(totalSegs / MAX_SAMPLES);
66409
- var distFn = spherical ? greatCircleDistance : distance2D;
68663
+ var distFn = spherical ? greatCircleDistance : distance2D$1;
66410
68664
  var lens = [];
66411
68665
  var counter = 0;
66412
68666
  arcs.forEach3(function(xx, yy) {
@@ -67735,6 +69989,28 @@ ${svg}
67735
69989
  name == 'comment' || name == 'rename-layers';
67736
69990
  }
67737
69991
 
69992
+ // Commands in this group keep datasets separate and run once for each grouped
69993
+ // target returned by findCommandTargets(). Commands that consume all targets
69994
+ // together (e.g. -frame) or merge targets (-union, -merge-layers) are
69995
+ // intentionally excluded.
69996
+ function commandRunsPerTargetDataset(name, opts) {
69997
+ if (name == 'rectangle') {
69998
+ return !opts.source && !opts.bbox;
69999
+ }
70000
+ return [
70001
+ 'affine', 'alpha-shapes', 'blur', 'buffer', 'calc', 'check-geometry',
70002
+ 'classify', 'clean', 'clip', 'cluster', 'dashlines', 'data-fill',
70003
+ 'densify', 'dissolve', 'dissolve2', 'divide', 'dots', 'each', 'erase',
70004
+ 'explode', 'filter',
70005
+ 'filter-detail', 'filter-fields', 'filter-geom', 'filter-islands',
70006
+ 'filter-islands2', 'filter-points', 'filter-slivers', 'grid', 'grid2',
70007
+ 'fuzzy-join', 'ignore', 'inlay', 'innerlines', 'inspect', 'join',
70008
+ 'lines', 'mosaic', 'points', 'polygons', 'rectangles', 'rename-fields',
70009
+ 'shapes', 'simplify', 'slice', 'smooth', 'sort', 'split',
70010
+ 'split-on-grid', 'stitch', 'style', 'subdivide', 'symbols', 'uniq'
70011
+ ].indexOf(name) > -1;
70012
+ }
70013
+
67738
70014
  function commandAcceptsEmptyTarget(name) {
67739
70015
  return name == 'graticule' || name == 'i' || name == 'help' ||
67740
70016
  name == 'point-grid' || name == 'shape' || name == 'rectangle' || name == 'frame' ||
@@ -67766,13 +70042,32 @@ ${svg}
67766
70042
  }
67767
70043
 
67768
70044
  if (!job) job = new Job();
70045
+
70046
+ // Preserve the existing single-dataset dispatch and postprocessing paths by
70047
+ // invoking them once per dataset. Collect the resulting targets so a
70048
+ // multi-dataset command does not leave only its final dataset selected.
70049
+ if (!command._targetOverride && commandRunsPerTargetDataset(name, opts)) {
70050
+ targets = job.catalog.findCommandTargets(opts.target);
70051
+ if (targets.length > 1) {
70052
+ validatePerDatasetTargets(name, targets, opts);
70053
+ return runCommandOnEachTarget(command, job, targets);
70054
+ }
70055
+ }
70056
+
67769
70057
  job.startCommand(command);
67770
70058
 
67771
70059
 
67772
70060
  try { // catch errors from synchronous functions
67773
70061
  T$1.start();
67774
70062
 
67775
- if (name == 'rename-layers') {
70063
+ if (command._targetOverride) {
70064
+ targets = [command._targetOverride];
70065
+ targetDataset = targets[0].dataset;
70066
+ arcs = targetDataset.arcs;
70067
+ targetLayers = targets[0].layers;
70068
+ job.catalog.setDefaultTarget(targetLayers, targetDataset);
70069
+
70070
+ } else if (name == 'rename-layers') {
67776
70071
  targets = job.catalog.findCommandTargets(opts.target);
67777
70072
  targetLayers = targets.reduce(function(memo, obj) {
67778
70073
  return memo.concat(obj.layers);
@@ -67816,7 +70111,9 @@ ${svg}
67816
70111
  }
67817
70112
  }
67818
70113
 
67819
- if (opts.source) {
70114
+ if (Object.prototype.hasOwnProperty.call(command, '_sourceOverride')) {
70115
+ source = command._sourceOverride;
70116
+ } else if (opts.source) {
67820
70117
  source = findCommandSource(convertSourceName(opts.source, targets), job.catalog, opts);
67821
70118
  }
67822
70119
 
@@ -68214,6 +70511,141 @@ ${svg}
68214
70511
  }
68215
70512
  }
68216
70513
 
70514
+ async function runCommandOnEachTarget(command, job, targets) {
70515
+ var outputTargets = [];
70516
+ var source = null;
70517
+ var items = targets.map(function(target, i) {
70518
+ return {target: target, index: i};
70519
+ });
70520
+ var disposableSourceTargetIndex = -1;
70521
+
70522
+ if (command.options.source && !sourceNameIsInterpolated(command.options.source)) {
70523
+ source = resolveSourceForTargets(command, job, targets);
70524
+ items = moveSourceTargetLast(items, source);
70525
+ disposableSourceTargetIndex = findLastDisposableSourceTarget(
70526
+ items,
70527
+ source,
70528
+ command.name,
70529
+ command.options
70530
+ );
70531
+ }
70532
+
70533
+ for (var i = 0; i < items.length; i++) {
70534
+ var item = items[i];
70535
+ var target = {
70536
+ dataset: item.target.dataset,
70537
+ layers: item.target.layers.concat()
70538
+ };
70539
+ var command2 = Object.assign({}, command, {_targetOverride: target});
70540
+ if (source) {
70541
+ command2._sourceOverride = getSourceForTarget(
70542
+ source,
70543
+ target,
70544
+ command.name,
70545
+ item.index == disposableSourceTargetIndex,
70546
+ command.options
70547
+ );
70548
+ }
70549
+ await runCommand(command2, job);
70550
+ outputTargets[item.index] = copyCommandTargets(job.catalog.getDefaultTargets());
70551
+ }
70552
+ job.catalog.setDefaultTargets(outputTargets.reduce(function(memo, arr) {
70553
+ return memo.concat(arr);
70554
+ }, []));
70555
+ return job;
70556
+ }
70557
+
70558
+ function resolveSourceForTargets(command, job, targets) {
70559
+ job.startCommand(command);
70560
+ try {
70561
+ return findCommandSource(
70562
+ convertSourceName(command.options.source, targets),
70563
+ job.catalog,
70564
+ command.options
70565
+ );
70566
+ } finally {
70567
+ job.endCommand();
70568
+ }
70569
+ }
70570
+
70571
+ function sourceNameIsInterpolated(name) {
70572
+ return /[$][{]/.test(name);
70573
+ }
70574
+
70575
+ // If a retained catalog source is also targeted, process its group last so
70576
+ // earlier groups always read the original source. Output target ordering is
70577
+ // restored after execution.
70578
+ function moveSourceTargetLast(items, source) {
70579
+ if (!source || source.disposable) return items;
70580
+ return items.filter(function(item) {
70581
+ return item.target.layers.indexOf(source.layer) == -1;
70582
+ }).concat(items.filter(function(item) {
70583
+ return item.target.layers.indexOf(source.layer) > -1;
70584
+ }));
70585
+ }
70586
+
70587
+ function commandMutatesDisposableSource(name, target, source, opts) {
70588
+ var overlayCommand = (name == 'clip' || name == 'erase' || name == 'slice' ||
70589
+ name == 'inlay' || name == 'divide') &&
70590
+ utils.some(target.layers, layerHasPaths);
70591
+ var polygonMosaicJoin = name == 'join' && !opts.keys && !opts.point_method &&
70592
+ source.layer.geometry_type == 'polygon' &&
70593
+ target.layers.some(function(lyr) { return lyr.geometry_type == 'polygon'; });
70594
+ return overlayCommand || polygonMosaicJoin;
70595
+ }
70596
+
70597
+ // A file source is disposable, so the final topology-changing target can use it
70598
+ // directly. Earlier topology-changing targets receive only a shape copy; the
70599
+ // potentially large source ArcCollection is safely shared.
70600
+ function findLastDisposableSourceTarget(items, source, name, opts) {
70601
+ if (!source?.disposable) return -1;
70602
+ for (var i = items.length - 1; i >= 0; i--) {
70603
+ if (commandMutatesDisposableSource(name, items[i].target, source, opts)) {
70604
+ return items[i].index;
70605
+ }
70606
+ }
70607
+ return -1;
70608
+ }
70609
+
70610
+ function getSourceForTarget(source, target, name, useOriginal, opts) {
70611
+ if (!source.disposable ||
70612
+ !commandMutatesDisposableSource(name, target, source, opts) ||
70613
+ useOriginal) {
70614
+ return source;
70615
+ }
70616
+ var layer = copyLayerShapes(source.layer);
70617
+ var dataset = Object.assign({}, source.dataset, {layers: [layer]});
70618
+ return {dataset: dataset, layer: layer, disposable: true};
70619
+ }
70620
+
70621
+ function copyCommandTargets(targets) {
70622
+ return targets.map(function(target) {
70623
+ return {
70624
+ dataset: target.dataset,
70625
+ layers: target.layers.concat()
70626
+ };
70627
+ });
70628
+ }
70629
+
70630
+ // Fail cardinality checks before the first dataset is modified.
70631
+ function validatePerDatasetTargets(name, targets, opts) {
70632
+ if (opts.source && sourceNameIsInterpolated(opts.source) && targets.some(function(target) {
70633
+ return target.layers.length != 1;
70634
+ })) {
70635
+ stop$1('Interpolated names are not compatible with multiple targets.');
70636
+ }
70637
+ if (name == 'mosaic' && targets.some(function(target) {
70638
+ return target.layers.length != 1;
70639
+ })) {
70640
+ stop$1('Command takes a single target layer from each dataset');
70641
+ }
70642
+ if (name == 'simplify' && opts.variable && targets.some(function(target) {
70643
+ return target.layers.length != 1;
70644
+ })) {
70645
+ stop$1('Variable simplification requires a single target layer from each dataset');
70646
+ }
70647
+ }
70648
+
68217
70649
  function outputLayersAreDifferent(output, input) {
68218
70650
  return !utils.some(input, function(lyr) {
68219
70651
  return output.indexOf(lyr) > -1;
@@ -68228,7 +70660,7 @@ ${svg}
68228
70660
  return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
68229
70661
  }
68230
70662
 
68231
- var version = "0.7.41";
70663
+ var version = "0.7.45";
68232
70664
 
68233
70665
  // Parse command line args into commands and run them
68234
70666
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -70092,6 +72524,8 @@ ${svg}
70092
72524
  ClipPoints,
70093
72525
  Colorizer,
70094
72526
  CustomProjections,
72527
+ Dymaxion,
72528
+ ButterflyProjections,
70095
72529
  DataAggregation,
70096
72530
  DatasetUtils,
70097
72531
  DataUtils,
@@ -70168,6 +72602,7 @@ ${svg}
70168
72602
  Proj,
70169
72603
  Projections,
70170
72604
  ProjectionParams,
72605
+ ProjectionTopology,
70171
72606
  Rectangle,
70172
72607
  RectangleUtils,
70173
72608
  Rounding,