mapshaper 0.7.41 → 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/mapshaper.js +441 -8
- package/package.json +2 -2
- package/www/mapshaper-gui.js +119 -24
- package/www/mapshaper.js +441 -8
- package/www/modules.js +294 -24
package/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
|
-
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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.
|
|
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,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mapshaper",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.44",
|
|
4
4
|
"description": "A tool for editing geospatial data for mapping and GIS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"shapefile",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"idb-keyval": "^6.2.0",
|
|
72
72
|
"jpeg-js": "^0.4.4",
|
|
73
73
|
"kdbush": "^3.0.0",
|
|
74
|
-
"mproj": "0.1.
|
|
74
|
+
"mproj": "0.1.5",
|
|
75
75
|
"msgpackr": "^1.11.12",
|
|
76
76
|
"open": "^11.0.0",
|
|
77
77
|
"pngjs": "^7.0.0",
|