mapshaper 0.7.5 → 0.7.6

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.
Files changed (3) hide show
  1. package/mapshaper.js +134 -36
  2. package/package.json +1 -1
  3. package/www/mapshaper.js +134 -36
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,
@@ -19446,7 +19474,9 @@ ${svg}
19446
19474
 
19447
19475
  function exportDbfFile(lyr, dataset, opts) {
19448
19476
  var data = lyr.data,
19449
- buf;
19477
+ buf,
19478
+ files,
19479
+ encoding;
19450
19480
  // create empty data table if missing a table or table is being cut out
19451
19481
  if (!data || opts.cut_table || opts.drop_table) {
19452
19482
  data = new DataTable(lyr.shapes ? lyr.shapes.length : 0);
@@ -19460,14 +19490,34 @@ ${svg}
19460
19490
  } else {
19461
19491
  buf = Dbf.exportRecords(data.getRecords(), opts.encoding, opts.field_order);
19462
19492
  }
19493
+ encoding = getDbfExportEncoding(data, opts);
19463
19494
  if (utils.isInteger(opts.ldid)) {
19464
19495
  new Uint8Array(buf)[29] = opts.ldid; // set language driver id
19465
19496
  }
19466
- // TODO: also export .cpg page
19467
- return [{
19497
+ files = [{
19468
19498
  content: buf,
19469
19499
  filename: lyr.name + '.dbf'
19470
19500
  }];
19501
+ if (opts.export_cpg && encoding) {
19502
+ files.push({
19503
+ content: formatCpgEncoding(encoding),
19504
+ filename: lyr.name + '.cpg'
19505
+ });
19506
+ }
19507
+ return files;
19508
+ }
19509
+
19510
+ function getDbfExportEncoding(data, opts) {
19511
+ if (data && data.getDbfEncoding) {
19512
+ return data.getDbfEncoding(opts);
19513
+ }
19514
+ return opts.encoding || 'utf8';
19515
+ }
19516
+
19517
+ function formatCpgEncoding(encoding) {
19518
+ // Canonical spelling for the default.
19519
+ if (/^utf-?8$/i.test(String(encoding))) return 'UTF-8';
19520
+ return String(encoding);
19471
19521
  }
19472
19522
 
19473
19523
  var decoder;
@@ -22232,8 +22282,9 @@ ${svg}
22232
22282
  function exportShapefile(dataset, opts) {
22233
22283
  return dataset.layers.reduce(function(files, lyr) {
22234
22284
  var prj = exportPrjFile(lyr, dataset);
22285
+ var dbfOpts = utils.defaults({export_cpg: true}, opts);
22235
22286
  files = files.concat(exportShpAndShxFiles(lyr, dataset));
22236
- files = files.concat(exportDbfFile(lyr, dataset, opts));
22287
+ files = files.concat(exportDbfFile(lyr, dataset, dbfOpts));
22237
22288
  if (prj) files.push(prj);
22238
22289
  return files;
22239
22290
  }, []);
@@ -30251,6 +30302,7 @@ ${svg}
30251
30302
  };
30252
30303
 
30253
30304
  this.getFields = getFieldNames;
30305
+ this.getEncoding = getEncoding;
30254
30306
 
30255
30307
  // TODO: switch to streaming output under Node.js
30256
30308
  this.getBuffer = function() {
@@ -30570,6 +30622,19 @@ ${svg}
30570
30622
  return Dbf.exportRecords(getTable().getRecords(), opts.encoding, opts.field_order);
30571
30623
  };
30572
30624
 
30625
+ // Return the text encoding that exportAsDbf() will use for the current
30626
+ // options. If we're exporting the untouched original DBF bytes, use the
30627
+ // reader's detected/declared encoding; otherwise we regenerate with either
30628
+ // opts.encoding or the writer default (utf8).
30629
+ this.getDbfEncoding = function(optsArg) {
30630
+ var opts = optsArg || {};
30631
+ var useOriginal = !!reader && !altered && !opts.field_order && !opts.encoding;
30632
+ if (useOriginal) {
30633
+ return reader.getEncoding();
30634
+ }
30635
+ return opts.encoding || 'utf8';
30636
+ };
30637
+
30573
30638
  this.getReadOnlyRecordAt = function(i) {
30574
30639
  return reader ? reader.readRow(i) : table.getReadOnlyRecordAt(i);
30575
30640
  };
@@ -31409,7 +31474,7 @@ ${svg}
31409
31474
  (srcCollection.features || srcCollection.geometries || []).forEach(importer.parseObject);
31410
31475
  dataset = importer.done();
31411
31476
  importCRS(dataset, srcObj); // TODO: remove this
31412
- warnIfProjectedCoords(dataset, srcObj);
31477
+ warnIfProjectedCoords(dataset, srcObj, opts);
31413
31478
  return dataset;
31414
31479
  }
31415
31480
 
@@ -31417,12 +31482,13 @@ ${svg}
31417
31482
  // `crs` member). If the imported file makes no CRS claim but its coordinates
31418
31483
  // are clearly outside lat-long range, warn the user: silent fallthrough here
31419
31484
  // usually surfaces later as cryptic -proj errors or grossly wrong output.
31420
- function warnIfProjectedCoords(dataset, jsonObj) {
31485
+ function warnIfProjectedCoords(dataset, jsonObj, opts) {
31486
+ if (!opts.warn_projected_coords) return;
31421
31487
  if (jsonObj && jsonObj.crs) return; // user has explicitly declared a CRS
31422
31488
  var bounds = getRoughDatasetBounds(dataset);
31423
31489
  if (!bounds || !bounds.hasBounds()) return;
31424
31490
  if (probablyDecimalDegreeBounds(bounds)) return;
31425
- warn('Imported GeoJSON has coordinates outside the lat-long range — ' +
31491
+ warn('Imported GeoJSON has coordinates outside the lat-long range -- ' +
31426
31492
  ' importing as projected geometry with unknown CRS. Commands ' +
31427
31493
  'like -proj that require a CRS will not work until you set a source ' +
31428
31494
  'CRS, e.g. -proj init=EPSG:3857.');
@@ -32485,7 +32551,7 @@ ${svg}
32485
32551
  if (fmt == 'topojson') {
32486
32552
  retn.dataset = importTopoJSON(content, opts);
32487
32553
  } else if (fmt == 'geojson') {
32488
- retn.dataset = importGeoJSON(content, opts);
32554
+ retn.dataset = importGeoJSON(content, getGeoJSONImportOpts(opts));
32489
32555
  } else if (fmt == 'json') {
32490
32556
  retn.dataset = importJSONTable(content);
32491
32557
  } else {
@@ -32497,6 +32563,12 @@ ${svg}
32497
32563
  return retn;
32498
32564
  }
32499
32565
 
32566
+ function getGeoJSONImportOpts(opts) {
32567
+ return Object.assign({}, opts, {
32568
+ warn_projected_coords: true
32569
+ });
32570
+ }
32571
+
32500
32572
  // path: path from top-level to the target object
32501
32573
  function selectFromObject(o, path) {
32502
32574
  var arrayRxp = /(.*)\[([0-9]+)\]$/; // array bracket notation w/ index
@@ -43429,20 +43501,24 @@ ${svg}
43429
43501
  }
43430
43502
  }
43431
43503
 
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
- });
43504
+ // Opt-in summary logging. Some commands (e.g. -divide) reuse this join
43505
+ // helper internally and don't want user-facing join diagnostics.
43506
+ if (opts.show_join_message) {
43507
+ printJoinMessage({
43508
+ matches: matchCount,
43509
+ n: n,
43510
+ joins: countJoins(joinCounts),
43511
+ m: srcRecords.length,
43512
+ skipped: skipCount,
43513
+ collisions: collisionCount,
43514
+ collisionFields: collisionFields,
43515
+ destKey: destKey,
43516
+ srcKey: srcKey,
43517
+ unmatchedTargetKeys: takeSampleKeys(unmatchedTargetKeys),
43518
+ unusedSourceKeys: takeSampleKeys(unusedSourceKeys),
43519
+ opts: opts
43520
+ });
43521
+ }
43446
43522
 
43447
43523
  if (opts.unjoined) {
43448
43524
  retn.unjoined = {
@@ -46804,7 +46880,7 @@ ${svg}
46804
46880
  // Works for lcc, aea, tmerc, etc.
46805
46881
  // TODO: add more projections
46806
46882
  //
46807
- function expandProjDefn(str, dataset) {
46883
+ function expandProjDefn(str, dataset, targetLayers) {
46808
46884
  var mproj = require$1('mproj');
46809
46885
  var proj4, params, bbox, isConic2SP, isCentered, decimals;
46810
46886
  if (str in mproj.internal.pj_list === false) {
@@ -46815,7 +46891,7 @@ ${svg}
46815
46891
  isCentered = ['tmerc', 'etmerc'].includes(str);
46816
46892
  proj4 = '+proj=' + str;
46817
46893
  if (isConic2SP || isCentered) {
46818
- bbox = getBBox(dataset); // TODO: support projected datasets
46894
+ bbox = getBBox(dataset, targetLayers); // TODO: support projected datasets
46819
46895
  decimals = getBoundsPrecisionForDisplay(bbox);
46820
46896
  params = isCentered ? getCenterParams(bbox, decimals) : getConicParams(bbox, decimals);
46821
46897
  proj4 += ' ' + params;
@@ -46824,11 +46900,16 @@ ${svg}
46824
46900
  return proj4;
46825
46901
  }
46826
46902
 
46827
- function getBBox(dataset) {
46903
+ function getBBox(dataset, targetLayers) {
46904
+ var source = targetLayers && targetLayers.length > 0 ? {
46905
+ arcs: dataset.arcs,
46906
+ layers: targetLayers,
46907
+ info: dataset.info
46908
+ } : dataset;
46828
46909
  if (!isLatLngCRS(getDatasetCRS(dataset))) {
46829
46910
  stop$1('Expected unprojected data');
46830
46911
  }
46831
- return getDatasetBounds(dataset).toArray();
46912
+ return getDatasetBounds(source).toArray();
46832
46913
  }
46833
46914
 
46834
46915
  // See: Savric & Jenny, "Automating the selection of standard parallels for conic map projections"
@@ -46854,8 +46935,9 @@ ${svg}
46854
46935
  getConicParams: getConicParams
46855
46936
  });
46856
46937
 
46857
- cmd.proj = function(dataset, catalog, opts) {
46938
+ cmd.proj = function(dataset, catalog, opts, targetLayers) {
46858
46939
  var srcInfo, destInfo, destStr;
46940
+ var implicitlyProjectedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasGeometry);
46859
46941
  if (opts.init) {
46860
46942
  srcInfo = fetchCrsInfo(opts.init, catalog);
46861
46943
  if (!srcInfo.crs) stop$1("Unknown projection source:", opts.init);
@@ -46864,11 +46946,17 @@ ${svg}
46864
46946
  if (opts.match) {
46865
46947
  destInfo = fetchCrsInfo(opts.match, catalog);
46866
46948
  } else if (opts.crs) {
46867
- destStr = expandProjDefn(opts.crs, dataset);
46949
+ destStr = expandProjDefn(opts.crs, dataset, targetLayers);
46868
46950
  destInfo = getCrsInfo(destStr);
46869
46951
  }
46870
46952
  if (destInfo) {
46871
- projCmd(dataset, destInfo, opts);
46953
+ var didProject = projCmd(dataset, destInfo, opts);
46954
+ if (didProject && implicitlyProjectedNames.length > 0) {
46955
+ message(
46956
+ 'Also projected non-target layer' + utils.pluralSuffix(implicitlyProjectedNames.length) +
46957
+ ' from the same dataset: ' + implicitlyProjectedNames.join(', ')
46958
+ );
46959
+ }
46872
46960
  }
46873
46961
  };
46874
46962
 
@@ -46886,7 +46974,7 @@ ${svg}
46886
46974
  if (!datasetHasGeometry(dataset)) {
46887
46975
  // still set the crs of datasets that are missing geometry
46888
46976
  setDatasetCrsInfo(dataset, destInfo);
46889
- return;
46977
+ return false;
46890
46978
  }
46891
46979
 
46892
46980
  var srcInfo = getDatasetCrsInfo(dataset);
@@ -46896,7 +46984,7 @@ ${svg}
46896
46984
 
46897
46985
  if (crsAreEqual(srcInfo.crs, destInfo.crs)) {
46898
46986
  message("Source and destination CRS are the same");
46899
- return;
46987
+ return false;
46900
46988
  }
46901
46989
 
46902
46990
  if (dataset.arcs) {
@@ -46922,9 +47010,9 @@ ${svg}
46922
47010
  // replace original layers with modified layers
46923
47011
  utils.extend(lyr, target.layers[i]);
46924
47012
  });
47013
+ return true;
46925
47014
  }
46926
47015
 
46927
-
46928
47016
  // name: a layer identifier, .prj file or projection defn
46929
47017
  // Converts layer ids and .prj files to CRS defn
46930
47018
  // Returns projection defn
@@ -48477,6 +48565,9 @@ ${svg}
48477
48565
  }
48478
48566
 
48479
48567
  cmd.join = function(targetLyr, targetDataset, src, opts) {
48568
+ // Opt in to the post-join summary emitted by joinTableToLayer().
48569
+ // Other commands can reuse the helper without showing this message.
48570
+ opts = Object.assign({}, opts, {show_join_message: true});
48480
48571
  var srcType, targetType, retn;
48481
48572
  if (!src || !src.dataset) {
48482
48573
  stop$1("Missing a joinable data source");
@@ -51094,8 +51185,9 @@ ${svg}
51094
51185
  replaceInArray: replaceInArray
51095
51186
  });
51096
51187
 
51097
- cmd.simplify = function(dataset, opts) {
51188
+ cmd.simplify = function(dataset, opts, targetLayers) {
51098
51189
  var arcs = dataset.arcs;
51190
+ var implicitlySimplifiedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasPaths);
51099
51191
  if (!arcs || arcs.size() === 0) return; // removed in v0.4.125: stop("Missing path data");
51100
51192
  opts = getStandardSimplifyOpts(dataset, opts); // standardize options
51101
51193
  simplifyPaths(arcs, opts);
@@ -51114,6 +51206,12 @@ ${svg}
51114
51206
  }
51115
51207
 
51116
51208
  finalizeSimplification(dataset, opts);
51209
+ if (implicitlySimplifiedNames.length > 0) {
51210
+ message(
51211
+ 'Also simplified non-target layer' + utils.pluralSuffix(implicitlySimplifiedNames.length) +
51212
+ ' from the same dataset: ' + implicitlySimplifiedNames.join(', ')
51213
+ );
51214
+ }
51117
51215
  };
51118
51216
 
51119
51217
  function finalizeSimplification(dataset, opts) {
@@ -52807,7 +52905,7 @@ ${svg}
52807
52905
  await initProjLibrary(opts);
52808
52906
  job.resumeCommand();
52809
52907
  targets.forEach(function(targ) {
52810
- cmd.proj(targ.dataset, job.catalog, opts);
52908
+ cmd.proj(targ.dataset, job.catalog, opts, targ.layers);
52811
52909
  });
52812
52910
 
52813
52911
  } else if (name == 'rectangle') {
@@ -52850,7 +52948,7 @@ ${svg}
52850
52948
  if (opts.variable) {
52851
52949
  cmd.variableSimplify(targetLayers, targetDataset, opts);
52852
52950
  } else {
52853
- cmd.simplify(targetDataset, opts);
52951
+ cmd.simplify(targetDataset, opts, targetLayers);
52854
52952
  }
52855
52953
 
52856
52954
  } else if (name == 'slice') {
@@ -52976,7 +53074,7 @@ ${svg}
52976
53074
  });
52977
53075
  }
52978
53076
 
52979
- var version = "0.7.5";
53077
+ var version = "0.7.6";
52980
53078
 
52981
53079
  // Parse command line args into commands and run them
52982
53080
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.7.5",
3
+ "version": "0.7.6",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
package/www/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,
@@ -19446,7 +19474,9 @@ ${svg}
19446
19474
 
19447
19475
  function exportDbfFile(lyr, dataset, opts) {
19448
19476
  var data = lyr.data,
19449
- buf;
19477
+ buf,
19478
+ files,
19479
+ encoding;
19450
19480
  // create empty data table if missing a table or table is being cut out
19451
19481
  if (!data || opts.cut_table || opts.drop_table) {
19452
19482
  data = new DataTable(lyr.shapes ? lyr.shapes.length : 0);
@@ -19460,14 +19490,34 @@ ${svg}
19460
19490
  } else {
19461
19491
  buf = Dbf.exportRecords(data.getRecords(), opts.encoding, opts.field_order);
19462
19492
  }
19493
+ encoding = getDbfExportEncoding(data, opts);
19463
19494
  if (utils.isInteger(opts.ldid)) {
19464
19495
  new Uint8Array(buf)[29] = opts.ldid; // set language driver id
19465
19496
  }
19466
- // TODO: also export .cpg page
19467
- return [{
19497
+ files = [{
19468
19498
  content: buf,
19469
19499
  filename: lyr.name + '.dbf'
19470
19500
  }];
19501
+ if (opts.export_cpg && encoding) {
19502
+ files.push({
19503
+ content: formatCpgEncoding(encoding),
19504
+ filename: lyr.name + '.cpg'
19505
+ });
19506
+ }
19507
+ return files;
19508
+ }
19509
+
19510
+ function getDbfExportEncoding(data, opts) {
19511
+ if (data && data.getDbfEncoding) {
19512
+ return data.getDbfEncoding(opts);
19513
+ }
19514
+ return opts.encoding || 'utf8';
19515
+ }
19516
+
19517
+ function formatCpgEncoding(encoding) {
19518
+ // Canonical spelling for the default.
19519
+ if (/^utf-?8$/i.test(String(encoding))) return 'UTF-8';
19520
+ return String(encoding);
19471
19521
  }
19472
19522
 
19473
19523
  var decoder;
@@ -22232,8 +22282,9 @@ ${svg}
22232
22282
  function exportShapefile(dataset, opts) {
22233
22283
  return dataset.layers.reduce(function(files, lyr) {
22234
22284
  var prj = exportPrjFile(lyr, dataset);
22285
+ var dbfOpts = utils.defaults({export_cpg: true}, opts);
22235
22286
  files = files.concat(exportShpAndShxFiles(lyr, dataset));
22236
- files = files.concat(exportDbfFile(lyr, dataset, opts));
22287
+ files = files.concat(exportDbfFile(lyr, dataset, dbfOpts));
22237
22288
  if (prj) files.push(prj);
22238
22289
  return files;
22239
22290
  }, []);
@@ -30251,6 +30302,7 @@ ${svg}
30251
30302
  };
30252
30303
 
30253
30304
  this.getFields = getFieldNames;
30305
+ this.getEncoding = getEncoding;
30254
30306
 
30255
30307
  // TODO: switch to streaming output under Node.js
30256
30308
  this.getBuffer = function() {
@@ -30570,6 +30622,19 @@ ${svg}
30570
30622
  return Dbf.exportRecords(getTable().getRecords(), opts.encoding, opts.field_order);
30571
30623
  };
30572
30624
 
30625
+ // Return the text encoding that exportAsDbf() will use for the current
30626
+ // options. If we're exporting the untouched original DBF bytes, use the
30627
+ // reader's detected/declared encoding; otherwise we regenerate with either
30628
+ // opts.encoding or the writer default (utf8).
30629
+ this.getDbfEncoding = function(optsArg) {
30630
+ var opts = optsArg || {};
30631
+ var useOriginal = !!reader && !altered && !opts.field_order && !opts.encoding;
30632
+ if (useOriginal) {
30633
+ return reader.getEncoding();
30634
+ }
30635
+ return opts.encoding || 'utf8';
30636
+ };
30637
+
30573
30638
  this.getReadOnlyRecordAt = function(i) {
30574
30639
  return reader ? reader.readRow(i) : table.getReadOnlyRecordAt(i);
30575
30640
  };
@@ -31409,7 +31474,7 @@ ${svg}
31409
31474
  (srcCollection.features || srcCollection.geometries || []).forEach(importer.parseObject);
31410
31475
  dataset = importer.done();
31411
31476
  importCRS(dataset, srcObj); // TODO: remove this
31412
- warnIfProjectedCoords(dataset, srcObj);
31477
+ warnIfProjectedCoords(dataset, srcObj, opts);
31413
31478
  return dataset;
31414
31479
  }
31415
31480
 
@@ -31417,12 +31482,13 @@ ${svg}
31417
31482
  // `crs` member). If the imported file makes no CRS claim but its coordinates
31418
31483
  // are clearly outside lat-long range, warn the user: silent fallthrough here
31419
31484
  // usually surfaces later as cryptic -proj errors or grossly wrong output.
31420
- function warnIfProjectedCoords(dataset, jsonObj) {
31485
+ function warnIfProjectedCoords(dataset, jsonObj, opts) {
31486
+ if (!opts.warn_projected_coords) return;
31421
31487
  if (jsonObj && jsonObj.crs) return; // user has explicitly declared a CRS
31422
31488
  var bounds = getRoughDatasetBounds(dataset);
31423
31489
  if (!bounds || !bounds.hasBounds()) return;
31424
31490
  if (probablyDecimalDegreeBounds(bounds)) return;
31425
- warn('Imported GeoJSON has coordinates outside the lat-long range — ' +
31491
+ warn('Imported GeoJSON has coordinates outside the lat-long range -- ' +
31426
31492
  ' importing as projected geometry with unknown CRS. Commands ' +
31427
31493
  'like -proj that require a CRS will not work until you set a source ' +
31428
31494
  'CRS, e.g. -proj init=EPSG:3857.');
@@ -32485,7 +32551,7 @@ ${svg}
32485
32551
  if (fmt == 'topojson') {
32486
32552
  retn.dataset = importTopoJSON(content, opts);
32487
32553
  } else if (fmt == 'geojson') {
32488
- retn.dataset = importGeoJSON(content, opts);
32554
+ retn.dataset = importGeoJSON(content, getGeoJSONImportOpts(opts));
32489
32555
  } else if (fmt == 'json') {
32490
32556
  retn.dataset = importJSONTable(content);
32491
32557
  } else {
@@ -32497,6 +32563,12 @@ ${svg}
32497
32563
  return retn;
32498
32564
  }
32499
32565
 
32566
+ function getGeoJSONImportOpts(opts) {
32567
+ return Object.assign({}, opts, {
32568
+ warn_projected_coords: true
32569
+ });
32570
+ }
32571
+
32500
32572
  // path: path from top-level to the target object
32501
32573
  function selectFromObject(o, path) {
32502
32574
  var arrayRxp = /(.*)\[([0-9]+)\]$/; // array bracket notation w/ index
@@ -43429,20 +43501,24 @@ ${svg}
43429
43501
  }
43430
43502
  }
43431
43503
 
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
- });
43504
+ // Opt-in summary logging. Some commands (e.g. -divide) reuse this join
43505
+ // helper internally and don't want user-facing join diagnostics.
43506
+ if (opts.show_join_message) {
43507
+ printJoinMessage({
43508
+ matches: matchCount,
43509
+ n: n,
43510
+ joins: countJoins(joinCounts),
43511
+ m: srcRecords.length,
43512
+ skipped: skipCount,
43513
+ collisions: collisionCount,
43514
+ collisionFields: collisionFields,
43515
+ destKey: destKey,
43516
+ srcKey: srcKey,
43517
+ unmatchedTargetKeys: takeSampleKeys(unmatchedTargetKeys),
43518
+ unusedSourceKeys: takeSampleKeys(unusedSourceKeys),
43519
+ opts: opts
43520
+ });
43521
+ }
43446
43522
 
43447
43523
  if (opts.unjoined) {
43448
43524
  retn.unjoined = {
@@ -46804,7 +46880,7 @@ ${svg}
46804
46880
  // Works for lcc, aea, tmerc, etc.
46805
46881
  // TODO: add more projections
46806
46882
  //
46807
- function expandProjDefn(str, dataset) {
46883
+ function expandProjDefn(str, dataset, targetLayers) {
46808
46884
  var mproj = require$1('mproj');
46809
46885
  var proj4, params, bbox, isConic2SP, isCentered, decimals;
46810
46886
  if (str in mproj.internal.pj_list === false) {
@@ -46815,7 +46891,7 @@ ${svg}
46815
46891
  isCentered = ['tmerc', 'etmerc'].includes(str);
46816
46892
  proj4 = '+proj=' + str;
46817
46893
  if (isConic2SP || isCentered) {
46818
- bbox = getBBox(dataset); // TODO: support projected datasets
46894
+ bbox = getBBox(dataset, targetLayers); // TODO: support projected datasets
46819
46895
  decimals = getBoundsPrecisionForDisplay(bbox);
46820
46896
  params = isCentered ? getCenterParams(bbox, decimals) : getConicParams(bbox, decimals);
46821
46897
  proj4 += ' ' + params;
@@ -46824,11 +46900,16 @@ ${svg}
46824
46900
  return proj4;
46825
46901
  }
46826
46902
 
46827
- function getBBox(dataset) {
46903
+ function getBBox(dataset, targetLayers) {
46904
+ var source = targetLayers && targetLayers.length > 0 ? {
46905
+ arcs: dataset.arcs,
46906
+ layers: targetLayers,
46907
+ info: dataset.info
46908
+ } : dataset;
46828
46909
  if (!isLatLngCRS(getDatasetCRS(dataset))) {
46829
46910
  stop$1('Expected unprojected data');
46830
46911
  }
46831
- return getDatasetBounds(dataset).toArray();
46912
+ return getDatasetBounds(source).toArray();
46832
46913
  }
46833
46914
 
46834
46915
  // See: Savric & Jenny, "Automating the selection of standard parallels for conic map projections"
@@ -46854,8 +46935,9 @@ ${svg}
46854
46935
  getConicParams: getConicParams
46855
46936
  });
46856
46937
 
46857
- cmd.proj = function(dataset, catalog, opts) {
46938
+ cmd.proj = function(dataset, catalog, opts, targetLayers) {
46858
46939
  var srcInfo, destInfo, destStr;
46940
+ var implicitlyProjectedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasGeometry);
46859
46941
  if (opts.init) {
46860
46942
  srcInfo = fetchCrsInfo(opts.init, catalog);
46861
46943
  if (!srcInfo.crs) stop$1("Unknown projection source:", opts.init);
@@ -46864,11 +46946,17 @@ ${svg}
46864
46946
  if (opts.match) {
46865
46947
  destInfo = fetchCrsInfo(opts.match, catalog);
46866
46948
  } else if (opts.crs) {
46867
- destStr = expandProjDefn(opts.crs, dataset);
46949
+ destStr = expandProjDefn(opts.crs, dataset, targetLayers);
46868
46950
  destInfo = getCrsInfo(destStr);
46869
46951
  }
46870
46952
  if (destInfo) {
46871
- projCmd(dataset, destInfo, opts);
46953
+ var didProject = projCmd(dataset, destInfo, opts);
46954
+ if (didProject && implicitlyProjectedNames.length > 0) {
46955
+ message(
46956
+ 'Also projected non-target layer' + utils.pluralSuffix(implicitlyProjectedNames.length) +
46957
+ ' from the same dataset: ' + implicitlyProjectedNames.join(', ')
46958
+ );
46959
+ }
46872
46960
  }
46873
46961
  };
46874
46962
 
@@ -46886,7 +46974,7 @@ ${svg}
46886
46974
  if (!datasetHasGeometry(dataset)) {
46887
46975
  // still set the crs of datasets that are missing geometry
46888
46976
  setDatasetCrsInfo(dataset, destInfo);
46889
- return;
46977
+ return false;
46890
46978
  }
46891
46979
 
46892
46980
  var srcInfo = getDatasetCrsInfo(dataset);
@@ -46896,7 +46984,7 @@ ${svg}
46896
46984
 
46897
46985
  if (crsAreEqual(srcInfo.crs, destInfo.crs)) {
46898
46986
  message("Source and destination CRS are the same");
46899
- return;
46987
+ return false;
46900
46988
  }
46901
46989
 
46902
46990
  if (dataset.arcs) {
@@ -46922,9 +47010,9 @@ ${svg}
46922
47010
  // replace original layers with modified layers
46923
47011
  utils.extend(lyr, target.layers[i]);
46924
47012
  });
47013
+ return true;
46925
47014
  }
46926
47015
 
46927
-
46928
47016
  // name: a layer identifier, .prj file or projection defn
46929
47017
  // Converts layer ids and .prj files to CRS defn
46930
47018
  // Returns projection defn
@@ -48477,6 +48565,9 @@ ${svg}
48477
48565
  }
48478
48566
 
48479
48567
  cmd.join = function(targetLyr, targetDataset, src, opts) {
48568
+ // Opt in to the post-join summary emitted by joinTableToLayer().
48569
+ // Other commands can reuse the helper without showing this message.
48570
+ opts = Object.assign({}, opts, {show_join_message: true});
48480
48571
  var srcType, targetType, retn;
48481
48572
  if (!src || !src.dataset) {
48482
48573
  stop$1("Missing a joinable data source");
@@ -51094,8 +51185,9 @@ ${svg}
51094
51185
  replaceInArray: replaceInArray
51095
51186
  });
51096
51187
 
51097
- cmd.simplify = function(dataset, opts) {
51188
+ cmd.simplify = function(dataset, opts, targetLayers) {
51098
51189
  var arcs = dataset.arcs;
51190
+ var implicitlySimplifiedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasPaths);
51099
51191
  if (!arcs || arcs.size() === 0) return; // removed in v0.4.125: stop("Missing path data");
51100
51192
  opts = getStandardSimplifyOpts(dataset, opts); // standardize options
51101
51193
  simplifyPaths(arcs, opts);
@@ -51114,6 +51206,12 @@ ${svg}
51114
51206
  }
51115
51207
 
51116
51208
  finalizeSimplification(dataset, opts);
51209
+ if (implicitlySimplifiedNames.length > 0) {
51210
+ message(
51211
+ 'Also simplified non-target layer' + utils.pluralSuffix(implicitlySimplifiedNames.length) +
51212
+ ' from the same dataset: ' + implicitlySimplifiedNames.join(', ')
51213
+ );
51214
+ }
51117
51215
  };
51118
51216
 
51119
51217
  function finalizeSimplification(dataset, opts) {
@@ -52807,7 +52905,7 @@ ${svg}
52807
52905
  await initProjLibrary(opts);
52808
52906
  job.resumeCommand();
52809
52907
  targets.forEach(function(targ) {
52810
- cmd.proj(targ.dataset, job.catalog, opts);
52908
+ cmd.proj(targ.dataset, job.catalog, opts, targ.layers);
52811
52909
  });
52812
52910
 
52813
52911
  } else if (name == 'rectangle') {
@@ -52850,7 +52948,7 @@ ${svg}
52850
52948
  if (opts.variable) {
52851
52949
  cmd.variableSimplify(targetLayers, targetDataset, opts);
52852
52950
  } else {
52853
- cmd.simplify(targetDataset, opts);
52951
+ cmd.simplify(targetDataset, opts, targetLayers);
52854
52952
  }
52855
52953
 
52856
52954
  } else if (name == 'slice') {
@@ -52976,7 +53074,7 @@ ${svg}
52976
53074
  });
52977
53075
  }
52978
53076
 
52979
- var version = "0.7.5";
53077
+ var version = "0.7.6";
52980
53078
 
52981
53079
  // Parse command line args into commands and run them
52982
53080
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.