mapshaper 0.7.40 → 0.7.44

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/www/mapshaper.js CHANGED
@@ -32622,6 +32622,10 @@ ${svg}
32622
32622
  describe: 'create a polygon to match the outline of the graticule',
32623
32623
  type: 'flag'
32624
32624
  })
32625
+ .option('outline', {
32626
+ describe: 'create a polyline matching the outline of the graticule',
32627
+ type: 'flag'
32628
+ })
32625
32629
  .option('name', nameOpt)
32626
32630
  .option('target', targetOpt);
32627
32631
 
@@ -52573,6 +52577,120 @@ ${svg}
52573
52577
  polygonsToLines: polygonsToLines
52574
52578
  });
52575
52579
 
52580
+ // Geographic topology used to prepare paths for projections whose forward
52581
+ // transform is piecewise. A "cut" seam opens a visible gap; an "attached"
52582
+ // seam (reserved for polyhedral projections) marks a transform boundary that
52583
+ // must gain vertices without disconnecting the path.
52584
+ var landTopology = {
52585
+ regions: [
52586
+ region('northwest', [-180, 0, -40, 90], -100),
52587
+ region('northeast', [-40, 0, 180, 90], 30),
52588
+ region('southwest', [-180, -90, -100, 0], -160),
52589
+ region('south_central_west', [-100, -90, -20, 0], -60),
52590
+ region('south_central_east', [-20, -90, 80, 0], 20),
52591
+ region('southeast', [80, -90, 180, 0], 140)
52592
+ ],
52593
+ seams: [
52594
+ cutSeam([[-40, 0], [-40, 91]]),
52595
+ cutSeam([[-100, -91], [-100, 0]]),
52596
+ cutSeam([[-20, -91], [-20, 0]]),
52597
+ cutSeam([[80, -91], [80, 0]])
52598
+ ]
52599
+ };
52600
+
52601
+ var oceanTopology = {
52602
+ regions: [
52603
+ region('northwest', [-180, 0, -90, 90], -140),
52604
+ region('north_central', [-90, 0, 60, 90], -10),
52605
+ region('northeast', [60, 0, 180, 90], 130),
52606
+ region('southwest', [-180, -90, -60, 0], -110),
52607
+ region('south_central', [-60, -90, 90, 0], 20),
52608
+ region('southeast', [90, -90, 180, 0], 150)
52609
+ ],
52610
+ seams: [
52611
+ cutSeam([[-90, 0], [-90, 91]]),
52612
+ cutSeam([[60, 0], [60, 91]]),
52613
+ cutSeam([[-60, -91], [-60, 0]]),
52614
+ cutSeam([[90, -91], [90, 0]])
52615
+ ]
52616
+ };
52617
+
52618
+ var projectionTopologies = {
52619
+ igh: landTopology,
52620
+ imoll: landTopology,
52621
+ igh_o: oceanTopology,
52622
+ imoll_o: oceanTopology
52623
+ };
52624
+
52625
+ function getProjectionTopology(P) {
52626
+ var defn = P && projectionTopologies[getCrsSlug(P)];
52627
+ if (!defn) return null;
52628
+ var lon0 = P.lam0 * 180 / Math.PI;
52629
+ return {
52630
+ regions: defn.regions.map(function(o) {
52631
+ return {
52632
+ id: o.id,
52633
+ boundary: rotatePath(o.boundary, lon0),
52634
+ transform: {
52635
+ lon_0: normalizeLongitude(o.transform.lon_0 + lon0)
52636
+ }
52637
+ };
52638
+ }),
52639
+ seams: defn.seams.map(function(o) {
52640
+ return {
52641
+ type: o.type,
52642
+ coordinates: rotatePath(o.coordinates, lon0)
52643
+ };
52644
+ })
52645
+ };
52646
+ }
52647
+
52648
+ function isInterruptedProjection(P) {
52649
+ var defn = P && projectionTopologies[getCrsSlug(P)];
52650
+ return !!defn && defn.seams.some(function(o) {
52651
+ return o.type == 'cut';
52652
+ });
52653
+ }
52654
+
52655
+ function region(id, bbox, lon0) {
52656
+ return {
52657
+ id: id,
52658
+ boundary: [
52659
+ [bbox[0], bbox[1]],
52660
+ [bbox[0], bbox[3]],
52661
+ [bbox[2], bbox[3]],
52662
+ [bbox[2], bbox[1]],
52663
+ [bbox[0], bbox[1]]
52664
+ ],
52665
+ transform: {lon_0: lon0}
52666
+ };
52667
+ }
52668
+
52669
+ function cutSeam(coordinates) {
52670
+ return {
52671
+ type: 'cut',
52672
+ coordinates: coordinates
52673
+ };
52674
+ }
52675
+
52676
+ function rotatePath(path, degrees) {
52677
+ return path.map(function(p) {
52678
+ return [normalizeLongitude(p[0] + degrees), p[1]];
52679
+ });
52680
+ }
52681
+
52682
+ function normalizeLongitude(lon) {
52683
+ while (lon > 180) lon -= 360;
52684
+ while (lon < -180) lon += 360;
52685
+ return lon;
52686
+ }
52687
+
52688
+ var ProjectionTopology = /*#__PURE__*/Object.freeze({
52689
+ __proto__: null,
52690
+ getProjectionTopology: getProjectionTopology,
52691
+ isInterruptedProjection: isInterruptedProjection
52692
+ });
52693
+
52576
52694
  function getClippingDataset(src, dest, opts) {
52577
52695
  return getUnprojectedBoundingPolygon(src, dest, opts);
52578
52696
  }
@@ -52608,20 +52726,50 @@ ${svg}
52608
52726
  dataset = getBoundingRectangle(dest, {clip_bbox: [-180,-90,180,90]});
52609
52727
  }
52610
52728
  projectDataset(dataset, src, dest, {no_clip: false, quiet: true});
52729
+ if (isInterruptedProjection(dest)) {
52730
+ removeTinyFootprintRings(dataset);
52731
+ }
52611
52732
  return dataset;
52612
52733
  }
52613
52734
 
52614
52735
  // Return projected outline of clipped projections
52615
52736
  function getOutlineDataset(src, dest, opts) {
52616
52737
  var dataset = getUnprojectedBoundingPolygon(src, dest, opts);
52738
+ if (!dataset && isInterruptedProjection(dest)) {
52739
+ dataset = getBoundingRectangle(dest, {clip_bbox: [-180, -90, 180, 90]});
52740
+ }
52617
52741
  if (dataset) {
52618
52742
  // project, with cutting & cleanup
52619
52743
  projectDataset(dataset, src, dest, {no_clip: false, quiet: true});
52744
+ if (isInterruptedProjection(dest)) {
52745
+ removeTinyFootprintRings(dataset);
52746
+ }
52620
52747
  dataset.layers[0].geometry_type = 'polyline';
52621
52748
  }
52622
52749
  return dataset || null;
52623
52750
  }
52624
52751
 
52752
+ // Narrow pre-projection gutters can leave zero-area rings after a rotated
52753
+ // world boundary is cleaned. Remove only rings that are negligible relative
52754
+ // to the main footprint; legitimate disconnected lobes are retained.
52755
+ function removeTinyFootprintRings(dataset) {
52756
+ var lyr = dataset.layers[0];
52757
+ var arcs = dataset.arcs;
52758
+ var maxArea = 0;
52759
+ lyr.shapes.forEach(function(shp) {
52760
+ (shp || []).forEach(function(path) {
52761
+ maxArea = Math.max(maxArea, Math.abs(geom.getPathArea(path, arcs)));
52762
+ });
52763
+ });
52764
+ if (maxArea === 0) return;
52765
+ editShapes(lyr.shapes, function(path) {
52766
+ if (Math.abs(geom.getPathArea(path, arcs)) <= maxArea * 1e-12) {
52767
+ return null;
52768
+ }
52769
+ });
52770
+ dissolveArcs(dataset);
52771
+ }
52772
+
52625
52773
  function getBoundingRectangle(dest, opts) {
52626
52774
  var bbox = opts.clip_bbox || getDefaultClipBBox(dest);
52627
52775
  var rotation = getRotationParams(dest);
@@ -52735,6 +52883,7 @@ ${svg}
52735
52883
  // rotated normal-aspect projections can generally have a thin slice removed
52736
52884
  // from the rotated antimeridian, instead of clipping them
52737
52885
  var cut = insertPreProjectionCuts(dataset, src, dest);
52886
+ cut = insertProjectionTopologyCuts(dataset, dest) || cut;
52738
52887
  var clipped = false;
52739
52888
  var clipData;
52740
52889
  // experimental -- we can probably get away with just clamping some CRSs that
@@ -52795,6 +52944,24 @@ ${svg}
52795
52944
  return false;
52796
52945
  }
52797
52946
 
52947
+ function insertProjectionTopologyCuts(dataset, dest) {
52948
+ var topology = getProjectionTopology(dest);
52949
+ var pathLayers = dataset.layers.filter(layerHasPaths);
52950
+ var cutPaths, geojson, clip;
52951
+ if (!topology || pathLayers.length === 0) return false;
52952
+ cutPaths = topology.seams
52953
+ .filter(function(o) { return o.type == 'cut'; })
52954
+ .reduce(function(memo, o) {
52955
+ return memo.concat(getCutSeamPaths(o.coordinates));
52956
+ }, []);
52957
+ if (cutPaths.length === 0) return false;
52958
+ geojson = getCutMaskGeoJSON(cutPaths);
52959
+ if (geojson.features.length === 0) return false;
52960
+ clip = importGeoJSON(geojson);
52961
+ clipLayersInPlace(pathLayers, clip, dataset, 'erase', getInternalClipOpts());
52962
+ return true;
52963
+ }
52964
+
52798
52965
  function clampDataset(dataset, bbox) {
52799
52966
  transformPoints(dataset, function(x, y) {
52800
52967
  return [utils.clamp(x, bbox[0], bbox[2]), utils.clamp(y, bbox[1], bbox[3])];
@@ -52830,6 +52997,65 @@ ${svg}
52830
52997
  clipLayersInPlace(pathLayers, clip, dataset, 'erase', getInternalClipOpts());
52831
52998
  }
52832
52999
 
53000
+ // Duplicate a seam on the opposite edge of the standard longitude range.
53001
+ // This is needed when a rotated seam coincides with the antimeridian.
53002
+ function getCutSeamPaths(coords) {
53003
+ var paths = [coords];
53004
+ var onEdge = coords.every(function(p) {
53005
+ return Math.abs(Math.abs(p[0]) - 180) < 1e-12;
53006
+ });
53007
+ if (onEdge) {
53008
+ paths.push(coords.map(function(p) {
53009
+ return [p[0] == 180 ? -180 : 180, p[1]];
53010
+ }));
53011
+ }
53012
+ return paths;
53013
+ }
53014
+
53015
+ // Convert seam paths to narrow polygon masks. Densification is required so
53016
+ // their projected edges follow the projection instead of becoming straight
53017
+ // chords between seam endpoints.
53018
+ function getCutMaskGeoJSON(paths) {
53019
+ var e = 1e-8;
53020
+ var features = paths.map(function(coords) {
53021
+ var ring = getCutMaskRing(densifyPathByInterval(coords, 0.5), e);
53022
+ return {
53023
+ type: 'Feature',
53024
+ properties: {},
53025
+ geometry: {
53026
+ type: 'Polygon',
53027
+ coordinates: [ring]
53028
+ }
53029
+ };
53030
+ }).filter(function(feature) {
53031
+ return feature.geometry.coordinates[0].length > 3;
53032
+ });
53033
+ return {
53034
+ type: 'FeatureCollection',
53035
+ features: features
53036
+ };
53037
+ }
53038
+
53039
+ function getCutMaskRing(coords, width) {
53040
+ var left = [];
53041
+ var right = [];
53042
+ for (var i = 0; i < coords.length; i++) {
53043
+ var a = coords[i > 0 ? i - 1 : i];
53044
+ var b = coords[i < coords.length - 1 ? i + 1 : i];
53045
+ var dx = b[0] - a[0];
53046
+ var dy = b[1] - a[1];
53047
+ var len = Math.sqrt(dx * dx + dy * dy);
53048
+ if (len === 0) continue;
53049
+ var nx = -dy / len * width;
53050
+ var ny = dx / len * width;
53051
+ left.push([coords[i][0] + nx, coords[i][1] + ny]);
53052
+ right.push([coords[i][0] - nx, coords[i][1] - ny]);
53053
+ }
53054
+ var ring = left.concat(right.reverse());
53055
+ if (ring.length > 0) ring.push(ring[0].concat());
53056
+ return ring;
53057
+ }
53058
+
52833
53059
  function getInternalClipOpts() {
52834
53060
  return {no_cleanup: true, no_warn: true};
52835
53061
  }
@@ -53699,9 +53925,18 @@ ${svg}
53699
53925
  }
53700
53926
 
53701
53927
  function projectDataset(dataset, src, dest, opts) {
53928
+ if (isProjectedCRS(src) && !isInvertibleCRS(src)) {
53929
+ stop$1('Unable to project from a coordinate system that has no inverse transform');
53930
+ }
53931
+ if (isInterruptedProjection(dest) && dataset.layers.some(layerHasRaster)) {
53932
+ stop$1('Raster reprojection is not currently supported for interrupted projections');
53933
+ }
53702
53934
  var proj = getProjTransform2(src, dest); // v2 returns null points instead of throwing an error
53703
53935
  var badArcs = 0;
53704
53936
  var badPoints = 0;
53937
+ var healAntimeridian = isLatLngCRS(src) &&
53938
+ isInterruptedProjection(dest) &&
53939
+ Math.abs(dest.lam0) > 1e-12;
53705
53940
  var clipped = preProjectionClip(dataset, src, dest, opts);
53706
53941
  dataset.layers.forEach(function(lyr) {
53707
53942
  if (layerHasPoints(lyr)) {
@@ -53721,7 +53956,7 @@ ${svg}
53721
53956
  if (clipped) {
53722
53957
  // TODO: could more selective in cleaning clipped layers
53723
53958
  // (probably only needed when clipped area crosses the antimeridian or includes a pole)
53724
- cleanProjectedPathLayers(dataset);
53959
+ cleanProjectedPathLayers(dataset, {heal_antimeridian: healAntimeridian});
53725
53960
  }
53726
53961
 
53727
53962
  if (badArcs > 0 && !opts.quiet) {
@@ -53749,9 +53984,21 @@ ${svg}
53749
53984
  // * Removes line intersections
53750
53985
  // * TODO: what if a layer contains polygons with desired overlaps? should
53751
53986
  // we ignore overlaps between different features?
53752
- function cleanProjectedPathLayers(dataset) {
53987
+ function cleanProjectedPathLayers(dataset, opts) {
53988
+ opts = opts || {};
53753
53989
  // TODO: only clean affected polygons (cleaning all polygons can be slow)
53754
53990
  var polygonLayers = dataset.layers.filter(lyr => lyr.geometry_type == 'polygon');
53991
+ if (opts.heal_antimeridian && dataset.arcs) {
53992
+ // A rotated projection moves the source +/-180 degree boundary into the
53993
+ // map interior. Mathematically identical boundary vertices can diverge by
53994
+ // a few floating-point units during projection, preventing topology from
53995
+ // joining polygon parts that were split at the source antimeridian.
53996
+ var arcs = dataset.arcs;
53997
+ var snapDist = getHighPrecisionSnapInterval(arcs.getBounds().toArray());
53998
+ if (snapCoordsByInterval(arcs, snapDist) > 0) {
53999
+ arcs.dedupCoords();
54000
+ }
54001
+ }
53755
54002
  // clean options: force a topology update (by default, this only happens when
53756
54003
  // vertices change during cleaning, but reprojection can require a topology update
53757
54004
  // even if clean does not change vertices)
@@ -61765,22 +62012,29 @@ ${svg}
61765
62012
  }
61766
62013
 
61767
62014
  cmd.graticule = function(dataset, opts) {
61768
- var name = opts.name || opts.polygon && 'polygon' || 'graticule';
62015
+ if (opts.polygon && opts.outline) {
62016
+ stop$1('The polygon and outline options cannot be used together');
62017
+ }
62018
+ var boundary = opts.polygon || opts.outline;
62019
+ var name = opts.name || opts.outline && 'outline' || opts.polygon && 'polygon' || 'graticule';
61769
62020
  var graticule, destInfo;
61770
62021
  if (dataset && !isLatLngDataset(dataset)) {
61771
62022
  // project graticule to match dataset
61772
62023
  destInfo = getDatasetCrsInfo(dataset);
61773
62024
  if (!destInfo.crs) stop$1("Coordinate system is unknown, unable to create a graticule");
61774
- graticule = opts.polygon ?
62025
+ graticule = boundary ?
61775
62026
  createProjectedPolygon(destInfo.crs, opts) :
61776
62027
  createProjectedGraticule(destInfo.crs, opts);
61777
62028
  setDatasetCrsInfo(graticule, destInfo);
61778
62029
  } else {
61779
- graticule = opts.polygon ?
62030
+ graticule = boundary ?
61780
62031
  createUnprojectedPolygon(opts) :
61781
62032
  createUnprojectedGraticule(opts);
61782
62033
  setDatasetCrsInfo(graticule, getCrsInfo('wgs84'));
61783
62034
  }
62035
+ if (opts.outline) {
62036
+ graticule.layers[0].geometry_type = 'polyline';
62037
+ }
61784
62038
  graticule.layers[0].name = name;
61785
62039
  return graticule;
61786
62040
  };
@@ -67735,6 +67989,28 @@ ${svg}
67735
67989
  name == 'comment' || name == 'rename-layers';
67736
67990
  }
67737
67991
 
67992
+ // Commands in this group keep datasets separate and run once for each grouped
67993
+ // target returned by findCommandTargets(). Commands that consume all targets
67994
+ // together (e.g. -frame) or merge targets (-union, -merge-layers) are
67995
+ // intentionally excluded.
67996
+ function commandRunsPerTargetDataset(name, opts) {
67997
+ if (name == 'rectangle') {
67998
+ return !opts.source && !opts.bbox;
67999
+ }
68000
+ return [
68001
+ 'affine', 'alpha-shapes', 'blur', 'buffer', 'calc', 'check-geometry',
68002
+ 'classify', 'clean', 'clip', 'cluster', 'dashlines', 'data-fill',
68003
+ 'densify', 'dissolve', 'dissolve2', 'divide', 'dots', 'each', 'erase',
68004
+ 'explode', 'filter',
68005
+ 'filter-detail', 'filter-fields', 'filter-geom', 'filter-islands',
68006
+ 'filter-islands2', 'filter-points', 'filter-slivers', 'grid', 'grid2',
68007
+ 'fuzzy-join', 'ignore', 'inlay', 'innerlines', 'inspect', 'join',
68008
+ 'lines', 'mosaic', 'points', 'polygons', 'rectangles', 'rename-fields',
68009
+ 'shapes', 'simplify', 'slice', 'smooth', 'sort', 'split',
68010
+ 'split-on-grid', 'stitch', 'style', 'subdivide', 'symbols', 'uniq'
68011
+ ].indexOf(name) > -1;
68012
+ }
68013
+
67738
68014
  function commandAcceptsEmptyTarget(name) {
67739
68015
  return name == 'graticule' || name == 'i' || name == 'help' ||
67740
68016
  name == 'point-grid' || name == 'shape' || name == 'rectangle' || name == 'frame' ||
@@ -67766,13 +68042,32 @@ ${svg}
67766
68042
  }
67767
68043
 
67768
68044
  if (!job) job = new Job();
68045
+
68046
+ // Preserve the existing single-dataset dispatch and postprocessing paths by
68047
+ // invoking them once per dataset. Collect the resulting targets so a
68048
+ // multi-dataset command does not leave only its final dataset selected.
68049
+ if (!command._targetOverride && commandRunsPerTargetDataset(name, opts)) {
68050
+ targets = job.catalog.findCommandTargets(opts.target);
68051
+ if (targets.length > 1) {
68052
+ validatePerDatasetTargets(name, targets, opts);
68053
+ return runCommandOnEachTarget(command, job, targets);
68054
+ }
68055
+ }
68056
+
67769
68057
  job.startCommand(command);
67770
68058
 
67771
68059
 
67772
68060
  try { // catch errors from synchronous functions
67773
68061
  T$1.start();
67774
68062
 
67775
- if (name == 'rename-layers') {
68063
+ if (command._targetOverride) {
68064
+ targets = [command._targetOverride];
68065
+ targetDataset = targets[0].dataset;
68066
+ arcs = targetDataset.arcs;
68067
+ targetLayers = targets[0].layers;
68068
+ job.catalog.setDefaultTarget(targetLayers, targetDataset);
68069
+
68070
+ } else if (name == 'rename-layers') {
67776
68071
  targets = job.catalog.findCommandTargets(opts.target);
67777
68072
  targetLayers = targets.reduce(function(memo, obj) {
67778
68073
  return memo.concat(obj.layers);
@@ -67816,7 +68111,9 @@ ${svg}
67816
68111
  }
67817
68112
  }
67818
68113
 
67819
- if (opts.source) {
68114
+ if (Object.prototype.hasOwnProperty.call(command, '_sourceOverride')) {
68115
+ source = command._sourceOverride;
68116
+ } else if (opts.source) {
67820
68117
  source = findCommandSource(convertSourceName(opts.source, targets), job.catalog, opts);
67821
68118
  }
67822
68119
 
@@ -68214,6 +68511,141 @@ ${svg}
68214
68511
  }
68215
68512
  }
68216
68513
 
68514
+ async function runCommandOnEachTarget(command, job, targets) {
68515
+ var outputTargets = [];
68516
+ var source = null;
68517
+ var items = targets.map(function(target, i) {
68518
+ return {target: target, index: i};
68519
+ });
68520
+ var disposableSourceTargetIndex = -1;
68521
+
68522
+ if (command.options.source && !sourceNameIsInterpolated(command.options.source)) {
68523
+ source = resolveSourceForTargets(command, job, targets);
68524
+ items = moveSourceTargetLast(items, source);
68525
+ disposableSourceTargetIndex = findLastDisposableSourceTarget(
68526
+ items,
68527
+ source,
68528
+ command.name,
68529
+ command.options
68530
+ );
68531
+ }
68532
+
68533
+ for (var i = 0; i < items.length; i++) {
68534
+ var item = items[i];
68535
+ var target = {
68536
+ dataset: item.target.dataset,
68537
+ layers: item.target.layers.concat()
68538
+ };
68539
+ var command2 = Object.assign({}, command, {_targetOverride: target});
68540
+ if (source) {
68541
+ command2._sourceOverride = getSourceForTarget(
68542
+ source,
68543
+ target,
68544
+ command.name,
68545
+ item.index == disposableSourceTargetIndex,
68546
+ command.options
68547
+ );
68548
+ }
68549
+ await runCommand(command2, job);
68550
+ outputTargets[item.index] = copyCommandTargets(job.catalog.getDefaultTargets());
68551
+ }
68552
+ job.catalog.setDefaultTargets(outputTargets.reduce(function(memo, arr) {
68553
+ return memo.concat(arr);
68554
+ }, []));
68555
+ return job;
68556
+ }
68557
+
68558
+ function resolveSourceForTargets(command, job, targets) {
68559
+ job.startCommand(command);
68560
+ try {
68561
+ return findCommandSource(
68562
+ convertSourceName(command.options.source, targets),
68563
+ job.catalog,
68564
+ command.options
68565
+ );
68566
+ } finally {
68567
+ job.endCommand();
68568
+ }
68569
+ }
68570
+
68571
+ function sourceNameIsInterpolated(name) {
68572
+ return /[$][{]/.test(name);
68573
+ }
68574
+
68575
+ // If a retained catalog source is also targeted, process its group last so
68576
+ // earlier groups always read the original source. Output target ordering is
68577
+ // restored after execution.
68578
+ function moveSourceTargetLast(items, source) {
68579
+ if (!source || source.disposable) return items;
68580
+ return items.filter(function(item) {
68581
+ return item.target.layers.indexOf(source.layer) == -1;
68582
+ }).concat(items.filter(function(item) {
68583
+ return item.target.layers.indexOf(source.layer) > -1;
68584
+ }));
68585
+ }
68586
+
68587
+ function commandMutatesDisposableSource(name, target, source, opts) {
68588
+ var overlayCommand = (name == 'clip' || name == 'erase' || name == 'slice' ||
68589
+ name == 'inlay' || name == 'divide') &&
68590
+ utils.some(target.layers, layerHasPaths);
68591
+ var polygonMosaicJoin = name == 'join' && !opts.keys && !opts.point_method &&
68592
+ source.layer.geometry_type == 'polygon' &&
68593
+ target.layers.some(function(lyr) { return lyr.geometry_type == 'polygon'; });
68594
+ return overlayCommand || polygonMosaicJoin;
68595
+ }
68596
+
68597
+ // A file source is disposable, so the final topology-changing target can use it
68598
+ // directly. Earlier topology-changing targets receive only a shape copy; the
68599
+ // potentially large source ArcCollection is safely shared.
68600
+ function findLastDisposableSourceTarget(items, source, name, opts) {
68601
+ if (!source?.disposable) return -1;
68602
+ for (var i = items.length - 1; i >= 0; i--) {
68603
+ if (commandMutatesDisposableSource(name, items[i].target, source, opts)) {
68604
+ return items[i].index;
68605
+ }
68606
+ }
68607
+ return -1;
68608
+ }
68609
+
68610
+ function getSourceForTarget(source, target, name, useOriginal, opts) {
68611
+ if (!source.disposable ||
68612
+ !commandMutatesDisposableSource(name, target, source, opts) ||
68613
+ useOriginal) {
68614
+ return source;
68615
+ }
68616
+ var layer = copyLayerShapes(source.layer);
68617
+ var dataset = Object.assign({}, source.dataset, {layers: [layer]});
68618
+ return {dataset: dataset, layer: layer, disposable: true};
68619
+ }
68620
+
68621
+ function copyCommandTargets(targets) {
68622
+ return targets.map(function(target) {
68623
+ return {
68624
+ dataset: target.dataset,
68625
+ layers: target.layers.concat()
68626
+ };
68627
+ });
68628
+ }
68629
+
68630
+ // Fail cardinality checks before the first dataset is modified.
68631
+ function validatePerDatasetTargets(name, targets, opts) {
68632
+ if (opts.source && sourceNameIsInterpolated(opts.source) && targets.some(function(target) {
68633
+ return target.layers.length != 1;
68634
+ })) {
68635
+ stop$1('Interpolated names are not compatible with multiple targets.');
68636
+ }
68637
+ if (name == 'mosaic' && targets.some(function(target) {
68638
+ return target.layers.length != 1;
68639
+ })) {
68640
+ stop$1('Command takes a single target layer from each dataset');
68641
+ }
68642
+ if (name == 'simplify' && opts.variable && targets.some(function(target) {
68643
+ return target.layers.length != 1;
68644
+ })) {
68645
+ stop$1('Variable simplification requires a single target layer from each dataset');
68646
+ }
68647
+ }
68648
+
68217
68649
  function outputLayersAreDifferent(output, input) {
68218
68650
  return !utils.some(input, function(lyr) {
68219
68651
  return output.indexOf(lyr) > -1;
@@ -68228,7 +68660,7 @@ ${svg}
68228
68660
  return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
68229
68661
  }
68230
68662
 
68231
- var version = "0.7.40";
68663
+ var version = "0.7.44";
68232
68664
 
68233
68665
  // Parse command line args into commands and run them
68234
68666
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -70168,6 +70600,7 @@ ${svg}
70168
70600
  Proj,
70169
70601
  Projections,
70170
70602
  ProjectionParams,
70603
+ ProjectionTopology,
70171
70604
  Rectangle,
70172
70605
  RectangleUtils,
70173
70606
  Rounding,