mapshaper 0.5.76 → 0.5.77
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/CHANGELOG.md +4 -0
- package/bin/mapshaper-gui +1 -1
- package/mapshaper.js +244 -166
- package/package.json +1 -1
- package/www/mapshaper-gui.js +1 -0
- package/www/mapshaper.js +244 -166
package/CHANGELOG.md
CHANGED
package/bin/mapshaper-gui
CHANGED
package/mapshaper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
|
|
3
|
-
var VERSION = "0.5.
|
|
3
|
+
var VERSION = "0.5.76";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
var utils = /*#__PURE__*/Object.freeze({
|
|
@@ -18973,6 +18973,28 @@ ${svg}
|
|
|
18973
18973
|
'$ mapshaper data.json -colorizer name=getColor nodata=#eee breaks=20,40 \\\n' +
|
|
18974
18974
|
' colors=#e0f3db,#a8ddb5,#43a2ca -each \'fill = getColor(RATING)\' -o output.json');
|
|
18975
18975
|
|
|
18976
|
+
parser.command('dashlines')
|
|
18977
|
+
.describe('split lines into sections, with or without a gap')
|
|
18978
|
+
.oldAlias('split-lines')
|
|
18979
|
+
.option('dash-length', {
|
|
18980
|
+
type: 'distance',
|
|
18981
|
+
describe: 'length of split-apart lines (e.g. 200km)'
|
|
18982
|
+
})
|
|
18983
|
+
.option('gap-length', {
|
|
18984
|
+
type: 'distance',
|
|
18985
|
+
describe: 'length of gaps between dashes (default is 0)'
|
|
18986
|
+
})
|
|
18987
|
+
.option('scaled', {
|
|
18988
|
+
type: 'flag',
|
|
18989
|
+
describe: 'scale dashes and gaps to prevent partial dashes'
|
|
18990
|
+
})
|
|
18991
|
+
.option('planar', {
|
|
18992
|
+
type: 'flag',
|
|
18993
|
+
describe: 'use planar geometry'
|
|
18994
|
+
})
|
|
18995
|
+
.option('where', whereOpt)
|
|
18996
|
+
.option('target', targetOpt);
|
|
18997
|
+
|
|
18976
18998
|
parser.command('define')
|
|
18977
18999
|
// .describe('define expression variables')
|
|
18978
19000
|
.option('expression', {
|
|
@@ -19744,23 +19766,6 @@ ${svg}
|
|
|
19744
19766
|
.option('target', targetOpt)
|
|
19745
19767
|
.option('no-replace', noReplaceOpt);
|
|
19746
19768
|
|
|
19747
|
-
parser.command('split-lines')
|
|
19748
|
-
// .describe('divide lines into sections')
|
|
19749
|
-
.option('dash-length', {
|
|
19750
|
-
type: 'distance',
|
|
19751
|
-
describe: 'length of split-apart lines'
|
|
19752
|
-
})
|
|
19753
|
-
.option('gap-length', {
|
|
19754
|
-
type: 'distance',
|
|
19755
|
-
describe: 'length of gap between segments'
|
|
19756
|
-
})
|
|
19757
|
-
.option('planar', {
|
|
19758
|
-
type: 'flag',
|
|
19759
|
-
describe: 'use planar geometry'
|
|
19760
|
-
})
|
|
19761
|
-
.option('where', whereOpt)
|
|
19762
|
-
.option('target', targetOpt);
|
|
19763
|
-
|
|
19764
19769
|
parser.command('split-on-grid')
|
|
19765
19770
|
.describe('split features into separate layers using a grid')
|
|
19766
19771
|
.validate(validateGridOpts)
|
|
@@ -29729,6 +29734,178 @@ ${svg}
|
|
|
29729
29734
|
getColorizerFunction: getColorizerFunction
|
|
29730
29735
|
});
|
|
29731
29736
|
|
|
29737
|
+
function expressionUsesGeoJSON(exp) {
|
|
29738
|
+
return exp.includes('this.geojson');
|
|
29739
|
+
}
|
|
29740
|
+
|
|
29741
|
+
function getFeatureEditor(lyr, dataset) {
|
|
29742
|
+
var changed = false;
|
|
29743
|
+
var api = {};
|
|
29744
|
+
// need to copy attribute to avoid circular references if geojson is assigned
|
|
29745
|
+
// to a data property.
|
|
29746
|
+
var copy = copyLayer(lyr);
|
|
29747
|
+
var features = exportLayerAsGeoJSON(copy, dataset, {}, true);
|
|
29748
|
+
|
|
29749
|
+
api.get = function(i) {
|
|
29750
|
+
return features[i];
|
|
29751
|
+
};
|
|
29752
|
+
|
|
29753
|
+
api.set = function(feat, i) {
|
|
29754
|
+
changed = true;
|
|
29755
|
+
if (utils.isString(feat)) {
|
|
29756
|
+
feat = JSON.parse(feat);
|
|
29757
|
+
}
|
|
29758
|
+
features[i] = GeoJSON.toFeature(feat); // TODO: validate
|
|
29759
|
+
};
|
|
29760
|
+
|
|
29761
|
+
api.done = function() {
|
|
29762
|
+
if (!changed) return; // read-only expression
|
|
29763
|
+
// TODO: validate number of features, etc.
|
|
29764
|
+
var geojson = {
|
|
29765
|
+
type: 'FeatureCollection',
|
|
29766
|
+
features: features
|
|
29767
|
+
};
|
|
29768
|
+
|
|
29769
|
+
// console.log(JSON.stringify(geojson, null, 2))
|
|
29770
|
+
return importGeoJSON(geojson);
|
|
29771
|
+
};
|
|
29772
|
+
return api;
|
|
29773
|
+
}
|
|
29774
|
+
|
|
29775
|
+
cmd.dashlines = function(lyr, dataset, opts) {
|
|
29776
|
+
var crs = getDatasetCRS(dataset);
|
|
29777
|
+
var defs = getStateVar('defs');
|
|
29778
|
+
var exp = `this.geojson = splitFeature(this.geojson)`;
|
|
29779
|
+
requirePolylineLayer(lyr);
|
|
29780
|
+
defs.splitFeature = getSplitFeatureFunction(crs, opts);
|
|
29781
|
+
cmd.evaluateEachFeature(lyr, dataset, exp, opts);
|
|
29782
|
+
delete defs.splitFeature;
|
|
29783
|
+
};
|
|
29784
|
+
|
|
29785
|
+
function getSplitFeatureFunction(crs, opts) {
|
|
29786
|
+
var dashLen = opts.dash_length ? convertDistanceParam(opts.dash_length, crs) : 0;
|
|
29787
|
+
var gapLen = opts.gap_length ? convertDistanceParam(opts.gap_length, crs) : 0;
|
|
29788
|
+
if (dashLen > 0 === false) {
|
|
29789
|
+
stop('Missing required dash-length parameter');
|
|
29790
|
+
}
|
|
29791
|
+
if (gapLen >= 0 == false) {
|
|
29792
|
+
stop('Invalid gap-length option');
|
|
29793
|
+
}
|
|
29794
|
+
var splitLine = getSplitLineFunction(crs, dashLen, gapLen, opts);
|
|
29795
|
+
return function(feat) {
|
|
29796
|
+
var geom = feat.geometry;
|
|
29797
|
+
if (!geom) return feat;
|
|
29798
|
+
if (geom.type == 'LineString') {
|
|
29799
|
+
geom.type = 'MultiLineString';
|
|
29800
|
+
geom.coordinates = [geom.coordinates];
|
|
29801
|
+
}
|
|
29802
|
+
if (geom.type != 'MultiLineString') {
|
|
29803
|
+
error('Unexpected geometry:', geom.type);
|
|
29804
|
+
}
|
|
29805
|
+
geom.coordinates = geom.coordinates.reduce(function(memo, coords) {
|
|
29806
|
+
try {
|
|
29807
|
+
var parts = splitLine(coords);
|
|
29808
|
+
memo = memo.concat(parts);
|
|
29809
|
+
} catch(e) {
|
|
29810
|
+
console.error(e);
|
|
29811
|
+
throw e;
|
|
29812
|
+
}
|
|
29813
|
+
return memo;
|
|
29814
|
+
}, []);
|
|
29815
|
+
|
|
29816
|
+
return feat;
|
|
29817
|
+
};
|
|
29818
|
+
}
|
|
29819
|
+
|
|
29820
|
+
function getSplitLineFunction(crs, dashLen, gapLen, opts) {
|
|
29821
|
+
var planar = !!opts.planar;
|
|
29822
|
+
var interpolate = getInterpolationFunction(planar ? null : crs);
|
|
29823
|
+
var distance = isLatLngCRS(crs) ? greatCircleDistance : distance2D;
|
|
29824
|
+
var inDash, parts2, interval, scale;
|
|
29825
|
+
function addPart(coords) {
|
|
29826
|
+
if (inDash) parts2.push(coords);
|
|
29827
|
+
if (gapLen > 0) {
|
|
29828
|
+
inDash = !inDash;
|
|
29829
|
+
interval = scale * (inDash ? dashLen : gapLen);
|
|
29830
|
+
}
|
|
29831
|
+
}
|
|
29832
|
+
|
|
29833
|
+
return function splitLineString(coords) {
|
|
29834
|
+
var elapsedDist = 0;
|
|
29835
|
+
var p = coords[0];
|
|
29836
|
+
var coords2 = [p];
|
|
29837
|
+
var segLen, pct, prev;
|
|
29838
|
+
if (opts.scaled) {
|
|
29839
|
+
scale = scaleDashes(dashLen, gapLen, getLineLength(coords, distance));
|
|
29840
|
+
} else {
|
|
29841
|
+
scale = 1;
|
|
29842
|
+
}
|
|
29843
|
+
// init this LineString
|
|
29844
|
+
inDash = gapLen > 0 ? false : true;
|
|
29845
|
+
interval = scale * (inDash ? dashLen : gapLen);
|
|
29846
|
+
if (!inDash) {
|
|
29847
|
+
// start gapped lines with a half-gap
|
|
29848
|
+
// (a half-gap or a half-dash is probably better for rings and intersecting lines)
|
|
29849
|
+
interval *= 0.5;
|
|
29850
|
+
}
|
|
29851
|
+
parts2 = [];
|
|
29852
|
+
for (var i=1, n=coords.length; i<n; i++) {
|
|
29853
|
+
prev = p;
|
|
29854
|
+
p = coords[i];
|
|
29855
|
+
segLen = distance(prev[0], prev[1], p[0], p[1]);
|
|
29856
|
+
if (segLen <= 0) continue;
|
|
29857
|
+
while (elapsedDist + segLen >= interval) {
|
|
29858
|
+
// this segment contains a break either within it or at the far endpoint
|
|
29859
|
+
pct = (interval - elapsedDist) / segLen;
|
|
29860
|
+
if (pct > 0.999 && i == n - 1) {
|
|
29861
|
+
// snap to endpoint (so fp rounding errors don't result in a tiny
|
|
29862
|
+
// last segment)
|
|
29863
|
+
pct = 1;
|
|
29864
|
+
}
|
|
29865
|
+
if (pct < 1) {
|
|
29866
|
+
prev = interpolate(prev[0], prev[1], p[0], p[1], pct);
|
|
29867
|
+
} else {
|
|
29868
|
+
prev = p;
|
|
29869
|
+
}
|
|
29870
|
+
coords2.push(prev);
|
|
29871
|
+
addPart(coords2);
|
|
29872
|
+
// start a new part
|
|
29873
|
+
coords2 = pct < 1 ? [prev] : [];
|
|
29874
|
+
elapsedDist = 0;
|
|
29875
|
+
segLen = (1 - pct) * segLen;
|
|
29876
|
+
}
|
|
29877
|
+
coords2.push(p);
|
|
29878
|
+
elapsedDist += segLen;
|
|
29879
|
+
}
|
|
29880
|
+
if (elapsedDist > 0 && coords2.length > 1) {
|
|
29881
|
+
addPart(coords2);
|
|
29882
|
+
}
|
|
29883
|
+
return parts2;
|
|
29884
|
+
};
|
|
29885
|
+
}
|
|
29886
|
+
|
|
29887
|
+
function getLineLength(coords, distance) {
|
|
29888
|
+
var len = 0;
|
|
29889
|
+
for (var i=1, n=coords.length; i<n; i++) {
|
|
29890
|
+
len += distance(coords[i-1][0], coords[i-1][1], coords[i][0], coords[i][1]);
|
|
29891
|
+
}
|
|
29892
|
+
return len;
|
|
29893
|
+
}
|
|
29894
|
+
|
|
29895
|
+
function scaleDashes(dash, gap, len) {
|
|
29896
|
+
var dash2, gap2;
|
|
29897
|
+
var n = len / (dash + gap); // number of dashes
|
|
29898
|
+
var n1 = Math.floor(n);
|
|
29899
|
+
var n2 = Math.ceil(n);
|
|
29900
|
+
var k1 = len / (n1 * (dash + gap)); // scaled-up dashes, >1
|
|
29901
|
+
var k2 = len / (n2 * (dash + gap)); // scaled-down dashes <1
|
|
29902
|
+
var k = k2;
|
|
29903
|
+
if (k1 < 1/k2 && n1 > 0) {
|
|
29904
|
+
k = k1; // pick the smaller of the two scales
|
|
29905
|
+
}
|
|
29906
|
+
return k;
|
|
29907
|
+
}
|
|
29908
|
+
|
|
29732
29909
|
// This function creates a continuous mosaic of data values in a
|
|
29733
29910
|
// given field by assigning data from adjacent polygon features to polygons
|
|
29734
29911
|
// that contain null values.
|
|
@@ -31253,44 +31430,6 @@ ${svg}
|
|
|
31253
31430
|
}
|
|
31254
31431
|
};
|
|
31255
31432
|
|
|
31256
|
-
function expressionUsesGeoJSON(exp) {
|
|
31257
|
-
return exp.includes('this.geojson');
|
|
31258
|
-
}
|
|
31259
|
-
|
|
31260
|
-
function getFeatureEditor(lyr, dataset) {
|
|
31261
|
-
var changed = false;
|
|
31262
|
-
var api = {};
|
|
31263
|
-
// need to copy attribute to avoid circular references if geojson is assigned
|
|
31264
|
-
// to a data property.
|
|
31265
|
-
var copy = copyLayer(lyr);
|
|
31266
|
-
var features = exportLayerAsGeoJSON(copy, dataset, {}, true);
|
|
31267
|
-
|
|
31268
|
-
api.get = function(i) {
|
|
31269
|
-
return features[i];
|
|
31270
|
-
};
|
|
31271
|
-
|
|
31272
|
-
api.set = function(feat, i) {
|
|
31273
|
-
changed = true;
|
|
31274
|
-
if (utils.isString(feat)) {
|
|
31275
|
-
feat = JSON.parse(feat);
|
|
31276
|
-
}
|
|
31277
|
-
features[i] = GeoJSON.toFeature(feat); // TODO: validate
|
|
31278
|
-
};
|
|
31279
|
-
|
|
31280
|
-
api.done = function() {
|
|
31281
|
-
if (!changed) return; // read-only expression
|
|
31282
|
-
// TODO: validate number of features, etc.
|
|
31283
|
-
var geojson = {
|
|
31284
|
-
type: 'FeatureCollection',
|
|
31285
|
-
features: features
|
|
31286
|
-
};
|
|
31287
|
-
|
|
31288
|
-
// console.log(JSON.stringify(geojson, null, 2))
|
|
31289
|
-
return importGeoJSON(geojson);
|
|
31290
|
-
};
|
|
31291
|
-
return api;
|
|
31292
|
-
}
|
|
31293
|
-
|
|
31294
31433
|
cmd.evaluateEachFeature = function(lyr, dataset, exp, opts) {
|
|
31295
31434
|
var n = getFeatureCount(lyr),
|
|
31296
31435
|
arcs = dataset.arcs,
|
|
@@ -31896,7 +32035,6 @@ ${svg}
|
|
|
31896
32035
|
return joinTableToLayer(targetLyr, polygonLyr.data, joinFunction, opts);
|
|
31897
32036
|
}
|
|
31898
32037
|
|
|
31899
|
-
|
|
31900
32038
|
function prepJoinLayers(targetLyr, srcLyr) {
|
|
31901
32039
|
if (!targetLyr.data) {
|
|
31902
32040
|
// create an empty data table if target layer is missing attributes
|
|
@@ -34702,6 +34840,38 @@ ${svg}
|
|
|
34702
34840
|
}
|
|
34703
34841
|
}
|
|
34704
34842
|
|
|
34843
|
+
function pointsFromPolylinesForJoin(lyr, dataset) {
|
|
34844
|
+
var shapes = lyr.shapes.map(function(shp) {
|
|
34845
|
+
return polylineToMidpoints(shp, dataset.arcs);
|
|
34846
|
+
});
|
|
34847
|
+
return {
|
|
34848
|
+
geometry_type: 'point',
|
|
34849
|
+
shapes: shapes,
|
|
34850
|
+
data: lyr.data // TODO copy if needed
|
|
34851
|
+
};
|
|
34852
|
+
}
|
|
34853
|
+
|
|
34854
|
+
function validateOpts(opts) {
|
|
34855
|
+
if (!opts.point_method) {
|
|
34856
|
+
stop('The "point-method" flag is required for polyline-polygon joins');
|
|
34857
|
+
}
|
|
34858
|
+
}
|
|
34859
|
+
|
|
34860
|
+
function joinPolylinesToPolygons(targetLyr, targetDataset, source, opts) {
|
|
34861
|
+
validateOpts(opts);
|
|
34862
|
+
var pointLyr = pointsFromPolylinesForJoin(source.layer, source.dataset);
|
|
34863
|
+
var retn = joinPointsToPolygons(targetLyr, targetDataset.arcs, pointLyr, opts);
|
|
34864
|
+
return retn;
|
|
34865
|
+
}
|
|
34866
|
+
|
|
34867
|
+
function joinPolygonsToPolylines(targetLyr, targetDataset, source, opts) {
|
|
34868
|
+
validateOpts(opts);
|
|
34869
|
+
var pointLyr = pointsFromPolylinesForJoin(targetLyr, targetDataset);
|
|
34870
|
+
var retn = joinPolygonsToPoints(pointLyr, source.layer, source.dataset.arcs, opts);
|
|
34871
|
+
targetLyr.data = pointLyr.data;
|
|
34872
|
+
return retn;
|
|
34873
|
+
}
|
|
34874
|
+
|
|
34705
34875
|
class TinyQueue {
|
|
34706
34876
|
constructor(data = [], compare = defaultCompare) {
|
|
34707
34877
|
this.data = data;
|
|
@@ -35025,7 +35195,7 @@ ${svg}
|
|
|
35025
35195
|
|
|
35026
35196
|
cmd.join = function(targetLyr, targetDataset, src, opts) {
|
|
35027
35197
|
var srcType, targetType, retn;
|
|
35028
|
-
if (!src || !src.
|
|
35198
|
+
if (!src || !src.dataset) {
|
|
35029
35199
|
stop("Missing a joinable data source");
|
|
35030
35200
|
}
|
|
35031
35201
|
if (opts.keys) {
|
|
@@ -35033,9 +35203,18 @@ ${svg}
|
|
|
35033
35203
|
if (opts.keys.length != 2) {
|
|
35034
35204
|
stop("Expected two key fields: a target field and a source field");
|
|
35035
35205
|
}
|
|
35206
|
+
if (!src.layer.data) {
|
|
35207
|
+
stop("Source layer is missing attribute data");
|
|
35208
|
+
}
|
|
35036
35209
|
retn = joinAttributesToFeatures(targetLyr, src.layer.data, opts);
|
|
35037
35210
|
} else {
|
|
35038
35211
|
// spatial join
|
|
35212
|
+
if (!src.layer.data) {
|
|
35213
|
+
// KLUDGE -- users might want to join a layer without attributes
|
|
35214
|
+
// to test for intersection... the simplest way to support this is
|
|
35215
|
+
// to add an empty data table to the source layer
|
|
35216
|
+
initDataTable(src.layer);
|
|
35217
|
+
}
|
|
35039
35218
|
requireDatasetsHaveCompatibleCRS([targetDataset, src.dataset]);
|
|
35040
35219
|
srcType = src.layer.geometry_type;
|
|
35041
35220
|
targetType = targetLyr.geometry_type;
|
|
@@ -35047,6 +35226,10 @@ ${svg}
|
|
|
35047
35226
|
retn = joinPointsToPoints(targetLyr, src.layer, getDatasetCRS(targetDataset), opts);
|
|
35048
35227
|
} else if (srcType == 'polygon' && targetType == 'polygon') {
|
|
35049
35228
|
retn = joinPolygonsToPolygons(targetLyr, targetDataset, src, opts);
|
|
35229
|
+
} else if (srcType == 'polyline' && targetType == 'polygon') {
|
|
35230
|
+
retn = joinPolylinesToPolygons(targetLyr, targetDataset, src, opts);
|
|
35231
|
+
} else if (srcType == 'polygon' && targetType == 'polyline') {
|
|
35232
|
+
retn = joinPolygonsToPolylines(targetLyr, targetDataset, src, opts);
|
|
35050
35233
|
} else {
|
|
35051
35234
|
stop(utils.format("Unable to join %s geometry to %s geometry",
|
|
35052
35235
|
srcType || 'null', targetType || 'null'));
|
|
@@ -37310,111 +37493,6 @@ ${svg}
|
|
|
37310
37493
|
getSplitNameFunction: getSplitNameFunction
|
|
37311
37494
|
});
|
|
37312
37495
|
|
|
37313
|
-
cmd.splitLines = function(lyr, dataset, opts) {
|
|
37314
|
-
var crs = getDatasetCRS(dataset);
|
|
37315
|
-
requirePolylineLayer(lyr);
|
|
37316
|
-
var splitFeature = getSplitFeatureFunction(crs, opts);
|
|
37317
|
-
|
|
37318
|
-
// TODO: remove duplication with mapshaper-each.js
|
|
37319
|
-
var editor = getFeatureEditor(lyr, dataset);
|
|
37320
|
-
var exprOpts = {
|
|
37321
|
-
geojson_editor: editor,
|
|
37322
|
-
context: {splitFeature}
|
|
37323
|
-
};
|
|
37324
|
-
var exp = `this.geojson = splitFeature(this.geojson)`;
|
|
37325
|
-
|
|
37326
|
-
var compiled = compileFeatureExpression(exp, lyr, dataset.arcs, exprOpts);
|
|
37327
|
-
var n = getFeatureCount(lyr);
|
|
37328
|
-
var filter;
|
|
37329
|
-
if (opts && opts.where) {
|
|
37330
|
-
filter = compileValueExpression(opts.where, lyr, dataset.arcs);
|
|
37331
|
-
}
|
|
37332
|
-
for (var i=0; i<n; i++) {
|
|
37333
|
-
if (!filter || filter(i)) {
|
|
37334
|
-
compiled(i);
|
|
37335
|
-
}
|
|
37336
|
-
}
|
|
37337
|
-
replaceLayerContents(lyr, dataset, editor.done());
|
|
37338
|
-
};
|
|
37339
|
-
|
|
37340
|
-
function getSplitFeatureFunction(crs, opts) {
|
|
37341
|
-
var dashLen = opts.dash_length ? convertDistanceParam(opts.dash_length, crs) : 0;
|
|
37342
|
-
var gapLen = opts.gap_length ? convertDistanceParam(opts.gap_length, crs) : 0;
|
|
37343
|
-
if (dashLen > 0 === false) {
|
|
37344
|
-
stop('Missing required segment-length parameter');
|
|
37345
|
-
}
|
|
37346
|
-
if (gapLen >= 0 == false) {
|
|
37347
|
-
stop('Invalid gap-length option');
|
|
37348
|
-
}
|
|
37349
|
-
var splitLine = getSplitLineFunction(crs, dashLen, gapLen, !!opts.planar);
|
|
37350
|
-
return function(feat) {
|
|
37351
|
-
var geom = feat.geometry;
|
|
37352
|
-
if (!geom) return feat;
|
|
37353
|
-
if (geom.type == 'LineString') {
|
|
37354
|
-
geom.type = 'MultiLineString';
|
|
37355
|
-
geom.coordinates = [geom.coordinates];
|
|
37356
|
-
}
|
|
37357
|
-
if (geom.type != 'MultiLineString') {
|
|
37358
|
-
error('Unexpected geometry:', geom.type);
|
|
37359
|
-
}
|
|
37360
|
-
geom.coordinates = geom.coordinates.reduce(function(memo, coords) {
|
|
37361
|
-
try {
|
|
37362
|
-
var parts = splitLine(coords);
|
|
37363
|
-
memo = memo.concat(parts);
|
|
37364
|
-
} catch(e) {
|
|
37365
|
-
console.error(e);
|
|
37366
|
-
throw e;
|
|
37367
|
-
}
|
|
37368
|
-
return memo;
|
|
37369
|
-
}, []);
|
|
37370
|
-
|
|
37371
|
-
return feat;
|
|
37372
|
-
};
|
|
37373
|
-
}
|
|
37374
|
-
|
|
37375
|
-
function getSplitLineFunction(crs, dashLen, gapLen, planar) {
|
|
37376
|
-
var interpolate = getInterpolationFunction(planar ? null : crs);
|
|
37377
|
-
var distance = isLatLngCRS(crs) ? greatCircleDistance : distance2D;
|
|
37378
|
-
var inDash, parts2, interval;
|
|
37379
|
-
function addPart(coords) {
|
|
37380
|
-
if (inDash) parts2.push(coords);
|
|
37381
|
-
if (gapLen > 0) {
|
|
37382
|
-
inDash = !inDash;
|
|
37383
|
-
interval = inDash ? dashLen : gapLen;
|
|
37384
|
-
}
|
|
37385
|
-
}
|
|
37386
|
-
return function splitLineString(coords) {
|
|
37387
|
-
var elapsedDist = 0;
|
|
37388
|
-
var p = coords[0];
|
|
37389
|
-
var coords2 = [p];
|
|
37390
|
-
var segLen, k, prev;
|
|
37391
|
-
// init this LineString
|
|
37392
|
-
inDash = true;
|
|
37393
|
-
parts2 = [];
|
|
37394
|
-
interval = gapLen;
|
|
37395
|
-
for (var i=1, n=coords.length; i<n; i++) {
|
|
37396
|
-
prev = p;
|
|
37397
|
-
p = coords[i];
|
|
37398
|
-
segLen = distance(prev[0], prev[1], p[0], p[1]);
|
|
37399
|
-
while (elapsedDist + segLen >= interval) {
|
|
37400
|
-
k = (interval - elapsedDist) / segLen;
|
|
37401
|
-
prev = interpolate(prev[0], prev[1], p[0], p[1], k);
|
|
37402
|
-
elapsedDist = 0;
|
|
37403
|
-
coords2.push(prev);
|
|
37404
|
-
addPart(coords2);
|
|
37405
|
-
coords2 = [prev];
|
|
37406
|
-
segLen = distance(prev[0], prev[1], p[0], p[1]);
|
|
37407
|
-
}
|
|
37408
|
-
coords2.push(p);
|
|
37409
|
-
elapsedDist += segLen;
|
|
37410
|
-
}
|
|
37411
|
-
if (elapsedDist > 0 && coords2.length > 1) {
|
|
37412
|
-
addPart(coords2);
|
|
37413
|
-
}
|
|
37414
|
-
return parts2;
|
|
37415
|
-
};
|
|
37416
|
-
}
|
|
37417
|
-
|
|
37418
37496
|
cmd.svgStyle = function(lyr, dataset, opts) {
|
|
37419
37497
|
var filter;
|
|
37420
37498
|
if (!lyr.data) {
|
|
@@ -38176,6 +38254,9 @@ ${svg}
|
|
|
38176
38254
|
} else if (name == 'colorizer') {
|
|
38177
38255
|
outputLayers = cmd.colorizer(opts);
|
|
38178
38256
|
|
|
38257
|
+
} else if (name == 'dashlines') {
|
|
38258
|
+
applyCommandToEachLayer(cmd.dashlines, targetLayers, targetDataset, opts);
|
|
38259
|
+
|
|
38179
38260
|
} else if (name == 'define') {
|
|
38180
38261
|
cmd.define(opts);
|
|
38181
38262
|
|
|
@@ -38376,9 +38457,6 @@ ${svg}
|
|
|
38376
38457
|
} else if (name == 'split') {
|
|
38377
38458
|
outputLayers = applyCommandToEachLayer(cmd.splitLayer, targetLayers, opts.expression, opts);
|
|
38378
38459
|
|
|
38379
|
-
} else if (name == 'split-lines') {
|
|
38380
|
-
applyCommandToEachLayer(cmd.splitLines, targetLayers, targetDataset, opts);
|
|
38381
|
-
|
|
38382
38460
|
} else if (name == 'split-on-grid') {
|
|
38383
38461
|
outputLayers = applyCommandToEachLayer(cmd.splitLayerOnGrid, targetLayers, arcs, opts);
|
|
38384
38462
|
|
package/package.json
CHANGED
package/www/mapshaper-gui.js
CHANGED
|
@@ -2292,6 +2292,7 @@
|
|
|
2292
2292
|
function onPaste(e) {
|
|
2293
2293
|
// paste plain text (remove any copied HTML tags)
|
|
2294
2294
|
e.preventDefault();
|
|
2295
|
+
e.stopPropagation(); // don't try to import pasted text as data (see gui-import-control.js)
|
|
2295
2296
|
var str = (e.originalEvent || e).clipboardData.getData('text/plain');
|
|
2296
2297
|
document.execCommand("insertHTML", false, str);
|
|
2297
2298
|
}
|
package/www/mapshaper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
|
|
3
|
-
var VERSION = "0.5.
|
|
3
|
+
var VERSION = "0.5.76";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
var utils = /*#__PURE__*/Object.freeze({
|
|
@@ -18973,6 +18973,28 @@ ${svg}
|
|
|
18973
18973
|
'$ mapshaper data.json -colorizer name=getColor nodata=#eee breaks=20,40 \\\n' +
|
|
18974
18974
|
' colors=#e0f3db,#a8ddb5,#43a2ca -each \'fill = getColor(RATING)\' -o output.json');
|
|
18975
18975
|
|
|
18976
|
+
parser.command('dashlines')
|
|
18977
|
+
.describe('split lines into sections, with or without a gap')
|
|
18978
|
+
.oldAlias('split-lines')
|
|
18979
|
+
.option('dash-length', {
|
|
18980
|
+
type: 'distance',
|
|
18981
|
+
describe: 'length of split-apart lines (e.g. 200km)'
|
|
18982
|
+
})
|
|
18983
|
+
.option('gap-length', {
|
|
18984
|
+
type: 'distance',
|
|
18985
|
+
describe: 'length of gaps between dashes (default is 0)'
|
|
18986
|
+
})
|
|
18987
|
+
.option('scaled', {
|
|
18988
|
+
type: 'flag',
|
|
18989
|
+
describe: 'scale dashes and gaps to prevent partial dashes'
|
|
18990
|
+
})
|
|
18991
|
+
.option('planar', {
|
|
18992
|
+
type: 'flag',
|
|
18993
|
+
describe: 'use planar geometry'
|
|
18994
|
+
})
|
|
18995
|
+
.option('where', whereOpt)
|
|
18996
|
+
.option('target', targetOpt);
|
|
18997
|
+
|
|
18976
18998
|
parser.command('define')
|
|
18977
18999
|
// .describe('define expression variables')
|
|
18978
19000
|
.option('expression', {
|
|
@@ -19744,23 +19766,6 @@ ${svg}
|
|
|
19744
19766
|
.option('target', targetOpt)
|
|
19745
19767
|
.option('no-replace', noReplaceOpt);
|
|
19746
19768
|
|
|
19747
|
-
parser.command('split-lines')
|
|
19748
|
-
// .describe('divide lines into sections')
|
|
19749
|
-
.option('dash-length', {
|
|
19750
|
-
type: 'distance',
|
|
19751
|
-
describe: 'length of split-apart lines'
|
|
19752
|
-
})
|
|
19753
|
-
.option('gap-length', {
|
|
19754
|
-
type: 'distance',
|
|
19755
|
-
describe: 'length of gap between segments'
|
|
19756
|
-
})
|
|
19757
|
-
.option('planar', {
|
|
19758
|
-
type: 'flag',
|
|
19759
|
-
describe: 'use planar geometry'
|
|
19760
|
-
})
|
|
19761
|
-
.option('where', whereOpt)
|
|
19762
|
-
.option('target', targetOpt);
|
|
19763
|
-
|
|
19764
19769
|
parser.command('split-on-grid')
|
|
19765
19770
|
.describe('split features into separate layers using a grid')
|
|
19766
19771
|
.validate(validateGridOpts)
|
|
@@ -29729,6 +29734,178 @@ ${svg}
|
|
|
29729
29734
|
getColorizerFunction: getColorizerFunction
|
|
29730
29735
|
});
|
|
29731
29736
|
|
|
29737
|
+
function expressionUsesGeoJSON(exp) {
|
|
29738
|
+
return exp.includes('this.geojson');
|
|
29739
|
+
}
|
|
29740
|
+
|
|
29741
|
+
function getFeatureEditor(lyr, dataset) {
|
|
29742
|
+
var changed = false;
|
|
29743
|
+
var api = {};
|
|
29744
|
+
// need to copy attribute to avoid circular references if geojson is assigned
|
|
29745
|
+
// to a data property.
|
|
29746
|
+
var copy = copyLayer(lyr);
|
|
29747
|
+
var features = exportLayerAsGeoJSON(copy, dataset, {}, true);
|
|
29748
|
+
|
|
29749
|
+
api.get = function(i) {
|
|
29750
|
+
return features[i];
|
|
29751
|
+
};
|
|
29752
|
+
|
|
29753
|
+
api.set = function(feat, i) {
|
|
29754
|
+
changed = true;
|
|
29755
|
+
if (utils.isString(feat)) {
|
|
29756
|
+
feat = JSON.parse(feat);
|
|
29757
|
+
}
|
|
29758
|
+
features[i] = GeoJSON.toFeature(feat); // TODO: validate
|
|
29759
|
+
};
|
|
29760
|
+
|
|
29761
|
+
api.done = function() {
|
|
29762
|
+
if (!changed) return; // read-only expression
|
|
29763
|
+
// TODO: validate number of features, etc.
|
|
29764
|
+
var geojson = {
|
|
29765
|
+
type: 'FeatureCollection',
|
|
29766
|
+
features: features
|
|
29767
|
+
};
|
|
29768
|
+
|
|
29769
|
+
// console.log(JSON.stringify(geojson, null, 2))
|
|
29770
|
+
return importGeoJSON(geojson);
|
|
29771
|
+
};
|
|
29772
|
+
return api;
|
|
29773
|
+
}
|
|
29774
|
+
|
|
29775
|
+
cmd.dashlines = function(lyr, dataset, opts) {
|
|
29776
|
+
var crs = getDatasetCRS(dataset);
|
|
29777
|
+
var defs = getStateVar('defs');
|
|
29778
|
+
var exp = `this.geojson = splitFeature(this.geojson)`;
|
|
29779
|
+
requirePolylineLayer(lyr);
|
|
29780
|
+
defs.splitFeature = getSplitFeatureFunction(crs, opts);
|
|
29781
|
+
cmd.evaluateEachFeature(lyr, dataset, exp, opts);
|
|
29782
|
+
delete defs.splitFeature;
|
|
29783
|
+
};
|
|
29784
|
+
|
|
29785
|
+
function getSplitFeatureFunction(crs, opts) {
|
|
29786
|
+
var dashLen = opts.dash_length ? convertDistanceParam(opts.dash_length, crs) : 0;
|
|
29787
|
+
var gapLen = opts.gap_length ? convertDistanceParam(opts.gap_length, crs) : 0;
|
|
29788
|
+
if (dashLen > 0 === false) {
|
|
29789
|
+
stop('Missing required dash-length parameter');
|
|
29790
|
+
}
|
|
29791
|
+
if (gapLen >= 0 == false) {
|
|
29792
|
+
stop('Invalid gap-length option');
|
|
29793
|
+
}
|
|
29794
|
+
var splitLine = getSplitLineFunction(crs, dashLen, gapLen, opts);
|
|
29795
|
+
return function(feat) {
|
|
29796
|
+
var geom = feat.geometry;
|
|
29797
|
+
if (!geom) return feat;
|
|
29798
|
+
if (geom.type == 'LineString') {
|
|
29799
|
+
geom.type = 'MultiLineString';
|
|
29800
|
+
geom.coordinates = [geom.coordinates];
|
|
29801
|
+
}
|
|
29802
|
+
if (geom.type != 'MultiLineString') {
|
|
29803
|
+
error('Unexpected geometry:', geom.type);
|
|
29804
|
+
}
|
|
29805
|
+
geom.coordinates = geom.coordinates.reduce(function(memo, coords) {
|
|
29806
|
+
try {
|
|
29807
|
+
var parts = splitLine(coords);
|
|
29808
|
+
memo = memo.concat(parts);
|
|
29809
|
+
} catch(e) {
|
|
29810
|
+
console.error(e);
|
|
29811
|
+
throw e;
|
|
29812
|
+
}
|
|
29813
|
+
return memo;
|
|
29814
|
+
}, []);
|
|
29815
|
+
|
|
29816
|
+
return feat;
|
|
29817
|
+
};
|
|
29818
|
+
}
|
|
29819
|
+
|
|
29820
|
+
function getSplitLineFunction(crs, dashLen, gapLen, opts) {
|
|
29821
|
+
var planar = !!opts.planar;
|
|
29822
|
+
var interpolate = getInterpolationFunction(planar ? null : crs);
|
|
29823
|
+
var distance = isLatLngCRS(crs) ? greatCircleDistance : distance2D;
|
|
29824
|
+
var inDash, parts2, interval, scale;
|
|
29825
|
+
function addPart(coords) {
|
|
29826
|
+
if (inDash) parts2.push(coords);
|
|
29827
|
+
if (gapLen > 0) {
|
|
29828
|
+
inDash = !inDash;
|
|
29829
|
+
interval = scale * (inDash ? dashLen : gapLen);
|
|
29830
|
+
}
|
|
29831
|
+
}
|
|
29832
|
+
|
|
29833
|
+
return function splitLineString(coords) {
|
|
29834
|
+
var elapsedDist = 0;
|
|
29835
|
+
var p = coords[0];
|
|
29836
|
+
var coords2 = [p];
|
|
29837
|
+
var segLen, pct, prev;
|
|
29838
|
+
if (opts.scaled) {
|
|
29839
|
+
scale = scaleDashes(dashLen, gapLen, getLineLength(coords, distance));
|
|
29840
|
+
} else {
|
|
29841
|
+
scale = 1;
|
|
29842
|
+
}
|
|
29843
|
+
// init this LineString
|
|
29844
|
+
inDash = gapLen > 0 ? false : true;
|
|
29845
|
+
interval = scale * (inDash ? dashLen : gapLen);
|
|
29846
|
+
if (!inDash) {
|
|
29847
|
+
// start gapped lines with a half-gap
|
|
29848
|
+
// (a half-gap or a half-dash is probably better for rings and intersecting lines)
|
|
29849
|
+
interval *= 0.5;
|
|
29850
|
+
}
|
|
29851
|
+
parts2 = [];
|
|
29852
|
+
for (var i=1, n=coords.length; i<n; i++) {
|
|
29853
|
+
prev = p;
|
|
29854
|
+
p = coords[i];
|
|
29855
|
+
segLen = distance(prev[0], prev[1], p[0], p[1]);
|
|
29856
|
+
if (segLen <= 0) continue;
|
|
29857
|
+
while (elapsedDist + segLen >= interval) {
|
|
29858
|
+
// this segment contains a break either within it or at the far endpoint
|
|
29859
|
+
pct = (interval - elapsedDist) / segLen;
|
|
29860
|
+
if (pct > 0.999 && i == n - 1) {
|
|
29861
|
+
// snap to endpoint (so fp rounding errors don't result in a tiny
|
|
29862
|
+
// last segment)
|
|
29863
|
+
pct = 1;
|
|
29864
|
+
}
|
|
29865
|
+
if (pct < 1) {
|
|
29866
|
+
prev = interpolate(prev[0], prev[1], p[0], p[1], pct);
|
|
29867
|
+
} else {
|
|
29868
|
+
prev = p;
|
|
29869
|
+
}
|
|
29870
|
+
coords2.push(prev);
|
|
29871
|
+
addPart(coords2);
|
|
29872
|
+
// start a new part
|
|
29873
|
+
coords2 = pct < 1 ? [prev] : [];
|
|
29874
|
+
elapsedDist = 0;
|
|
29875
|
+
segLen = (1 - pct) * segLen;
|
|
29876
|
+
}
|
|
29877
|
+
coords2.push(p);
|
|
29878
|
+
elapsedDist += segLen;
|
|
29879
|
+
}
|
|
29880
|
+
if (elapsedDist > 0 && coords2.length > 1) {
|
|
29881
|
+
addPart(coords2);
|
|
29882
|
+
}
|
|
29883
|
+
return parts2;
|
|
29884
|
+
};
|
|
29885
|
+
}
|
|
29886
|
+
|
|
29887
|
+
function getLineLength(coords, distance) {
|
|
29888
|
+
var len = 0;
|
|
29889
|
+
for (var i=1, n=coords.length; i<n; i++) {
|
|
29890
|
+
len += distance(coords[i-1][0], coords[i-1][1], coords[i][0], coords[i][1]);
|
|
29891
|
+
}
|
|
29892
|
+
return len;
|
|
29893
|
+
}
|
|
29894
|
+
|
|
29895
|
+
function scaleDashes(dash, gap, len) {
|
|
29896
|
+
var dash2, gap2;
|
|
29897
|
+
var n = len / (dash + gap); // number of dashes
|
|
29898
|
+
var n1 = Math.floor(n);
|
|
29899
|
+
var n2 = Math.ceil(n);
|
|
29900
|
+
var k1 = len / (n1 * (dash + gap)); // scaled-up dashes, >1
|
|
29901
|
+
var k2 = len / (n2 * (dash + gap)); // scaled-down dashes <1
|
|
29902
|
+
var k = k2;
|
|
29903
|
+
if (k1 < 1/k2 && n1 > 0) {
|
|
29904
|
+
k = k1; // pick the smaller of the two scales
|
|
29905
|
+
}
|
|
29906
|
+
return k;
|
|
29907
|
+
}
|
|
29908
|
+
|
|
29732
29909
|
// This function creates a continuous mosaic of data values in a
|
|
29733
29910
|
// given field by assigning data from adjacent polygon features to polygons
|
|
29734
29911
|
// that contain null values.
|
|
@@ -31253,44 +31430,6 @@ ${svg}
|
|
|
31253
31430
|
}
|
|
31254
31431
|
};
|
|
31255
31432
|
|
|
31256
|
-
function expressionUsesGeoJSON(exp) {
|
|
31257
|
-
return exp.includes('this.geojson');
|
|
31258
|
-
}
|
|
31259
|
-
|
|
31260
|
-
function getFeatureEditor(lyr, dataset) {
|
|
31261
|
-
var changed = false;
|
|
31262
|
-
var api = {};
|
|
31263
|
-
// need to copy attribute to avoid circular references if geojson is assigned
|
|
31264
|
-
// to a data property.
|
|
31265
|
-
var copy = copyLayer(lyr);
|
|
31266
|
-
var features = exportLayerAsGeoJSON(copy, dataset, {}, true);
|
|
31267
|
-
|
|
31268
|
-
api.get = function(i) {
|
|
31269
|
-
return features[i];
|
|
31270
|
-
};
|
|
31271
|
-
|
|
31272
|
-
api.set = function(feat, i) {
|
|
31273
|
-
changed = true;
|
|
31274
|
-
if (utils.isString(feat)) {
|
|
31275
|
-
feat = JSON.parse(feat);
|
|
31276
|
-
}
|
|
31277
|
-
features[i] = GeoJSON.toFeature(feat); // TODO: validate
|
|
31278
|
-
};
|
|
31279
|
-
|
|
31280
|
-
api.done = function() {
|
|
31281
|
-
if (!changed) return; // read-only expression
|
|
31282
|
-
// TODO: validate number of features, etc.
|
|
31283
|
-
var geojson = {
|
|
31284
|
-
type: 'FeatureCollection',
|
|
31285
|
-
features: features
|
|
31286
|
-
};
|
|
31287
|
-
|
|
31288
|
-
// console.log(JSON.stringify(geojson, null, 2))
|
|
31289
|
-
return importGeoJSON(geojson);
|
|
31290
|
-
};
|
|
31291
|
-
return api;
|
|
31292
|
-
}
|
|
31293
|
-
|
|
31294
31433
|
cmd.evaluateEachFeature = function(lyr, dataset, exp, opts) {
|
|
31295
31434
|
var n = getFeatureCount(lyr),
|
|
31296
31435
|
arcs = dataset.arcs,
|
|
@@ -31896,7 +32035,6 @@ ${svg}
|
|
|
31896
32035
|
return joinTableToLayer(targetLyr, polygonLyr.data, joinFunction, opts);
|
|
31897
32036
|
}
|
|
31898
32037
|
|
|
31899
|
-
|
|
31900
32038
|
function prepJoinLayers(targetLyr, srcLyr) {
|
|
31901
32039
|
if (!targetLyr.data) {
|
|
31902
32040
|
// create an empty data table if target layer is missing attributes
|
|
@@ -34702,6 +34840,38 @@ ${svg}
|
|
|
34702
34840
|
}
|
|
34703
34841
|
}
|
|
34704
34842
|
|
|
34843
|
+
function pointsFromPolylinesForJoin(lyr, dataset) {
|
|
34844
|
+
var shapes = lyr.shapes.map(function(shp) {
|
|
34845
|
+
return polylineToMidpoints(shp, dataset.arcs);
|
|
34846
|
+
});
|
|
34847
|
+
return {
|
|
34848
|
+
geometry_type: 'point',
|
|
34849
|
+
shapes: shapes,
|
|
34850
|
+
data: lyr.data // TODO copy if needed
|
|
34851
|
+
};
|
|
34852
|
+
}
|
|
34853
|
+
|
|
34854
|
+
function validateOpts(opts) {
|
|
34855
|
+
if (!opts.point_method) {
|
|
34856
|
+
stop('The "point-method" flag is required for polyline-polygon joins');
|
|
34857
|
+
}
|
|
34858
|
+
}
|
|
34859
|
+
|
|
34860
|
+
function joinPolylinesToPolygons(targetLyr, targetDataset, source, opts) {
|
|
34861
|
+
validateOpts(opts);
|
|
34862
|
+
var pointLyr = pointsFromPolylinesForJoin(source.layer, source.dataset);
|
|
34863
|
+
var retn = joinPointsToPolygons(targetLyr, targetDataset.arcs, pointLyr, opts);
|
|
34864
|
+
return retn;
|
|
34865
|
+
}
|
|
34866
|
+
|
|
34867
|
+
function joinPolygonsToPolylines(targetLyr, targetDataset, source, opts) {
|
|
34868
|
+
validateOpts(opts);
|
|
34869
|
+
var pointLyr = pointsFromPolylinesForJoin(targetLyr, targetDataset);
|
|
34870
|
+
var retn = joinPolygonsToPoints(pointLyr, source.layer, source.dataset.arcs, opts);
|
|
34871
|
+
targetLyr.data = pointLyr.data;
|
|
34872
|
+
return retn;
|
|
34873
|
+
}
|
|
34874
|
+
|
|
34705
34875
|
class TinyQueue {
|
|
34706
34876
|
constructor(data = [], compare = defaultCompare) {
|
|
34707
34877
|
this.data = data;
|
|
@@ -35025,7 +35195,7 @@ ${svg}
|
|
|
35025
35195
|
|
|
35026
35196
|
cmd.join = function(targetLyr, targetDataset, src, opts) {
|
|
35027
35197
|
var srcType, targetType, retn;
|
|
35028
|
-
if (!src || !src.
|
|
35198
|
+
if (!src || !src.dataset) {
|
|
35029
35199
|
stop("Missing a joinable data source");
|
|
35030
35200
|
}
|
|
35031
35201
|
if (opts.keys) {
|
|
@@ -35033,9 +35203,18 @@ ${svg}
|
|
|
35033
35203
|
if (opts.keys.length != 2) {
|
|
35034
35204
|
stop("Expected two key fields: a target field and a source field");
|
|
35035
35205
|
}
|
|
35206
|
+
if (!src.layer.data) {
|
|
35207
|
+
stop("Source layer is missing attribute data");
|
|
35208
|
+
}
|
|
35036
35209
|
retn = joinAttributesToFeatures(targetLyr, src.layer.data, opts);
|
|
35037
35210
|
} else {
|
|
35038
35211
|
// spatial join
|
|
35212
|
+
if (!src.layer.data) {
|
|
35213
|
+
// KLUDGE -- users might want to join a layer without attributes
|
|
35214
|
+
// to test for intersection... the simplest way to support this is
|
|
35215
|
+
// to add an empty data table to the source layer
|
|
35216
|
+
initDataTable(src.layer);
|
|
35217
|
+
}
|
|
35039
35218
|
requireDatasetsHaveCompatibleCRS([targetDataset, src.dataset]);
|
|
35040
35219
|
srcType = src.layer.geometry_type;
|
|
35041
35220
|
targetType = targetLyr.geometry_type;
|
|
@@ -35047,6 +35226,10 @@ ${svg}
|
|
|
35047
35226
|
retn = joinPointsToPoints(targetLyr, src.layer, getDatasetCRS(targetDataset), opts);
|
|
35048
35227
|
} else if (srcType == 'polygon' && targetType == 'polygon') {
|
|
35049
35228
|
retn = joinPolygonsToPolygons(targetLyr, targetDataset, src, opts);
|
|
35229
|
+
} else if (srcType == 'polyline' && targetType == 'polygon') {
|
|
35230
|
+
retn = joinPolylinesToPolygons(targetLyr, targetDataset, src, opts);
|
|
35231
|
+
} else if (srcType == 'polygon' && targetType == 'polyline') {
|
|
35232
|
+
retn = joinPolygonsToPolylines(targetLyr, targetDataset, src, opts);
|
|
35050
35233
|
} else {
|
|
35051
35234
|
stop(utils.format("Unable to join %s geometry to %s geometry",
|
|
35052
35235
|
srcType || 'null', targetType || 'null'));
|
|
@@ -37310,111 +37493,6 @@ ${svg}
|
|
|
37310
37493
|
getSplitNameFunction: getSplitNameFunction
|
|
37311
37494
|
});
|
|
37312
37495
|
|
|
37313
|
-
cmd.splitLines = function(lyr, dataset, opts) {
|
|
37314
|
-
var crs = getDatasetCRS(dataset);
|
|
37315
|
-
requirePolylineLayer(lyr);
|
|
37316
|
-
var splitFeature = getSplitFeatureFunction(crs, opts);
|
|
37317
|
-
|
|
37318
|
-
// TODO: remove duplication with mapshaper-each.js
|
|
37319
|
-
var editor = getFeatureEditor(lyr, dataset);
|
|
37320
|
-
var exprOpts = {
|
|
37321
|
-
geojson_editor: editor,
|
|
37322
|
-
context: {splitFeature}
|
|
37323
|
-
};
|
|
37324
|
-
var exp = `this.geojson = splitFeature(this.geojson)`;
|
|
37325
|
-
|
|
37326
|
-
var compiled = compileFeatureExpression(exp, lyr, dataset.arcs, exprOpts);
|
|
37327
|
-
var n = getFeatureCount(lyr);
|
|
37328
|
-
var filter;
|
|
37329
|
-
if (opts && opts.where) {
|
|
37330
|
-
filter = compileValueExpression(opts.where, lyr, dataset.arcs);
|
|
37331
|
-
}
|
|
37332
|
-
for (var i=0; i<n; i++) {
|
|
37333
|
-
if (!filter || filter(i)) {
|
|
37334
|
-
compiled(i);
|
|
37335
|
-
}
|
|
37336
|
-
}
|
|
37337
|
-
replaceLayerContents(lyr, dataset, editor.done());
|
|
37338
|
-
};
|
|
37339
|
-
|
|
37340
|
-
function getSplitFeatureFunction(crs, opts) {
|
|
37341
|
-
var dashLen = opts.dash_length ? convertDistanceParam(opts.dash_length, crs) : 0;
|
|
37342
|
-
var gapLen = opts.gap_length ? convertDistanceParam(opts.gap_length, crs) : 0;
|
|
37343
|
-
if (dashLen > 0 === false) {
|
|
37344
|
-
stop('Missing required segment-length parameter');
|
|
37345
|
-
}
|
|
37346
|
-
if (gapLen >= 0 == false) {
|
|
37347
|
-
stop('Invalid gap-length option');
|
|
37348
|
-
}
|
|
37349
|
-
var splitLine = getSplitLineFunction(crs, dashLen, gapLen, !!opts.planar);
|
|
37350
|
-
return function(feat) {
|
|
37351
|
-
var geom = feat.geometry;
|
|
37352
|
-
if (!geom) return feat;
|
|
37353
|
-
if (geom.type == 'LineString') {
|
|
37354
|
-
geom.type = 'MultiLineString';
|
|
37355
|
-
geom.coordinates = [geom.coordinates];
|
|
37356
|
-
}
|
|
37357
|
-
if (geom.type != 'MultiLineString') {
|
|
37358
|
-
error('Unexpected geometry:', geom.type);
|
|
37359
|
-
}
|
|
37360
|
-
geom.coordinates = geom.coordinates.reduce(function(memo, coords) {
|
|
37361
|
-
try {
|
|
37362
|
-
var parts = splitLine(coords);
|
|
37363
|
-
memo = memo.concat(parts);
|
|
37364
|
-
} catch(e) {
|
|
37365
|
-
console.error(e);
|
|
37366
|
-
throw e;
|
|
37367
|
-
}
|
|
37368
|
-
return memo;
|
|
37369
|
-
}, []);
|
|
37370
|
-
|
|
37371
|
-
return feat;
|
|
37372
|
-
};
|
|
37373
|
-
}
|
|
37374
|
-
|
|
37375
|
-
function getSplitLineFunction(crs, dashLen, gapLen, planar) {
|
|
37376
|
-
var interpolate = getInterpolationFunction(planar ? null : crs);
|
|
37377
|
-
var distance = isLatLngCRS(crs) ? greatCircleDistance : distance2D;
|
|
37378
|
-
var inDash, parts2, interval;
|
|
37379
|
-
function addPart(coords) {
|
|
37380
|
-
if (inDash) parts2.push(coords);
|
|
37381
|
-
if (gapLen > 0) {
|
|
37382
|
-
inDash = !inDash;
|
|
37383
|
-
interval = inDash ? dashLen : gapLen;
|
|
37384
|
-
}
|
|
37385
|
-
}
|
|
37386
|
-
return function splitLineString(coords) {
|
|
37387
|
-
var elapsedDist = 0;
|
|
37388
|
-
var p = coords[0];
|
|
37389
|
-
var coords2 = [p];
|
|
37390
|
-
var segLen, k, prev;
|
|
37391
|
-
// init this LineString
|
|
37392
|
-
inDash = true;
|
|
37393
|
-
parts2 = [];
|
|
37394
|
-
interval = gapLen;
|
|
37395
|
-
for (var i=1, n=coords.length; i<n; i++) {
|
|
37396
|
-
prev = p;
|
|
37397
|
-
p = coords[i];
|
|
37398
|
-
segLen = distance(prev[0], prev[1], p[0], p[1]);
|
|
37399
|
-
while (elapsedDist + segLen >= interval) {
|
|
37400
|
-
k = (interval - elapsedDist) / segLen;
|
|
37401
|
-
prev = interpolate(prev[0], prev[1], p[0], p[1], k);
|
|
37402
|
-
elapsedDist = 0;
|
|
37403
|
-
coords2.push(prev);
|
|
37404
|
-
addPart(coords2);
|
|
37405
|
-
coords2 = [prev];
|
|
37406
|
-
segLen = distance(prev[0], prev[1], p[0], p[1]);
|
|
37407
|
-
}
|
|
37408
|
-
coords2.push(p);
|
|
37409
|
-
elapsedDist += segLen;
|
|
37410
|
-
}
|
|
37411
|
-
if (elapsedDist > 0 && coords2.length > 1) {
|
|
37412
|
-
addPart(coords2);
|
|
37413
|
-
}
|
|
37414
|
-
return parts2;
|
|
37415
|
-
};
|
|
37416
|
-
}
|
|
37417
|
-
|
|
37418
37496
|
cmd.svgStyle = function(lyr, dataset, opts) {
|
|
37419
37497
|
var filter;
|
|
37420
37498
|
if (!lyr.data) {
|
|
@@ -38176,6 +38254,9 @@ ${svg}
|
|
|
38176
38254
|
} else if (name == 'colorizer') {
|
|
38177
38255
|
outputLayers = cmd.colorizer(opts);
|
|
38178
38256
|
|
|
38257
|
+
} else if (name == 'dashlines') {
|
|
38258
|
+
applyCommandToEachLayer(cmd.dashlines, targetLayers, targetDataset, opts);
|
|
38259
|
+
|
|
38179
38260
|
} else if (name == 'define') {
|
|
38180
38261
|
cmd.define(opts);
|
|
38181
38262
|
|
|
@@ -38376,9 +38457,6 @@ ${svg}
|
|
|
38376
38457
|
} else if (name == 'split') {
|
|
38377
38458
|
outputLayers = applyCommandToEachLayer(cmd.splitLayer, targetLayers, opts.expression, opts);
|
|
38378
38459
|
|
|
38379
|
-
} else if (name == 'split-lines') {
|
|
38380
|
-
applyCommandToEachLayer(cmd.splitLines, targetLayers, targetDataset, opts);
|
|
38381
|
-
|
|
38382
38460
|
} else if (name == 'split-on-grid') {
|
|
38383
38461
|
outputLayers = applyCommandToEachLayer(cmd.splitLayerOnGrid, targetLayers, arcs, opts);
|
|
38384
38462
|
|