@math.gl/geospatial 5.0.0-beta.1 → 5.0.0-beta.2

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.
Files changed (90) hide show
  1. package/dist/crs/crs-identifier.d.ts +23 -0
  2. package/dist/crs/crs-identifier.d.ts.map +1 -0
  3. package/dist/crs/crs-identifier.js +74 -0
  4. package/dist/crs/crs-identifier.js.map +1 -0
  5. package/dist/crs/index.cjs +48 -0
  6. package/dist/crs/index.cjs.map +2 -2
  7. package/dist/crs/index.d.ts +1 -0
  8. package/dist/crs/index.d.ts.map +1 -1
  9. package/dist/crs/index.js +1 -0
  10. package/dist/crs/index.js.map +1 -1
  11. package/dist/discontinuous/cap-map.d.ts.map +1 -1
  12. package/dist/discontinuous/cap-map.js +197 -13
  13. package/dist/discontinuous/cap-map.js.map +1 -1
  14. package/dist/discontinuous/elastic/geometry.d.ts +1 -1
  15. package/dist/discontinuous/elastic/geometry.d.ts.map +1 -1
  16. package/dist/discontinuous/elastic/geometry.js +7 -7
  17. package/dist/discontinuous/elastic/geometry.js.map +1 -1
  18. package/dist/discontinuous/elastic/map.d.ts.map +1 -1
  19. package/dist/discontinuous/elastic/map.js +263 -23
  20. package/dist/discontinuous/elastic/map.js.map +1 -1
  21. package/dist/discontinuous/elastic/source-arc.d.ts +10 -0
  22. package/dist/discontinuous/elastic/source-arc.d.ts.map +1 -0
  23. package/dist/discontinuous/elastic/source-arc.js +111 -0
  24. package/dist/discontinuous/elastic/source-arc.js.map +1 -0
  25. package/dist/discontinuous/global-polygons.d.ts +1 -1
  26. package/dist/discontinuous/global-polygons.d.ts.map +1 -1
  27. package/dist/discontinuous/global-polygons.js +49 -1
  28. package/dist/discontinuous/global-polygons.js.map +1 -1
  29. package/dist/discontinuous/map-engine.d.ts +9 -0
  30. package/dist/discontinuous/map-engine.d.ts.map +1 -1
  31. package/dist/discontinuous/map-layout.d.ts +21 -0
  32. package/dist/discontinuous/map-layout.d.ts.map +1 -1
  33. package/dist/discontinuous/map-layout.js +116 -1
  34. package/dist/discontinuous/map-layout.js.map +1 -1
  35. package/dist/discontinuous/map-transition.d.ts.map +1 -1
  36. package/dist/discontinuous/map-transition.js +66 -3
  37. package/dist/discontinuous/map-transition.js.map +1 -1
  38. package/dist/discontinuous/mercator-map.d.ts +2 -2
  39. package/dist/discontinuous/mercator-map.d.ts.map +1 -1
  40. package/dist/discontinuous/mercator-map.js +123 -11
  41. package/dist/discontinuous/mercator-map.js.map +1 -1
  42. package/dist/discontinuous/polygon-constraints.d.ts +16 -0
  43. package/dist/discontinuous/polygon-constraints.d.ts.map +1 -0
  44. package/dist/discontinuous/polygon-constraints.js +157 -0
  45. package/dist/discontinuous/polygon-constraints.js.map +1 -0
  46. package/dist/discontinuous/prepare-features.d.ts +27 -0
  47. package/dist/discontinuous/prepare-features.d.ts.map +1 -1
  48. package/dist/discontinuous/prepare-features.js +106 -5
  49. package/dist/discontinuous/prepare-features.js.map +1 -1
  50. package/dist/discontinuous/rotated-map.d.ts +9 -0
  51. package/dist/discontinuous/rotated-map.d.ts.map +1 -1
  52. package/dist/discontinuous/source-arc.d.ts +4 -0
  53. package/dist/discontinuous/source-arc.d.ts.map +1 -0
  54. package/dist/discontinuous/source-arc.js +23 -0
  55. package/dist/discontinuous/source-arc.js.map +1 -0
  56. package/dist/discontinuous/world-kernels.d.ts +1 -1
  57. package/dist/discontinuous.cjs +1047 -75
  58. package/dist/discontinuous.cjs.map +3 -3
  59. package/dist/index.cjs +48 -0
  60. package/dist/index.cjs.map +2 -2
  61. package/dist/index.d.ts +1 -1
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +1 -1
  64. package/dist/index.js.map +1 -1
  65. package/dist/turf.cjs +69 -5
  66. package/dist/turf.cjs.map +2 -2
  67. package/dist/turf.d.ts +9 -0
  68. package/dist/turf.d.ts.map +1 -1
  69. package/dist/turf.js +67 -0
  70. package/dist/turf.js.map +1 -1
  71. package/dist/workers/index.cjs +1075 -79
  72. package/dist/workers/index.cjs.map +3 -3
  73. package/dist/workers/operations.cjs +1049 -77
  74. package/dist/workers/operations.cjs.map +3 -3
  75. package/package.json +5 -5
  76. package/src/crs/crs-identifier.ts +86 -0
  77. package/src/crs/index.ts +1 -0
  78. package/src/discontinuous/cap-map.ts +248 -19
  79. package/src/discontinuous/elastic/geometry.ts +18 -7
  80. package/src/discontinuous/elastic/map.ts +334 -30
  81. package/src/discontinuous/elastic/source-arc.ts +129 -0
  82. package/src/discontinuous/global-polygons.ts +56 -2
  83. package/src/discontinuous/map-layout.ts +165 -2
  84. package/src/discontinuous/map-transition.ts +67 -3
  85. package/src/discontinuous/mercator-map.ts +151 -12
  86. package/src/discontinuous/polygon-constraints.ts +184 -0
  87. package/src/discontinuous/prepare-features.ts +154 -6
  88. package/src/discontinuous/source-arc.ts +31 -0
  89. package/src/index.ts +1 -0
  90. package/src/turf.ts +86 -0
@@ -809,8 +809,49 @@ function cutSphericalPolygon(rings, options) {
809
809
  }
810
810
  function projectSphericalPolygon(map, rings, options) {
811
811
  const result = [];
812
+ if (options.polygonEdgeProvenance && (!map.regions || !map.seams))
813
+ throw new Error("Atlas provenance requires map region and seam metadata");
812
814
  for (const part of cutSphericalPolygon(rings, options))
813
- result.push(...map.projectPolygon(part.rings, options));
815
+ for (const polygon of map.projectPolygon(part.rings, options)) {
816
+ if (options.polygonEdgeProvenance) {
817
+ if (!polygon.edgeProvenance)
818
+ throw new Error("Map backend does not support polygon edge provenance");
819
+ const region = map.regions.find((r) => r.id === polygon.regionId);
820
+ polygon.edgeProvenance = polygon.edgeProvenance.map((edges, ring) => edges.map((edge2, i) => {
821
+ if (edge2.kind !== "source-boundary")
822
+ return edge2;
823
+ const parent = part.edges[edge2.ring][edge2.segment];
824
+ if (parent.kind === "source")
825
+ return {
826
+ ...edge2,
827
+ ring: parent.ringIndex,
828
+ segment: parent.edgeIndex,
829
+ t0: parent.startFraction + edge2.t0 * (parent.endFraction - parent.startFraction),
830
+ t1: parent.startFraction + edge2.t1 * (parent.endFraction - parent.startFraction)
831
+ };
832
+ const a = geographic(polygon.sourceCoordinates[ring][i]), b = geographic(polygon.sourceCoordinates[ring][i + 1]), kinds = [];
833
+ region.boundary.forEach((p, j) => {
834
+ const normal = cross(geographic(p), geographic(region.boundary[(j + 1) % region.boundary.length]));
835
+ const length = Math.hypot(...normal);
836
+ if (length < 1e-12)
837
+ return;
838
+ if ([a, b].every((v) => Math.abs(dot(normal, v)) / length < 1e-10)) {
839
+ const seam = map.seams.find((s) => s.regions.some((id, k2) => id === region.id && s.edges[k2] === j));
840
+ if (seam)
841
+ kinds.push(seam.kind);
842
+ }
843
+ });
844
+ return {
845
+ kind: kinds.includes("boundary") ? "coverage-boundary" : kinds.includes("cut") ? "chart-cut" : "continuous-chart-join",
846
+ ring: 4294967295,
847
+ segment: 4294967295,
848
+ t0: 0,
849
+ t1: 0
850
+ };
851
+ }));
852
+ }
853
+ result.push(polygon);
854
+ }
814
855
  return result;
815
856
  }
816
857
  var import_polygon_clipping, TOL, arcCross, angle2;
@@ -841,33 +882,13 @@ function inRing(p, ring, includeBoundary = true) {
841
882
  }
842
883
  return inside;
843
884
  }
844
- function clipSegment(a, b, polygons) {
845
- const ts = [0, 1], dx = b[0] - a[0], dy = b[1] - a[1];
846
- for (const rings of polygons)
847
- for (const ring of rings)
848
- for (let i = 1; i < ring.length; i++) {
849
- const c = ring[i - 1], d = ring[i], ex = d[0] - c[0], ey = d[1] - c[1], det = dx * ey - dy * ex;
850
- if (Math.abs(det) < 1e-14)
851
- continue;
852
- const t = ((c[0] - a[0]) * ey - (c[1] - a[1]) * ex) / det;
853
- const u = ((c[0] - a[0]) * dy - (c[1] - a[1]) * dx) / det;
854
- if (t > 0 && t < 1 && u >= -1e-10 && u <= 1 + 1e-10)
855
- ts.push(t);
856
- }
857
- ts.sort((a2, b2) => a2 - b2);
858
- const result = [];
859
- for (let i = 1; i < ts.length; i++)
860
- if (ts[i] - ts[i - 1] > 1e-12 && contains(polygons, lerp(a, b, (ts[i] + ts[i - 1]) / 2)))
861
- result.push([lerp(a, b, ts[i - 1]), lerp(a, b, ts[i])]);
862
- return result;
863
- }
864
885
  function sampling(options) {
865
886
  const step = options.maxAngularStep ?? 2, tolerance = options.tolerance ?? 1e-4;
866
887
  if (!Number.isFinite(step) || step <= 0 || step > 90 || !Number.isFinite(tolerance) || tolerance <= 0)
867
888
  throw new Error("Invalid map sampling options");
868
889
  return { step, tolerance };
869
890
  }
870
- function sample(line, project, options, cellStep) {
891
+ function sample(line, project, options, cellStep, interpolate) {
871
892
  const { step, tolerance } = sampling(options), source = [], xy = [];
872
893
  const emit = (p, q) => {
873
894
  if (source.length >= 1e5)
@@ -878,23 +899,23 @@ function sample(line, project, options, cellStep) {
878
899
  const at = (p) => {
879
900
  const q = project(p);
880
901
  if (!q)
881
- throw new Error("Elastic grid is undefined inside its coverage");
902
+ throw new Error(`Elastic grid is undefined inside its coverage at ${p}`);
882
903
  return q;
883
904
  };
884
- const visit = (a, b, qa, qb, depth) => {
885
- const mid = lerp(a, b, 0.5), qm = at(mid), chord2 = lerp(qa, qb, 0.5);
905
+ const visit = (a, b, qa, qb, depth, segment, t0, t1) => {
906
+ const tm = (t0 + t1) / 2, mid = interpolate ? interpolate(segment, tm) : lerp(a, b, 0.5), qm = at(mid), chord2 = lerp(qa, qb, 0.5);
886
907
  if (Math.hypot(b[0] - a[0], b[1] - a[1]) > Math.min(step, cellStep / 2) || Math.hypot(qm[0] - chord2[0], qm[1] - chord2[1]) > tolerance) {
887
908
  if (depth >= 24)
888
909
  throw new Error("Elastic sampling limit exceeded");
889
- visit(a, mid, qa, qm, depth + 1);
890
- visit(mid, b, qm, qb, depth + 1);
910
+ visit(a, mid, qa, qm, depth + 1, segment, t0, tm);
911
+ visit(mid, b, qm, qb, depth + 1, segment, tm, t1);
891
912
  } else
892
913
  emit(b, qb);
893
914
  };
894
915
  if (line.length) {
895
916
  emit(line[0], at(line[0]));
896
917
  for (let i = 1; i < line.length; i++)
897
- visit(line[i - 1], line[i], at(line[i - 1]), at(line[i]), 0);
918
+ visit(line[i - 1], line[i], at(line[i - 1]), at(line[i]), 0, i - 1, 0, 1);
898
919
  }
899
920
  return { source, xy };
900
921
  }
@@ -1001,7 +1022,7 @@ function fullCoverage(map) {
1001
1022
  const overlaps = domains.some((domain, i) => domains.slice(i + 1).some((other) => import_polygon_clipping2.default.intersection(domain, other).length > 0));
1002
1023
  covered = completeSections && !overlaps && import_polygon_clipping2.default.difference(world, ...domains).length === 0;
1003
1024
  } else if (d.type === "world") {
1004
- covered = ["sinusoidal", "mollweide", "robinson"].includes(d.projection);
1025
+ covered = directWorldProjections.has(d.projection) && Object.keys(d.parameters ?? {}).length === 0;
1005
1026
  }
1006
1027
  coverageCache.set(map, covered);
1007
1028
  return covered;
@@ -1016,12 +1037,71 @@ function geographicRings(rings, center3) {
1016
1037
  });
1017
1038
  });
1018
1039
  }
1019
- var import_polygon_clipping2, MapTransitionGeometry, MapTransition, coverageCache;
1040
+ var import_polygon_clipping2, directWorldProjections, MapTransitionGeometry, MapTransition, coverageCache;
1020
1041
  var init_map_transition = __esm({
1021
1042
  "dist/discontinuous/map-transition.js"() {
1022
1043
  import_polygon_clipping2 = __toESM(require("polygon-clipping"), 1);
1023
1044
  init_math2();
1024
1045
  init_geometry();
1046
+ directWorldProjections = /* @__PURE__ */ new Set([
1047
+ "gilbert",
1048
+ "aitoff",
1049
+ "august",
1050
+ "baker",
1051
+ "boggs",
1052
+ "bromley",
1053
+ "collignon",
1054
+ "craster",
1055
+ "eckert1",
1056
+ "eckert2",
1057
+ "eckert3",
1058
+ "eckert4",
1059
+ "eckert5",
1060
+ "eckert6",
1061
+ "fahey",
1062
+ "foucaut",
1063
+ "ginzburg4",
1064
+ "ginzburg5",
1065
+ "ginzburg6",
1066
+ "ginzburg8",
1067
+ "ginzburg9",
1068
+ "homolosine",
1069
+ "kavrayskiy7",
1070
+ "larrivee",
1071
+ "laskowski",
1072
+ "miller",
1073
+ "mollweide",
1074
+ "mt-flat-polar-parabolic",
1075
+ "mt-flat-polar-quartic",
1076
+ "mt-flat-polar-sinusoidal",
1077
+ "natural-earth2",
1078
+ "nell-hammer",
1079
+ "patterson",
1080
+ "robinson",
1081
+ "sinusoidal",
1082
+ "times",
1083
+ "van-der-grinten",
1084
+ "van-der-grinten2",
1085
+ "van-der-grinten3",
1086
+ "van-der-grinten4",
1087
+ "wagner4",
1088
+ "wagner6",
1089
+ "winkel3",
1090
+ "bonne",
1091
+ "bottomley",
1092
+ "cylindrical-equal-area",
1093
+ "cylindrical-stereographic",
1094
+ "foucaut-sinusoidal",
1095
+ "hammer",
1096
+ "hill",
1097
+ "hufnagel",
1098
+ "hyperelliptical",
1099
+ "lagrange",
1100
+ "loximuthal",
1101
+ "wagner7",
1102
+ "quartic-authalic",
1103
+ "wagner"
1104
+ ]);
1025
1105
  MapTransitionGeometry = class {
1026
1106
  constructor(parts, strategy, radius2) {
1027
1107
  this.strategy = strategy;
@@ -1234,6 +1314,16 @@ function prepareMapFeatures(map, features, options = {}) {
1234
1314
  throw new Error("Expected a feature array");
1235
1315
  if (options.polygonOutlines !== void 0 && typeof options.polygonOutlines !== "boolean")
1236
1316
  throw new Error("polygonOutlines must be boolean");
1317
+ const provenance = options.provenance;
1318
+ if (provenance !== void 0 && (!provenance || typeof provenance !== "object" || Array.isArray(provenance)))
1319
+ throw new Error("Unsupported provenance policy");
1320
+ if ((provenance == null ? void 0 : provenance.distances) !== void 0 && (!provenance.distances || typeof provenance.distances !== "object"))
1321
+ throw new Error("Unsupported provenance distance policy");
1322
+ if (provenance && (typeof provenance !== "object" || Object.keys(provenance).some((k2) => !["sourceSegments", "boundaries", "distances", "polygonEdges"].includes(k2)) || [provenance.sourceSegments, provenance.boundaries, provenance.polygonEdges].some((v) => v !== void 0 && typeof v !== "boolean") || provenance.distances && (provenance.distances.metric !== "spherical" || !Number.isFinite(provenance.distances.radius) || provenance.distances.radius <= 0)))
1323
+ throw new Error("Unsupported provenance policy");
1324
+ const edgeKinds = [], edgeRings = [], edgeSegments = [], edgeParameters = [];
1325
+ const lineage = Boolean((provenance == null ? void 0 : provenance.sourceSegments) || (provenance == null ? void 0 : provenance.boundaries) || (provenance == null ? void 0 : provenance.distances));
1326
+ const segments = [], parameters = [], originalVertices = [], distances = [], startReasons = [], endReasons = [];
1237
1327
  const regionIds = map.regions.map((r) => r.id), regionIndices = new Map(regionIds.map((id, i) => [id, i]));
1238
1328
  const points = vertices(), paths = vertices(), polygons = vertices();
1239
1329
  const pointParts = [], pathParts = [], polygonParts = [];
@@ -1254,7 +1344,27 @@ function prepareMapFeatures(map, features, options = {}) {
1254
1344
  const isRing = ringIndex !== void 0;
1255
1345
  if (isRing && !closed(line))
1256
1346
  line.push([...line[0]]);
1257
- for (const p of map.projectLine(line, options.sampling)) {
1347
+ for (const p of map.projectLine(line, {
1348
+ ...options.sampling,
1349
+ pathProvenance: lineage,
1350
+ pathDistances: Boolean(provenance == null ? void 0 : provenance.distances),
1351
+ pathBoundaries: Boolean(provenance == null ? void 0 : provenance.boundaries)
1352
+ })) {
1353
+ if (lineage) {
1354
+ if (!p.provenance)
1355
+ throw new Error("Map backend does not support path provenance");
1356
+ if (provenance == null ? void 0 : provenance.sourceSegments) {
1357
+ segments.push(...p.provenance.segments);
1358
+ parameters.push(...p.provenance.parameters);
1359
+ originalVertices.push(...p.provenance.vertices);
1360
+ }
1361
+ if (provenance == null ? void 0 : provenance.distances)
1362
+ distances.push(...p.provenance.angularDistances.map((d) => d * provenance.distances.radius));
1363
+ if (provenance == null ? void 0 : provenance.boundaries) {
1364
+ startReasons.push(p.provenance.startReason);
1365
+ endReasons.push(p.provenance.endReason);
1366
+ }
1367
+ }
1258
1368
  append(paths, p.coordinates, p.sourceCoordinates);
1259
1369
  pathParts.push({
1260
1370
  ...part(featureIndex, geometryPath, p.regionId),
@@ -1266,12 +1376,41 @@ function prepareMapFeatures(map, features, options = {}) {
1266
1376
  }
1267
1377
  };
1268
1378
  const addPolygon = (input, featureIndex, geometryPath) => {
1379
+ var _a2;
1269
1380
  if (!input.length)
1270
1381
  return;
1271
1382
  const rings = input.map((r) => r.map(position));
1272
- const parts = options.sphericalCuts ? map.projectGlobalPolygon(rings, { ...options.sampling, ...options.sphericalCuts }) : map.projectPolygon(rings, options.sampling);
1383
+ const parts = options.sphericalCuts ? map.projectGlobalPolygon(rings, {
1384
+ ...options.sampling,
1385
+ ...options.sphericalCuts,
1386
+ polygonEdgeProvenance: provenance == null ? void 0 : provenance.polygonEdges
1387
+ }) : map.projectPolygon(rings, {
1388
+ ...options.sampling,
1389
+ polygonEdgeProvenance: provenance == null ? void 0 : provenance.polygonEdges
1390
+ });
1273
1391
  for (const p of parts) {
1274
1392
  for (let i = 0; i < p.coordinates.length; i++) {
1393
+ if (provenance == null ? void 0 : provenance.polygonEdges) {
1394
+ const edges = (_a2 = p.edgeProvenance) == null ? void 0 : _a2[i];
1395
+ if (!edges)
1396
+ throw new Error("Map backend does not support polygon edge provenance");
1397
+ const kinds = [
1398
+ "source-boundary",
1399
+ "coverage-boundary",
1400
+ "chart-cut",
1401
+ "continuous-chart-join"
1402
+ ];
1403
+ for (const edge2 of edges) {
1404
+ edgeKinds.push(kinds.indexOf(edge2.kind) + 1);
1405
+ edgeRings.push(edge2.ring);
1406
+ edgeSegments.push(edge2.segment);
1407
+ edgeParameters.push(edge2.t0, edge2.t1);
1408
+ }
1409
+ edgeKinds.push(0);
1410
+ edgeRings.push(4294967295);
1411
+ edgeSegments.push(4294967295);
1412
+ edgeParameters.push(0, 0);
1413
+ }
1275
1414
  append(polygons, p.coordinates[i], p.sourceCoordinates[i]);
1276
1415
  ringStarts.push(polygons.positions.length / 2);
1277
1416
  }
@@ -1335,12 +1474,38 @@ function prepareMapFeatures(map, features, options = {}) {
1335
1474
  featureIds,
1336
1475
  regionIds,
1337
1476
  points: { ...pack(points), parts: pointParts },
1338
- paths: { ...pack(paths), parts: pathParts, pathStarts: new Uint32Array(pathStarts) },
1477
+ paths: {
1478
+ ...pack(paths),
1479
+ parts: pathParts,
1480
+ pathStarts: new Uint32Array(pathStarts),
1481
+ ...lineage ? {
1482
+ provenance: {
1483
+ ...(provenance == null ? void 0 : provenance.sourceSegments) ? {
1484
+ sourceSegmentIndices: new Uint32Array(segments),
1485
+ sourceSegmentParameters: new Float64Array(parameters),
1486
+ sourceVertexIndices: new Uint32Array(originalVertices)
1487
+ } : {},
1488
+ ...(provenance == null ? void 0 : provenance.distances) ? {
1489
+ sourceDistances: new Float64Array(distances),
1490
+ distanceRadius: provenance.distances.radius
1491
+ } : {},
1492
+ ...(provenance == null ? void 0 : provenance.boundaries) ? { startReasons, endReasons } : {}
1493
+ }
1494
+ } : {}
1495
+ },
1339
1496
  polygons: {
1340
1497
  ...pack(polygons),
1341
1498
  parts: polygonParts,
1342
1499
  ringStarts: new Uint32Array(ringStarts),
1343
- polygonStarts: new Uint32Array(polygonStarts)
1500
+ polygonStarts: new Uint32Array(polygonStarts),
1501
+ ...(provenance == null ? void 0 : provenance.polygonEdges) ? {
1502
+ edgeProvenance: {
1503
+ kinds: new Uint8Array(edgeKinds),
1504
+ sourceRingIndices: new Uint32Array(edgeRings),
1505
+ sourceSegmentIndices: new Uint32Array(edgeSegments),
1506
+ sourceParameterRanges: new Float64Array(edgeParameters)
1507
+ }
1508
+ } : {}
1344
1509
  }
1345
1510
  };
1346
1511
  }
@@ -1355,6 +1520,181 @@ var init_prepare_features = __esm({
1355
1520
  }
1356
1521
  });
1357
1522
 
1523
+ // dist/discontinuous/polygon-constraints.js
1524
+ function tracePolygonConstraints(input, output, tolerance = 1e-10, priorities, accept, degenerate) {
1525
+ const constraints = input.flatMap((ring, r) => ring.slice(0, -1).map((a, i) => ({ a, b: ring[i + 1], r, i })));
1526
+ let work = 0;
1527
+ const tick = () => {
1528
+ if (++work > 2e7)
1529
+ throw new Error("Polygon constraint work limit exceeded");
1530
+ };
1531
+ const fraction = (p, a, b) => {
1532
+ const dx = b[0] - a[0], dy = b[1] - a[1], l = dx * dx + dy * dy;
1533
+ return l ? ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / l : NaN;
1534
+ };
1535
+ const on = (p, a, b) => {
1536
+ tick();
1537
+ const t = fraction(p, a, b), length = Math.hypot(b[0] - a[0], b[1] - a[1]);
1538
+ return length > 0 && t >= -tolerance && t <= 1 + tolerance && Math.hypot(p[0] - a[0] - t * (b[0] - a[0]), p[1] - a[1] - t * (b[1] - a[1])) <= tolerance * Math.max(1, length);
1539
+ };
1540
+ const boxes = constraints.map((c) => [
1541
+ Math.min(c.a[0], c.b[0]),
1542
+ Math.min(c.a[1], c.b[1]),
1543
+ Math.max(c.a[0], c.b[0]),
1544
+ Math.max(c.a[1], c.b[1])
1545
+ ]);
1546
+ const build = (ids) => {
1547
+ const box = [Infinity, Infinity, -Infinity, -Infinity];
1548
+ for (const id of ids) {
1549
+ tick();
1550
+ const b = boxes[id];
1551
+ box[0] = Math.min(box[0], b[0]);
1552
+ box[1] = Math.min(box[1], b[1]);
1553
+ box[2] = Math.max(box[2], b[2]);
1554
+ box[3] = Math.max(box[3], b[3]);
1555
+ }
1556
+ if (ids.length <= 16)
1557
+ return { box, ids };
1558
+ const axis = box[2] - box[0] >= box[3] - box[1] ? 0 : 1;
1559
+ ids.sort((a, b) => {
1560
+ tick();
1561
+ return boxes[a][axis] + boxes[a][axis + 2] - boxes[b][axis] - boxes[b][axis + 2];
1562
+ });
1563
+ const middle = Math.floor(ids.length / 2);
1564
+ return { box, left: build(ids.slice(0, middle)), right: build(ids.slice(middle)) };
1565
+ };
1566
+ const root = build(constraints.map((_, i) => i));
1567
+ const padding = tolerance * Math.max(1, root.box[2] - root.box[0], root.box[3] - root.box[1]);
1568
+ const query = (a, b) => {
1569
+ const box = [
1570
+ Math.min(a[0], b[0]) - padding,
1571
+ Math.min(a[1], b[1]) - padding,
1572
+ Math.max(a[0], b[0]) + padding,
1573
+ Math.max(a[1], b[1]) + padding
1574
+ ], ids = [];
1575
+ const overlaps = (b2) => b2[0] <= box[2] && b2[2] >= box[0] && b2[1] <= box[3] && b2[3] >= box[1];
1576
+ const visit = (node) => {
1577
+ tick();
1578
+ if (!overlaps(node.box))
1579
+ return;
1580
+ if (node.ids) {
1581
+ for (const id of node.ids) {
1582
+ tick();
1583
+ if (overlaps(boxes[id]))
1584
+ ids.push(id);
1585
+ }
1586
+ } else {
1587
+ visit(node.left);
1588
+ visit(node.right);
1589
+ }
1590
+ };
1591
+ visit(root);
1592
+ return ids.map((id) => constraints[id]);
1593
+ };
1594
+ const rings = [], owners = [];
1595
+ for (const ring of output) {
1596
+ const points = [];
1597
+ for (let i = 1; i < ring.length; i++) {
1598
+ const a = ring[i - 1], b = ring[i];
1599
+ if (a.every((v, k2) => v === b[k2]))
1600
+ continue;
1601
+ const breaks = [{ t: 0, p: a }];
1602
+ for (const c of query(a, b))
1603
+ if (on(c.a, a, b) && (!accept || accept(c.a))) {
1604
+ const t = fraction(c.a, a, b);
1605
+ if (t > 1e-12 && t < 1 - 1e-12)
1606
+ breaks.push({ t, p: c.a });
1607
+ }
1608
+ breaks.sort((a2, b2) => a2.t - b2.t);
1609
+ for (const item of breaks)
1610
+ if (!points.length || Math.hypot(...points[points.length - 1].map((v, k2) => v - item.p[k2])) > 1e-14)
1611
+ points.push([...item.p]);
1612
+ }
1613
+ if (points.length < 3)
1614
+ continue;
1615
+ points.push([...points[0]]);
1616
+ const tags = [];
1617
+ for (let i = 1; i < points.length; i++) {
1618
+ const a = points[i - 1], b = points[i];
1619
+ if (degenerate == null ? void 0 : degenerate(a, b)) {
1620
+ tags.push(null);
1621
+ continue;
1622
+ }
1623
+ let candidates = query(a, b).filter((c) => on(a, c.a, c.b) && on(b, c.a, c.b));
1624
+ const same = (p, q) => Math.hypot(...p.map((v, k2) => v - q[k2])) <= 64 * Number.EPSILON * Math.max(1, ...p.map(Math.abs), ...q.map(Math.abs));
1625
+ const exact = candidates.filter((c) => same(a, c.a) && same(b, c.b) || same(a, c.b) && same(b, c.a));
1626
+ if (exact.length)
1627
+ candidates = exact;
1628
+ if (candidates.some((c) => {
1629
+ var _a2, _b;
1630
+ return ((_b = (_a2 = priorities == null ? void 0 : priorities[c.r]) == null ? void 0 : _a2[c.i]) == null ? void 0 : _b.kind) === "source-boundary";
1631
+ }))
1632
+ candidates = candidates.filter((c) => {
1633
+ var _a2, _b;
1634
+ return ((_b = (_a2 = priorities == null ? void 0 : priorities[c.r]) == null ? void 0 : _a2[c.i]) == null ? void 0 : _b.kind) === "source-boundary";
1635
+ });
1636
+ if (candidates.length > 1) {
1637
+ const score = (c) => Math.max(...[a, b].map((p) => {
1638
+ const t = Math.min(1, Math.max(0, fraction(p, c.a, c.b)));
1639
+ return Math.hypot(p[0] - c.a[0] - t * (c.b[0] - c.a[0]), p[1] - c.a[1] - t * (c.b[1] - c.a[1]));
1640
+ }));
1641
+ candidates.sort((a2, b2) => score(a2) - score(b2));
1642
+ const epsilon7 = 64 * Number.EPSILON * Math.max(1, ...a.map(Math.abs), ...b.map(Math.abs));
1643
+ if (score(candidates[1]) - score(candidates[0]) > epsilon7)
1644
+ candidates = [candidates[0]];
1645
+ else if (Math.hypot(a[0] - b[0], a[1] - b[1]) <= 2 * epsilon7)
1646
+ candidates = [];
1647
+ else
1648
+ throw new Error("Ambiguous polygon clipping constraint ancestry");
1649
+ }
1650
+ tags.push(candidates.length ? [candidates[0].r, candidates[0].i] : null);
1651
+ }
1652
+ rings.push(points);
1653
+ owners.push(tags);
1654
+ }
1655
+ return { rings, owners };
1656
+ }
1657
+ function composePolygonEdge(parent, a, b, start, end) {
1658
+ if (parent.kind !== "source-boundary")
1659
+ return { ...parent };
1660
+ const v = geographic(a), length = angle(v, geographic(b));
1661
+ const parameter = (p) => length ? Math.min(1, angle(v, geographic(p)) / length) : 0;
1662
+ return {
1663
+ ...parent,
1664
+ t0: parent.t0 + parameter(start) * (parent.t1 - parent.t0),
1665
+ t1: parent.t0 + parameter(end) * (parent.t1 - parent.t0)
1666
+ };
1667
+ }
1668
+ var init_polygon_constraints = __esm({
1669
+ "dist/discontinuous/polygon-constraints.js"() {
1670
+ init_math2();
1671
+ }
1672
+ });
1673
+
1674
+ // dist/discontinuous/source-arc.js
1675
+ function sourceArc(a, b) {
1676
+ const va = geographic(a), vb = geographic(b), length = angle(va, vb);
1677
+ const poleA = Math.abs(a[1]) === 90, poleB = Math.abs(b[1]) === 90;
1678
+ const start = poleA && !poleB ? [b[0], a[1]] : [...a], end = poleB && !poleA ? [a[0], b[1]] : [...b];
1679
+ return (t) => {
1680
+ if (t === 0)
1681
+ return [...start];
1682
+ if (t === 1)
1683
+ return [...end];
1684
+ if (length < 1e-14)
1685
+ return [a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1])];
1686
+ const p = coordinates(unit(va.map((v, k2) => (Math.sin((1 - t) * length) * v + Math.sin(t * length) * vb[k2]) / Math.sin(length))));
1687
+ const reference = start[0] + t * (end[0] - start[0]);
1688
+ p[0] += 360 * Math.round((reference - p[0]) / 360);
1689
+ return p;
1690
+ };
1691
+ }
1692
+ var init_source_arc = __esm({
1693
+ "dist/discontinuous/source-arc.js"() {
1694
+ init_math2();
1695
+ }
1696
+ });
1697
+
1358
1698
  // dist/discontinuous/map-layout.js
1359
1699
  function sampling2(options) {
1360
1700
  const step = options.maxAngularStep ?? 2, tolerance = options.tolerance ?? 1e-4;
@@ -1546,6 +1886,10 @@ var init_map_layout = __esm({
1546
1886
  for (let i = 1; i < points.length; i++)
1547
1887
  validateArc(points[i - 1], points[i]);
1548
1888
  const results = [];
1889
+ const cumulative = options.pathDistances ? [0] : void 0;
1890
+ if (cumulative)
1891
+ for (let i = 1; i < points.length; i++)
1892
+ cumulative.push(cumulative[i - 1] + angle(points[i - 1], points[i]));
1549
1893
  for (const r of this.compiled) {
1550
1894
  let current = null;
1551
1895
  for (let i = 1; i < points.length; i++) {
@@ -1554,18 +1898,42 @@ var init_map_layout = __esm({
1554
1898
  current = null;
1555
1899
  continue;
1556
1900
  }
1901
+ const reason = (p, endpoint, isEndpoint) => {
1902
+ if (isEndpoint && angle(p, endpoint) < 1e-12)
1903
+ return "source-endpoint";
1904
+ const kinds = r.normals.map((normal, edge2) => {
1905
+ var _a2;
1906
+ return Math.abs(dot(normal, p)) < 1e-9 ? (_a2 = this.seams.find((s) => s.regions.some((id, i2) => id === r.definition.id && s.edges[i2] === edge2))) == null ? void 0 : _a2.kind : void 0;
1907
+ }).filter(Boolean);
1908
+ return kinds.includes("boundary") ? "coverage-boundary" : kinds.includes("cut") ? "chart-cut" : kinds.includes("join") ? "continuous-chart-join" : "coverage-boundary";
1909
+ };
1557
1910
  const sampled = this.sample(r, clipped[0], clipped[1], settings);
1558
1911
  if (sampled.length < 2)
1559
1912
  continue;
1560
1913
  if (!current || angle(geographic(current.sourceCoordinates[current.sourceCoordinates.length - 1]), clipped[0]) > EPS || !this.sameProjectedPoint(current.coordinates[current.coordinates.length - 1], this.project(r, sampled[0]))) {
1561
1914
  current = { regionId: r.definition.id, coordinates: [], sourceCoordinates: [] };
1915
+ if (options.pathProvenance)
1916
+ current.provenance = { segments: [], parameters: [], vertices: [], angularDistances: [] };
1917
+ if (current.provenance && options.pathBoundaries)
1918
+ current.provenance.startReason = reason(clipped[0], points[0], i === 1);
1562
1919
  results.push(current);
1563
1920
  } else
1564
1921
  sampled.shift();
1565
1922
  for (const p of sampled) {
1566
1923
  current.coordinates.push(this.project(r, p));
1567
1924
  current.sourceCoordinates.push(coordinates(p));
1925
+ if (current.provenance) {
1926
+ const length = angle(points[i - 1], points[i]);
1927
+ const t = length ? Math.min(1, Math.max(0, angle(points[i - 1], p) / length)) : 0;
1928
+ current.provenance.segments.push(i - 1);
1929
+ current.provenance.parameters.push(t);
1930
+ current.provenance.vertices.push(angle(points[i - 1], p) < 1e-12 ? i - 1 : angle(points[i], p) < 1e-12 ? i : 4294967295);
1931
+ if (cumulative)
1932
+ current.provenance.angularDistances.push(cumulative[i - 1] + t * length);
1933
+ }
1568
1934
  }
1935
+ if (current.provenance && options.pathBoundaries)
1936
+ current.provenance.endReason = reason(clipped[1], points[points.length - 1], i === points.length - 1);
1569
1937
  if (!this.contains(r, points[i]))
1570
1938
  current = null;
1571
1939
  }
@@ -1662,26 +2030,91 @@ var init_map_layout = __esm({
1662
2030
  coordinates: [],
1663
2031
  sourceCoordinates: []
1664
2032
  };
2033
+ if (options.polygonEdgeProvenance)
2034
+ output.edgeProvenance = [];
1665
2035
  polygon.forEach((ring, index) => {
1666
2036
  const sphere = ring.map((p) => unit(add(center3, add(scale(x, p[0]), scale(y, p[1])))));
1667
2037
  const points = [];
2038
+ const edges = [];
2039
+ const classify = (a, b) => {
2040
+ if (angle(a, b) < 64 * Number.EPSILON)
2041
+ return {
2042
+ kind: "continuous-chart-join",
2043
+ ring: 4294967295,
2044
+ segment: 4294967295,
2045
+ t0: 0,
2046
+ t1: 0
2047
+ };
2048
+ const candidates = [];
2049
+ vectors.forEach((source, ring2) => source.forEach((u, segment) => {
2050
+ const v = source[(segment + 1) % source.length], length = angle(u, v);
2051
+ if (length > 1e-12 && [a, b].every((p) => Math.abs(dot(unit(cross(u, v)), p)) < 1e-12 && Math.abs(angle(u, p) + angle(p, v) - length) < 1e-10))
2052
+ candidates.push({ ring: ring2, segment });
2053
+ }));
2054
+ if (candidates.length > 1)
2055
+ throw new Error("Ambiguous polygon boundary provenance");
2056
+ if (candidates.length) {
2057
+ const { ring: ring2, segment } = candidates[0], source = vectors[ring2], u = source[segment], length = angle(u, source[(segment + 1) % source.length]);
2058
+ return {
2059
+ kind: "source-boundary",
2060
+ ring: ring2,
2061
+ segment,
2062
+ t0: Math.min(1, angle(u, a) / length),
2063
+ t1: Math.min(1, angle(u, b) / length)
2064
+ };
2065
+ }
2066
+ const kinds = r.normals.map((n, edge2) => {
2067
+ var _a2;
2068
+ return [a, b].every((p) => Math.abs(dot(n, p)) < 1e-9) ? (_a2 = this.seams.find((s) => s.regions.some((id, i) => id === r.definition.id && s.edges[i] === edge2))) == null ? void 0 : _a2.kind : void 0;
2069
+ }).filter(Boolean);
2070
+ if (!kinds.length)
2071
+ throw new Error("Unknown polygon clipping constraint");
2072
+ return {
2073
+ kind: kinds.includes("boundary") ? "coverage-boundary" : kinds.includes("cut") ? "chart-cut" : "continuous-chart-join",
2074
+ ring: 4294967295,
2075
+ segment: 4294967295,
2076
+ t0: 0,
2077
+ t1: 0
2078
+ };
2079
+ };
1668
2080
  for (let i = 1; i < sphere.length; i++) {
2081
+ const origin2 = options.polygonEdgeProvenance && !sphere[i - 1].every((value2, k2) => value2 === sphere[i][k2]) ? classify(sphere[i - 1], sphere[i]) : void 0;
1669
2082
  const sampled = this.sample(r, sphere[i - 1], sphere[i], settings);
1670
2083
  for (const point of sampled) {
1671
2084
  const previous = points[points.length - 1];
1672
- if (!previous || !point.every((value2, k2) => value2 === previous[k2]))
2085
+ if (!previous || !point.every((value2, k2) => value2 === previous[k2])) {
2086
+ if (previous && origin2) {
2087
+ const edge2 = { ...origin2 };
2088
+ if (edge2.kind === "source-boundary") {
2089
+ const source = vectors[edge2.ring], u = source[edge2.segment], length = angle(u, source[(edge2.segment + 1) % source.length]);
2090
+ edge2.t0 = Math.min(1, angle(u, previous) / length);
2091
+ edge2.t1 = Math.min(1, angle(u, point) / length);
2092
+ }
2093
+ edges.push(edge2);
2094
+ }
1673
2095
  points.push(point);
2096
+ }
1674
2097
  }
1675
2098
  }
1676
2099
  if (points.length < 3)
1677
2100
  return;
1678
2101
  if (points[points.length - 1].every((value2, k2) => value2 === points[0][k2]))
1679
2102
  points.pop();
2103
+ if (options.polygonEdgeProvenance && edges.length < points.length)
2104
+ edges.push(classify(points[points.length - 1], points[0]));
1680
2105
  points.push(points[0]);
1681
2106
  const xy = points.map((p) => this.project(r, p));
1682
2107
  if (ringOrientation(xy) > 0 !== (index === 0)) {
1683
2108
  xy.reverse();
1684
2109
  points.reverse();
2110
+ edges.reverse();
2111
+ for (const e of edges)
2112
+ [e.t0, e.t1] = [e.t1, e.t0];
2113
+ }
2114
+ if (output.edgeProvenance) {
2115
+ if (edges.length !== points.length - 1)
2116
+ throw new Error("Mismatched polygon edge provenance");
2117
+ output.edgeProvenance.push(edges);
1685
2118
  }
1686
2119
  output.coordinates.push(xy);
1687
2120
  output.sourceCoordinates.push(points.map(coordinates));
@@ -1794,6 +2227,8 @@ var CapMap;
1794
2227
  var init_cap_map = __esm({
1795
2228
  "dist/discontinuous/cap-map.js"() {
1796
2229
  init_clip_circle();
2230
+ init_polygon_constraints();
2231
+ init_source_arc();
1797
2232
  init_circle();
1798
2233
  init_map_layout();
1799
2234
  init_math2();
@@ -1850,10 +2285,11 @@ var init_cap_map = __esm({
1850
2285
  unprojectPoint(p, id) {
1851
2286
  return super.unprojectPoint(p, id).filter((q) => this.visible(q.position));
1852
2287
  }
1853
- clip(rings, polygon, options, regionId) {
2288
+ clip(rings, polygon, options, regionId, segmentIndices) {
1854
2289
  const output = [];
1855
2290
  const originals = /* @__PURE__ */ new Map();
1856
- let ring = [];
2291
+ let ring = [], indices = [], activeSegment = 0;
2292
+ let previousInput, currentInput;
1857
2293
  const precision = Math.min((options == null ? void 0 : options.maxAngularStep) ?? 2, Math.sqrt(((options == null ? void 0 : options.tolerance) ?? 1e-4) / this.radius) / RAD / 4) * RAD;
1858
2294
  if (!Number.isFinite(precision) || 2 * Math.PI / precision > 1e6)
1859
2295
  throw new Error("Projection sampling limit exceeded; relax tolerance");
@@ -1908,14 +2344,49 @@ var init_cap_map = __esm({
1908
2344
  point: (x, y) => {
1909
2345
  if (++emitted > 1e6)
1910
2346
  throw new Error("Projection sampling limit exceeded; relax tolerance");
1911
- ring.push(originals.get(`${x},${y}`) ?? this.source([x / RAD, y / RAD]));
2347
+ let source = originals.get(`${x},${y}`);
2348
+ if (!source && !polygon && this.nonlinear && previousInput && currentInput) {
2349
+ const a = geographic(previousInput), b = geographic(currentInput), length = angle(a, b);
2350
+ const at = (t) => length < 1e-14 ? [...currentInput] : coordinates(unit(a.map((v, k2) => (Math.sin((1 - t) * length) * v + Math.sin(t * length) * b[k2]) / Math.sin(length))));
2351
+ const value2 = (t) => geographic(local(at(t)))[0] - Math.cos(this.cap);
2352
+ const approximate = this.source([x / RAD, y / RAD]);
2353
+ const reference = length ? Math.min(1, angle(a, geographic(approximate)) / length) : 0;
2354
+ const roots = [];
2355
+ for (let j = 0; j < 32; j++) {
2356
+ let lo = j / 32, hi = (j + 1) / 32, f = value2(lo), g = value2(hi);
2357
+ if (Math.abs(f) < 1e-14)
2358
+ roots.push(lo);
2359
+ if (f * g >= 0)
2360
+ continue;
2361
+ for (let k2 = 0; k2 < 52; k2++) {
2362
+ const mid = (lo + hi) / 2;
2363
+ if (value2(mid) >= 0 === f >= 0)
2364
+ lo = mid;
2365
+ else
2366
+ hi = mid;
2367
+ }
2368
+ roots.push((lo + hi) / 2);
2369
+ }
2370
+ if (Math.abs(value2(1)) < 1e-14)
2371
+ roots.push(1);
2372
+ if (!roots.length)
2373
+ throw new Error("Unresolved nonlinear cap source intersection");
2374
+ roots.sort((a2, b2) => Math.abs(a2 - reference) - Math.abs(b2 - reference));
2375
+ source = at(roots[0]);
2376
+ }
2377
+ ring.push(source ?? this.source([x / RAD, y / RAD]));
2378
+ if (segmentIndices)
2379
+ indices.push(activeSegment);
1912
2380
  },
1913
2381
  lineStart: () => {
1914
2382
  ring = [];
2383
+ indices = [];
1915
2384
  },
1916
2385
  lineEnd: () => {
1917
- if (ring.length > 1)
2386
+ if (ring.length > 1) {
1918
2387
  output.push(ring);
2388
+ segmentIndices == null ? void 0 : segmentIndices.push(indices);
2389
+ }
1919
2390
  },
1920
2391
  polygonStart: () => {
1921
2392
  },
@@ -1932,7 +2403,9 @@ var init_cap_map = __esm({
1932
2403
  }
1933
2404
  const x = q[0] * RAD, y = q[1] * RAD;
1934
2405
  originals.set(`${x},${y}`, [...p]);
2406
+ currentInput = p;
1935
2407
  stream.point(x, y);
2408
+ previousInput = p;
1936
2409
  };
1937
2410
  const subdivide = (a, b, depth = 0) => {
1938
2411
  if (this.nonlinear && depth < 20) {
@@ -1949,11 +2422,14 @@ var init_cap_map = __esm({
1949
2422
  emit(b);
1950
2423
  };
1951
2424
  for (const points of rings) {
2425
+ previousInput = void 0;
1952
2426
  stream.lineStart();
1953
2427
  if (points.length)
1954
2428
  emit(points[0]);
1955
- for (let i = 1; i < points.length; i++)
2429
+ for (let i = 1; i < points.length; i++) {
2430
+ activeSegment = i - 1;
1956
2431
  subdivide(points[i - 1], points[i]);
2432
+ }
1957
2433
  if (polygon && points.length)
1958
2434
  subdivide(points[points.length - 1], points[0]);
1959
2435
  stream.lineEnd();
@@ -1965,7 +2441,41 @@ var init_cap_map = __esm({
1965
2441
  projectLine(line, options) {
1966
2442
  if (this.cap === Math.PI / 2 && !this.nonlinear)
1967
2443
  return super.projectLine(line, options);
1968
- return super.projectLine(line, options).flatMap((part) => this.clip([part.sourceCoordinates], false, options, part.regionId).flatMap((line2) => super.projectLine(line2, options).filter((q) => q.regionId === part.regionId)));
2444
+ return super.projectLine(line, options).flatMap((part) => {
2445
+ const indices = part.provenance ? [] : void 0;
2446
+ const clipped = this.clip([part.sourceCoordinates], false, options, part.regionId, indices);
2447
+ return clipped.flatMap((points, piece) => super.projectLine(points, options).filter((q) => q.regionId === part.regionId).map((q) => {
2448
+ const parent = part.provenance, child2 = q.provenance;
2449
+ if (!parent || !child2)
2450
+ return q;
2451
+ const provenance = {
2452
+ segments: [],
2453
+ parameters: [],
2454
+ vertices: [],
2455
+ angularDistances: []
2456
+ };
2457
+ for (let j = 0; j < q.sourceCoordinates.length; j++) {
2458
+ const k2 = child2.segments[j];
2459
+ const edge2 = indices[piece][k2 + 1];
2460
+ const a = geographic(part.sourceCoordinates[edge2]), b = geographic(part.sourceCoordinates[edge2 + 1]), p = geographic(q.sourceCoordinates[j]);
2461
+ const length = angle(a, b), t = length ? Math.min(1, angle(a, p) / length) : 0;
2462
+ const endpoint = t < 1e-12 ? edge2 : t > 1 - 1e-12 ? edge2 + 1 : -1;
2463
+ const segment = parent.segments[edge2 + 1];
2464
+ const from = parent.segments[edge2] === segment ? parent.parameters[edge2] : 0;
2465
+ provenance.segments.push(endpoint >= 0 ? parent.segments[endpoint] : segment);
2466
+ provenance.parameters.push(endpoint >= 0 ? parent.parameters[endpoint] : from + t * (parent.parameters[edge2 + 1] - from));
2467
+ provenance.vertices.push(endpoint >= 0 ? parent.vertices[endpoint] : 4294967295);
2468
+ if (options == null ? void 0 : options.pathDistances)
2469
+ provenance.angularDistances.push(parent.angularDistances[edge2] + t * (parent.angularDistances[edge2 + 1] - parent.angularDistances[edge2]));
2470
+ }
2471
+ if (options == null ? void 0 : options.pathBoundaries) {
2472
+ const same = (a, b) => angle(geographic(a), geographic(b)) < 1e-12;
2473
+ provenance.startReason = same(q.sourceCoordinates[0], part.sourceCoordinates[0]) ? parent.startReason : "coverage-boundary";
2474
+ provenance.endReason = same(q.sourceCoordinates[q.sourceCoordinates.length - 1], part.sourceCoordinates[part.sourceCoordinates.length - 1]) ? parent.endReason : "coverage-boundary";
2475
+ }
2476
+ return { ...q, provenance };
2477
+ }));
2478
+ });
1969
2479
  }
1970
2480
  projectPolygon(rings, options) {
1971
2481
  if (this.cap === Math.PI / 2 && !this.nonlinear)
@@ -2038,15 +2548,95 @@ var init_cap_map = __esm({
2038
2548
  };
2039
2549
  for (const ring of outer) {
2040
2550
  const plane = ring.map(planar), sources = [ring, ...holes.filter((h2) => contains2(plane, planar(h2[0])))];
2551
+ const parentPlane = part.sourceCoordinates.map((r) => r.map(planar));
2552
+ const fromPlane = (p) => coordinates(unit(center3.map((v, k2) => v + x[k2] * p[0] + y[k2] * p[1])));
2553
+ const traced = tracePolygonConstraints(parentPlane, sources.map((r) => r.map(planar)), this.nonlinear ? 2e-6 : 1e-10, part.edgeProvenance, (p) => this.visible(fromPlane(p)));
2554
+ const resolved2 = traced.rings.map((r) => r.map(fromPlane));
2555
+ traced.owners.forEach((owners, r) => owners.forEach((owner, i) => {
2556
+ if (!owner)
2557
+ return;
2558
+ const [pr, pe] = owner, a = part.sourceCoordinates[pr][pe], b = part.sourceCoordinates[pr][pe + 1], at = sourceArc(a, b);
2559
+ for (const j of [i, i + 1]) {
2560
+ const point = resolved2[r][j], distance3 = angle(geographic(a), geographic(b)), fraction = distance3 ? angle(geographic(a), geographic(point)) / distance3 : 0;
2561
+ if (fraction < 1e-10)
2562
+ resolved2[r][j] = [...a];
2563
+ else if (fraction > 1 - 1e-10)
2564
+ resolved2[r][j] = [...b];
2565
+ else if (Math.hypot(...cross(geographic(a), geographic(b))) > 1e-14 && Math.abs(dot(unit(cross(geographic(a), geographic(b))), geographic(point))) < 1e-12) {
2566
+ resolved2[r][j] = at(Math.min(1, Math.max(0, fraction)));
2567
+ } else {
2568
+ const value2 = (t) => geographic(this.local(at(t)))[0] - Math.cos(this.cap), roots = [];
2569
+ for (let k2 = 0; k2 < 32; k2++) {
2570
+ let lo = k2 / 32, hi = (k2 + 1) / 32, f = value2(lo), g = value2(hi);
2571
+ if (Math.abs(f) < 1e-13)
2572
+ roots.push(lo);
2573
+ if (f * g >= 0)
2574
+ continue;
2575
+ for (let n = 0; n < 52; n++) {
2576
+ const mid = (lo + hi) / 2;
2577
+ if (value2(mid) >= 0 === f >= 0)
2578
+ lo = mid;
2579
+ else
2580
+ hi = mid;
2581
+ }
2582
+ roots.push((lo + hi) / 2);
2583
+ }
2584
+ if (Math.abs(value2(1)) < 1e-13)
2585
+ roots.push(1);
2586
+ if (!roots.length)
2587
+ throw new Error("Unresolved cap polygon source intersection");
2588
+ roots.sort((a2, b2) => Math.abs(a2 - fraction) - Math.abs(b2 - fraction));
2589
+ resolved2[r][j] = at(roots[0]);
2590
+ }
2591
+ }
2592
+ }));
2593
+ for (const ring2 of resolved2)
2594
+ ring2[ring2.length - 1] = [...ring2[0]];
2041
2595
  result.push({
2042
2596
  regionId: part.regionId,
2043
- sourceCoordinates: sources,
2044
- coordinates: sources.map((r) => r.map((p) => {
2597
+ sourceCoordinates: resolved2,
2598
+ coordinates: resolved2.map((r) => r.map((p) => {
2045
2599
  const q = super.projectPoint(p, part.regionId);
2046
2600
  if (!q)
2047
- throw new Error(`Cap point outside chart ${part.regionId}: ${p}, owner ${super.findRegions(p)}`);
2601
+ throw new Error("Cap polygon source constraint left its owning chart");
2048
2602
  return q.position;
2049
- }))
2603
+ })),
2604
+ ...(options == null ? void 0 : options.polygonEdgeProvenance) ? {
2605
+ edgeProvenance: traced.owners.map((owners, r) => owners.map((owner, i) => {
2606
+ const a = resolved2[r][i], b = resolved2[r][i + 1];
2607
+ if (owner) {
2608
+ const [pr, pe] = owner;
2609
+ return composePolygonEdge(part.edgeProvenance[pr][pe], part.sourceCoordinates[pr][pe], part.sourceCoordinates[pr][pe + 1], a, b);
2610
+ }
2611
+ if (angle(geographic(a), geographic(b)) < 64 * Number.EPSILON)
2612
+ return {
2613
+ kind: "continuous-chart-join",
2614
+ ring: 4294967295,
2615
+ segment: 4294967295,
2616
+ t0: 0,
2617
+ t1: 0
2618
+ };
2619
+ const kinds = [];
2620
+ region.boundary.forEach((p, j) => {
2621
+ const n = cross(geographic(p), geographic(region.boundary[(j + 1) % region.boundary.length])), length = Math.hypot(...n);
2622
+ if (length && [a, b].every((p2) => Math.abs(dot(n, geographic(p2))) / length < 1e-9)) {
2623
+ const seam = this.seams.find((s) => s.regions.some((id, k2) => id === region.id && s.edges[k2] === j));
2624
+ if (seam)
2625
+ kinds.push(seam.kind);
2626
+ }
2627
+ });
2628
+ const horizon = [a, b].every((p) => Math.abs(geographic(this.local(p))[0] - Math.cos(this.cap)) < 1e-7);
2629
+ if (!horizon && !kinds.length)
2630
+ throw new Error("Unknown cap polygon clipping constraint");
2631
+ return {
2632
+ kind: horizon ? "coverage-boundary" : kinds.includes("boundary") ? "coverage-boundary" : kinds.includes("cut") ? "chart-cut" : "continuous-chart-join",
2633
+ ring: 4294967295,
2634
+ segment: 4294967295,
2635
+ t0: 0,
2636
+ t1: 0
2637
+ };
2638
+ }))
2639
+ } : {}
2050
2640
  });
2051
2641
  }
2052
2642
  }
@@ -4322,6 +4912,101 @@ var init_grid = __esm({
4322
4912
  }
4323
4913
  });
4324
4914
 
4915
+ // dist/discontinuous/elastic/source-arc.js
4916
+ function clipSourceArc(a, b, polygons) {
4917
+ const at = sourceArc(a, b), va = geographic(a), vb = geographic(b), omega = angle(va, vb), ts = [0, 1];
4918
+ let work = 0;
4919
+ const tick = () => {
4920
+ if (++work > 1e6)
4921
+ throw new Error("Elastic source clipping limit exceeded");
4922
+ };
4923
+ const linear = omega < 1e-14 || Math.abs(a[1]) < 1e-12 && Math.abs(b[1]) < 1e-12 || Math.abs(at(0)[0] - at(1)[0]) < 1e-12;
4924
+ const tangent = omega > 1e-14 ? va.map((v, k2) => (vb[k2] - v * Math.cos(omega)) / Math.sin(omega)) : [0, 0, 0];
4925
+ const momentum = Math.abs(omega * (va[0] * tangent[1] - va[1] * tangent[0])) + 64 * Number.EPSILON * omega;
4926
+ const vector = (t) => va.map((v, k2) => v * Math.cos(omega * t) + tangent[k2] * Math.sin(omega * t));
4927
+ const derivative = (t) => va.map((v, k2) => omega * (-v * Math.sin(omega * t) + tangent[k2] * Math.cos(omega * t)));
4928
+ for (const rings of polygons)
4929
+ for (const ring of rings)
4930
+ for (let edge2 = 1; edge2 < ring.length; edge2++) {
4931
+ const c = ring[edge2 - 1], d = ring[edge2], dx = d[0] - c[0], dy = d[1] - c[1], length = Math.hypot(dx, dy);
4932
+ if (!length)
4933
+ continue;
4934
+ const angular = omega / RAD, latitude = Math.min(90, Math.max(Math.abs(a[1]), Math.abs(b[1])) + angular), cosine2 = Math.cos(latitude * RAD), longitude3 = cosine2 > 1e-10 ? angular / cosine2 : 360;
4935
+ if (Math.max(c[1], d[1]) < Math.min(a[1], b[1]) - angular - 1e-10 || Math.min(c[1], d[1]) > Math.max(a[1], b[1]) + angular + 1e-10 || Math.max(c[0], d[0]) < Math.min(a[0], b[0]) - longitude3 - 1e-10 || Math.min(c[0], d[0]) > Math.max(a[0], b[0]) + longitude3 + 1e-10)
4936
+ continue;
4937
+ const value2 = (t) => {
4938
+ tick();
4939
+ const p = at(t);
4940
+ return ((p[0] - c[0]) * dy - (p[1] - c[1]) * dx) / length;
4941
+ };
4942
+ const put = (t) => {
4943
+ const p = at(t), u = ((p[0] - c[0]) * dx + (p[1] - c[1]) * dy) / (length * length);
4944
+ if (u >= -1e-10 && u <= 1 + 1e-10)
4945
+ ts.push(t);
4946
+ };
4947
+ const bisect = (lo, hi, f) => {
4948
+ for (let k2 = 0; k2 < 52; k2++) {
4949
+ const mid = (lo + hi) / 2;
4950
+ if (value2(mid) >= 0 === f >= 0)
4951
+ lo = mid;
4952
+ else
4953
+ hi = mid;
4954
+ }
4955
+ put((lo + hi) / 2);
4956
+ };
4957
+ const visit = (lo, hi, depth) => {
4958
+ tick();
4959
+ const f = value2(lo), g = value2(hi), mid = (lo + hi) / 2, h2 = (hi - lo) / 2;
4960
+ if (Math.abs(f) < 1e-12)
4961
+ put(lo);
4962
+ if (Math.abs(g) < 1e-12)
4963
+ put(hi);
4964
+ if (linear) {
4965
+ if (f * g < 0)
4966
+ bisect(lo, hi, f);
4967
+ return;
4968
+ }
4969
+ const v = vector(mid), dv = derivative(mid), r2 = v[0] * v[0] + v[1] * v[1];
4970
+ const df = ((v[0] * dv[1] - v[1] * dv[0]) / r2 * dy - dv[2] / Math.sqrt(r2) * dx) / (RAD * length);
4971
+ let z = Math.max(Math.abs(vector(lo)[2]), Math.abs(vector(hi)[2]));
4972
+ const phase = Math.atan2(tangent[2], va[2]);
4973
+ for (let k2 = -2; k2 <= 2; k2++) {
4974
+ const theta = phase + k2 * Math.PI;
4975
+ if (theta >= omega * lo && theta <= omega * hi)
4976
+ z = Math.max(z, Math.hypot(va[2], tangent[2]));
4977
+ }
4978
+ const r = Math.sqrt(Math.max(0, 1 - Math.min(1, z) ** 2));
4979
+ const second = r ? (2 * momentum * omega * Math.abs(dy) / r ** 4 + omega ** 2 * Math.abs(dx) * (1 / r + 1 / r ** 3)) / (RAD * length) : Infinity;
4980
+ if (Number.isFinite(df) && Math.abs(df) > second * h2 + 1e-12) {
4981
+ if (f * g < 0)
4982
+ bisect(lo, hi, f);
4983
+ return;
4984
+ }
4985
+ const speed = r ? (momentum * Math.abs(dy) / r ** 2 + omega * Math.abs(dx)) / (RAD * length) : Infinity;
4986
+ if (Math.abs(value2(mid)) > speed * h2 + 1e-12)
4987
+ return;
4988
+ if (depth >= 24)
4989
+ throw new Error("Unresolved Elastic source clipping tangent or pole");
4990
+ visit(lo, mid, depth + 1);
4991
+ visit(mid, hi, depth + 1);
4992
+ };
4993
+ visit(0, 1, 0);
4994
+ }
4995
+ ts.sort((a2, b2) => a2 - b2);
4996
+ const result = [];
4997
+ for (let i = 1; i < ts.length; i++)
4998
+ if (ts[i] - ts[i - 1] > 1e-12 && contains(polygons, at((ts[i] + ts[i - 1]) / 2)))
4999
+ result.push({ a: at(ts[i - 1]), b: at(ts[i]) });
5000
+ return result;
5001
+ }
5002
+ var init_source_arc2 = __esm({
5003
+ "dist/discontinuous/elastic/source-arc.js"() {
5004
+ init_math2();
5005
+ init_geometry();
5006
+ init_source_arc();
5007
+ }
5008
+ });
5009
+
4325
5010
  // dist/discontinuous/elastic/map.js
4326
5011
  function createElasticMap(data, options = {}) {
4327
5012
  return new ElasticMap(data, options.radius ?? 1);
@@ -4331,7 +5016,7 @@ function wrap2(lon) {
4331
5016
  return x > 180 ? x - 360 : x < -180 ? x + 360 : x;
4332
5017
  }
4333
5018
  function snapPolygon(rings) {
4334
- return rings.map((ring) => ring.map((p) => p.map((v) => Math.round(v * 1e10) / 1e10)));
5019
+ return rings.map((r) => r.map((p) => p.map((v) => Math.round(v * 1e10) / 1e10)));
4335
5020
  }
4336
5021
  var import_polygon_clipping4, kernel, splitter, ElasticMap;
4337
5022
  var init_map = __esm({
@@ -4341,6 +5026,9 @@ var init_map = __esm({
4341
5026
  init_math2();
4342
5027
  init_grid();
4343
5028
  init_geometry();
5029
+ init_source_arc2();
5030
+ init_source_arc();
5031
+ init_polygon_constraints();
4344
5032
  kernel = Object.assign((x, y) => [x, y], {
4345
5033
  invert: (x, y) => [x, y]
4346
5034
  });
@@ -4468,7 +5156,7 @@ var init_map = __esm({
4468
5156
  }
4469
5157
  sourceOptions(options) {
4470
5158
  const { step } = sampling(options);
4471
- return { maxAngularStep: Math.min(step, 1), tolerance: 1e-5 };
5159
+ return { ...options, maxAngularStep: Math.min(step, 1), tolerance: 1e-5 };
4472
5160
  }
4473
5161
  sampled(c, line, options) {
4474
5162
  return sample(line, (p) => c.grid.project(p), options, Math.min(180 / (c.grid.section.rows - 1), 360 / (c.grid.section.columns - 1)));
@@ -4477,23 +5165,70 @@ var init_map = __esm({
4477
5165
  const out = [];
4478
5166
  for (const part of splitter.projectLine(line, this.sourceOptions(options)))
4479
5167
  for (const c of this.charts.filter((c2) => c2.baseId === part.regionId)) {
4480
- let current = [];
4481
- const flush = () => {
4482
- if (current.length > 1) {
4483
- const { source, xy } = this.sampled(c, current, options);
4484
- out.push({ regionId: c.region.id, coordinates: xy, sourceCoordinates: source });
4485
- }
4486
- current = [];
5168
+ let current;
5169
+ const boundary = (point, vertex) => {
5170
+ if (vertex === 0 || vertex === line.length - 1)
5171
+ return "source-endpoint";
5172
+ let edge2 = 0;
5173
+ const kinds = [];
5174
+ for (const polygon of c.domain)
5175
+ for (const ring of polygon)
5176
+ for (let i = 1; i < ring.length; i++, edge2++)
5177
+ if (onSegment(point, ring[i - 1], ring[i])) {
5178
+ const seam = this.seams.find((s) => s.regions.some((id, j) => id === c.region.id && s.edges[j] === edge2));
5179
+ kinds.push((seam == null ? void 0 : seam.kind) ?? "boundary");
5180
+ }
5181
+ return kinds.includes("boundary") ? "coverage-boundary" : kinds.includes("cut") ? "chart-cut" : kinds.includes("join") ? "continuous-chart-join" : "coverage-boundary";
4487
5182
  };
4488
- for (let i = 1; i < part.coordinates.length; i++)
4489
- for (const [a, b] of clipSegment(part.coordinates[i - 1], part.coordinates[i], c.domain)) {
4490
- if (current.length && Math.hypot(current[current.length - 1][0] - a[0], current[current.length - 1][1] - a[1]) > 1e-8)
4491
- flush();
4492
- if (!current.length)
4493
- current.push(a);
4494
- current.push(b);
5183
+ for (let i = 1; i < part.sourceCoordinates.length; i++) {
5184
+ const a = part.coordinates[i - 1], b = part.coordinates[i];
5185
+ const intervals = clipSourceArc(a, b, c.domain);
5186
+ if (!intervals.length) {
5187
+ current = void 0;
5188
+ continue;
4495
5189
  }
4496
- flush();
5190
+ if (Math.hypot(intervals[0].a[0] - a[0], intervals[0].a[1] - a[1]) > 1e-8)
5191
+ current = void 0;
5192
+ for (const clipped of intervals) {
5193
+ const arc = sourceArc(clipped.a, clipped.b);
5194
+ const samples = sample([clipped.a, clipped.b], (p) => c.grid.project(p), options, Math.min(180 / (c.grid.section.rows - 1), 360 / (c.grid.section.columns - 1)), (_, t) => arc(t));
5195
+ const first = samples.source[0], last = samples.source[samples.source.length - 1];
5196
+ const joined = current && Math.hypot(...current.sourceCoordinates[current.sourceCoordinates.length - 1].map((v, k2) => v - first[k2])) < 1e-8;
5197
+ if (!joined) {
5198
+ current = { regionId: c.region.id, coordinates: [], sourceCoordinates: [] };
5199
+ if (part.provenance)
5200
+ current.provenance = {
5201
+ segments: [],
5202
+ parameters: [],
5203
+ vertices: [],
5204
+ angularDistances: []
5205
+ };
5206
+ out.push(current);
5207
+ }
5208
+ for (let j = joined ? 1 : 0; j < samples.source.length; j++) {
5209
+ const p = samples.source[j];
5210
+ current.sourceCoordinates.push(p);
5211
+ current.coordinates.push(samples.xy[j]);
5212
+ const parent = part.provenance, target = current.provenance;
5213
+ if (parent && target) {
5214
+ const length = angle(geographic(a), geographic(b)), t = length ? Math.min(1, angle(geographic(a), geographic(p)) / length) : 0;
5215
+ const endpoint = t < 1e-12 ? i - 1 : t > 1 - 1e-12 ? i : -1, segment = parent.segments[i], from = parent.segments[i - 1] === segment ? parent.parameters[i - 1] : 0;
5216
+ target.segments.push(endpoint >= 0 ? parent.segments[endpoint] : segment);
5217
+ target.parameters.push(endpoint >= 0 ? parent.parameters[endpoint] : from + t * (parent.parameters[i] - from));
5218
+ target.vertices.push(endpoint >= 0 ? parent.vertices[endpoint] : 4294967295);
5219
+ if (options.pathDistances)
5220
+ target.angularDistances.push(parent.angularDistances[i - 1] + t * (parent.angularDistances[i] - parent.angularDistances[i - 1]));
5221
+ if (options.pathBoundaries) {
5222
+ if (target.vertices.length === 1)
5223
+ target.startReason = boundary(p, target.vertices[0]);
5224
+ target.endReason = boundary(p, target.vertices[target.vertices.length - 1]);
5225
+ }
5226
+ }
5227
+ }
5228
+ if (Math.hypot(last[0] - b[0], last[1] - b[1]) > 1e-8)
5229
+ current = void 0;
5230
+ }
5231
+ }
4497
5232
  }
4498
5233
  return out;
4499
5234
  }
@@ -4501,13 +5236,175 @@ var init_map = __esm({
4501
5236
  const out = [];
4502
5237
  for (const part of splitter.projectPolygon(rings, this.sourceOptions(options)))
4503
5238
  for (const c of this.charts.filter((c2) => c2.baseId === part.regionId)) {
4504
- const clipped = import_polygon_clipping4.default.intersection(snapPolygon(part.coordinates), c.domain.map(snapPolygon));
5239
+ const prepared = [], preparedEdges = [];
5240
+ part.coordinates.forEach((ring, r) => {
5241
+ const points = [], edges = [];
5242
+ for (let i = 1; i < ring.length; i++) {
5243
+ const a = ring[i - 1], b = ring[i], arc = sourceArc(a, b), length = angle(geographic(a), geographic(b)), fractions = [0, 1];
5244
+ for (const interval of clipSourceArc(a, b, c.domain))
5245
+ for (const p of [interval.a, interval.b]) {
5246
+ const t = length > 1e-14 ? angle(geographic(a), geographic(p)) / length : Math.abs(b[0] - a[0]) > 1e-14 ? (p[0] - a[0]) / (b[0] - a[0]) : 0;
5247
+ if (t > 1e-12 && t < 1 - 1e-12)
5248
+ fractions.push(t);
5249
+ }
5250
+ fractions.sort((a2, b2) => a2 - b2);
5251
+ const split = fractions.filter((t, i2) => !i2 || t - fractions[i2 - 1] > 1e-12);
5252
+ const append2 = (point) => {
5253
+ if (points.length >= 1e5)
5254
+ throw new Error("Elastic polygon constraint vertex limit exceeded");
5255
+ if (points.length) {
5256
+ const previous = points[points.length - 1];
5257
+ if (options.polygonEdgeProvenance)
5258
+ edges.push(composePolygonEdge(part.edgeProvenance[r][i - 1], a, b, previous, point));
5259
+ }
5260
+ points.push(point);
5261
+ };
5262
+ const first = arc(0);
5263
+ if (!points.length)
5264
+ points.push(first);
5265
+ else if (points[points.length - 1].some((v, k2) => v !== first[k2])) {
5266
+ if (angle(geographic(points[points.length - 1]), geographic(first)) > 1e-10)
5267
+ throw new Error("Discontinuous Elastic input source constraint");
5268
+ if (options.polygonEdgeProvenance)
5269
+ edges.push({
5270
+ kind: "continuous-chart-join",
5271
+ ring: 4294967295,
5272
+ segment: 4294967295,
5273
+ t0: 0,
5274
+ t1: 0
5275
+ });
5276
+ points.push(first);
5277
+ }
5278
+ for (let j = 1; j < split.length; j++) {
5279
+ append2(arc((split[j - 1] + split[j]) / 2));
5280
+ append2(arc(split[j]));
5281
+ }
5282
+ }
5283
+ if (points[points.length - 1].some((v, k2) => v !== points[0][k2])) {
5284
+ if (angle(geographic(points[points.length - 1]), geographic(points[0])) > 1e-10)
5285
+ throw new Error("Unclosed Elastic input source constraint");
5286
+ if (options.polygonEdgeProvenance)
5287
+ edges.push({
5288
+ kind: "continuous-chart-join",
5289
+ ring: 4294967295,
5290
+ segment: 4294967295,
5291
+ t0: 0,
5292
+ t1: 0
5293
+ });
5294
+ points.push([...points[0]]);
5295
+ }
5296
+ prepared.push(points);
5297
+ preparedEdges.push(edges);
5298
+ });
5299
+ const clipped = import_polygon_clipping4.default.intersection(snapPolygon(prepared), c.domain.map(snapPolygon));
4505
5300
  for (const polygon of clipped) {
4506
- const samples = polygon.map((r) => this.sampled(c, r, options));
5301
+ const input = snapPolygon(prepared), traced = tracePolygonConstraints(input, polygon, 1e-9, options.polygonEdgeProvenance ? preparedEdges : void 0, void 0, (a, b) => angle(geographic(a), geographic(b)) < 64 * Number.EPSILON), resolved2 = traced.rings.map((r) => r.map((p) => [...p]));
5302
+ traced.owners.forEach((owners, r) => owners.forEach((owner, i) => {
5303
+ if (!owner)
5304
+ return;
5305
+ const [pr, pe] = owner, a = prepared[pr][pe], b = prepared[pr][pe + 1], length = angle(geographic(a), geographic(b)), arc = sourceArc(a, b);
5306
+ for (const j of [i, i + 1]) {
5307
+ const q = traced.rings[r][j], start = input[pr][pe], end = input[pr][pe + 1];
5308
+ if (Math.hypot(q[0] - start[0], q[1] - start[1]) < 1e-8)
5309
+ resolved2[r][j] = [...a];
5310
+ else if (Math.hypot(q[0] - end[0], q[1] - end[1]) < 1e-8)
5311
+ resolved2[r][j] = [...b];
5312
+ else {
5313
+ const candidates = clipSourceArc(a, b, c.domain).flatMap((interval) => [
5314
+ interval.a,
5315
+ interval.b
5316
+ ]);
5317
+ if (!candidates.length)
5318
+ throw new Error("Unresolved Elastic polygon source intersection");
5319
+ const reference = length ? angle(geographic(a), geographic(q)) / length : 0;
5320
+ candidates.sort((x, y) => Math.abs((length ? angle(geographic(a), geographic(x)) / length : 0) - reference) - Math.abs((length ? angle(geographic(a), geographic(y)) / length : 0) - reference));
5321
+ const fraction = length ? angle(geographic(a), geographic(candidates[0])) / length : 0;
5322
+ resolved2[r][j] = arc(Math.min(1, Math.max(0, fraction)));
5323
+ }
5324
+ }
5325
+ }));
5326
+ for (const ring of resolved2)
5327
+ ring[ring.length - 1] = [...ring[0]];
5328
+ const generated = (a, b) => {
5329
+ if (angle(geographic(a), geographic(b)) < 64 * Number.EPSILON)
5330
+ return "continuous-chart-join";
5331
+ let edge2 = 0;
5332
+ const kinds = [];
5333
+ for (const polygon2 of c.domain)
5334
+ for (const ring of polygon2)
5335
+ for (let j = 1; j < ring.length; j++, edge2++)
5336
+ if (onSegment(a, ring[j - 1], ring[j]) && onSegment(b, ring[j - 1], ring[j])) {
5337
+ const seam = this.seams.find((s) => s.regions.some((id, k2) => id === c.region.id && s.edges[k2] === edge2));
5338
+ kinds.push((seam == null ? void 0 : seam.kind) ?? "boundary");
5339
+ }
5340
+ if (!kinds.length)
5341
+ throw new Error("Unknown Elastic polygon clipping constraint");
5342
+ return kinds.includes("boundary") ? "coverage-boundary" : kinds.includes("cut") ? "chart-cut" : "continuous-chart-join";
5343
+ };
5344
+ const coordinates2 = [], sourceCoordinates = [], edgeProvenance = [];
5345
+ resolved2.forEach((ring, r) => {
5346
+ const source = [], xy = [], edges = [];
5347
+ for (let i = 1; i < ring.length; i++) {
5348
+ const owner = traced.owners[r][i - 1], arc = owner ? sourceArc(ring[i - 1], ring[i]) : void 0;
5349
+ const samples = sample(arc ? [arc(0), arc(1)] : [ring[i - 1], ring[i]], (p) => c.grid.project(p), options, Math.min(180 / (c.grid.section.rows - 1), 360 / (c.grid.section.columns - 1)), arc ? (_, t) => arc(t) : void 0);
5350
+ if (!source.length) {
5351
+ source.push(samples.source[0]);
5352
+ xy.push(samples.xy[0]);
5353
+ } else if (source[source.length - 1].some((v, k2) => Math.abs(v - samples.source[0][k2]) > 1e-10)) {
5354
+ if (angle(geographic(source[source.length - 1]), geographic(samples.source[0])) > 1e-10)
5355
+ throw new Error("Discontinuous Elastic polygon source constraint");
5356
+ if (options.polygonEdgeProvenance)
5357
+ edges.push({
5358
+ kind: "continuous-chart-join",
5359
+ ring: 4294967295,
5360
+ segment: 4294967295,
5361
+ t0: 0,
5362
+ t1: 0
5363
+ });
5364
+ source.push(samples.source[0]);
5365
+ xy.push(samples.xy[0]);
5366
+ }
5367
+ for (let j = 1; j < samples.source.length; j++) {
5368
+ if (options.polygonEdgeProvenance) {
5369
+ if (owner) {
5370
+ const [pr, pe] = owner;
5371
+ edges.push(composePolygonEdge(preparedEdges[pr][pe], prepared[pr][pe], prepared[pr][pe + 1], source[source.length - 1], samples.source[j]));
5372
+ } else
5373
+ edges.push({
5374
+ kind: generated(ring[i - 1], ring[i]),
5375
+ ring: 4294967295,
5376
+ segment: 4294967295,
5377
+ t0: 0,
5378
+ t1: 0
5379
+ });
5380
+ }
5381
+ source.push(samples.source[j]);
5382
+ xy.push(samples.xy[j]);
5383
+ }
5384
+ }
5385
+ if (source.length && source[source.length - 1].some((v, k2) => v !== source[0][k2])) {
5386
+ if (angle(geographic(source[source.length - 1]), geographic(source[0])) > 1e-10)
5387
+ throw new Error("Unclosed Elastic polygon source constraint");
5388
+ if (options.polygonEdgeProvenance)
5389
+ edges.push({
5390
+ kind: "continuous-chart-join",
5391
+ ring: 4294967295,
5392
+ segment: 4294967295,
5393
+ t0: 0,
5394
+ t1: 0
5395
+ });
5396
+ source.push([...source[0]]);
5397
+ xy.push([...xy[0]]);
5398
+ }
5399
+ coordinates2.push(xy);
5400
+ sourceCoordinates.push(source);
5401
+ edgeProvenance.push(edges);
5402
+ });
4507
5403
  out.push({
4508
5404
  regionId: c.region.id,
4509
- coordinates: samples.map((s) => s.xy),
4510
- sourceCoordinates: samples.map((s) => s.source)
5405
+ coordinates: coordinates2,
5406
+ sourceCoordinates,
5407
+ ...options.polygonEdgeProvenance ? { edgeProvenance } : {}
4511
5408
  });
4512
5409
  }
4513
5410
  }
@@ -7242,7 +8139,7 @@ function restoreLayout(data) {
7242
8139
  const { mesh, panels, targetAngles } = data;
7243
8140
  if (panels.length !== mesh.faces.length || targetAngles.length !== panels.length || !targetAngles.every(Number.isFinite) || data.rootTransform.length !== 16 || !data.rootTransform.every(Number.isFinite))
7244
8141
  throw new Error("Invalid prepared hinge layout");
7245
- const tree = new import_geometry3.HingeTree(panels);
8142
+ const tree = new import_geometry4.HingeTree(panels);
7246
8143
  const rootTransform = new import_core2.Matrix4(data.rootTransform);
7247
8144
  const poses = new Float64Array(panels.length * 16), angles = new Float64Array(panels.length);
7248
8145
  const evaluate2 = (amount) => {
@@ -7285,11 +8182,11 @@ function countOverlaps(faces) {
7285
8182
  }
7286
8183
  return count;
7287
8184
  }
7288
- var import_core2, import_geometry3;
8185
+ var import_core2, import_geometry4;
7289
8186
  var init_unfold = __esm({
7290
8187
  "dist/discontinuous/myriahedral/unfold.js"() {
7291
8188
  import_core2 = require("@math.gl/core");
7292
- import_geometry3 = require("@math.gl/geometry");
8189
+ import_geometry4 = require("@math.gl/geometry");
7293
8190
  init_mesh();
7294
8191
  }
7295
8192
  });
@@ -7488,6 +8385,8 @@ var import_polygon_clipping5, MercatorMap;
7488
8385
  var init_mercator_map = __esm({
7489
8386
  "dist/discontinuous/mercator-map.js"() {
7490
8387
  import_polygon_clipping5 = __toESM(require("polygon-clipping"), 1);
8388
+ init_polygon_constraints();
8389
+ init_source_arc();
7491
8390
  init_map_layout();
7492
8391
  init_math2();
7493
8392
  MercatorMap = class extends DiscontinuousMap {
@@ -7567,17 +8466,65 @@ var init_mercator_map = __esm({
7567
8466
  continue;
7568
8467
  }
7569
8468
  const at = (t) => [a[0] + (b[0] - a[0]) * t, a[1] + dy * t];
7570
- const start = at(t0), end = at(t1);
8469
+ const endpoint = (t) => {
8470
+ if (t === 0 || t === 1)
8471
+ return { xy: at(t), source: part.sourceCoordinates[i - 1 + t], t };
8472
+ const va = geographic(part.sourceCoordinates[i - 1]), vb = geographic(part.sourceCoordinates[i]), arc = angle(va, vb);
8473
+ const sourceAt = (t2) => {
8474
+ if (!arc)
8475
+ return part.sourceCoordinates[i - 1];
8476
+ const a2 = Math.sin((1 - t2) * arc) / Math.sin(arc), b2 = Math.sin(t2 * arc) / Math.sin(arc);
8477
+ return coordinates(unit(va.map((v, k2) => a2 * v + b2 * vb[k2])));
8478
+ };
8479
+ const latitude = Math.sign(at(t)[1]) * this.limit;
8480
+ let lo = 0, hi = 1;
8481
+ for (let j = 0; j < 52; j++) {
8482
+ const mid = (lo + hi) / 2;
8483
+ if (sourceAt(mid)[1] < latitude === part.sourceCoordinates[i - 1][1] < latitude)
8484
+ lo = mid;
8485
+ else
8486
+ hi = mid;
8487
+ }
8488
+ const f = (lo + hi) / 2, source = sourceAt(f);
8489
+ const xy = super.projectPoint(source, part.regionId).position;
8490
+ return { xy, source, t: f };
8491
+ };
8492
+ const start = endpoint(t0), end = endpoint(t1);
8493
+ const append2 = (point, index) => {
8494
+ current.coordinates.push(point.xy);
8495
+ current.sourceCoordinates.push(point.source);
8496
+ const source = part.provenance, target = current.provenance;
8497
+ if (!source || !target)
8498
+ return;
8499
+ const segment = source.segments[i];
8500
+ const from = source.segments[i - 1] === segment ? source.parameters[i - 1] : 0;
8501
+ target.segments.push(point.t === 0 || point.t === 1 ? source.segments[index] : segment);
8502
+ target.parameters.push(point.t === 0 || point.t === 1 ? source.parameters[index] : from + point.t * (source.parameters[i] - from));
8503
+ target.vertices.push(point.t === 0 || point.t === 1 ? source.vertices[index] : 4294967295);
8504
+ if (options == null ? void 0 : options.pathDistances)
8505
+ target.angularDistances.push(source.angularDistances[i - 1] + point.t * (source.angularDistances[i] - source.angularDistances[i - 1]));
8506
+ };
7571
8507
  if (!current || t0 > 0) {
7572
8508
  current = {
7573
8509
  regionId: part.regionId,
7574
- coordinates: [start],
7575
- sourceCoordinates: [this.source(start)]
8510
+ coordinates: [],
8511
+ sourceCoordinates: [],
8512
+ ...part.provenance ? {
8513
+ provenance: {
8514
+ segments: [],
8515
+ parameters: [],
8516
+ vertices: [],
8517
+ angularDistances: [],
8518
+ ...(options == null ? void 0 : options.pathBoundaries) ? { startReason: t0 > 0 ? "coverage-boundary" : part.provenance.startReason } : {}
8519
+ }
8520
+ } : {}
7576
8521
  };
7577
8522
  result.push(current);
8523
+ append2(start, i - 1);
7578
8524
  }
7579
- current.coordinates.push(end);
7580
- current.sourceCoordinates.push(this.source(end));
8525
+ append2(end, i);
8526
+ if (current.provenance && (options == null ? void 0 : options.pathBoundaries))
8527
+ current.provenance.endReason = t1 < 1 ? "coverage-boundary" : part.provenance.endReason;
7581
8528
  if (t1 < 1)
7582
8529
  current = void 0;
7583
8530
  }
@@ -7595,11 +8542,60 @@ var init_mercator_map = __esm({
7595
8542
  [-x, -y]
7596
8543
  ]
7597
8544
  ];
7598
- return super.projectPolygon(rings, options).flatMap((part) => import_polygon_clipping5.default.intersection(part.coordinates, box).map((coordinates2) => ({
7599
- regionId: part.regionId,
7600
- coordinates: coordinates2,
7601
- sourceCoordinates: coordinates2.map((ring) => ring.map((p) => this.source(p)))
7602
- })));
8545
+ return super.projectPolygon(rings, options).flatMap((part) => import_polygon_clipping5.default.intersection(part.coordinates, box).map((polygon) => {
8546
+ const traced = tracePolygonConstraints(part.coordinates, polygon, 1e-10, part.edgeProvenance);
8547
+ const sources = traced.rings.map((r) => r.map((p) => this.source(p)));
8548
+ traced.owners.forEach((owners, r) => owners.forEach((owner, i) => {
8549
+ if (!owner)
8550
+ return;
8551
+ const [parentRing, parentEdge] = owner, a = part.sourceCoordinates[parentRing][parentEdge], b = part.sourceCoordinates[parentRing][parentEdge + 1], at = sourceArc(a, b);
8552
+ for (const j of [i, i + 1]) {
8553
+ const q = traced.rings[r][j], first = part.coordinates[parentRing][parentEdge], last = part.coordinates[parentRing][parentEdge + 1];
8554
+ if (Math.hypot(q[0] - first[0], q[1] - first[1]) < 1e-10 * this.radius)
8555
+ sources[r][j] = [...a];
8556
+ else if (Math.hypot(q[0] - last[0], q[1] - last[1]) < 1e-10 * this.radius)
8557
+ sources[r][j] = [...b];
8558
+ else {
8559
+ const latitude = Math.sign(q[1]) * this.limit;
8560
+ let lo = 0, hi = 1;
8561
+ if (a[1] < latitude === b[1] < latitude)
8562
+ throw new Error("Unresolved Mercator source constraint intersection");
8563
+ for (let k2 = 0; k2 < 52; k2++) {
8564
+ const mid = (lo + hi) / 2;
8565
+ if (at(mid)[1] < latitude === a[1] < latitude)
8566
+ lo = mid;
8567
+ else
8568
+ hi = mid;
8569
+ }
8570
+ sources[r][j] = at((lo + hi) / 2);
8571
+ }
8572
+ }
8573
+ }));
8574
+ for (const r of sources)
8575
+ r[r.length - 1] = [...r[0]];
8576
+ return {
8577
+ regionId: part.regionId,
8578
+ sourceCoordinates: sources,
8579
+ coordinates: sources.map((r) => r.map((p) => super.projectPoint(p, part.regionId).position)),
8580
+ ...(options == null ? void 0 : options.polygonEdgeProvenance) ? {
8581
+ edgeProvenance: traced.owners.map((owners, r) => owners.map((owner, i) => {
8582
+ if (owner) {
8583
+ const [pr, pe] = owner;
8584
+ return composePolygonEdge(part.edgeProvenance[pr][pe], part.sourceCoordinates[pr][pe], part.sourceCoordinates[pr][pe + 1], sources[r][i], sources[r][i + 1]);
8585
+ }
8586
+ if (!sources[r].slice(i, i + 2).every((p) => Math.abs(Math.abs(p[1]) - this.limit) < 1e-7))
8587
+ throw new Error("Unknown Mercator clipping constraint");
8588
+ return {
8589
+ kind: "coverage-boundary",
8590
+ ring: 4294967295,
8591
+ segment: 4294967295,
8592
+ t0: 0,
8593
+ t1: 0
8594
+ };
8595
+ }))
8596
+ } : {}
8597
+ };
8598
+ }));
7603
8599
  }
7604
8600
  };
7605
8601
  }