mapshaper 0.7.8 → 0.7.9

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.
@@ -2099,7 +2099,7 @@
2099
2099
  }
2100
2100
 
2101
2101
  async function loadGeoParquetLib() {
2102
- if (!window.modules || !window.modules.hyparquet || !window.modules['hyparquet-writer']) {
2102
+ if (!window.modules || !window.modules.hyparquet || !window.modules['hyparquet-compressors'] || !window.modules['hyparquet-writer'] || !window.modules['zstd-codec']) {
2103
2103
  if (!geoParquetPromise) {
2104
2104
  geoParquetPromise = loadScript('geoparquet.js');
2105
2105
  }
@@ -4113,6 +4113,116 @@
4113
4113
  }
4114
4114
  };
4115
4115
 
4116
+ var FIELD_TYPE_SCAN_LIMIT = 1000;
4117
+ var RECENT_COMMAND_LIMIT = 20;
4118
+ var RECENT_MESSAGE_LIMIT = 10;
4119
+
4120
+ function getRuntimeStateContext(gui) {
4121
+ var layers = gui.model.getLayers();
4122
+ var active = gui.model.getActiveLayer();
4123
+ var history = gui.session.getHistorySnapshot();
4124
+ var messages = gui.getMessages ? gui.getMessages() : [];
4125
+
4126
+ return {
4127
+ schema: 'mapshaper-runtime-context',
4128
+ schema_version: 1,
4129
+ generated_at: new Date().toISOString(),
4130
+ privacy_note: 'Contains project metadata only: layer names, field names/types, counts, CRS, recent commands and recent messages. It does not include geometry or attribute records.',
4131
+ ui: getUiContext(gui),
4132
+ active_layer: active ? getLayerTargetId(gui, active.layer) : null,
4133
+ default_targets: getDefaultTargets(gui),
4134
+ layers: layers.map(function(o, i) {
4135
+ return getLayerContext(gui, o.layer, o.dataset, i, active && active.layer);
4136
+ }),
4137
+ session: {
4138
+ command_count: history.commands.length,
4139
+ recent_commands: history.commands.slice(-RECENT_COMMAND_LIMIT),
4140
+ unsaved_changes: gui.session.unsavedChanges()
4141
+ },
4142
+ messages: messages.slice(0, RECENT_MESSAGE_LIMIT)
4143
+ };
4144
+ }
4145
+
4146
+ function stringifyRuntimeStateContext(gui) {
4147
+ return JSON.stringify(getRuntimeStateContext(gui), null, 2);
4148
+ }
4149
+
4150
+ function getUiContext(gui) {
4151
+ return {
4152
+ interface: 'web_app',
4153
+ console_open: gui.consoleIsOpen(),
4154
+ panel_mode: gui.getMode(),
4155
+ interaction_mode: gui.interaction ? gui.interaction.getMode() : null,
4156
+ dataset_count: gui.model.getDatasets().length,
4157
+ layer_count: gui.model.getLayers().length,
4158
+ has_data: !gui.model.isEmpty()
4159
+ };
4160
+ }
4161
+
4162
+ function getDefaultTargets(gui) {
4163
+ return gui.model.getDefaultTargets().map(function(target) {
4164
+ return {
4165
+ layers: target.layers.map(function(lyr) {
4166
+ return getLayerTargetId(gui, lyr);
4167
+ })
4168
+ };
4169
+ });
4170
+ }
4171
+
4172
+ function getLayerContext(gui, lyr, dataset, i, activeLyr) {
4173
+ var fields = getFieldContexts(lyr);
4174
+ var context = {
4175
+ id: i + 1,
4176
+ target_id: getLayerTargetId(gui, lyr),
4177
+ name: lyr.name || null,
4178
+ active: lyr == activeLyr,
4179
+ geometry_type: lyr.geometry_type || null,
4180
+ feature_count: internal.getFeatureCount(lyr),
4181
+ field_count: fields.length,
4182
+ fields: fields
4183
+ };
4184
+ if (lyr.geometry_type) {
4185
+ context.crs = getCrsContext(dataset);
4186
+ }
4187
+ if (dataset.info && dataset.info.input_formats) {
4188
+ context.input_formats = dataset.info.input_formats.slice();
4189
+ }
4190
+ return context;
4191
+ }
4192
+
4193
+ function getCrsContext(dataset) {
4194
+ var crs = null;
4195
+ try {
4196
+ crs = internal.getProjInfo(dataset);
4197
+ } catch(e) {}
4198
+ return crs || null;
4199
+ }
4200
+
4201
+ function getFieldContexts(lyr) {
4202
+ if (!lyr.data || lyr.data.size() === 0) return [];
4203
+ return lyr.data.getFields().map(function(name) {
4204
+ return {
4205
+ name: name,
4206
+ type: getSampledFieldType(lyr.data, name)
4207
+ };
4208
+ });
4209
+ }
4210
+
4211
+ function getSampledFieldType(table, name) {
4212
+ var n = Math.min(table.size(), FIELD_TYPE_SCAN_LIMIT);
4213
+ var rec, type;
4214
+ for (var i = 0; i < n; i++) {
4215
+ rec = table.getReadOnlyRecordAt(i);
4216
+ type = rec ? internal.getValueType(rec[name]) : null;
4217
+ if (type) return type;
4218
+ }
4219
+ return null;
4220
+ }
4221
+
4222
+ function getLayerTargetId(gui, lyr) {
4223
+ return internal.formatOptionValue(internal.getLayerTargetId(gui.model, lyr));
4224
+ }
4225
+
4116
4226
  function Console(gui) {
4117
4227
  var model = gui.model;
4118
4228
  var CURSOR = '$ ';
@@ -4476,6 +4586,10 @@
4476
4586
  clear();
4477
4587
  } else if (cmd == 'tips') {
4478
4588
  printExamples();
4589
+ } else if (cmd == 'context') {
4590
+ toLog(stringifyRuntimeStateContext(gui));
4591
+ } else if (cmd == 'context download') {
4592
+ saveRuntimeContext();
4479
4593
  } else if (cmd == 'history') {
4480
4594
  toLog(gui.session.toCommandLineString());
4481
4595
  } else if (cmd == 'layers') {
@@ -4628,12 +4742,19 @@
4628
4742
  printExample("See a list of all console commands", "$ help");
4629
4743
  printExample("Get help using a single command", "$ help innerlines");
4630
4744
  printExample("Get information about imported datasets", "$ info");
4745
+ printExample("Print bot/debug runtime context as JSON", "$ context");
4631
4746
  printExample("Display browser session as shell commands", "$ history");
4632
4747
  printExample("Delete one state from a national dataset","$ filter 'STATE != \"Alaska\"'");
4633
4748
  printExample("Aggregate counties to states by dissolving shared edges" ,"$ dissolve 'STATE'");
4634
4749
  printExample("Clear the console", "$ clear");
4635
4750
  }
4636
4751
 
4752
+ function saveRuntimeContext() {
4753
+ var json = stringifyRuntimeStateContext(gui);
4754
+ saveBlobToLocalFile2('mapshaper-runtime-context.json', new Blob([json], {type: 'application/json'}));
4755
+ toLog('Saved runtime context to mapshaper-runtime-context.json', 'console-message');
4756
+ }
4757
+
4637
4758
  }
4638
4759
 
4639
4760
  // Test if map should be re-framed to show updated layer
@@ -15159,6 +15280,18 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
15159
15280
  }
15160
15281
  };
15161
15282
 
15283
+ gui.getMessages = function() {
15284
+ return entries.map(function(entry) {
15285
+ return {
15286
+ severity: entry.severity,
15287
+ title: entry.title,
15288
+ body: entry.body,
15289
+ count: entry.count,
15290
+ time: entry.time.toISOString()
15291
+ };
15292
+ });
15293
+ };
15294
+
15162
15295
  function severityRank(s) {
15163
15296
  return s === 'error' ? 2 : s === 'warn' ? 1 : 0;
15164
15297
  }
@@ -15327,6 +15460,14 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
15327
15460
  return gui.container.hasClass('console-open');
15328
15461
  };
15329
15462
 
15463
+ gui.getRuntimeStateContext = function() {
15464
+ return getRuntimeStateContext(gui);
15465
+ };
15466
+
15467
+ gui.stringifyRuntimeStateContext = function() {
15468
+ return stringifyRuntimeStateContext(gui);
15469
+ };
15470
+
15330
15471
  // Make this instance interactive and editable
15331
15472
  gui.focus = function() {
15332
15473
  var curr = GUI.__active;
@@ -15440,6 +15581,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
15440
15581
  new LayerControl(gui);
15441
15582
  HeaderMenu();
15442
15583
  gui.console = new Console(gui);
15584
+ window.mapshaper.getRuntimeStateContext = gui.getRuntimeStateContext;
15585
+ window.mapshaper.stringifyRuntimeStateContext = gui.stringifyRuntimeStateContext;
15443
15586
 
15444
15587
  startEditing = function() {};
15445
15588
 
package/www/mapshaper.js CHANGED
@@ -25683,52 +25683,71 @@ ${svg}
25683
25683
  }
25684
25684
 
25685
25685
  var writerPromise = null;
25686
+ var zstdPromise = null;
25686
25687
  var dynamicImportModule$1 = Function('id', 'return import(id)');
25687
25688
 
25688
25689
  async function exportGeoParquet(dataset, opts, filenameOverride) {
25689
25690
  var writer = await loadGeoParquetWriter();
25691
+ var compression = await getGeoParquetCompression(opts);
25690
25692
  var extension = opts.extension || 'parquet';
25693
+ var files = [];
25691
25694
  if (opts.file) {
25692
25695
  extension = getFileExtension(opts.file) || extension;
25693
25696
  }
25694
- return dataset.layers.map(function(lyr) {
25695
- if (!lyr.geometry_type) {
25696
- stop$1('GeoParquet export requires a geometry layer');
25697
- }
25697
+ dataset.layers.forEach(function(lyr) {
25698
25698
  var features = exportLayerAsGeoJSON(lyr, dataset, opts, true, null);
25699
- var output = buildGeoParquetColumns(features);
25700
- var geoMetadata = buildGeoMetadata(features, dataset);
25701
- var content = writer.parquetWriteBuffer({
25699
+ var hasGeometry = features.some(function(feat) {
25700
+ return !!feat.geometry;
25701
+ });
25702
+ var output = buildGeoParquetColumns(features, hasGeometry);
25703
+ var writeOptions = {
25702
25704
  columnData: output.columnData,
25703
- kvMetadata: [{
25705
+ codec: compression.codec,
25706
+ compressors: compression.compressors,
25707
+ pageSize: compression.pageSize
25708
+ };
25709
+ if (hasGeometry) {
25710
+ writeOptions.kvMetadata = [{
25704
25711
  key: 'geo',
25705
- value: JSON.stringify(geoMetadata)
25706
- }]
25707
- });
25708
- return {
25712
+ value: JSON.stringify(buildGeoMetadata(features, dataset))
25713
+ }];
25714
+ } else {
25715
+ warn('GeoParquet export: layer has no geometry; writing attribute data only.');
25716
+ }
25717
+ var content = writer.parquetWriteBuffer(writeOptions);
25718
+ files.push({
25709
25719
  filename: filenameOverride || (lyr.name + '.' + extension),
25710
25720
  content: content
25711
- };
25721
+ });
25712
25722
  });
25723
+ return files;
25713
25724
  }
25714
25725
 
25715
- function buildGeoParquetColumns(features, writer) {
25726
+ function buildGeoParquetColumns(features, includeGeometry) {
25716
25727
  var geometryName = 'geometry';
25717
25728
  var names = getPropertyNames(features);
25718
25729
  var columnData = [];
25719
- columnData.push({
25720
- name: geometryName,
25721
- data: features.map(function(feat) {
25722
- return feat.geometry || null;
25723
- }),
25724
- type: 'GEOMETRY'
25725
- });
25730
+ if (features.length === 0) {
25731
+ stop$1('GeoParquet export requires at least one record');
25732
+ }
25733
+ if (includeGeometry) {
25734
+ columnData.push({
25735
+ name: geometryName,
25736
+ data: features.map(function(feat) {
25737
+ return feat.geometry || null;
25738
+ }),
25739
+ type: 'GEOMETRY'
25740
+ });
25741
+ }
25726
25742
  names.forEach(function(name) {
25727
25743
  var values = features.map(function(feat) {
25728
25744
  return feat.properties ? feat.properties[name] : null;
25729
25745
  });
25730
25746
  columnData.push(buildAttributeColumn(name, values));
25731
25747
  });
25748
+ if (columnData.length === 0) {
25749
+ stop$1('GeoParquet export requires geometry or attribute data');
25750
+ }
25732
25751
  return {columnData: columnData, geometryColumn: geometryName};
25733
25752
  }
25734
25753
 
@@ -25886,6 +25905,99 @@ ${svg}
25886
25905
  return nodeMod.default && !nodeMod.parquetWriteBuffer ? nodeMod.default : nodeMod;
25887
25906
  }
25888
25907
 
25908
+ async function getGeoParquetCompression(opts) {
25909
+ var codec = normalizeGeoParquetCompression(opts.compression);
25910
+ var level = validateGeoParquetCompressionLevel(opts.level, codec);
25911
+ if (codec != 'ZSTD') {
25912
+ return {codec: codec, compressors: null};
25913
+ }
25914
+ var zstd = await loadZstdLib();
25915
+ if (!zstd || typeof zstd.compress != 'function') {
25916
+ stop$1('GeoParquet ZSTD compressor is not loaded');
25917
+ }
25918
+ return {
25919
+ codec: codec,
25920
+ pageSize: getGeoParquetPageSize(level),
25921
+ compressors: {
25922
+ ZSTD: function(bytes) {
25923
+ return compressZstdPage(zstd, bytes, level);
25924
+ }
25925
+ }
25926
+ };
25927
+ }
25928
+
25929
+ function compressZstdPage(zstd, bytes, level) {
25930
+ var compressed;
25931
+ try {
25932
+ compressed = zstd.compress(bytes, level);
25933
+ } catch (e) {
25934
+ stop$1('Unable to apply GeoParquet ZSTD compression. Try a lower level= value.');
25935
+ }
25936
+ if (!compressed) {
25937
+ stop$1('Unable to apply GeoParquet ZSTD compression. Try a lower level= value.');
25938
+ }
25939
+ return compressed;
25940
+ }
25941
+
25942
+ function getGeoParquetPageSize(level) {
25943
+ return level >= 10 ? 64 * 1024 : undefined;
25944
+ }
25945
+
25946
+ function normalizeGeoParquetCompression(compression) {
25947
+ var str = compression === undefined || compression === null ? 'snappy' : String(compression).toLowerCase();
25948
+ if (str == 'snappy') return 'SNAPPY';
25949
+ if (str == 'zstd') return 'ZSTD';
25950
+ if (str == 'none' || str == 'uncompressed') return null;
25951
+ stop$1('Unsupported GeoParquet compression:', compression);
25952
+ }
25953
+
25954
+ function validateGeoParquetCompressionLevel(level, codec) {
25955
+ if (level === undefined) return undefined;
25956
+ if (codec != 'ZSTD') {
25957
+ stop$1('The level= option only applies with compression=zstd');
25958
+ }
25959
+ if (level >= 1 && level <= 22 && Math.floor(level) === level) {
25960
+ return level;
25961
+ }
25962
+ stop$1('GeoParquet ZSTD level= option must be an integer from 1 to 22');
25963
+ }
25964
+
25965
+ async function loadZstdLib() {
25966
+ var mod;
25967
+ if (runningInBrowser()) {
25968
+ mod = require$1('zstd-codec');
25969
+ } else {
25970
+ if (!zstdPromise) {
25971
+ zstdPromise = dynamicImportModule$1('zstd-codec');
25972
+ }
25973
+ mod = await zstdPromise;
25974
+ }
25975
+ if (mod && mod.default && !mod.ZstdCodec) {
25976
+ mod = mod.default;
25977
+ }
25978
+ if (!mod || !mod.ZstdCodec || typeof mod.ZstdCodec.run != 'function') {
25979
+ stop$1('GeoParquet ZSTD compressor is not loaded');
25980
+ }
25981
+ return initZstdCodec(mod.ZstdCodec);
25982
+ }
25983
+
25984
+ function initZstdCodec(codec) {
25985
+ return new Promise(function(resolve, reject) {
25986
+ try {
25987
+ codec.run(function(zstd) {
25988
+ var simple = new zstd.Simple();
25989
+ resolve({
25990
+ compress: function(bytes, level) {
25991
+ return simple.compress(bytes, level);
25992
+ }
25993
+ });
25994
+ });
25995
+ } catch (e) {
25996
+ reject(e);
25997
+ }
25998
+ });
25999
+ }
26000
+
25889
26001
  function getOutputFormat(dataset, opts) {
25890
26002
  var outFile = opts.file || null,
25891
26003
  inFmt = dataset.info && dataset.info.input_formats && dataset.info.input_formats[0],
@@ -25991,6 +26103,9 @@ ${svg}
25991
26103
  async function exportDatasets(datasets, opts) {
25992
26104
  var format = getOutputFormat(datasets[0], opts);
25993
26105
  var files;
26106
+ if (format != 'geoparquet' && (opts.compression || opts.level !== undefined)) {
26107
+ error('The compression= and level= options only apply to GeoParquet output');
26108
+ }
25994
26109
  if (format == PACKAGE_EXT) {
25995
26110
  opts = utils.defaults({compact: true}, opts);
25996
26111
  return exportPackedDatasets(datasets, opts);
@@ -28669,6 +28784,13 @@ ${svg}
28669
28784
  type: 'flag',
28670
28785
  describe: '[CSV] export numbers with decimal commas not points'
28671
28786
  })
28787
+ .option('compression', {
28788
+ describe: '[GeoParquet] options: snappy,zstd,none (default is snappy)'
28789
+ })
28790
+ .option('level', {
28791
+ describe: '[GeoParquet] zstd compression level',
28792
+ type: 'integer'
28793
+ })
28672
28794
  .option('show-all', {
28673
28795
  type: 'flag',
28674
28796
  describe: '[Snapshot] show all layers in Web UI'
@@ -33675,6 +33797,7 @@ ${svg}
33675
33797
  }
33676
33798
 
33677
33799
  var hyparquetPromise = null;
33800
+ var compressorsPromise = null;
33678
33801
  var dynamicImportModule = Function('id', 'return import(id)');
33679
33802
  var mproj = null;
33680
33803
 
@@ -33685,8 +33808,10 @@ ${svg}
33685
33808
  var file = await getHyparquetFile(source, hyparquet);
33686
33809
  var metadata = await hyparquet.parquetMetadataAsync(file);
33687
33810
  var geo = parseGeoParquetMetadata(metadata);
33811
+ var compressors = await loadHyparquetCompressors();
33688
33812
  var rows = await hyparquet.parquetReadObjects({
33689
33813
  file: file,
33814
+ compressors: compressors,
33690
33815
  rowFormat: 'object'
33691
33816
  });
33692
33817
  var geometryColumn = getGeoParquetGeometryColumn(rows, geo);
@@ -33726,6 +33851,26 @@ ${svg}
33726
33851
  return nodeMod.default && !nodeMod.parquetReadObjects ? nodeMod.default : nodeMod;
33727
33852
  }
33728
33853
 
33854
+ async function loadHyparquetCompressors() {
33855
+ if (runningInBrowser()) {
33856
+ return getHyparquetCompressors(require$1('hyparquet-compressors'));
33857
+ }
33858
+ if (!compressorsPromise) {
33859
+ compressorsPromise = dynamicImportModule('hyparquet-compressors');
33860
+ }
33861
+ return getHyparquetCompressors(await compressorsPromise);
33862
+ }
33863
+
33864
+ function getHyparquetCompressors(mod) {
33865
+ if (mod && mod.default && !mod.compressors) {
33866
+ mod = mod.default;
33867
+ }
33868
+ if (!mod || !mod.compressors) {
33869
+ stop$1('GeoParquet compression library is not loaded');
33870
+ }
33871
+ return mod.compressors;
33872
+ }
33873
+
33729
33874
  function getGeoParquetSource(input) {
33730
33875
  if (!input) return null;
33731
33876
  if (Object.prototype.hasOwnProperty.call(input, 'content')) {
@@ -54267,7 +54412,7 @@ ${svg}
54267
54412
  });
54268
54413
  }
54269
54414
 
54270
- var version = "0.7.8";
54415
+ var version = "0.7.9";
54271
54416
 
54272
54417
  // Parse command line args into commands and run them
54273
54418
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.