mapshaper 0.7.5 → 0.7.7

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 CHANGED
@@ -4629,6 +4629,32 @@
4629
4629
  return inputs && inputs[0] || '';
4630
4630
  }
4631
4631
 
4632
+ // Return layers in @dataset that were *not* selected by the current target
4633
+ // set but still match a command-specific geometry predicate.
4634
+ //
4635
+ // Used for command side-effect messaging, e.g.:
4636
+ // - -proj (shared dataset reprojection)
4637
+ // - -simplify (shared arc simplification)
4638
+ //
4639
+ // @targetLayers should be the layer selection that triggered the command.
4640
+ // If all layers are targeted (or target selection is missing), returns [].
4641
+ // @filterFn optional predicate (lyr -> bool) for command-specific relevance.
4642
+ function getImplicitlyTargetedLayers(dataset, targetLayers, filterFn) {
4643
+ if (!targetLayers || targetLayers.length === 0 || targetLayers.length >= dataset.layers.length) {
4644
+ return [];
4645
+ }
4646
+ var targeted = new Set(targetLayers);
4647
+ return dataset.layers.filter(function(lyr) {
4648
+ return !targeted.has(lyr) && (!filterFn || filterFn(lyr));
4649
+ });
4650
+ }
4651
+
4652
+ function getImplicitlyTargetedLayerNames(dataset, targetLayers, filterFn) {
4653
+ return getImplicitlyTargetedLayers(dataset, targetLayers, filterFn).map(function(lyr) {
4654
+ return lyr.name || '[unnamed]';
4655
+ });
4656
+ }
4657
+
4632
4658
  // Divide a collection of features with mixed types into layers of a single type
4633
4659
  // (Used for importing TopoJSON and GeoJSON features)
4634
4660
  function divideFeaturesByType(shapes, properties, types) {
@@ -4769,6 +4795,8 @@
4769
4795
  filterPathLayerByArcIds: filterPathLayerByArcIds,
4770
4796
  getArcPresenceTest2: getArcPresenceTest2,
4771
4797
  getFeatureCount: getFeatureCount,
4798
+ getImplicitlyTargetedLayerNames: getImplicitlyTargetedLayerNames,
4799
+ getImplicitlyTargetedLayers: getImplicitlyTargetedLayers,
4772
4800
  getLayerBounds: getLayerBounds,
4773
4801
  getLayerDataTable: getLayerDataTable,
4774
4802
  getLayerSourceFile: getLayerSourceFile,
@@ -18415,7 +18443,7 @@
18415
18443
  function exportSVG(dataset, opts) {
18416
18444
  var namespace = 'xmlns="http://www.w3.org/2000/svg"';
18417
18445
  var defs = [];
18418
- var frame, svg, layers;
18446
+ var frame, svg, layers, metadataJSON;
18419
18447
  var style = '';
18420
18448
 
18421
18449
  // kludge for map keys
@@ -18439,6 +18467,9 @@
18439
18467
  frame = getFrameData(dataset, opts);
18440
18468
  fitDatasetToFrame(dataset, frame);
18441
18469
  setCoordinatePrecision(dataset, opts.precision || 0.01);
18470
+ if (opts.metadata) {
18471
+ metadataJSON = JSON.stringify(getGeospatialMetadata(dataset, frame));
18472
+ }
18442
18473
 
18443
18474
  // error if one or more svg_data fields are not present in any layers
18444
18475
  if (opts.svg_data) validateSvgDataFields(dataset.layers, opts.svg_data);
@@ -18458,6 +18489,10 @@
18458
18489
  return stringify(obj);
18459
18490
  }).join('\n');
18460
18491
 
18492
+ if (metadataJSON) {
18493
+ svg = getMetadataBlock(metadataJSON, [0, 0, frame.width, frame.height]) + svg;
18494
+ }
18495
+
18461
18496
  if (defs.length > 0) {
18462
18497
  svg = '<defs>\n' + utils.pluck(defs, 'svg').join('') + '</defs>\n' + svg;
18463
18498
  }
@@ -18485,6 +18520,57 @@ ${svg}
18485
18520
  }];
18486
18521
  }
18487
18522
 
18523
+ function getMetadataBlock(metadataJSON, viewBox) {
18524
+ var x = 0;
18525
+ var y = 0;
18526
+ var width = viewBox && viewBox[2] || 0;
18527
+ var height = viewBox && viewBox[3] || 0;
18528
+ return '<metadata>' + metadataJSON + '</metadata>\n' +
18529
+ '<g id="mapshaper-metadata">\n' +
18530
+ '<rect x="' + x + '" y="' + y + '" width="' + width + '" height="' + height + '" opacity="0"/>\n' +
18531
+ '<text opacity="0" font-size="0.1">' + metadataJSON + '</text>\n' +
18532
+ '</g>\n';
18533
+ }
18534
+
18535
+ function getGeospatialMetadata(dataset, frame) {
18536
+ return {
18537
+ crs: getSVGMetadataCRS(dataset),
18538
+ bbox: frame.bbox || null
18539
+ };
18540
+ }
18541
+
18542
+ function getSVGMetadataCRS(dataset) {
18543
+ var crs = getDatasetCRS(dataset);
18544
+ var info = getDatasetCrsInfo(dataset);
18545
+ var proj4 = null;
18546
+ if (crs) {
18547
+ proj4 = crsToProj4(crs);
18548
+ }
18549
+ if (proj4) return proj4;
18550
+ return getAuthorityCodeString(info) || null;
18551
+ }
18552
+
18553
+ function getAuthorityCodeString(info) {
18554
+ var authority = null;
18555
+ if (info && info.crs_string) {
18556
+ authority = parseAuthorityCodeString(info.crs_string);
18557
+ }
18558
+ if (!authority && info && info.wkt1) {
18559
+ authority = parseAuthorityCodeFromWkt(info.wkt1);
18560
+ }
18561
+ if (!authority && info && info.geopackage_crs) {
18562
+ authority = parseGeoPackageAuthority(info.geopackage_crs);
18563
+ }
18564
+ return authority ? authority.authority + ':' + authority.code : null;
18565
+ }
18566
+
18567
+ function parseGeoPackageAuthority(o) {
18568
+ if (!o) return null;
18569
+ var authority = o.organization || o.authority || null;
18570
+ var code = o.organization_coordsys_id || o.code || null;
18571
+ return authority && code != null ? {authority: String(authority).toUpperCase(), code: String(code)} : null;
18572
+ }
18573
+
18488
18574
  function exportFurnitureLayerForSVG(lyr, frame, opts) {
18489
18575
  var layerObj = getEmptyLayerForSVG(lyr, opts);
18490
18576
  layerObj.children = renderFurnitureLayer(lyr, frame);
@@ -19446,7 +19532,9 @@ ${svg}
19446
19532
 
19447
19533
  function exportDbfFile(lyr, dataset, opts) {
19448
19534
  var data = lyr.data,
19449
- buf;
19535
+ buf,
19536
+ files,
19537
+ encoding;
19450
19538
  // create empty data table if missing a table or table is being cut out
19451
19539
  if (!data || opts.cut_table || opts.drop_table) {
19452
19540
  data = new DataTable(lyr.shapes ? lyr.shapes.length : 0);
@@ -19460,14 +19548,34 @@ ${svg}
19460
19548
  } else {
19461
19549
  buf = Dbf.exportRecords(data.getRecords(), opts.encoding, opts.field_order);
19462
19550
  }
19551
+ encoding = getDbfExportEncoding(data, opts);
19463
19552
  if (utils.isInteger(opts.ldid)) {
19464
19553
  new Uint8Array(buf)[29] = opts.ldid; // set language driver id
19465
19554
  }
19466
- // TODO: also export .cpg page
19467
- return [{
19555
+ files = [{
19468
19556
  content: buf,
19469
19557
  filename: lyr.name + '.dbf'
19470
19558
  }];
19559
+ if (opts.export_cpg && encoding) {
19560
+ files.push({
19561
+ content: formatCpgEncoding(encoding),
19562
+ filename: lyr.name + '.cpg'
19563
+ });
19564
+ }
19565
+ return files;
19566
+ }
19567
+
19568
+ function getDbfExportEncoding(data, opts) {
19569
+ if (data && data.getDbfEncoding) {
19570
+ return data.getDbfEncoding(opts);
19571
+ }
19572
+ return opts.encoding || 'utf8';
19573
+ }
19574
+
19575
+ function formatCpgEncoding(encoding) {
19576
+ // Canonical spelling for the default.
19577
+ if (/^utf-?8$/i.test(String(encoding))) return 'UTF-8';
19578
+ return String(encoding);
19471
19579
  }
19472
19580
 
19473
19581
  var decoder;
@@ -22232,8 +22340,9 @@ ${svg}
22232
22340
  function exportShapefile(dataset, opts) {
22233
22341
  return dataset.layers.reduce(function(files, lyr) {
22234
22342
  var prj = exportPrjFile(lyr, dataset);
22343
+ var dbfOpts = utils.defaults({export_cpg: true}, opts);
22235
22344
  files = files.concat(exportShpAndShxFiles(lyr, dataset));
22236
- files = files.concat(exportDbfFile(lyr, dataset, opts));
22345
+ files = files.concat(exportDbfFile(lyr, dataset, dbfOpts));
22237
22346
  if (prj) files.push(prj);
22238
22347
  return files;
22239
22348
  }, []);
@@ -27966,6 +28075,10 @@ ${svg}
27966
28075
  old_alias: 'json-subtree',
27967
28076
  describe: '[JSON] path to JSON input data; separator is /'
27968
28077
  })
28078
+ .option('ndjson', {
28079
+ type: 'flag',
28080
+ describe: '[JSON/GeoJSON] input is newline-delimited JSON objects'
28081
+ })
27969
28082
  .option('single-part', {
27970
28083
  type: 'flag',
27971
28084
  // describe: '[GeoJSON] split multi-part features into single-part features'
@@ -28058,10 +28171,6 @@ ${svg}
28058
28171
  describe: '[TopoJSON] export coordinates without quantization',
28059
28172
  type: 'flag'
28060
28173
  })
28061
- .option('metadata', {
28062
- // describe: '[TopoJSON] Add a metadata object containing CRS information',
28063
- type: 'flag'
28064
- })
28065
28174
  .option('no-point-quantization', {
28066
28175
  // describe: '[TopoJSON] export point coordinates without quantization',
28067
28176
  type: 'flag'
@@ -28104,6 +28213,10 @@ ${svg}
28104
28213
  describe: '[GeoJSON/JSON] output newline-delimited features or records',
28105
28214
  type: 'flag'
28106
28215
  })
28216
+ .option('metadata', {
28217
+ describe: '[SVG/TopoJSON] include metadata in output',
28218
+ type: 'flag'
28219
+ })
28107
28220
  .option('width', {
28108
28221
  describe: '[SVG/TopoJSON] pixel width of output (SVG default is 800)',
28109
28222
  type: 'number'
@@ -28166,10 +28279,7 @@ ${svg}
28166
28279
  .option('final', {
28167
28280
  type: 'flag' // for testing
28168
28281
  })
28169
- .option('metadata', {
28170
- // describe: '[TopoJSON] add a metadata object',
28171
- type: 'flag'
28172
- });
28282
+ ;
28173
28283
 
28174
28284
  parser.section('Editing commands');
28175
28285
 
@@ -30251,6 +30361,7 @@ ${svg}
30251
30361
  };
30252
30362
 
30253
30363
  this.getFields = getFieldNames;
30364
+ this.getEncoding = getEncoding;
30254
30365
 
30255
30366
  // TODO: switch to streaming output under Node.js
30256
30367
  this.getBuffer = function() {
@@ -30570,6 +30681,19 @@ ${svg}
30570
30681
  return Dbf.exportRecords(getTable().getRecords(), opts.encoding, opts.field_order);
30571
30682
  };
30572
30683
 
30684
+ // Return the text encoding that exportAsDbf() will use for the current
30685
+ // options. If we're exporting the untouched original DBF bytes, use the
30686
+ // reader's detected/declared encoding; otherwise we regenerate with either
30687
+ // opts.encoding or the writer default (utf8).
30688
+ this.getDbfEncoding = function(optsArg) {
30689
+ var opts = optsArg || {};
30690
+ var useOriginal = !!reader && !altered && !opts.field_order && !opts.encoding;
30691
+ if (useOriginal) {
30692
+ return reader.getEncoding();
30693
+ }
30694
+ return opts.encoding || 'utf8';
30695
+ };
30696
+
30573
30697
  this.getReadOnlyRecordAt = function(i) {
30574
30698
  return reader ? reader.readRow(i) : table.getReadOnlyRecordAt(i);
30575
30699
  };
@@ -31409,7 +31533,7 @@ ${svg}
31409
31533
  (srcCollection.features || srcCollection.geometries || []).forEach(importer.parseObject);
31410
31534
  dataset = importer.done();
31411
31535
  importCRS(dataset, srcObj); // TODO: remove this
31412
- warnIfProjectedCoords(dataset, srcObj);
31536
+ warnIfProjectedCoords(dataset, srcObj, opts);
31413
31537
  return dataset;
31414
31538
  }
31415
31539
 
@@ -31417,12 +31541,13 @@ ${svg}
31417
31541
  // `crs` member). If the imported file makes no CRS claim but its coordinates
31418
31542
  // are clearly outside lat-long range, warn the user: silent fallthrough here
31419
31543
  // usually surfaces later as cryptic -proj errors or grossly wrong output.
31420
- function warnIfProjectedCoords(dataset, jsonObj) {
31544
+ function warnIfProjectedCoords(dataset, jsonObj, opts) {
31545
+ if (!opts.warn_projected_coords) return;
31421
31546
  if (jsonObj && jsonObj.crs) return; // user has explicitly declared a CRS
31422
31547
  var bounds = getRoughDatasetBounds(dataset);
31423
31548
  if (!bounds || !bounds.hasBounds()) return;
31424
31549
  if (probablyDecimalDegreeBounds(bounds)) return;
31425
- warn('Imported GeoJSON has coordinates outside the lat-long range &mdash; ' +
31550
+ warn('Imported GeoJSON has coordinates outside the lat-long range -- ' +
31426
31551
  ' importing as projected geometry with unknown CRS. Commands ' +
31427
31552
  'like -proj that require a CRS will not work until you set a source ' +
31428
31553
  'CRS, e.g. -proj init=EPSG:3857.');
@@ -32413,7 +32538,13 @@ ${svg}
32413
32538
  var str = readFirstChars(reader, 1000);
32414
32539
  var type = identifyJSONString(str, opts);
32415
32540
  var dataset, retn;
32416
- if (type == 'geojson') { // consider only for larger files
32541
+ if (opts.ndjson) {
32542
+ // NDJSON can represent either newline-delimited GeoJSON features/geometries
32543
+ // or plain JSON records. Defer type detection until after line parsing.
32544
+ retn = {
32545
+ content: reader.toString('utf8')
32546
+ };
32547
+ } else if (type == 'geojson') { // consider only for larger files
32417
32548
  dataset = importGeoJSONFile(reader, opts);
32418
32549
  retn = {
32419
32550
  dataset: dataset,
@@ -32465,6 +32596,12 @@ ${svg}
32465
32596
  }
32466
32597
 
32467
32598
  if (content) {
32599
+ if (opts.ndjson) {
32600
+ var nd = importNDJSON(content, opts);
32601
+ retn.dataset = nd.dataset;
32602
+ retn.format = nd.format;
32603
+ return retn;
32604
+ }
32468
32605
  if (utils.isString(content)) {
32469
32606
  try {
32470
32607
  content = JSON.parse(content); // ~3sec for 100MB string
@@ -32485,7 +32622,7 @@ ${svg}
32485
32622
  if (fmt == 'topojson') {
32486
32623
  retn.dataset = importTopoJSON(content, opts);
32487
32624
  } else if (fmt == 'geojson') {
32488
- retn.dataset = importGeoJSON(content, opts);
32625
+ retn.dataset = importGeoJSON(content, getGeoJSONImportOpts(opts));
32489
32626
  } else if (fmt == 'json') {
32490
32627
  retn.dataset = importJSONTable(content);
32491
32628
  } else {
@@ -32497,6 +32634,60 @@ ${svg}
32497
32634
  return retn;
32498
32635
  }
32499
32636
 
32637
+ function getGeoJSONImportOpts(opts) {
32638
+ return Object.assign({}, opts, {
32639
+ warn_projected_coords: true
32640
+ });
32641
+ }
32642
+
32643
+ function importNDJSON(content, opts) {
32644
+ var lines = utils.isString(content) ? utils.splitLines(content) : [];
32645
+ var objects = [];
32646
+ var firstGeo = null;
32647
+ for (var i = 0; i < lines.length; i++) {
32648
+ var raw = lines[i].trim();
32649
+ if (!raw) continue;
32650
+ var obj;
32651
+ try {
32652
+ obj = JSON.parse(raw);
32653
+ } catch (e) {
32654
+ stop$1('NDJSON parsing error on line ' + (i + 1) + ': ' + e.message);
32655
+ }
32656
+ objects.push(obj);
32657
+ if (firstGeo === null) {
32658
+ firstGeo = isGeoJSONObject(obj);
32659
+ } else if (firstGeo !== isGeoJSONObject(obj)) {
32660
+ stop$1('NDJSON input mixes GeoJSON and non-GeoJSON objects');
32661
+ }
32662
+ }
32663
+ if (objects.length === 0) {
32664
+ stop$1('NDJSON input does not contain any JSON objects');
32665
+ }
32666
+ if (!firstGeo) {
32667
+ return {dataset: importJSONTable(objects), format: 'json'};
32668
+ }
32669
+ var parser = new GeoJSONParser(getGeoJSONImportOpts(opts));
32670
+ objects.forEach(function(obj) {
32671
+ if (obj && obj.type == 'FeatureCollection') {
32672
+ (obj.features || []).forEach(parser.parseObject);
32673
+ } else if (obj && obj.type == 'GeometryCollection') {
32674
+ (obj.geometries || []).forEach(parser.parseObject);
32675
+ } else {
32676
+ parser.parseObject(obj);
32677
+ }
32678
+ });
32679
+ return {dataset: parser.done(), format: 'geojson'};
32680
+ }
32681
+
32682
+ function isGeoJSONObject(obj) {
32683
+ if (!obj || typeof obj != 'object') return false;
32684
+ var type = obj.type;
32685
+ return type == 'Feature' || type == 'FeatureCollection' ||
32686
+ type == 'GeometryCollection' || type == 'Point' || type == 'MultiPoint' ||
32687
+ type == 'LineString' || type == 'MultiLineString' ||
32688
+ type == 'Polygon' || type == 'MultiPolygon';
32689
+ }
32690
+
32500
32691
  // path: path from top-level to the target object
32501
32692
  function selectFromObject(o, path) {
32502
32693
  var arrayRxp = /(.*)\[([0-9]+)\]$/; // array bracket notation w/ index
@@ -33087,6 +33278,7 @@ ${svg}
33087
33278
  var Parser = typeof DOMParser == 'undefined' ? require$1('@xmldom/xmldom').DOMParser : DOMParser;
33088
33279
  var doc = new Parser().parseFromString(str, 'text/xml');
33089
33280
  var root = doc && doc.documentElement;
33281
+ var geometadata = parseSVGGeometadata(root, str);
33090
33282
  var groups = getTopLevelLayerGroups(root);
33091
33283
  var layerData = [];
33092
33284
  var datasets = [];
@@ -33103,7 +33295,11 @@ ${svg}
33103
33295
  });
33104
33296
  });
33105
33297
 
33106
- flipYCoordinates(layerData);
33298
+ if (geometadata && geometadata.bbox && geometadata.viewBox) {
33299
+ remapCoordinatesFromSVGToMap(layerData, geometadata.viewBox, geometadata.bbox);
33300
+ } else {
33301
+ flipYCoordinates(layerData);
33302
+ }
33107
33303
 
33108
33304
  layerData.forEach(function(layer) {
33109
33305
  datasets.push(importLayerFeatures(layer.name, layer.features, opts));
@@ -33113,9 +33309,237 @@ ${svg}
33113
33309
  return {layers: [], info: {}};
33114
33310
  }
33115
33311
  if (datasets.length === 1) {
33116
- return datasets[0];
33312
+ return applySVGGeometadata(datasets[0], geometadata);
33117
33313
  }
33118
- return mergeDatasets(datasets);
33314
+ return applySVGGeometadata(mergeDatasets(datasets), geometadata);
33315
+ }
33316
+
33317
+ function applySVGGeometadata(dataset, geometadata) {
33318
+ if (geometadata && geometadata.crs) {
33319
+ setDatasetCrsInfo(dataset, getCrsInfo(geometadata.crs));
33320
+ }
33321
+ return dataset;
33322
+ }
33323
+
33324
+ function parseSVGGeometadata(root, svgText) {
33325
+ var json = findMetadataJSONString(root, svgText);
33326
+ var obj;
33327
+ var metadataViewport = getMetadataViewportRect(root);
33328
+ if (!json) return null;
33329
+ obj = parseMetadataJSONObject(json);
33330
+ if (!obj) return null;
33331
+ return {
33332
+ crs: normalizeMetadataCRS(obj.crs),
33333
+ bbox: normalizeMetadataBBox(obj.bbox),
33334
+ viewBox: metadataViewport || parseSVGViewBox(root && root.getAttribute && root.getAttribute('viewBox'))
33335
+ };
33336
+ }
33337
+
33338
+ function findMetadataJSONString(root, svgText) {
33339
+ var text = getMetadataTagText(root) || getHiddenMetadataText(root);
33340
+ if (!text && svgText) {
33341
+ text = extractHiddenMetadataFromSource(svgText);
33342
+ }
33343
+ return normalizeMetadataJSONText(text);
33344
+ }
33345
+
33346
+ function getMetadataTagText(root) {
33347
+ var nodes = getElementChildren(root);
33348
+ for (var i = 0; i < nodes.length; i++) {
33349
+ if (getTagName(nodes[i]) == 'metadata') {
33350
+ return nodes[i].textContent || '';
33351
+ }
33352
+ }
33353
+ return null;
33354
+ }
33355
+
33356
+ function getHiddenMetadataText(root) {
33357
+ var node = getMetadataGroupNode(root);
33358
+ if (node) {
33359
+ return node.textContent || '';
33360
+ }
33361
+ return null;
33362
+ }
33363
+
33364
+ function getMetadataGroupNode(root) {
33365
+ var nodes = getElementChildren(root);
33366
+ var i, node;
33367
+ for (i = 0; i < nodes.length; i++) {
33368
+ node = nodes[i];
33369
+ if (getTagName(node) == 'g' && node.getAttribute && node.getAttribute('id') == 'mapshaper-metadata') {
33370
+ return node;
33371
+ }
33372
+ }
33373
+ return null;
33374
+ }
33375
+
33376
+ function getMetadataViewportRect(root) {
33377
+ var node = getMetadataGroupNode(root);
33378
+ var children, i, rect;
33379
+ if (!node) return null;
33380
+ children = getElementChildren(node);
33381
+ for (i = 0; i < children.length; i++) {
33382
+ if (getTagName(children[i]) == 'rect') {
33383
+ rect = parseMetadataRectNode(children[i]);
33384
+ if (rect) return rect;
33385
+ }
33386
+ }
33387
+ return null;
33388
+ }
33389
+
33390
+ function parseMetadataRectNode(node) {
33391
+ var x = parseNumber(node.getAttribute('x')) || 0;
33392
+ var y = parseNumber(node.getAttribute('y')) || 0;
33393
+ var width = parseNumber(node.getAttribute('width'));
33394
+ var height = parseNumber(node.getAttribute('height'));
33395
+ var pts, i, p;
33396
+ var xmin = Infinity, ymin = Infinity, xmax = -Infinity, ymax = -Infinity;
33397
+ if (!isFiniteNumber(width) || !isFiniteNumber(height) || width === 0 || height === 0) {
33398
+ return null;
33399
+ }
33400
+ pts = [
33401
+ [x, y],
33402
+ [x + width, y],
33403
+ [x, y + height],
33404
+ [x + width, y + height]
33405
+ ];
33406
+ for (i = 0; i < pts.length; i++) {
33407
+ p = applyNodeTransformChain(node, pts[i]);
33408
+ xmin = Math.min(xmin, p[0]);
33409
+ ymin = Math.min(ymin, p[1]);
33410
+ xmax = Math.max(xmax, p[0]);
33411
+ ymax = Math.max(ymax, p[1]);
33412
+ }
33413
+ if (!isFiniteNumber(xmin) || !isFiniteNumber(ymin) || !isFiniteNumber(xmax) || !isFiniteNumber(ymax)) {
33414
+ return null;
33415
+ }
33416
+ return [xmin, ymin, xmax - xmin, ymax - ymin];
33417
+ }
33418
+
33419
+ function applyNodeTransformChain(node, point) {
33420
+ var chain = [];
33421
+ var p = [point[0], point[1]];
33422
+ var i;
33423
+ while (node && node.nodeType == 1) {
33424
+ chain.unshift(node);
33425
+ node = node.parentNode;
33426
+ }
33427
+ for (i = 0; i < chain.length; i++) {
33428
+ p = applyTransformString(chain[i].getAttribute && chain[i].getAttribute('transform'), p);
33429
+ }
33430
+ return p;
33431
+ }
33432
+
33433
+ function applyTransformString(str, point) {
33434
+ var re = /([a-z]+)\s*\(([^)]*)\)/ig;
33435
+ var m = null;
33436
+ var p = [point[0], point[1]];
33437
+ while ((m = re.exec(String(str || '')))) {
33438
+ p = applyTransformCommand(m[1].toLowerCase(), parseTransformNumbers(m[2]), p);
33439
+ }
33440
+ return p;
33441
+ }
33442
+
33443
+ function parseTransformNumbers(str) {
33444
+ return String(str || '')
33445
+ .trim()
33446
+ .split(/[\s,]+/)
33447
+ .filter(Boolean)
33448
+ .map(Number)
33449
+ .filter(function(v) { return isFiniteNumber(v); });
33450
+ }
33451
+
33452
+ function applyTransformCommand(cmd, nums, point) {
33453
+ var x = point[0], y = point[1];
33454
+ var sx, sy;
33455
+ if (cmd == 'translate') {
33456
+ return [x + (nums[0] || 0), y + (nums.length > 1 ? nums[1] : 0)];
33457
+ }
33458
+ if (cmd == 'scale') {
33459
+ sx = nums.length > 0 ? nums[0] : 1;
33460
+ sy = nums.length > 1 ? nums[1] : sx;
33461
+ return [x * sx, y * sy];
33462
+ }
33463
+ if (cmd == 'matrix' && nums.length >= 6) {
33464
+ return [
33465
+ nums[0] * x + nums[2] * y + nums[4],
33466
+ nums[1] * x + nums[3] * y + nums[5]
33467
+ ];
33468
+ }
33469
+ return point;
33470
+ }
33471
+
33472
+ function extractHiddenMetadataFromSource(str) {
33473
+ var m = String(str || '').match(/<g[^>]*\bid\s*=\s*["']mapshaper-metadata["'][^>]*>([\s\S]*?)<\/g>/i);
33474
+ if (!m) return null;
33475
+ return stripMarkupTags(m[1]);
33476
+ }
33477
+
33478
+ function stripMarkupTags(str) {
33479
+ return String(str || '').replace(/<[^>]*>/g, '');
33480
+ }
33481
+
33482
+ function normalizeMetadataJSONText(str) {
33483
+ if (!str) return null;
33484
+ return decodeHTMLEntities(stripMarkupTags(String(str))).trim();
33485
+ }
33486
+
33487
+ function decodeHTMLEntities(str) {
33488
+ return String(str || '')
33489
+ .replace(/&#34;|&#x22;/gi, '"')
33490
+ .replace(/&#39;|&#x27;/gi, "'")
33491
+ .replace(/&#38;|&#x26;/gi, '&')
33492
+ .replace(/&#60;|&#x3c;/gi, '<')
33493
+ .replace(/&#62;|&#x3e;/gi, '>')
33494
+ .replace(/&quot;/g, '"')
33495
+ .replace(/&apos;/g, "'")
33496
+ .replace(/&amp;/g, '&')
33497
+ .replace(/&lt;/g, '<')
33498
+ .replace(/&gt;/g, '>');
33499
+ }
33500
+
33501
+ function parseMetadataJSONObject(text) {
33502
+ try {
33503
+ return JSON.parse(text);
33504
+ } catch (e) {
33505
+ return null;
33506
+ }
33507
+ }
33508
+
33509
+ function normalizeMetadataCRS(crs) {
33510
+ return typeof crs == 'string' && crs.trim() ? crs.trim() : null;
33511
+ }
33512
+
33513
+ function normalizeMetadataBBox(bbox) {
33514
+ if (!Array.isArray(bbox) || bbox.length != 4) return null;
33515
+ var out = bbox.map(function(v) { return Number(v); });
33516
+ return out.every(isFiniteNumber) ? out : null;
33517
+ }
33518
+
33519
+ function parseSVGViewBox(str) {
33520
+ var parts = String(str || '').trim().split(/[\s,]+/).map(Number);
33521
+ if (parts.length != 4 || !parts.every(isFiniteNumber) || parts[2] === 0 || parts[3] === 0) {
33522
+ return null;
33523
+ }
33524
+ return parts;
33525
+ }
33526
+
33527
+ function remapCoordinatesFromSVGToMap(layerData, viewBox, bbox) {
33528
+ var vx = viewBox[0], vy = viewBox[1], vw = viewBox[2], vh = viewBox[3];
33529
+ var minX = bbox[0], minY = bbox[1], maxX = bbox[2], maxY = bbox[3];
33530
+ var scaleX = (maxX - minX) / vw;
33531
+ var scaleY = (maxY - minY) / vh;
33532
+
33533
+ layerData.forEach(function(layer) {
33534
+ layer.features.forEach(function(feature) {
33535
+ forEachGeometryCoordinate(feature && feature.geometry, function(coord) {
33536
+ var sx = coord[0];
33537
+ var sy = coord[1];
33538
+ coord[0] = minX + (sx - vx) * scaleX;
33539
+ coord[1] = maxY - (sy - vy) * scaleY;
33540
+ });
33541
+ });
33542
+ });
33119
33543
  }
33120
33544
 
33121
33545
  function importLayerFeatures(layerName, features, opts) {
@@ -33143,6 +33567,7 @@ ${svg}
33143
33567
  childNodes.forEach(function(node) {
33144
33568
  var tag = getTagName(node);
33145
33569
  if (tag == 'defs') return;
33570
+ if (tag == 'g' && node.getAttribute && node.getAttribute('id') == 'mapshaper-metadata') return;
33146
33571
  if (tag == 'g') {
33147
33572
  groups.push({
33148
33573
  node: node,
@@ -41193,7 +41618,7 @@ ${svg}
41193
41618
  // @type: 'clip' or 'erase'
41194
41619
  function clipLayers(targetLayers, clipSrc, targetDataset, type, opts) {
41195
41620
  profileStart('clipLayers');
41196
- opts = opts || {no_fcleanup: true}; // TODO: update testing functions
41621
+ opts = opts || {no_cleanup: true}; // TODO: update testing functions
41197
41622
  var usingPathClip = utils.some(targetLayers, layerHasPaths);
41198
41623
  var mergedDataset, clipLyr, nodes, result;
41199
41624
  var clipDataset = normalizeOverlaySource(clipSrc, targetDataset, opts);
@@ -43429,20 +43854,24 @@ ${svg}
43429
43854
  }
43430
43855
  }
43431
43856
 
43432
- printJoinMessage({
43433
- matches: matchCount,
43434
- n: n,
43435
- joins: countJoins(joinCounts),
43436
- m: srcRecords.length,
43437
- skipped: skipCount,
43438
- collisions: collisionCount,
43439
- collisionFields: collisionFields,
43440
- destKey: destKey,
43441
- srcKey: srcKey,
43442
- unmatchedTargetKeys: takeSampleKeys(unmatchedTargetKeys),
43443
- unusedSourceKeys: takeSampleKeys(unusedSourceKeys),
43444
- opts: opts
43445
- });
43857
+ // Opt-in summary logging. Some commands (e.g. -divide) reuse this join
43858
+ // helper internally and don't want user-facing join diagnostics.
43859
+ if (opts.show_join_message) {
43860
+ printJoinMessage({
43861
+ matches: matchCount,
43862
+ n: n,
43863
+ joins: countJoins(joinCounts),
43864
+ m: srcRecords.length,
43865
+ skipped: skipCount,
43866
+ collisions: collisionCount,
43867
+ collisionFields: collisionFields,
43868
+ destKey: destKey,
43869
+ srcKey: srcKey,
43870
+ unmatchedTargetKeys: takeSampleKeys(unmatchedTargetKeys),
43871
+ unusedSourceKeys: takeSampleKeys(unusedSourceKeys),
43872
+ opts: opts
43873
+ });
43874
+ }
43446
43875
 
43447
43876
  if (opts.unjoined) {
43448
43877
  retn.unjoined = {
@@ -46804,7 +47233,7 @@ ${svg}
46804
47233
  // Works for lcc, aea, tmerc, etc.
46805
47234
  // TODO: add more projections
46806
47235
  //
46807
- function expandProjDefn(str, dataset) {
47236
+ function expandProjDefn(str, dataset, targetLayers) {
46808
47237
  var mproj = require$1('mproj');
46809
47238
  var proj4, params, bbox, isConic2SP, isCentered, decimals;
46810
47239
  if (str in mproj.internal.pj_list === false) {
@@ -46815,7 +47244,7 @@ ${svg}
46815
47244
  isCentered = ['tmerc', 'etmerc'].includes(str);
46816
47245
  proj4 = '+proj=' + str;
46817
47246
  if (isConic2SP || isCentered) {
46818
- bbox = getBBox(dataset); // TODO: support projected datasets
47247
+ bbox = getBBox(dataset, targetLayers); // TODO: support projected datasets
46819
47248
  decimals = getBoundsPrecisionForDisplay(bbox);
46820
47249
  params = isCentered ? getCenterParams(bbox, decimals) : getConicParams(bbox, decimals);
46821
47250
  proj4 += ' ' + params;
@@ -46824,11 +47253,16 @@ ${svg}
46824
47253
  return proj4;
46825
47254
  }
46826
47255
 
46827
- function getBBox(dataset) {
47256
+ function getBBox(dataset, targetLayers) {
47257
+ var source = targetLayers && targetLayers.length > 0 ? {
47258
+ arcs: dataset.arcs,
47259
+ layers: targetLayers,
47260
+ info: dataset.info
47261
+ } : dataset;
46828
47262
  if (!isLatLngCRS(getDatasetCRS(dataset))) {
46829
47263
  stop$1('Expected unprojected data');
46830
47264
  }
46831
- return getDatasetBounds(dataset).toArray();
47265
+ return getDatasetBounds(source).toArray();
46832
47266
  }
46833
47267
 
46834
47268
  // See: Savric & Jenny, "Automating the selection of standard parallels for conic map projections"
@@ -46854,8 +47288,9 @@ ${svg}
46854
47288
  getConicParams: getConicParams
46855
47289
  });
46856
47290
 
46857
- cmd.proj = function(dataset, catalog, opts) {
47291
+ cmd.proj = function(dataset, catalog, opts, targetLayers) {
46858
47292
  var srcInfo, destInfo, destStr;
47293
+ var implicitlyProjectedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasGeometry);
46859
47294
  if (opts.init) {
46860
47295
  srcInfo = fetchCrsInfo(opts.init, catalog);
46861
47296
  if (!srcInfo.crs) stop$1("Unknown projection source:", opts.init);
@@ -46864,11 +47299,17 @@ ${svg}
46864
47299
  if (opts.match) {
46865
47300
  destInfo = fetchCrsInfo(opts.match, catalog);
46866
47301
  } else if (opts.crs) {
46867
- destStr = expandProjDefn(opts.crs, dataset);
47302
+ destStr = expandProjDefn(opts.crs, dataset, targetLayers);
46868
47303
  destInfo = getCrsInfo(destStr);
46869
47304
  }
46870
47305
  if (destInfo) {
46871
- projCmd(dataset, destInfo, opts);
47306
+ var didProject = projCmd(dataset, destInfo, opts);
47307
+ if (didProject && implicitlyProjectedNames.length > 0) {
47308
+ message(
47309
+ 'Also projected non-target layer' + utils.pluralSuffix(implicitlyProjectedNames.length) +
47310
+ ' from the same dataset: ' + implicitlyProjectedNames.join(', ')
47311
+ );
47312
+ }
46872
47313
  }
46873
47314
  };
46874
47315
 
@@ -46886,7 +47327,7 @@ ${svg}
46886
47327
  if (!datasetHasGeometry(dataset)) {
46887
47328
  // still set the crs of datasets that are missing geometry
46888
47329
  setDatasetCrsInfo(dataset, destInfo);
46889
- return;
47330
+ return false;
46890
47331
  }
46891
47332
 
46892
47333
  var srcInfo = getDatasetCrsInfo(dataset);
@@ -46896,7 +47337,7 @@ ${svg}
46896
47337
 
46897
47338
  if (crsAreEqual(srcInfo.crs, destInfo.crs)) {
46898
47339
  message("Source and destination CRS are the same");
46899
- return;
47340
+ return false;
46900
47341
  }
46901
47342
 
46902
47343
  if (dataset.arcs) {
@@ -46922,9 +47363,9 @@ ${svg}
46922
47363
  // replace original layers with modified layers
46923
47364
  utils.extend(lyr, target.layers[i]);
46924
47365
  });
47366
+ return true;
46925
47367
  }
46926
47368
 
46927
-
46928
47369
  // name: a layer identifier, .prj file or projection defn
46929
47370
  // Converts layer ids and .prj files to CRS defn
46930
47371
  // Returns projection defn
@@ -48059,17 +48500,62 @@ ${svg}
48059
48500
  });
48060
48501
  }
48061
48502
 
48503
+ function parseWKTPoint(val) {
48504
+ if (!utils.isString(val)) return null;
48505
+ // Support 2D POINT WKT, case-insensitive, with optional Z/M tokens.
48506
+ // Examples:
48507
+ // POINT (1 2)
48508
+ // point(1 2)
48509
+ // POINT Z (1 2 3)
48510
+ // POINT M (1 2 3)
48511
+ // POINT ZM (1 2 3 4)
48512
+ var m = /^\s*POINT(?:\s+Z(?:M)?|\s+M)?\s*\(\s*([^\s,]+)\s+([^\s,]+)(?:\s+[^)]*)?\)\s*$/i.exec(val);
48513
+ if (!m) return null;
48514
+ var x = coordinateFromValue(m[1]);
48515
+ var y = coordinateFromValue(m[2]);
48516
+ if (isNaN(x) || isNaN(y)) return null;
48517
+ return [x, y];
48518
+ }
48519
+
48520
+ function findWKTField(records, fields) {
48521
+ var sampleSize = Math.min(records.length, 50);
48522
+ return utils.find(fields, function(name) {
48523
+ var seen = 0;
48524
+ var matches = 0;
48525
+ for (var i = 0; i < sampleSize; i++) {
48526
+ var rec = records[i];
48527
+ if (!rec) continue;
48528
+ var val = rec[name];
48529
+ if (!utils.isString(val) || val.trim() === '') continue;
48530
+ seen++;
48531
+ if (parseWKTPoint(val)) matches++;
48532
+ }
48533
+ // Require at least one non-empty sample and majority parseable as POINT.
48534
+ return seen > 0 && matches / seen >= 0.6;
48535
+ });
48536
+ }
48537
+
48062
48538
  function pointsFromDataTableAuto(data) {
48063
48539
  var fields = data ? data.getFields() : [];
48540
+ var records = data ? data.getRecords() : [];
48064
48541
  var opts = {
48065
48542
  x: findXField(fields),
48066
48543
  y: findYField(fields)
48067
48544
  };
48545
+ if (!opts.x || !opts.y) {
48546
+ opts.wkt = findWKTField(records, fields);
48547
+ }
48068
48548
  return pointsFromDataTable(data, opts);
48069
48549
  }
48070
48550
 
48071
48551
  function pointsFromDataTable(data, opts) {
48072
48552
  if (!data) stop$1("Layer is missing a data table");
48553
+ if (opts.wkt && data.fieldExists(opts.wkt)) {
48554
+ return data.getRecords().map(function(rec) {
48555
+ var p = parseWKTPoint(rec[opts.wkt]);
48556
+ return p ? [p] : null;
48557
+ });
48558
+ }
48073
48559
  if (!opts.x || !opts.y || !data.fieldExists(opts.x) || !data.fieldExists(opts.y)) {
48074
48560
  stop$1("Missing x,y data fields");
48075
48561
  }
@@ -48087,8 +48573,10 @@ ${svg}
48087
48573
  var Points = /*#__PURE__*/Object.freeze({
48088
48574
  __proto__: null,
48089
48575
  coordinateFromValue: coordinateFromValue,
48576
+ findWKTField: findWKTField,
48090
48577
  findXField: findXField,
48091
48578
  findYField: findYField,
48579
+ parseWKTPoint: parseWKTPoint,
48092
48580
  pointsFromPolygons: pointsFromPolygons
48093
48581
  });
48094
48582
 
@@ -48477,6 +48965,9 @@ ${svg}
48477
48965
  }
48478
48966
 
48479
48967
  cmd.join = function(targetLyr, targetDataset, src, opts) {
48968
+ // Opt in to the post-join summary emitted by joinTableToLayer().
48969
+ // Other commands can reuse the helper without showing this message.
48970
+ opts = Object.assign({}, opts, {show_join_message: true});
48480
48971
  var srcType, targetType, retn;
48481
48972
  if (!src || !src.dataset) {
48482
48973
  stop$1("Missing a joinable data source");
@@ -51094,8 +51585,9 @@ ${svg}
51094
51585
  replaceInArray: replaceInArray
51095
51586
  });
51096
51587
 
51097
- cmd.simplify = function(dataset, opts) {
51588
+ cmd.simplify = function(dataset, opts, targetLayers) {
51098
51589
  var arcs = dataset.arcs;
51590
+ var implicitlySimplifiedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasPaths);
51099
51591
  if (!arcs || arcs.size() === 0) return; // removed in v0.4.125: stop("Missing path data");
51100
51592
  opts = getStandardSimplifyOpts(dataset, opts); // standardize options
51101
51593
  simplifyPaths(arcs, opts);
@@ -51114,6 +51606,12 @@ ${svg}
51114
51606
  }
51115
51607
 
51116
51608
  finalizeSimplification(dataset, opts);
51609
+ if (implicitlySimplifiedNames.length > 0) {
51610
+ message(
51611
+ 'Also simplified non-target layer' + utils.pluralSuffix(implicitlySimplifiedNames.length) +
51612
+ ' from the same dataset: ' + implicitlySimplifiedNames.join(', ')
51613
+ );
51614
+ }
51117
51615
  };
51118
51616
 
51119
51617
  function finalizeSimplification(dataset, opts) {
@@ -52807,7 +53305,7 @@ ${svg}
52807
53305
  await initProjLibrary(opts);
52808
53306
  job.resumeCommand();
52809
53307
  targets.forEach(function(targ) {
52810
- cmd.proj(targ.dataset, job.catalog, opts);
53308
+ cmd.proj(targ.dataset, job.catalog, opts, targ.layers);
52811
53309
  });
52812
53310
 
52813
53311
  } else if (name == 'rectangle') {
@@ -52850,7 +53348,7 @@ ${svg}
52850
53348
  if (opts.variable) {
52851
53349
  cmd.variableSimplify(targetLayers, targetDataset, opts);
52852
53350
  } else {
52853
- cmd.simplify(targetDataset, opts);
53351
+ cmd.simplify(targetDataset, opts, targetLayers);
52854
53352
  }
52855
53353
 
52856
53354
  } else if (name == 'slice') {
@@ -52976,7 +53474,7 @@ ${svg}
52976
53474
  });
52977
53475
  }
52978
53476
 
52979
- var version = "0.7.5";
53477
+ var version = "0.7.7";
52980
53478
 
52981
53479
  // Parse command line args into commands and run them
52982
53480
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.