mapshaper 0.7.0 → 0.7.1
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/README.md +5 -3
- package/mapshaper.js +363 -62
- package/package.json +5 -2
- package/www/assets/jetbrains-mono-regular.woff2 +0 -0
- package/www/assets/static-page.css +179 -0
- package/www/docs/_assets/cmd-search.js +213 -0
- package/www/docs/_assets/docs.css +712 -0
- package/www/docs/_assets/docs.js +75 -0
- package/www/docs/_assets/highlight.css +10 -0
- package/www/docs/essentials/command-line.html +127 -0
- package/www/docs/essentials/command-line.html.md +112 -0
- package/www/docs/essentials/web-app.html +138 -0
- package/www/docs/essentials/web-app.html.md +106 -0
- package/www/docs/examples/basics.html +276 -0
- package/www/docs/examples/basics.html.md +371 -0
- package/www/docs/examples/data/Makefile +31 -0
- package/www/docs/examples/data/globe.msx +0 -0
- package/www/docs/examples/data/globe.svg +616 -0
- package/www/docs/examples/data/globe.txt +21 -0
- package/www/docs/examples/data/globe.zip +0 -0
- package/www/docs/examples/data/ne_50m_admin_0_countries.geojson +1 -0
- package/www/docs/examples/data/ne_50m_admin_1_states_provinces_lakes.geojson +1 -0
- package/www/docs/examples/data/us-states.msx +0 -0
- package/www/docs/examples/data/us-states.svg +56 -0
- package/www/docs/examples/data/us-states.txt +6 -0
- package/www/docs/examples/data/us-states.zip +0 -0
- package/www/docs/examples/globe.html +108 -0
- package/www/docs/examples/globe.html.md +64 -0
- package/www/docs/examples/us-states.html +88 -0
- package/www/docs/examples/us-states.html.md +44 -0
- package/www/docs/formats/csv.html +127 -0
- package/www/docs/formats/csv.html.md +97 -0
- package/www/docs/formats/dbf.html +87 -0
- package/www/docs/formats/dbf.html.md +39 -0
- package/www/docs/formats/flatgeobuf.html +86 -0
- package/www/docs/formats/flatgeobuf.html.md +42 -0
- package/www/docs/formats/geojson.html +107 -0
- package/www/docs/formats/geojson.html.md +65 -0
- package/www/docs/formats/geopackage.html +87 -0
- package/www/docs/formats/geopackage.html.md +42 -0
- package/www/docs/formats/json.html +83 -0
- package/www/docs/formats/json.html.md +35 -0
- package/www/docs/formats/kml.html +82 -0
- package/www/docs/formats/kml.html.md +39 -0
- package/www/docs/formats/overview.html +192 -0
- package/www/docs/formats/overview.html.md +35 -0
- package/www/docs/formats/shapefile.html +123 -0
- package/www/docs/formats/shapefile.html.md +84 -0
- package/www/docs/formats/snapshot.html +87 -0
- package/www/docs/formats/snapshot.html.md +39 -0
- package/www/docs/formats/svg.html +99 -0
- package/www/docs/formats/svg.html.md +51 -0
- package/www/docs/formats/topojson.html +102 -0
- package/www/docs/formats/topojson.html.md +54 -0
- package/www/docs/gallery/index.html +80 -0
- package/www/docs/gallery/index.html.md +29 -0
- package/www/docs/guides/combining-layers.html +105 -0
- package/www/docs/guides/combining-layers.html.md +81 -0
- package/www/docs/guides/expressions.html +600 -0
- package/www/docs/guides/expressions.html.md +376 -0
- package/www/docs/guides/programmatic.html +117 -0
- package/www/docs/guides/programmatic.html.md +91 -0
- package/www/docs/guides/projections.html +158 -0
- package/www/docs/guides/projections.html.md +118 -0
- package/www/docs/guides/simplification.html +110 -0
- package/www/docs/guides/simplification.html.md +94 -0
- package/www/docs/guides/topology.html +90 -0
- package/www/docs/guides/topology.html.md +63 -0
- package/www/docs/images/simplification-detail.png +0 -0
- package/www/docs/images/simplification-dp.png +0 -0
- package/www/docs/images/simplification-mod2.png +0 -0
- package/www/docs/index.html +101 -0
- package/www/docs/index.html.md +59 -0
- package/www/docs/reference.html +1302 -0
- package/www/docs/reference.html.md +1817 -0
- package/www/docs/whats-new.html +76 -0
- package/www/docs/whats-new.html.md +53 -0
- package/www/index.html +30 -3
- package/www/llms-full.txt +4040 -0
- package/www/llms.txt +55 -0
- package/www/mapshaper-gui.js +7 -1
- package/www/mapshaper.js +363 -62
- package/www/page.css +1 -1
- package/www/privacy.html +1 -112
- package/www/sponsor.html +4 -164
- package/www/terms.html +1 -112
package/mapshaper.js
CHANGED
|
@@ -1269,6 +1269,10 @@
|
|
|
1269
1269
|
LOGGING = true;
|
|
1270
1270
|
}
|
|
1271
1271
|
|
|
1272
|
+
function disableLogging() {
|
|
1273
|
+
LOGGING = false;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1272
1276
|
function loggingEnabled() {
|
|
1273
1277
|
return !!LOGGING;
|
|
1274
1278
|
}
|
|
@@ -1475,6 +1479,7 @@
|
|
|
1475
1479
|
NonFatalError: NonFatalError,
|
|
1476
1480
|
UserError: UserError,
|
|
1477
1481
|
debug: debug,
|
|
1482
|
+
disableLogging: disableLogging,
|
|
1478
1483
|
enableLogging: enableLogging,
|
|
1479
1484
|
error: error,
|
|
1480
1485
|
formatColumns: formatColumns,
|
|
@@ -5499,6 +5504,58 @@
|
|
|
5499
5504
|
return parseCrsString$1(wkt1ToProj(str));
|
|
5500
5505
|
}
|
|
5501
5506
|
|
|
5507
|
+
// Extract an EPSG (or other authority) code from a short string like
|
|
5508
|
+
// "epsg:4326" or "ESRI:54030". Returns {org, code} or null.
|
|
5509
|
+
function parseAuthorityCodeString(str) {
|
|
5510
|
+
if (!str || typeof str != 'string') return null;
|
|
5511
|
+
var match = str.match(/^([a-z]+):(\d+)$/i);
|
|
5512
|
+
if (!match) return null;
|
|
5513
|
+
var code = +match[2];
|
|
5514
|
+
if (!code) return null;
|
|
5515
|
+
return {
|
|
5516
|
+
org: match[1].toUpperCase(),
|
|
5517
|
+
code: code
|
|
5518
|
+
};
|
|
5519
|
+
}
|
|
5520
|
+
|
|
5521
|
+
// Extract the top-level AUTHORITY clause from a WKT1 .prj string.
|
|
5522
|
+
// Returns {org, code} or null. Skips nested AUTHORITY clauses on inner
|
|
5523
|
+
// elements like the datum or unit, which would otherwise produce the
|
|
5524
|
+
// wrong code for projected CRSes.
|
|
5525
|
+
function parseAuthorityCodeFromWkt(wkt) {
|
|
5526
|
+
if (!wkt || typeof wkt != 'string') return null;
|
|
5527
|
+
var depth = 0;
|
|
5528
|
+
var inQuote = false;
|
|
5529
|
+
for (var i = 0; i < wkt.length; i++) {
|
|
5530
|
+
var c = wkt.charAt(i);
|
|
5531
|
+
if (c == '"') {
|
|
5532
|
+
inQuote = !inQuote;
|
|
5533
|
+
continue;
|
|
5534
|
+
}
|
|
5535
|
+
if (inQuote) continue;
|
|
5536
|
+
if (c == '[' || c == '(') {
|
|
5537
|
+
depth++;
|
|
5538
|
+
continue;
|
|
5539
|
+
}
|
|
5540
|
+
if (c == ']' || c == ')') {
|
|
5541
|
+
depth--;
|
|
5542
|
+
continue;
|
|
5543
|
+
}
|
|
5544
|
+
if (depth != 1) continue;
|
|
5545
|
+
var slice = wkt.slice(i, i + 10).toUpperCase();
|
|
5546
|
+
if (slice == 'AUTHORITY[' || slice.slice(0, 10) == 'AUTHORITY(') {
|
|
5547
|
+
var match = wkt.slice(i).match(/^AUTHORITY\s*[[(]\s*"([^"]+)"\s*,\s*"?(\d+)"?\s*[\])]/i);
|
|
5548
|
+
if (match) {
|
|
5549
|
+
return {
|
|
5550
|
+
org: String(match[1]).toUpperCase(),
|
|
5551
|
+
code: +match[2]
|
|
5552
|
+
};
|
|
5553
|
+
}
|
|
5554
|
+
}
|
|
5555
|
+
}
|
|
5556
|
+
return null;
|
|
5557
|
+
}
|
|
5558
|
+
|
|
5502
5559
|
var Projections = /*#__PURE__*/Object.freeze({
|
|
5503
5560
|
__proto__: null,
|
|
5504
5561
|
crsAreEqual: crsAreEqual,
|
|
@@ -5522,6 +5579,8 @@
|
|
|
5522
5579
|
isWGS84: isWGS84,
|
|
5523
5580
|
isWebMercator: isWebMercator,
|
|
5524
5581
|
looksLikeProj4String: looksLikeProj4String,
|
|
5582
|
+
parseAuthorityCodeFromWkt: parseAuthorityCodeFromWkt,
|
|
5583
|
+
parseAuthorityCodeString: parseAuthorityCodeString,
|
|
5525
5584
|
parseCrsString: parseCrsString$1,
|
|
5526
5585
|
parsePrj: parsePrj,
|
|
5527
5586
|
printProjections: printProjections,
|
|
@@ -11928,6 +11987,39 @@
|
|
|
11928
11987
|
return /^\s*[{[]/.test(String(str));
|
|
11929
11988
|
}
|
|
11930
11989
|
|
|
11990
|
+
// Heuristic: detect inline comma-delimited data passed as an -i argument.
|
|
11991
|
+
// Required signals (intentionally strict to avoid false positives on filenames):
|
|
11992
|
+
// 1. Contains a real newline OR the literal escape sequence "\n"
|
|
11993
|
+
// 2. The first and second non-empty lines each contain at least one comma
|
|
11994
|
+
// Multi-character delimiters (tab, semicolon, pipe) are not detected here;
|
|
11995
|
+
// only comma-delimited input is supported as inline data for now.
|
|
11996
|
+
function stringLooksLikeCsv(str) {
|
|
11997
|
+
if (typeof str !== 'string' || str.length === 0) return false;
|
|
11998
|
+
if (!stringHasInlineCsvNewline(str)) return false;
|
|
11999
|
+
var normalized = unescapeInlineCsv(str);
|
|
12000
|
+
var lines = normalized.split(/\r?\n/).filter(function(line) {
|
|
12001
|
+
return line.length > 0;
|
|
12002
|
+
});
|
|
12003
|
+
if (lines.length < 2) return false;
|
|
12004
|
+
return lines[0].indexOf(',') > -1 && lines[1].indexOf(',') > -1;
|
|
12005
|
+
}
|
|
12006
|
+
|
|
12007
|
+
// True if @str contains either a real newline or the literal two-character
|
|
12008
|
+
// escape sequence "\n" (backslash + n) anywhere in the string.
|
|
12009
|
+
function stringHasInlineCsvNewline(str) {
|
|
12010
|
+
return str.indexOf('\n') > -1 || /\\n/.test(str);
|
|
12011
|
+
}
|
|
12012
|
+
|
|
12013
|
+
// Convert literal "\n" / "\r\n" escape sequences in an inline CSV string
|
|
12014
|
+
// into real newline characters. If the input already contains a real newline,
|
|
12015
|
+
// it is returned unchanged so that backslash-n sequences inside quoted cells
|
|
12016
|
+
// are preserved verbatim.
|
|
12017
|
+
function unescapeInlineCsv(str) {
|
|
12018
|
+
if (typeof str !== 'string') return str;
|
|
12019
|
+
if (str.indexOf('\n') > -1) return str;
|
|
12020
|
+
return str.replace(/\\r\\n/g, '\n').replace(/\\n/g, '\n');
|
|
12021
|
+
}
|
|
12022
|
+
|
|
11931
12023
|
function stringLooksLikeKML(str) {
|
|
11932
12024
|
str = String(str);
|
|
11933
12025
|
return str.includes('<kml ') && str.includes('xmlns="http://www.opengis.net/kml/');
|
|
@@ -12032,10 +12124,13 @@
|
|
|
12032
12124
|
isZipFile: isZipFile,
|
|
12033
12125
|
looksLikeContentFile: looksLikeContentFile,
|
|
12034
12126
|
looksLikeImportableFile: looksLikeImportableFile,
|
|
12127
|
+
stringHasInlineCsvNewline: stringHasInlineCsvNewline,
|
|
12035
12128
|
stringLooksLikeCommandFile: stringLooksLikeCommandFile,
|
|
12129
|
+
stringLooksLikeCsv: stringLooksLikeCsv,
|
|
12036
12130
|
stringLooksLikeJSON: stringLooksLikeJSON,
|
|
12037
12131
|
stringLooksLikeKML: stringLooksLikeKML,
|
|
12038
|
-
stringLooksLikeSVG: stringLooksLikeSVG
|
|
12132
|
+
stringLooksLikeSVG: stringLooksLikeSVG,
|
|
12133
|
+
unescapeInlineCsv: unescapeInlineCsv
|
|
12039
12134
|
});
|
|
12040
12135
|
|
|
12041
12136
|
// input: A file path or a buffer
|
|
@@ -12283,10 +12378,14 @@
|
|
|
12283
12378
|
return files;
|
|
12284
12379
|
};
|
|
12285
12380
|
|
|
12286
|
-
// Expand any wildcards.
|
|
12381
|
+
// Expand any wildcards. Inline data strings (JSON / CSV passed directly on
|
|
12382
|
+
// the command line) are passed through unchanged so that "*" appearing
|
|
12383
|
+
// inside the data isn't interpreted as a glob.
|
|
12287
12384
|
cli.expandInputFiles = function(files) {
|
|
12288
12385
|
return files.reduce(function(memo, name) {
|
|
12289
|
-
if (name
|
|
12386
|
+
if (stringLooksLikeJSON(name) || stringLooksLikeCsv(name)) {
|
|
12387
|
+
memo.push(name);
|
|
12388
|
+
} else if (name.indexOf('*') > -1) {
|
|
12290
12389
|
memo = memo.concat(cli.expandFileName(name));
|
|
12291
12390
|
} else {
|
|
12292
12391
|
memo.push(name);
|
|
@@ -15623,7 +15722,7 @@
|
|
|
15623
15722
|
if (utils.isObject(obj)) {
|
|
15624
15723
|
_records[_id] = obj;
|
|
15625
15724
|
} else {
|
|
15626
|
-
stop$1("Can't assign non-object to
|
|
15725
|
+
stop$1("Can't assign non-object to this.properties");
|
|
15627
15726
|
}
|
|
15628
15727
|
}, get: function() {
|
|
15629
15728
|
var rec = _records[_id];
|
|
@@ -15756,7 +15855,7 @@
|
|
|
15756
15855
|
if (!obj || utils.isArray(obj)) {
|
|
15757
15856
|
lyr.shapes[_id] = obj || null;
|
|
15758
15857
|
} else {
|
|
15759
|
-
stop$1("Can't assign non-array to
|
|
15858
|
+
stop$1("Can't assign non-array to this.coordinates");
|
|
15760
15859
|
}
|
|
15761
15860
|
}, get: function() {
|
|
15762
15861
|
return lyr.shapes[_id] || null;
|
|
@@ -16177,7 +16276,7 @@
|
|
|
16177
16276
|
// Calculate an expression across a group of features, print and return the result
|
|
16178
16277
|
// Supported functions include sum(), average(), max(), min(), median(), count()
|
|
16179
16278
|
// Functions receive an expression to be applied to each feature (like the -each command)
|
|
16180
|
-
// Examples: 'sum(
|
|
16279
|
+
// Examples: 'sum(this.area)' 'min(income)'
|
|
16181
16280
|
// opts.expression Expression to evaluate
|
|
16182
16281
|
// opts.where Optional filter expression (see -filter command)
|
|
16183
16282
|
//
|
|
@@ -17219,6 +17318,29 @@
|
|
|
17219
17318
|
profileStart('snapAndCut');
|
|
17220
17319
|
var arcs = dataset.arcs;
|
|
17221
17320
|
var cutOpts = snapDist > 0 ? {tolerance: snapDist} : {tolerance: 0};
|
|
17321
|
+
|
|
17322
|
+
// Probe for intersections before any modification. If the input has none,
|
|
17323
|
+
// every pass of the loop below is provably a no-op: snap() can only
|
|
17324
|
+
// consolidate FP noise around intersection points, cutPathsAtIntersections
|
|
17325
|
+
// inserts cut points only where segments cross, and buildTopology is gated
|
|
17326
|
+
// on coordsHaveChanged. So we can return early and skip a ~O(V log V) snap
|
|
17327
|
+
// that would snap zero points, which is the dominant cost of robust
|
|
17328
|
+
// dissolve on already-clean polygon input.
|
|
17329
|
+
//
|
|
17330
|
+
// limit=1 makes the probe cheap on dirty input too — it stops the stripe
|
|
17331
|
+
// scan after the first hit, so the probe's full cost is only paid on
|
|
17332
|
+
// truly clean input (where it replaces the equivalent scan that
|
|
17333
|
+
// cutPathsAtIntersections would otherwise do on pass 1).
|
|
17334
|
+
var probeOpts = {tolerance: cutOpts.tolerance, limit: 1};
|
|
17335
|
+
profileStart('probeIntersections');
|
|
17336
|
+
var probe = findSegmentIntersections(arcs, probeOpts);
|
|
17337
|
+
profileEnd('probeIntersections');
|
|
17338
|
+
if (probe.length === 0) {
|
|
17339
|
+
debug('[snapAndCut] skipped (no intersections)');
|
|
17340
|
+
profileEnd('snapAndCut');
|
|
17341
|
+
return false;
|
|
17342
|
+
}
|
|
17343
|
+
|
|
17222
17344
|
var coordsHaveChanged = false;
|
|
17223
17345
|
var snapCount = 0, dupeCount, cutCount;
|
|
17224
17346
|
var maxLoops = 4, loopCount = 0;
|
|
@@ -21408,18 +21530,18 @@ ${svg}
|
|
|
21408
21530
|
validateSvgDataFields: validateSvgDataFields
|
|
21409
21531
|
});
|
|
21410
21532
|
|
|
21411
|
-
// import { isKmzFile } from '../io/mapshaper-file-types';
|
|
21412
|
-
|
|
21413
21533
|
function exportKML(dataset, opts) {
|
|
21414
21534
|
var toKML = require$1("@placemarkio/tokml").toKML;
|
|
21415
21535
|
var geojsonOpts = Object.assign({combine_layers: true, geojson_type: 'FeatureCollection'}, opts);
|
|
21416
21536
|
var geojson = exportDatasetAsGeoJSON(dataset, geojsonOpts);
|
|
21417
21537
|
var kml = toKML(geojson);
|
|
21418
|
-
|
|
21419
|
-
|
|
21420
|
-
var
|
|
21538
|
+
var useKmz = !!(opts.file && isKmzFile(opts.file));
|
|
21539
|
+
var ofile = opts.file || getOutputFileBase(dataset) + (useKmz ? '.kmz' : '.kml');
|
|
21540
|
+
var content = useKmz
|
|
21541
|
+
? zipSync([{ filename: 'doc.kml', content: kml }])
|
|
21542
|
+
: kml;
|
|
21421
21543
|
return [{
|
|
21422
|
-
content:
|
|
21544
|
+
content: content,
|
|
21423
21545
|
filename: ofile
|
|
21424
21546
|
}];
|
|
21425
21547
|
}
|
|
@@ -24977,13 +25099,19 @@ ${svg}
|
|
|
24977
25099
|
// Keep behavior consistent with other exporters that honor explicit output filename.
|
|
24978
25100
|
extension = getFileExtension(opts.file) || extension;
|
|
24979
25101
|
}
|
|
25102
|
+
var crsMeta = resolveOutputCRS(dataset);
|
|
24980
25103
|
return dataset.layers.map(function(lyr) {
|
|
24981
25104
|
var geojson = getFeatureCollection(lyr, dataset, opts);
|
|
24982
25105
|
var content = serialize(geojson);
|
|
24983
|
-
|
|
25106
|
+
var filename = lyr.name + '.' + extension;
|
|
25107
|
+
if (crsMeta) {
|
|
25108
|
+
content = rewriteHeaderWithCRS(content, crsMeta);
|
|
25109
|
+
} else {
|
|
25110
|
+
message('Wrote', filename, 'without a CRS in the FlatGeobuf header (mapshaper could not derive an EPSG code for this dataset). Downstream tools may misinterpret the coordinates or refuse to load the file.');
|
|
25111
|
+
}
|
|
24984
25112
|
return {
|
|
24985
25113
|
content: content,
|
|
24986
|
-
filename:
|
|
25114
|
+
filename: filename
|
|
24987
25115
|
};
|
|
24988
25116
|
});
|
|
24989
25117
|
}
|
|
@@ -24999,10 +25127,55 @@ ${svg}
|
|
|
24999
25127
|
};
|
|
25000
25128
|
}
|
|
25001
25129
|
|
|
25002
|
-
|
|
25003
|
-
|
|
25004
|
-
|
|
25005
|
-
|
|
25130
|
+
// Try several strategies to derive an EPSG code for the dataset. Returns
|
|
25131
|
+
// a CRS-meta object suitable for buildHeaderWithCRS(), or null if no
|
|
25132
|
+
// EPSG code could be found. We don't write WKT-only CRSes -- mapshaper
|
|
25133
|
+
// can't reliably round-trip them and many readers ignore the WKT field.
|
|
25134
|
+
function resolveOutputCRS(dataset) {
|
|
25135
|
+
var info = (dataset && dataset.info) || {};
|
|
25136
|
+
var meta;
|
|
25137
|
+
|
|
25138
|
+
// 1. Round-tripped from another FlatGeobuf
|
|
25139
|
+
meta = normalizeCRS(info.flatgeobuf_crs);
|
|
25140
|
+
if (meta) return meta;
|
|
25141
|
+
|
|
25142
|
+
// 2. Round-tripped from a GeoPackage with an EPSG-coded SRS
|
|
25143
|
+
if (info.geopackage_crs &&
|
|
25144
|
+
String(info.geopackage_crs.organization || '').toUpperCase() === 'EPSG') {
|
|
25145
|
+
meta = normalizeCRS({
|
|
25146
|
+
org: 'EPSG',
|
|
25147
|
+
code: info.geopackage_crs.organization_coordsys_id || info.geopackage_crs.srs_id
|
|
25148
|
+
});
|
|
25149
|
+
if (meta) return meta;
|
|
25150
|
+
}
|
|
25151
|
+
|
|
25152
|
+
// 3. Explicit "epsg:NNNN" / "esri:NNNN" string set by -proj or alike
|
|
25153
|
+
meta = normalizeCRS(parseAuthorityCodeString(info.crs_string));
|
|
25154
|
+
if (meta) return meta;
|
|
25155
|
+
|
|
25156
|
+
// 4. AUTHORITY["EPSG", N] in a .prj/WKT1 string (typically from a Shapefile)
|
|
25157
|
+
meta = normalizeCRS(parseAuthorityCodeFromWkt(info.wkt1));
|
|
25158
|
+
if (meta) return meta;
|
|
25159
|
+
|
|
25160
|
+
// 5. Recognized CRS object: WGS-84 (any encoding) or Web Mercator.
|
|
25161
|
+
// getDatasetCrsInfo() also auto-detects WGS-84 from lat/lng-like bounds,
|
|
25162
|
+
// which is how GeoJSON sources end up with a usable CRS object.
|
|
25163
|
+
var crsInfo;
|
|
25164
|
+
try {
|
|
25165
|
+
crsInfo = getDatasetCrsInfo(dataset);
|
|
25166
|
+
} catch (e) {
|
|
25167
|
+
crsInfo = null;
|
|
25168
|
+
}
|
|
25169
|
+
if (crsInfo && crsInfo.crs) {
|
|
25170
|
+
if (isWGS84(crsInfo.crs)) {
|
|
25171
|
+
return normalizeCRS({org: 'EPSG', code: 4326});
|
|
25172
|
+
}
|
|
25173
|
+
if (isWebMercator(crsInfo.crs)) {
|
|
25174
|
+
return normalizeCRS({org: 'EPSG', code: 3857});
|
|
25175
|
+
}
|
|
25176
|
+
}
|
|
25177
|
+
|
|
25178
|
+
return null;
|
|
25006
25179
|
}
|
|
25007
25180
|
|
|
25008
25181
|
function normalizeCRS(crs) {
|
|
@@ -25645,10 +25818,22 @@ ${svg}
|
|
|
25645
25818
|
var bounds = getLayerBounds(target.layer, target.dataset.arcs);
|
|
25646
25819
|
opts = Object.assign({svg_bbox: bounds.toArray()}, opts);
|
|
25647
25820
|
}
|
|
25648
|
-
|
|
25649
|
-
var datasets
|
|
25650
|
-
|
|
25651
|
-
|
|
25821
|
+
var format = getOutputFormat(targets[0].dataset, opts);
|
|
25822
|
+
var datasets;
|
|
25823
|
+
if (format == PACKAGE_EXT && !runningInBrowser()) {
|
|
25824
|
+
// CLI .msx export captures the whole session: every dataset/layer in the
|
|
25825
|
+
// catalog ships in the snapshot, not just the -target subset. Targeted
|
|
25826
|
+
// layers come back visible (pinned) and stacked in the order matched by
|
|
25827
|
+
// -target; untargeted layers come along for the ride, hidden and parked
|
|
25828
|
+
// at the bottom of the GUI stack. This matches GUI snapshot semantics
|
|
25829
|
+
// and lets `mapshaper a.shp b.shp -target a -o foo.msx` produce a
|
|
25830
|
+
// shareable bundle without losing b.shp.
|
|
25831
|
+
datasets = prepareCatalogForCliPackExport(catalog, targets);
|
|
25832
|
+
} else {
|
|
25833
|
+
datasets = targets.map(function(target) {
|
|
25834
|
+
return utils.defaults({layers: target.layers}, target.dataset);
|
|
25835
|
+
});
|
|
25836
|
+
}
|
|
25652
25837
|
return exportDatasets(datasets, opts);
|
|
25653
25838
|
}
|
|
25654
25839
|
|
|
@@ -25895,6 +26080,62 @@ ${svg}
|
|
|
25895
26080
|
}
|
|
25896
26081
|
}
|
|
25897
26082
|
|
|
26083
|
+
// Prepare the full catalog for a CLI `-o foo.msx` export. Returns a
|
|
26084
|
+
// shallow-copied datasets/layers tree (the live model is left alone) where:
|
|
26085
|
+
//
|
|
26086
|
+
// - every dataset and every layer in the catalog is present, not just the
|
|
26087
|
+
// -target subset, so .msx round-trips the entire working session;
|
|
26088
|
+
// - layers matched by -target are marked `pinned: true` so the GUI shows
|
|
26089
|
+
// them on load (no `&display-all` URL flag needed);
|
|
26090
|
+
// - those targeted layers get menu_order values that follow the linear
|
|
26091
|
+
// -target list (first targeted = bottom of the stack, last = top),
|
|
26092
|
+
// matching the SVG draw order from the same -target line;
|
|
26093
|
+
// - untargeted layers get menu_order values below every targeted layer,
|
|
26094
|
+
// so they sit at the bottom of the GUI panel out of the way (they're
|
|
26095
|
+
// hidden, but if the user pins one later it doesn't pop above the
|
|
26096
|
+
// intended stack).
|
|
26097
|
+
//
|
|
26098
|
+
// Intra-dataset array order is preserved so re-importing with `mapshaper
|
|
26099
|
+
// foo.msx -target * -o bar.svg` still iterates layers in the order they
|
|
26100
|
+
// appeared during the original run.
|
|
26101
|
+
function prepareCatalogForCliPackExport(catalog, targets) {
|
|
26102
|
+
var targeted = new Set();
|
|
26103
|
+
targets.forEach(function(t) {
|
|
26104
|
+
t.layers.forEach(function(lyr) { targeted.add(lyr); });
|
|
26105
|
+
});
|
|
26106
|
+
var datasets = catalog.getDatasets();
|
|
26107
|
+
// Count untargeted layers globally so we can offset targeted layers'
|
|
26108
|
+
// menu_order to sit above them in a single consecutive range.
|
|
26109
|
+
var untargetedCount = 0;
|
|
26110
|
+
datasets.forEach(function(d) {
|
|
26111
|
+
d.layers.forEach(function(lyr) {
|
|
26112
|
+
if (!targeted.has(lyr)) untargetedCount++;
|
|
26113
|
+
});
|
|
26114
|
+
});
|
|
26115
|
+
var untargetedSeq = 0;
|
|
26116
|
+
return datasets.map(function(dataset) {
|
|
26117
|
+
var layers = dataset.layers.map(function(lyr) {
|
|
26118
|
+
var isTargeted = targeted.has(lyr);
|
|
26119
|
+
var menuOrder;
|
|
26120
|
+
if (isTargeted && lyr.target_id != null && lyr.target_id >= 0) {
|
|
26121
|
+
// target_id is 0-based across the whole -target list; offset past
|
|
26122
|
+
// the untargeted block so targeted layers occupy [untargetedCount+1
|
|
26123
|
+
// .. untargetedCount+N].
|
|
26124
|
+
menuOrder = untargetedCount + lyr.target_id + 1;
|
|
26125
|
+
} else {
|
|
26126
|
+
// Untargeted (or untargeted-shaped target_id): pack into the bottom
|
|
26127
|
+
// of the global stack, in catalog walk order.
|
|
26128
|
+
menuOrder = ++untargetedSeq;
|
|
26129
|
+
}
|
|
26130
|
+
return utils.defaults({
|
|
26131
|
+
pinned: isTargeted,
|
|
26132
|
+
menu_order: menuOrder
|
|
26133
|
+
}, lyr);
|
|
26134
|
+
});
|
|
26135
|
+
return utils.defaults({layers: layers}, dataset);
|
|
26136
|
+
});
|
|
26137
|
+
}
|
|
26138
|
+
|
|
25898
26139
|
var Export = /*#__PURE__*/Object.freeze({
|
|
25899
26140
|
__proto__: null,
|
|
25900
26141
|
assignUniqueFileNames: assignUniqueFileNames,
|
|
@@ -28405,7 +28646,7 @@ ${svg}
|
|
|
28405
28646
|
parser.command('each')
|
|
28406
28647
|
.describe('create/update/delete data fields using a JS expression')
|
|
28407
28648
|
.example('Add two calculated data fields to a layer of U.S. counties\n' +
|
|
28408
|
-
'$ mapshaper counties.shp -each \'STATE_FIPS=CNTY_FIPS.substr(0, 2), AREA
|
|
28649
|
+
'$ mapshaper counties.shp -each \'STATE_FIPS=CNTY_FIPS.substr(0, 2), AREA=this.area\'')
|
|
28409
28650
|
.option('expression', {
|
|
28410
28651
|
DEFAULT: true,
|
|
28411
28652
|
describe: 'JS expression to apply to each target feature'
|
|
@@ -28560,7 +28801,8 @@ ${svg}
|
|
|
28560
28801
|
describe: 'create a polygon to match the outline of the graticule',
|
|
28561
28802
|
type: 'flag'
|
|
28562
28803
|
})
|
|
28563
|
-
.option('name', nameOpt)
|
|
28804
|
+
.option('name', nameOpt)
|
|
28805
|
+
.option('target', targetOpt);
|
|
28564
28806
|
|
|
28565
28807
|
|
|
28566
28808
|
// for testing grid update
|
|
@@ -28970,7 +29212,7 @@ ${svg}
|
|
|
28970
29212
|
})
|
|
28971
29213
|
.option('interval', {
|
|
28972
29214
|
// alias: 'i',
|
|
28973
|
-
describe: 'output resolution as a distance (e.g.
|
|
29215
|
+
describe: 'output resolution as a distance (e.g. 100m)',
|
|
28974
29216
|
type: 'distance'
|
|
28975
29217
|
})
|
|
28976
29218
|
/*
|
|
@@ -29733,7 +29975,7 @@ ${svg}
|
|
|
29733
29975
|
parser.command('calc')
|
|
29734
29976
|
.describe('calculate statistics about the features in a layer')
|
|
29735
29977
|
.example('Calculate the total area of a polygon layer\n' +
|
|
29736
|
-
'$ mapshaper polygons.shp -calc \'sum(
|
|
29978
|
+
'$ mapshaper polygons.shp -calc \'sum(this.area)\'')
|
|
29737
29979
|
.example('Count census blocks in NY with zero population\n' +
|
|
29738
29980
|
'$ mapshaper ny-census-blocks.shp -calc \'count()\' where=\'POPULATION == 0\'')
|
|
29739
29981
|
.validate(validateExpressionOpt)
|
|
@@ -33665,7 +33907,12 @@ ${svg}
|
|
|
33665
33907
|
// load external files (e.g. epsg definitions) if needed in GUI
|
|
33666
33908
|
await initProjLibrary({crs: o.crs_string});
|
|
33667
33909
|
o.crs = parseCrsString$1(o.crs_string);
|
|
33910
|
+
} else if (o.wkt1) {
|
|
33911
|
+
// Shapefile-sourced snapshots typically carry wkt1 but no crs_string;
|
|
33912
|
+
// reconstitute the proj object from it so direct readers of info.crs work.
|
|
33913
|
+
o.crs = parsePrj(o.wkt1);
|
|
33668
33914
|
} else if (o.prj) {
|
|
33915
|
+
// legacy field name; older snapshots may have stored the .prj content here
|
|
33669
33916
|
o.crs = parsePrj(o.prj);
|
|
33670
33917
|
}
|
|
33671
33918
|
return o;
|
|
@@ -33753,22 +34000,39 @@ ${svg}
|
|
|
33753
34000
|
return target;
|
|
33754
34001
|
};
|
|
33755
34002
|
|
|
33756
|
-
//
|
|
34003
|
+
// Replace any inline data strings (JSON objects/arrays or comma-delimited
|
|
34004
|
+
// text) with synthetic filenames and stash the content in @cache so the
|
|
34005
|
+
// downstream importer can read it as if it had come from a file.
|
|
33757
34006
|
function convertDataObjects(files, cache) {
|
|
33758
|
-
var
|
|
33759
|
-
|
|
33760
|
-
if (
|
|
33761
|
-
|
|
33762
|
-
|
|
34007
|
+
var slots = files.map(classifyInlineData);
|
|
34008
|
+
var inlineCount = slots.filter(Boolean).length;
|
|
34009
|
+
if (inlineCount === 0) return;
|
|
34010
|
+
if (inlineCount > 1) {
|
|
34011
|
+
// ensure unique filenames when multiple inline strings are passed together
|
|
34012
|
+
var names = slots.filter(Boolean).map(function(s) { return s.filename; });
|
|
34013
|
+
var unique = utils.uniqifyNames(names, formatVersionedFileName);
|
|
34014
|
+
var idx = 0;
|
|
34015
|
+
slots.forEach(function(slot) {
|
|
34016
|
+
if (slot) slot.filename = unique[idx++];
|
|
34017
|
+
});
|
|
33763
34018
|
}
|
|
33764
|
-
|
|
33765
|
-
if (!
|
|
33766
|
-
|
|
33767
|
-
|
|
33768
|
-
files[i] = name;
|
|
34019
|
+
slots.forEach(function(slot, i) {
|
|
34020
|
+
if (!slot) return;
|
|
34021
|
+
cache[slot.filename] = slot.content;
|
|
34022
|
+
files[i] = slot.filename;
|
|
33769
34023
|
});
|
|
33770
34024
|
}
|
|
33771
34025
|
|
|
34026
|
+
function classifyInlineData(str) {
|
|
34027
|
+
if (stringLooksLikeJSON(str)) {
|
|
34028
|
+
return {filename: 'layer.json', content: str};
|
|
34029
|
+
}
|
|
34030
|
+
if (stringLooksLikeCsv(str)) {
|
|
34031
|
+
return {filename: 'layer.csv', content: unescapeInlineCsv(str)};
|
|
34032
|
+
}
|
|
34033
|
+
return null;
|
|
34034
|
+
}
|
|
34035
|
+
|
|
33772
34036
|
async function importMshpFile(file, catalog, opts) {
|
|
33773
34037
|
var buf = cli.readFile(file, null, opts.input);
|
|
33774
34038
|
var obj = await unpackSessionData(buf);
|
|
@@ -34347,6 +34611,7 @@ ${svg}
|
|
|
34347
34611
|
var job = {
|
|
34348
34612
|
catalog: catalog || new Catalog(),
|
|
34349
34613
|
defs: {},
|
|
34614
|
+
vars: {},
|
|
34350
34615
|
settings: {},
|
|
34351
34616
|
input_files: []
|
|
34352
34617
|
};
|
|
@@ -34382,6 +34647,7 @@ ${svg}
|
|
|
34382
34647
|
stashVar('VERBOSE', job.settings.VERBOSE || cmd.verbose);
|
|
34383
34648
|
stashVar('QUIET', job.settings.QUIET || cmd.quiet);
|
|
34384
34649
|
stashVar('defs', job.defs);
|
|
34650
|
+
stashVar('vars', job.vars);
|
|
34385
34651
|
stashVar('input_files', job.input_files);
|
|
34386
34652
|
}
|
|
34387
34653
|
|
|
@@ -42089,15 +42355,22 @@ ${svg}
|
|
|
42089
42355
|
}
|
|
42090
42356
|
|
|
42091
42357
|
// Resolve a single placeholder expression to a string. Recognised forms:
|
|
42092
|
-
// VAR -> defs[VAR]
|
|
42358
|
+
// VAR -> vars[VAR] if present, else defs[VAR]
|
|
42093
42359
|
// env.VAR -> process.env[VAR]
|
|
42094
42360
|
//
|
|
42361
|
+
// vars is the templating-scope object (-vars / -defaults writes).
|
|
42362
|
+
// defs is the expression-scope object (-define / -calc / -include /
|
|
42363
|
+
// -require / -colorizer writes). The fallback exists so that
|
|
42364
|
+
// "-define base = 'out'" -> "-o {{base}}.geojson" and
|
|
42365
|
+
// "-calc 'N = count()'" -> "-if '{{N}} > 100'" keep working without
|
|
42366
|
+
// the user having to know which scope a value lives in.
|
|
42367
|
+
//
|
|
42095
42368
|
// Throws on undefined names, invalid syntax, or non-primitive values.
|
|
42096
42369
|
//
|
|
42097
|
-
function resolvePlaceholder(expr, defs) {
|
|
42370
|
+
function resolvePlaceholder(expr, vars, defs) {
|
|
42098
42371
|
expr = expr.trim();
|
|
42099
42372
|
var envMatch = /^env\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(expr);
|
|
42100
|
-
var val;
|
|
42373
|
+
var val, source;
|
|
42101
42374
|
if (envMatch) {
|
|
42102
42375
|
val = lookupEnvVar(envMatch[1]);
|
|
42103
42376
|
if (val === undefined || val === null) {
|
|
@@ -42108,10 +42381,14 @@ ${svg}
|
|
|
42108
42381
|
if (!isValidVarName(expr)) {
|
|
42109
42382
|
stop$1('Invalid variable reference: {{' + expr + '}}');
|
|
42110
42383
|
}
|
|
42111
|
-
if (
|
|
42384
|
+
if (vars && expr in vars) {
|
|
42385
|
+
source = vars;
|
|
42386
|
+
} else if (defs && expr in defs) {
|
|
42387
|
+
source = defs;
|
|
42388
|
+
} else {
|
|
42112
42389
|
stop$1('Undefined variable: ' + expr);
|
|
42113
42390
|
}
|
|
42114
|
-
val =
|
|
42391
|
+
val = source[expr];
|
|
42115
42392
|
if (val === null || val === undefined) {
|
|
42116
42393
|
stop$1('Undefined variable: ' + expr);
|
|
42117
42394
|
}
|
|
@@ -42123,16 +42400,33 @@ ${svg}
|
|
|
42123
42400
|
return String(val);
|
|
42124
42401
|
}
|
|
42125
42402
|
|
|
42126
|
-
// Substitute {{...}} placeholders in @str
|
|
42127
|
-
//
|
|
42128
|
-
//
|
|
42129
|
-
//
|
|
42403
|
+
// Substitute {{...}} placeholders in @str.
|
|
42404
|
+
//
|
|
42405
|
+
// Two call signatures, kept for backward compatibility:
|
|
42406
|
+
// interpolateString(str, vars, defs) -- preferred, two-store form
|
|
42407
|
+
// interpolateString(str, defs) -- legacy, single-store form
|
|
42408
|
+
//
|
|
42409
|
+
// In the legacy form, the second argument is treated as the expression
|
|
42410
|
+
// scope (defs); there is no template scope. New callers should use the
|
|
42411
|
+
// two-store form.
|
|
42130
42412
|
//
|
|
42131
|
-
|
|
42413
|
+
// Placeholders preceded by a backslash are left literal (with the
|
|
42414
|
+
// backslash removed). Substitution is single-pass (no recursion) so
|
|
42415
|
+
// values containing "{{...}}" do not trigger further interpolation.
|
|
42416
|
+
//
|
|
42417
|
+
function interpolateString(str, varsOrDefs, defsArg) {
|
|
42132
42418
|
if (typeof str != 'string') return str;
|
|
42419
|
+
var vars, defs;
|
|
42420
|
+
if (arguments.length >= 3) {
|
|
42421
|
+
vars = varsOrDefs;
|
|
42422
|
+
defs = defsArg;
|
|
42423
|
+
} else {
|
|
42424
|
+
vars = null;
|
|
42425
|
+
defs = varsOrDefs;
|
|
42426
|
+
}
|
|
42133
42427
|
return str.replace(PLACEHOLDER_RXP, function(match, escape, expr) {
|
|
42134
42428
|
if (escape === '\\') return '{{' + expr + '}}';
|
|
42135
|
-
return resolvePlaceholder(expr, defs);
|
|
42429
|
+
return resolvePlaceholder(expr, vars, defs);
|
|
42136
42430
|
});
|
|
42137
42431
|
}
|
|
42138
42432
|
|
|
@@ -42140,23 +42434,27 @@ ${svg}
|
|
|
42140
42434
|
// -vars file.json [more ...] load primitives from a flat JSON object
|
|
42141
42435
|
// Mixed forms allowed; later args override earlier ones.
|
|
42142
42436
|
//
|
|
42143
|
-
// Writes into job.
|
|
42144
|
-
//
|
|
42437
|
+
// Writes into job.vars, the templating-scope object read by {{X}}
|
|
42438
|
+
// interpolation. Values written here are NOT visible by bare name in JS
|
|
42439
|
+
// expressions (-each, -filter, -define, etc.); use -define for that.
|
|
42440
|
+
// {{X}} substitution falls back to job.defs if a name is missing from
|
|
42441
|
+
// vars, so values set by -define / -calc / -include are still
|
|
42442
|
+
// referenceable from {{X}}.
|
|
42145
42443
|
cmd.vars = function(job, opts) {
|
|
42146
42444
|
var values = (opts && opts.values) || [];
|
|
42147
42445
|
if (!values.length) {
|
|
42148
42446
|
stop$1('-vars requires one or more KEY=value or file.json arguments');
|
|
42149
42447
|
}
|
|
42150
42448
|
var parsed = parseVarsArgs(values, opts && opts.input);
|
|
42151
|
-
if (!job.
|
|
42449
|
+
if (!job.vars) job.vars = {};
|
|
42152
42450
|
Object.keys(parsed).forEach(function(key) {
|
|
42153
|
-
job.
|
|
42451
|
+
job.vars[key] = parsed[key];
|
|
42154
42452
|
});
|
|
42155
42453
|
};
|
|
42156
42454
|
|
|
42157
42455
|
// -defaults KEY=value [KEY=value ...] set-if-unset
|
|
42158
42456
|
// Same syntax as -vars, but a key is only assigned if it is not already
|
|
42159
|
-
// present in job.
|
|
42457
|
+
// present in job.vars. Lets a command file declare overridable defaults
|
|
42160
42458
|
// that a CLI -vars can pre-empt.
|
|
42161
42459
|
cmd.defaults = function(job, opts) {
|
|
42162
42460
|
var values = (opts && opts.values) || [];
|
|
@@ -42164,10 +42462,10 @@ ${svg}
|
|
|
42164
42462
|
stop$1('-defaults requires one or more KEY=value or file.json arguments');
|
|
42165
42463
|
}
|
|
42166
42464
|
var parsed = parseVarsArgs(values, opts && opts.input);
|
|
42167
|
-
if (!job.
|
|
42465
|
+
if (!job.vars) job.vars = {};
|
|
42168
42466
|
Object.keys(parsed).forEach(function(key) {
|
|
42169
|
-
if (!(key in job.
|
|
42170
|
-
job.
|
|
42467
|
+
if (!(key in job.vars)) {
|
|
42468
|
+
job.vars[key] = parsed[key];
|
|
42171
42469
|
}
|
|
42172
42470
|
});
|
|
42173
42471
|
};
|
|
@@ -48871,8 +49169,9 @@ ${svg}
|
|
|
48871
49169
|
// "-i <token>" (data file).
|
|
48872
49170
|
//
|
|
48873
49171
|
// "{{VAR}}" placeholders are substituted at execution time, against the
|
|
48874
|
-
// live job.
|
|
48875
|
-
//
|
|
49172
|
+
// live job.vars object (with job.defs as a fallback for values written by
|
|
49173
|
+
// -define / -calc / -include). See mapshaper-vars-utils.mjs and the
|
|
49174
|
+
// late-binding hook in mapshaper-run-commands.mjs.
|
|
48876
49175
|
//
|
|
48877
49176
|
function parseCommandFileContent(content) {
|
|
48878
49177
|
if (typeof content != 'string') {
|
|
@@ -51828,7 +52127,7 @@ ${svg}
|
|
|
51828
52127
|
});
|
|
51829
52128
|
}
|
|
51830
52129
|
|
|
51831
|
-
var version = "0.7.
|
|
52130
|
+
var version = "0.7.1";
|
|
51832
52131
|
|
|
51833
52132
|
// Parse command line args into commands and run them
|
|
51834
52133
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -52077,8 +52376,9 @@ ${svg}
|
|
|
52077
52376
|
}
|
|
52078
52377
|
|
|
52079
52378
|
// Late-binding interpolation: just before each command runs, replace any
|
|
52080
|
-
// {{X}} placeholders in its source tokens against the live job.
|
|
52081
|
-
// then re-parse to get
|
|
52379
|
+
// {{X}} placeholders in its source tokens against the live job.vars and
|
|
52380
|
+
// job.defs objects (vars first, defs as fallback), then re-parse to get
|
|
52381
|
+
// fresh option values.
|
|
52082
52382
|
//
|
|
52083
52383
|
// Returns either the original cmd (no placeholders, no _tokens, or the
|
|
52084
52384
|
// command will be skipped) or a fresh cmd object with re-parsed options.
|
|
@@ -52093,11 +52393,12 @@ ${svg}
|
|
|
52093
52393
|
// variables shouldn't error here.
|
|
52094
52394
|
if (skipCommand(cmd.name, job)) return cmd;
|
|
52095
52395
|
|
|
52396
|
+
var vars = job.vars || {};
|
|
52096
52397
|
var defs = job.defs || {};
|
|
52097
52398
|
var interpolated;
|
|
52098
52399
|
try {
|
|
52099
52400
|
interpolated = tokens.map(function(tok) {
|
|
52100
|
-
return interpolateString(tok, defs);
|
|
52401
|
+
return interpolateString(tok, vars, defs);
|
|
52101
52402
|
});
|
|
52102
52403
|
} catch(e) {
|
|
52103
52404
|
e.message = '[' + cmd.name + '] ' + e.message;
|