mapshaper 0.6.22 → 0.6.24

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
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.6.22";
3
+ var VERSION = "0.6.24";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -10620,7 +10620,8 @@
10620
10620
  buf = new Uint8Array(buf);
10621
10621
  }
10622
10622
  opts = opts || {};
10623
- var out = await utils.promisify(gunzip)(buf, opts);
10623
+ var gunzip$1 = runningInBrowser() ? utils.promisify(gunzip) : utils.promisify(require('zlib').gunzip);
10624
+ var out = await gunzip$1(buf, opts);
10624
10625
  if (opts.filename && !isImportableAsBinary(opts.filename)) {
10625
10626
  out = strFromU8(out);
10626
10627
  }
@@ -10628,8 +10629,6 @@
10628
10629
  }
10629
10630
 
10630
10631
  function gunzipSync(buf, filename) {
10631
- // TODO: use native module in Node
10632
- // require('zlib').
10633
10632
  if (buf instanceof ArrayBuffer) {
10634
10633
  buf = new Uint8Array(buf);
10635
10634
  }
@@ -10768,16 +10767,39 @@
10768
10767
  // gui: (optional) gui instance
10769
10768
  //
10770
10769
  async function exportDatasetsToPack(datasets, opts) {
10771
- return {
10770
+ var obj = {
10772
10771
  version: 1,
10773
10772
  created: (new Date).toISOString(),
10774
- datasets: await Promise.all(datasets.map(d => exportDataset(d, opts || {})))
10773
+ datasets: await Promise.all(datasets.map(exportDataset))
10775
10774
  };
10775
+ if (opts.compact) {
10776
+ await applyCompression(obj);
10777
+ }
10778
+ return obj;
10779
+ }
10780
+
10781
+ async function applyCompression(obj, opts) {
10782
+ var promises = [];
10783
+ obj.datasets.forEach(d => {
10784
+ if (d.arcs) promises.push(compressArcs(d.arcs, opts));
10785
+ });
10786
+ await Promise.all(promises);
10787
+ }
10788
+
10789
+ async function compressArcs(obj, opts) {
10790
+ var gzipOpts = Object.assign({level: 1, consume: false}, opts);
10791
+ var promises = [gzipAsync(obj.nn, gzipOpts), gzipAsync(obj.xx, gzipOpts), gzipAsync(obj.yy, gzipOpts)];
10792
+ if (obj.zz) promises.push(gzipAsync(obj.zz, gzipOpts));
10793
+ var results = await Promise.all(promises);
10794
+ obj.nn = results.shift();
10795
+ obj.xx = results.shift();
10796
+ obj.yy = results.shift();
10797
+ if (obj.zz) obj.zz = results.shift();
10776
10798
  }
10777
10799
 
10778
10800
  async function exportDataset(dataset, opts) {
10779
10801
  return {
10780
- arcs: dataset.arcs ? await exportArcs(dataset.arcs, opts) : null,
10802
+ arcs: dataset.arcs ? exportArcs(dataset.arcs) : null,
10781
10803
  info: dataset.info ? exportInfo(dataset.info) : null,
10782
10804
  layers: await Promise.all((dataset.layers || []).map(exportLayer))
10783
10805
  };
@@ -10787,38 +10809,22 @@
10787
10809
  return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
10788
10810
  }
10789
10811
 
10790
- async function exportArcs(arcs, opts) {
10812
+ function exportArcs(arcs) {
10791
10813
  var data = arcs.getVertexData();
10792
- var obj = {
10814
+ return {
10793
10815
  nn: typedArrayToBuffer(data.nn),
10794
10816
  xx: typedArrayToBuffer(data.xx),
10795
10817
  yy: typedArrayToBuffer(data.yy),
10796
10818
  zz: data.zz ? typedArrayToBuffer(data.zz) : null,
10797
10819
  zlimit: arcs.getRetainedInterval()
10798
10820
  };
10799
-
10800
- // gzipping typically sees about 70% compression on unrounded coordinates
10801
- // -- possibly not worth the time
10802
- if (opts.compact) {
10803
- var gzipOpts = {level: 1, consume: false};
10804
- var promises = [gzipAsync(obj.nn, gzipOpts), gzipAsync(obj.xx, gzipOpts), gzipAsync(obj.yy, gzipOpts)];
10805
- if (obj.zz) promises.push(gzipAsync(obj.zz, gzipOpts));
10806
- var results = await Promise.all(promises);
10807
- obj.nn = results.shift();
10808
- obj.xx = results.shift();
10809
- obj.yy = results.shift();
10810
- if (obj.zz) obj.zz = results.shift();
10811
- }
10812
- return obj;
10813
10821
  }
10814
10822
 
10815
10823
  async function exportLayer(lyr) {
10816
- // console.time('table')
10817
10824
  var data = null;
10818
10825
  if (lyr.data) {
10819
10826
  data = await exportTable2(lyr.data);
10820
10827
  }
10821
- // console.timeEnd('table')
10822
10828
  return {
10823
10829
  name: lyr.name || null,
10824
10830
  geometry_type: lyr.geometry_type || null,
@@ -10845,6 +10851,7 @@
10845
10851
  exportPackedDatasets: exportPackedDatasets,
10846
10852
  pack: pack,
10847
10853
  exportDatasetsToPack: exportDatasetsToPack,
10854
+ applyCompression: applyCompression,
10848
10855
  exportDataset: exportDataset
10849
10856
  });
10850
10857
 
@@ -24922,6 +24929,9 @@ ${svg}
24922
24929
 
24923
24930
  parser.command('info')
24924
24931
  .describe('print information about data layers')
24932
+ .option('save-to', {
24933
+ describe: 'name of file to save info in JSON format'
24934
+ })
24925
24935
  .option('target', targetOpt);
24926
24936
 
24927
24937
  parser.command('inspect')
@@ -27457,12 +27467,14 @@ ${svg}
27457
27467
  // Import datasets contained in a BSON blob
27458
27468
  // Return command target as a dataset
27459
27469
  //
27460
- async function unpackSession(buf) {
27461
- var obj = unpack(buf, {});
27470
+ async function unpackSessionData(buf) {
27471
+ return restoreSessionData(unpack(buf, {}));
27472
+ }
27473
+
27474
+ async function restoreSessionData(obj) {
27462
27475
  if (!isValidSession(obj)) {
27463
27476
  stop('Invalid mapshaper session data object');
27464
27477
  }
27465
-
27466
27478
  var datasets = await Promise.all(obj.datasets.map(importDataset));
27467
27479
  return Object.assign(obj, {datasets: datasets});
27468
27480
  }
@@ -27539,7 +27551,8 @@ ${svg}
27539
27551
 
27540
27552
  var Unpack = /*#__PURE__*/Object.freeze({
27541
27553
  __proto__: null,
27542
- unpackSession: unpackSession
27554
+ unpackSessionData: unpackSessionData,
27555
+ restoreSessionData: restoreSessionData
27543
27556
  });
27544
27557
 
27545
27558
  cmd.importFiles = async function(catalog, opts) {
@@ -27593,7 +27606,7 @@ ${svg}
27593
27606
 
27594
27607
  async function importMshpFile(file, catalog, opts) {
27595
27608
  var buf = cli.readFile(file, null, opts.input);
27596
- var obj = await unpackSession(buf);
27609
+ var obj = await unpackSessionData(buf);
27597
27610
  obj.datasets.forEach(catalog.addDataset, catalog);
27598
27611
  return obj.target;
27599
27612
  }
@@ -38240,55 +38253,27 @@ ${svg}
38240
38253
 
38241
38254
  var MAX_RULE_LEN = 50;
38242
38255
 
38243
- cmd.printInfo = function(layers) {
38244
- var str = '';
38245
- layers.forEach(function(o, i) {
38246
- var title = 'Layer: ' + (o.layer.name || '[unnamed layer]');
38247
- var tableStr = getAttributeTableInfo(o.layer);
38248
- var tableWidth = measureLongestLine(tableStr);
38249
- var ruleLen = Math.min(Math.max(title.length, tableWidth), MAX_RULE_LEN);
38250
- str += '\n';
38251
- str += utils.lpad('', ruleLen, '=') + '\n';
38252
- str += title + '\n';
38253
- str += utils.lpad('', ruleLen, '-') + '\n';
38254
- str += getLayerInfo(o.layer, o.dataset);
38255
- str += tableStr;
38256
+ cmd.info = function(targets, opts) {
38257
+ var layers = expandCommandTargets(targets);
38258
+ var arr = layers.map(function(o) {
38259
+ return getLayerInfo(o.layer, o.dataset);
38256
38260
  });
38257
- message(str);
38258
- };
38259
-
38260
- function measureLongestLine(str) {
38261
- return Math.max.apply(null, str.split('\n').map(function(line) {return stringDisplayWidth(line);}));
38262
- }
38263
-
38264
- function stringDisplayWidth(str) {
38265
- var w = 0;
38266
- for (var i = 0, n=str.length; i < n; i++) {
38267
- w += charDisplayWidth(str.charCodeAt(i));
38261
+ message(formatInfo(arr));
38262
+ if (opts.save_to) {
38263
+ var output = [{
38264
+ filename: opts.save_to + (opts.save_to.endsWith('.json') ? '' : '.json'),
38265
+ content: JSON.stringify(arr, null, 2)
38266
+ }];
38267
+ writeFiles(output, opts);
38268
38268
  }
38269
- return w;
38270
- }
38269
+ };
38271
38270
 
38272
- // see https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
38273
- // this is a simplified version, focusing on double-width CJK chars and ignoring nonprinting etc chars
38274
- function charDisplayWidth(c) {
38275
- if (c >= 0x1100 &&
38276
- (c <= 0x115f || c == 0x2329 || c == 0x232a ||
38277
- (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || /* CJK ... Yi */
38278
- (c >= 0xac00 && c <= 0xd7a3) || /* Hangul Syllables */
38279
- (c >= 0xf900 && c <= 0xfaff) || /* CJK Compatibility Ideographs */
38280
- (c >= 0xfe10 && c <= 0xfe19) || /* Vertical forms */
38281
- (c >= 0xfe30 && c <= 0xfe6f) || /* CJK Compatibility Forms */
38282
- (c >= 0xff00 && c <= 0xff60) || /* Fullwidth Forms */
38283
- (c >= 0xffe0 && c <= 0xffe6) ||
38284
- (c >= 0x20000 && c <= 0x2fffd) ||
38285
- (c >= 0x30000 && c <= 0x3fffd))) return 2;
38286
- return 1;
38287
- }
38271
+ cmd.printInfo = cmd.info; // old name
38288
38272
 
38289
- function getLayerData(lyr, dataset) {
38273
+ function getLayerInfo(lyr, dataset) {
38290
38274
  var n = getFeatureCount(lyr);
38291
38275
  var o = {
38276
+ layer_name: lyr.name,
38292
38277
  geometry_type: lyr.geometry_type,
38293
38278
  feature_count: n,
38294
38279
  null_shape_count: 0,
@@ -38296,31 +38281,47 @@ ${svg}
38296
38281
  };
38297
38282
  if (lyr.shapes && lyr.shapes.length > 0) {
38298
38283
  o.null_shape_count = countNullShapes(lyr.shapes);
38299
- o.bbox =getLayerBounds(lyr, dataset.arcs).toArray();
38284
+ o.bbox = getLayerBounds(lyr, dataset.arcs).toArray();
38300
38285
  o.proj4 = getProjInfo(dataset);
38301
38286
  }
38287
+ o.source_file = getLayerSourceFile(lyr, dataset) || null;
38288
+ o.attribute_data = getAttributeTableInfo(lyr);
38302
38289
  return o;
38303
38290
  }
38304
38291
 
38305
- // TODO: consider polygons with zero area or other invalid geometries
38306
- function countNullShapes(shapes) {
38307
- var count = 0;
38308
- for (var i=0; i<shapes.length; i++) {
38309
- if (!shapes[i] || shapes[i].length === 0) count++;
38292
+ // i: (optional) record index
38293
+ function getAttributeTableInfo(lyr, i) {
38294
+ if (!lyr.data || lyr.data.size() === 0 || lyr.data.getFields().length === 0) {
38295
+ return null;
38310
38296
  }
38311
- return count;
38297
+ var fields = applyFieldOrder(lyr.data.getFields(), 'ascending');
38298
+ var valueName = i === undefined ? 'first_value' : 'value';
38299
+ return fields.map(function(fname) {
38300
+ return {
38301
+ field: fname,
38302
+ [valueName]: lyr.data.getReadOnlyRecordAt(i || 0)[fname]
38303
+ };
38304
+ });
38312
38305
  }
38313
38306
 
38314
- function countNullRecords(records) {
38315
- var count = 0;
38316
- for (var i=0; i<records.length; i++) {
38317
- if (!records[i]) count++;
38318
- }
38319
- return count;
38307
+ function formatInfo(arr) {
38308
+ var str = '';
38309
+ arr.forEach(function(info, i) {
38310
+ var title = 'Layer: ' + (info.layer_name || '[unnamed layer]');
38311
+ var tableStr = formatAttributeTableInfo(info.attribute_data);
38312
+ var tableWidth = measureLongestLine(tableStr);
38313
+ var ruleLen = Math.min(Math.max(title.length, tableWidth), MAX_RULE_LEN);
38314
+ str += '\n';
38315
+ str += utils.lpad('', ruleLen, '=') + '\n';
38316
+ str += title + '\n';
38317
+ str += utils.lpad('', ruleLen, '-') + '\n';
38318
+ str += formatLayerInfo(info);
38319
+ str += tableStr;
38320
+ });
38321
+ return str;
38320
38322
  }
38321
38323
 
38322
- function getLayerInfo(lyr, dataset) {
38323
- var data = getLayerData(lyr, dataset);
38324
+ function formatLayerInfo(data) {
38324
38325
  var str = '';
38325
38326
  str += "Type: " + (data.geometry_type || "tabular data") + "\n";
38326
38327
  str += utils.format("Records: %,d\n",data.feature_count);
@@ -38331,21 +38332,19 @@ ${svg}
38331
38332
  str += "Bounds: " + data.bbox.join(',') + "\n";
38332
38333
  str += "CRS: " + data.proj4 + "\n";
38333
38334
  }
38334
- str += "Source: " + (getLayerSourceFile(lyr, dataset) || 'n/a') + "\n";
38335
+ str += "Source: " + (data.source_file || 'n/a') + "\n";
38335
38336
  return str;
38336
38337
  }
38337
38338
 
38338
- function getAttributeTableInfo(lyr, i) {
38339
- if (!lyr.data || lyr.data.size() === 0 || lyr.data.getFields().length === 0) {
38340
- return "Attribute data: [none]\n";
38341
- }
38342
- return "\nAttribute data\n" + formatAttributeTable(lyr.data, i);
38343
- }
38344
-
38345
- function formatAttributeTable(data, i) {
38346
- var fields = applyFieldOrder(data.getFields(), 'ascending');
38347
- var vals = fields.map(function(fname) {
38348
- return data.getReadOnlyRecordAt(i || 0)[fname];
38339
+ function formatAttributeTableInfo(arr) {
38340
+ if (!arr) return "Attribute data: [none]\n";
38341
+ var header = "\nAttribute data\n";
38342
+ var valKey = 'first_value' in arr[0] ? 'first_value' : 'value';
38343
+ var vals = [];
38344
+ var fields = [];
38345
+ arr.forEach(function(o) {
38346
+ fields.push(o.field);
38347
+ vals.push(o[valKey]);
38349
38348
  });
38350
38349
  var maxIntegralChars = vals.reduce(function(max, val) {
38351
38350
  if (utils.isNumber(val)) {
@@ -38357,20 +38356,66 @@ ${svg}
38357
38356
  var col2Arr = vals.reduce(function(memo, val) {
38358
38357
  memo.push(formatTableValue(val, maxIntegralChars));
38359
38358
  return memo;
38360
- }, [i >= 0 ? 'Value' : 'First value']);
38359
+ }, [valKey == 'first_value' ? 'First value' : 'Value']);
38361
38360
  var col1Chars = maxChars(col1Arr);
38362
38361
  var col2Chars = maxChars(col2Arr);
38363
38362
  var sepStr = (utils.rpad('', col1Chars + 2, '-') + '+' +
38364
38363
  utils.rpad('', col2Chars + 2, '-')).substr(0, MAX_RULE_LEN);
38365
38364
  var sepLine = sepStr + '\n';
38366
- var table = sepLine;
38365
+ var table = '';
38367
38366
  col1Arr.forEach(function(col1, i) {
38368
38367
  var w = stringDisplayWidth(col1);
38369
38368
  table += ' ' + col1 + utils.rpad('', col1Chars - w, ' ') + ' | ' +
38370
38369
  col2Arr[i] + '\n';
38371
38370
  if (i === 0) table += sepLine; // separator after first line
38372
38371
  });
38373
- return table + sepLine;
38372
+ return header + sepLine + table + sepLine;
38373
+ }
38374
+
38375
+ function measureLongestLine(str) {
38376
+ return Math.max.apply(null, str.split('\n').map(function(line) {return stringDisplayWidth(line);}));
38377
+ }
38378
+
38379
+ function stringDisplayWidth(str) {
38380
+ var w = 0;
38381
+ for (var i = 0, n=str.length; i < n; i++) {
38382
+ w += charDisplayWidth(str.charCodeAt(i));
38383
+ }
38384
+ return w;
38385
+ }
38386
+
38387
+ // see https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
38388
+ // this is a simplified version, focusing on double-width CJK chars and ignoring nonprinting etc chars
38389
+ function charDisplayWidth(c) {
38390
+ if (c >= 0x1100 &&
38391
+ (c <= 0x115f || c == 0x2329 || c == 0x232a ||
38392
+ (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || /* CJK ... Yi */
38393
+ (c >= 0xac00 && c <= 0xd7a3) || /* Hangul Syllables */
38394
+ (c >= 0xf900 && c <= 0xfaff) || /* CJK Compatibility Ideographs */
38395
+ (c >= 0xfe10 && c <= 0xfe19) || /* Vertical forms */
38396
+ (c >= 0xfe30 && c <= 0xfe6f) || /* CJK Compatibility Forms */
38397
+ (c >= 0xff00 && c <= 0xff60) || /* Fullwidth Forms */
38398
+ (c >= 0xffe0 && c <= 0xffe6) ||
38399
+ (c >= 0x20000 && c <= 0x2fffd) ||
38400
+ (c >= 0x30000 && c <= 0x3fffd))) return 2;
38401
+ return 1;
38402
+ }
38403
+
38404
+ // TODO: consider polygons with zero area or other invalid geometries
38405
+ function countNullShapes(shapes) {
38406
+ var count = 0;
38407
+ for (var i=0; i<shapes.length; i++) {
38408
+ if (!shapes[i] || shapes[i].length === 0) count++;
38409
+ }
38410
+ return count;
38411
+ }
38412
+
38413
+ function countNullRecords(records) {
38414
+ var count = 0;
38415
+ for (var i=0; i<records.length; i++) {
38416
+ if (!records[i]) count++;
38417
+ }
38418
+ return count;
38374
38419
  }
38375
38420
 
38376
38421
  function maxChars(arr) {
@@ -38424,8 +38469,9 @@ ${svg}
38424
38469
 
38425
38470
  var Info = /*#__PURE__*/Object.freeze({
38426
38471
  __proto__: null,
38427
- getLayerData: getLayerData,
38472
+ getLayerInfo: getLayerInfo,
38428
38473
  getAttributeTableInfo: getAttributeTableInfo,
38474
+ formatAttributeTableInfo: formatAttributeTableInfo,
38429
38475
  formatTableValue: formatTableValue
38430
38476
  });
38431
38477
 
@@ -38477,7 +38523,7 @@ ${svg}
38477
38523
  function getFeatureInfo(id, lyr, arcs) {
38478
38524
  var msg = "Feature " + id + '\n';
38479
38525
  msg += getShapeInfo(id, lyr, arcs);
38480
- msg += getAttributeTableInfo(lyr, id);
38526
+ msg += formatAttributeTableInfo(getAttributeTableInfo(lyr, id));
38481
38527
  return msg;
38482
38528
  }
38483
38529
 
@@ -40589,7 +40635,7 @@ ${svg}
40589
40635
 
40590
40636
  function getRunCommandData(target) {
40591
40637
  var lyr = target.layers[0];
40592
- var data = getLayerData(lyr, target.dataset);
40638
+ var data = getLayerInfo(lyr, target.dataset);
40593
40639
  data.layer = lyr;
40594
40640
  data.dataset = target.dataset;
40595
40641
  return data;
@@ -42939,7 +42985,7 @@ ${svg}
42939
42985
  cmd.include(opts);
42940
42986
 
42941
42987
  } else if (name == 'info') {
42942
- cmd.printInfo(expandCommandTargets(targets));
42988
+ cmd.info(targets, opts);
42943
42989
 
42944
42990
  } else if (name == 'inlay') {
42945
42991
  outputLayers = cmd.inlay(targetLayers, source, targetDataset, opts);
@@ -43305,13 +43351,13 @@ ${svg}
43305
43351
  commands = runAndRemoveInfoCommands(commands);
43306
43352
  if (commands.length === 0) return done(null);
43307
43353
 
43308
- // add options to -i -o -join -clip -erase commands to bypass file i/o
43354
+ // add options to -i -o -join -clip -erase etc. commands to bypass file i/o
43309
43355
  // TODO: find a less kludgy solution
43310
43356
  commands.forEach(function(cmd) {
43311
43357
  if (commandTakesFileInput(cmd.name) && inputObj) {
43312
43358
  cmd.options.input = inputObj;
43313
43359
  }
43314
- if (cmd.name == 'o' && outputArr) {
43360
+ if (outputArr && (cmd.name == 'o' || cmd.name == 'info' && cmd.options.save_to)) {
43315
43361
  cmd.options.output = outputArr;
43316
43362
  }
43317
43363
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.6.22",
3
+ "version": "0.6.24",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
package/www/index.html CHANGED
@@ -102,7 +102,7 @@
102
102
  <img class="pin-btn pinned" src="images/eye2.png">
103
103
  </div>
104
104
  <div class="layer-list"></div>
105
- <h3>Files</h3>
105
+ <h4>Sources</h4>
106
106
  <div class="file-list"></div>
107
107
  <div>
108
108
  <div id="add-file-btn" class="dialog-btn btn">Add a file</div>
@@ -159,6 +159,8 @@
159
159
  "precision=0.001"</div></div></div></div>
160
160
  <!-- <div class="cancel-btn btn dialog-btn">Cancel</div> -->
161
161
  <div class="save-btn btn dialog-btn">Export</div>
162
+ <span id="save-preference"><input type="checkbox"/>choose directory</span>
163
+
162
164
  </div>
163
165
  </div>
164
166
 
@@ -807,6 +807,19 @@
807
807
  return !!(mapshaper.manifest && mapshaper.manifest.allow_saving) && typeof fetch == 'function';
808
808
  };
809
809
 
810
+ GUI.setSavedValue = function(name, val) {
811
+ try {
812
+ window.localStorage.setItem(name, JSON.stringify(val));
813
+ } catch(e) {}
814
+ };
815
+
816
+ GUI.getSavedValue = function(name) {
817
+ try {
818
+ return JSON.parse(window.localStorage.getItem(name));
819
+ } catch(e) {}
820
+ return null;
821
+ };
822
+
810
823
  GUI.getUrlVars = function() {
811
824
  var q = window.location.search.substring(1);
812
825
  return q.split('&').reduce(function(memo, chunk) {
@@ -1232,8 +1245,9 @@
1232
1245
  }
1233
1246
 
1234
1247
  async function saveBlobToLocalFile(filename, blob, done) {
1248
+ var chooseDir = GUI.getSavedValue('choose-save-dir');
1235
1249
  done = done || function() {};
1236
- if (window.showSaveFilePicker) {
1250
+ if (chooseDir) {
1237
1251
  saveBlobToSelectedFile(filename, blob, done);
1238
1252
  } else {
1239
1253
  saveBlobToDownloadsFolder(filename, blob, done);
@@ -1241,7 +1255,7 @@
1241
1255
  }
1242
1256
 
1243
1257
  function showSaveDialog(filename, blob, done) {
1244
- showPopupAlert(`Save ${filename} to:`)
1258
+ var alert = showPopupAlert(`Save ${filename} to:`)
1245
1259
  .button('selected folder', function() {
1246
1260
  saveBlobToSelectedFile(filename, blob, done);
1247
1261
  })
@@ -1251,6 +1265,7 @@
1251
1265
  .onCancel(done);
1252
1266
  }
1253
1267
 
1268
+
1254
1269
  async function saveBlobToSelectedFile(filename, blob, done) {
1255
1270
  // see: https://developer.chrome.com/articles/file-system-access/
1256
1271
  // note: saving multiple files to a directory using showDirectoryPicker()
@@ -1422,7 +1437,9 @@
1422
1437
  closeMenu(100);
1423
1438
  }).text('restore');
1424
1439
  El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
1425
- var buf = await idb.get(item.id);
1440
+ var obj = await idb.get(item.id);
1441
+ await internal.applyCompression(obj, {consume: true});
1442
+ var buf = internal.pack(obj);
1426
1443
  saveBlobToLocalFile('mapshaper_snapshot.msx', new Blob([buf]));
1427
1444
  }).text('export');
1428
1445
  El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
@@ -1470,20 +1487,24 @@
1470
1487
  }
1471
1488
 
1472
1489
  async function saveSnapshot(gui) {
1473
- var buf = await captureSnapshot(gui);
1490
+ var obj = await captureSnapshot(gui);
1491
+ // storing an unpacked object is usually a bit faster (~20%)
1492
+ // note: we don't know the size of unpacked snapshot objects
1493
+ // obj = internal.pack(obj);
1474
1494
  var entryId = String(++snapshotCount).padStart(3, '0');
1475
1495
  var snapshotId = sessionId + '_' + entryId; // e.g. session_d89fw_001
1496
+ var size = obj.length;
1476
1497
  var entry = {
1477
1498
  created: Date.now(),
1478
1499
  session: sessionId,
1479
1500
  id: snapshotId,
1480
1501
  name: snapshotCount + '.',
1481
1502
  number: snapshotCount,
1482
- size: buf.length,
1483
- display_size: formatSize(buf.length)
1503
+ size: size,
1504
+ display_size: formatSize(size)
1484
1505
  };
1485
1506
 
1486
- await idb.set(entry.id, buf);
1507
+ await idb.set(entry.id, obj);
1487
1508
  await addToIndex(entry);
1488
1509
  renderMenu();
1489
1510
  }
@@ -1492,9 +1513,8 @@
1492
1513
  function formatSize(bytes) {
1493
1514
  var kb = Math.round(bytes / 1000);
1494
1515
  var mb = (bytes / 1e6).toFixed(1);
1495
- if (kb < 990) {
1496
- return kb + 'kB';
1497
- }
1516
+ if (!kb) return '';
1517
+ if (kb < 990) return kb + 'kB';
1498
1518
  return mb + 'MB';
1499
1519
  }
1500
1520
 
@@ -1516,13 +1536,13 @@
1516
1536
  }
1517
1537
 
1518
1538
  async function restoreSnapshotById(id, gui) {
1519
- var buf = await idb.get(id);
1520
- if (!buf) {
1521
- console.log('Snapshot not available:', id);
1522
- return;
1539
+ var data;
1540
+ try {
1541
+ data = await internal.restoreSessionData(await idb.get(id));
1542
+ } catch(e) {
1543
+ console.error(e);
1544
+ stop$1('Snapshot is not available');
1523
1545
  }
1524
- var data = await internal.unpackSession(buf);
1525
- buf = null;
1526
1546
  gui.model.clear();
1527
1547
  importDatasets(data.datasets, gui);
1528
1548
  gui.clearMode();
@@ -1535,7 +1555,7 @@
1535
1555
  if (buf instanceof ArrayBuffer) {
1536
1556
  buf = new Uint8Array(buf);
1537
1557
  }
1538
- var data = await internal.unpackSession(buf);
1558
+ var data = await internal.unpackSessionData(buf);
1539
1559
  importDatasets(data.datasets, gui);
1540
1560
  }
1541
1561
 
@@ -1558,7 +1578,7 @@
1558
1578
  // console.timeEnd('msx')
1559
1579
  delete lyr.active;
1560
1580
  obj.gui = getGuiState(gui);
1561
- return internal.pack(obj);
1581
+ return obj;
1562
1582
  }
1563
1583
 
1564
1584
  // TODO: capture gui state information to allow restoring more of the UI
@@ -3025,22 +3045,12 @@
3025
3045
  }
3026
3046
 
3027
3047
  function getHistory() {
3028
- var hist;
3029
- try {
3030
- hist = JSON.parse(window.localStorage.getItem('console_history'));
3031
- } catch(e) {
3032
- }
3033
- return hist && hist.length > 0 ? hist : [];
3048
+ return GUI.getSavedValue('console_history') || [];
3034
3049
  }
3035
3050
 
3036
3051
  function saveHistory() {
3037
- try {
3038
- history = history.filter(Boolean); // TODO: fix condition that leaves a blank line on the history
3039
- if (history.length) {
3040
- window.localStorage.setItem('console_history', JSON.stringify(history.slice(-50)));
3041
- }
3042
- } catch(e) {
3043
- }
3052
+ history = history.filter(Boolean); // TODO: fix condition that leaves a blank line on the history
3053
+ GUI.setSavedValue('console_history', history.slice(-100));
3044
3054
  }
3045
3055
 
3046
3056
  function toLog(str, cname) {
@@ -3656,10 +3666,20 @@
3656
3666
  internal.writeFiles = function() {
3657
3667
  error$1(unsupportedMsg);
3658
3668
  };
3659
- } else {
3660
- new SimpleButton(menu.findChild('.save-btn').addClass('default-btn')).on('click', onExportClick);
3661
- gui.addMode('export', turnOn, turnOff, exportBtn);
3662
- gui.keyboard.onMenuSubmit(menu, onExportClick);
3669
+ return;
3670
+ }
3671
+
3672
+ new SimpleButton(menu.findChild('.save-btn').addClass('default-btn')).on('click', onExportClick);
3673
+ gui.addMode('export', turnOn, turnOff, exportBtn);
3674
+ gui.keyboard.onMenuSubmit(menu, onExportClick);
3675
+ if (window.showSaveFilePicker) {
3676
+ menu.findChild('#save-preference')
3677
+ .css('display', 'inline-block')
3678
+ .findChild('input')
3679
+ .on('change', function() {
3680
+ GUI.setSavedValue('choose-save-dir', this.checked);
3681
+ })
3682
+ .attr('checked', GUI.getSavedValue('choose-save-dir') || false);
3663
3683
  }
3664
3684
 
3665
3685
  function turnOn() {
@@ -8697,7 +8717,6 @@
8697
8717
  function active(e) {
8698
8718
  return e.id > -1 && gui.interaction.getMode() == 'add-points';
8699
8719
  }
8700
-
8701
8720
  }
8702
8721
 
8703
8722
  function Pencil(gui, mouse, hit) {
@@ -8720,10 +8739,7 @@
8720
8739
  }
8721
8740
 
8722
8741
  function initDrawing(gui, ext, mouse, hit) {
8723
-
8724
8742
  initPointDrawing(gui, new Pencil(gui, mouse, hit));
8725
-
8726
-
8727
8743
  }
8728
8744
 
8729
8745
  var darkStroke = "#334",
@@ -9780,19 +9796,26 @@
9780
9796
  bx = t.bx,
9781
9797
  by = t.by;
9782
9798
  var x, y, xp, yp;
9799
+ var count = 0;
9783
9800
  if (!vec.hasNext()) return;
9784
- x = xp = round(vec.x * mx + bx);
9785
- y = yp = round(vec.y * my + by);
9801
+ x = round(vec.x * mx + bx);
9802
+ y = round(vec.y * my + by);
9786
9803
  ctx.moveTo(x, y);
9787
9804
  while (vec.hasNext()) {
9805
+ xp = x;
9806
+ yp = y;
9788
9807
  x = round(vec.x * mx + bx);
9789
9808
  y = round(vec.y * my + by);
9790
9809
  if (x != xp || y != yp) {
9791
9810
  ctx.lineTo(x, y);
9792
- xp = x;
9793
- yp = y;
9811
+ count++;
9794
9812
  }
9795
9813
  }
9814
+ if (count === 0) {
9815
+ // draw a tiny line if all coords round to the same location,
9816
+ // so tiny shapes with strokes will consistently be drawn as dots,
9817
+ ctx.lineTo(x + 0.1, y);
9818
+ }
9796
9819
  }
9797
9820
 
9798
9821
  function roundToPix(x) {
package/www/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.6.22";
3
+ var VERSION = "0.6.24";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -10620,7 +10620,8 @@
10620
10620
  buf = new Uint8Array(buf);
10621
10621
  }
10622
10622
  opts = opts || {};
10623
- var out = await utils.promisify(gunzip)(buf, opts);
10623
+ var gunzip$1 = runningInBrowser() ? utils.promisify(gunzip) : utils.promisify(require('zlib').gunzip);
10624
+ var out = await gunzip$1(buf, opts);
10624
10625
  if (opts.filename && !isImportableAsBinary(opts.filename)) {
10625
10626
  out = strFromU8(out);
10626
10627
  }
@@ -10628,8 +10629,6 @@
10628
10629
  }
10629
10630
 
10630
10631
  function gunzipSync(buf, filename) {
10631
- // TODO: use native module in Node
10632
- // require('zlib').
10633
10632
  if (buf instanceof ArrayBuffer) {
10634
10633
  buf = new Uint8Array(buf);
10635
10634
  }
@@ -10768,16 +10767,39 @@
10768
10767
  // gui: (optional) gui instance
10769
10768
  //
10770
10769
  async function exportDatasetsToPack(datasets, opts) {
10771
- return {
10770
+ var obj = {
10772
10771
  version: 1,
10773
10772
  created: (new Date).toISOString(),
10774
- datasets: await Promise.all(datasets.map(d => exportDataset(d, opts || {})))
10773
+ datasets: await Promise.all(datasets.map(exportDataset))
10775
10774
  };
10775
+ if (opts.compact) {
10776
+ await applyCompression(obj);
10777
+ }
10778
+ return obj;
10779
+ }
10780
+
10781
+ async function applyCompression(obj, opts) {
10782
+ var promises = [];
10783
+ obj.datasets.forEach(d => {
10784
+ if (d.arcs) promises.push(compressArcs(d.arcs, opts));
10785
+ });
10786
+ await Promise.all(promises);
10787
+ }
10788
+
10789
+ async function compressArcs(obj, opts) {
10790
+ var gzipOpts = Object.assign({level: 1, consume: false}, opts);
10791
+ var promises = [gzipAsync(obj.nn, gzipOpts), gzipAsync(obj.xx, gzipOpts), gzipAsync(obj.yy, gzipOpts)];
10792
+ if (obj.zz) promises.push(gzipAsync(obj.zz, gzipOpts));
10793
+ var results = await Promise.all(promises);
10794
+ obj.nn = results.shift();
10795
+ obj.xx = results.shift();
10796
+ obj.yy = results.shift();
10797
+ if (obj.zz) obj.zz = results.shift();
10776
10798
  }
10777
10799
 
10778
10800
  async function exportDataset(dataset, opts) {
10779
10801
  return {
10780
- arcs: dataset.arcs ? await exportArcs(dataset.arcs, opts) : null,
10802
+ arcs: dataset.arcs ? exportArcs(dataset.arcs) : null,
10781
10803
  info: dataset.info ? exportInfo(dataset.info) : null,
10782
10804
  layers: await Promise.all((dataset.layers || []).map(exportLayer))
10783
10805
  };
@@ -10787,38 +10809,22 @@
10787
10809
  return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
10788
10810
  }
10789
10811
 
10790
- async function exportArcs(arcs, opts) {
10812
+ function exportArcs(arcs) {
10791
10813
  var data = arcs.getVertexData();
10792
- var obj = {
10814
+ return {
10793
10815
  nn: typedArrayToBuffer(data.nn),
10794
10816
  xx: typedArrayToBuffer(data.xx),
10795
10817
  yy: typedArrayToBuffer(data.yy),
10796
10818
  zz: data.zz ? typedArrayToBuffer(data.zz) : null,
10797
10819
  zlimit: arcs.getRetainedInterval()
10798
10820
  };
10799
-
10800
- // gzipping typically sees about 70% compression on unrounded coordinates
10801
- // -- possibly not worth the time
10802
- if (opts.compact) {
10803
- var gzipOpts = {level: 1, consume: false};
10804
- var promises = [gzipAsync(obj.nn, gzipOpts), gzipAsync(obj.xx, gzipOpts), gzipAsync(obj.yy, gzipOpts)];
10805
- if (obj.zz) promises.push(gzipAsync(obj.zz, gzipOpts));
10806
- var results = await Promise.all(promises);
10807
- obj.nn = results.shift();
10808
- obj.xx = results.shift();
10809
- obj.yy = results.shift();
10810
- if (obj.zz) obj.zz = results.shift();
10811
- }
10812
- return obj;
10813
10821
  }
10814
10822
 
10815
10823
  async function exportLayer(lyr) {
10816
- // console.time('table')
10817
10824
  var data = null;
10818
10825
  if (lyr.data) {
10819
10826
  data = await exportTable2(lyr.data);
10820
10827
  }
10821
- // console.timeEnd('table')
10822
10828
  return {
10823
10829
  name: lyr.name || null,
10824
10830
  geometry_type: lyr.geometry_type || null,
@@ -10845,6 +10851,7 @@
10845
10851
  exportPackedDatasets: exportPackedDatasets,
10846
10852
  pack: pack,
10847
10853
  exportDatasetsToPack: exportDatasetsToPack,
10854
+ applyCompression: applyCompression,
10848
10855
  exportDataset: exportDataset
10849
10856
  });
10850
10857
 
@@ -24922,6 +24929,9 @@ ${svg}
24922
24929
 
24923
24930
  parser.command('info')
24924
24931
  .describe('print information about data layers')
24932
+ .option('save-to', {
24933
+ describe: 'name of file to save info in JSON format'
24934
+ })
24925
24935
  .option('target', targetOpt);
24926
24936
 
24927
24937
  parser.command('inspect')
@@ -27457,12 +27467,14 @@ ${svg}
27457
27467
  // Import datasets contained in a BSON blob
27458
27468
  // Return command target as a dataset
27459
27469
  //
27460
- async function unpackSession(buf) {
27461
- var obj = unpack(buf, {});
27470
+ async function unpackSessionData(buf) {
27471
+ return restoreSessionData(unpack(buf, {}));
27472
+ }
27473
+
27474
+ async function restoreSessionData(obj) {
27462
27475
  if (!isValidSession(obj)) {
27463
27476
  stop('Invalid mapshaper session data object');
27464
27477
  }
27465
-
27466
27478
  var datasets = await Promise.all(obj.datasets.map(importDataset));
27467
27479
  return Object.assign(obj, {datasets: datasets});
27468
27480
  }
@@ -27539,7 +27551,8 @@ ${svg}
27539
27551
 
27540
27552
  var Unpack = /*#__PURE__*/Object.freeze({
27541
27553
  __proto__: null,
27542
- unpackSession: unpackSession
27554
+ unpackSessionData: unpackSessionData,
27555
+ restoreSessionData: restoreSessionData
27543
27556
  });
27544
27557
 
27545
27558
  cmd.importFiles = async function(catalog, opts) {
@@ -27593,7 +27606,7 @@ ${svg}
27593
27606
 
27594
27607
  async function importMshpFile(file, catalog, opts) {
27595
27608
  var buf = cli.readFile(file, null, opts.input);
27596
- var obj = await unpackSession(buf);
27609
+ var obj = await unpackSessionData(buf);
27597
27610
  obj.datasets.forEach(catalog.addDataset, catalog);
27598
27611
  return obj.target;
27599
27612
  }
@@ -38240,55 +38253,27 @@ ${svg}
38240
38253
 
38241
38254
  var MAX_RULE_LEN = 50;
38242
38255
 
38243
- cmd.printInfo = function(layers) {
38244
- var str = '';
38245
- layers.forEach(function(o, i) {
38246
- var title = 'Layer: ' + (o.layer.name || '[unnamed layer]');
38247
- var tableStr = getAttributeTableInfo(o.layer);
38248
- var tableWidth = measureLongestLine(tableStr);
38249
- var ruleLen = Math.min(Math.max(title.length, tableWidth), MAX_RULE_LEN);
38250
- str += '\n';
38251
- str += utils.lpad('', ruleLen, '=') + '\n';
38252
- str += title + '\n';
38253
- str += utils.lpad('', ruleLen, '-') + '\n';
38254
- str += getLayerInfo(o.layer, o.dataset);
38255
- str += tableStr;
38256
+ cmd.info = function(targets, opts) {
38257
+ var layers = expandCommandTargets(targets);
38258
+ var arr = layers.map(function(o) {
38259
+ return getLayerInfo(o.layer, o.dataset);
38256
38260
  });
38257
- message(str);
38258
- };
38259
-
38260
- function measureLongestLine(str) {
38261
- return Math.max.apply(null, str.split('\n').map(function(line) {return stringDisplayWidth(line);}));
38262
- }
38263
-
38264
- function stringDisplayWidth(str) {
38265
- var w = 0;
38266
- for (var i = 0, n=str.length; i < n; i++) {
38267
- w += charDisplayWidth(str.charCodeAt(i));
38261
+ message(formatInfo(arr));
38262
+ if (opts.save_to) {
38263
+ var output = [{
38264
+ filename: opts.save_to + (opts.save_to.endsWith('.json') ? '' : '.json'),
38265
+ content: JSON.stringify(arr, null, 2)
38266
+ }];
38267
+ writeFiles(output, opts);
38268
38268
  }
38269
- return w;
38270
- }
38269
+ };
38271
38270
 
38272
- // see https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
38273
- // this is a simplified version, focusing on double-width CJK chars and ignoring nonprinting etc chars
38274
- function charDisplayWidth(c) {
38275
- if (c >= 0x1100 &&
38276
- (c <= 0x115f || c == 0x2329 || c == 0x232a ||
38277
- (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || /* CJK ... Yi */
38278
- (c >= 0xac00 && c <= 0xd7a3) || /* Hangul Syllables */
38279
- (c >= 0xf900 && c <= 0xfaff) || /* CJK Compatibility Ideographs */
38280
- (c >= 0xfe10 && c <= 0xfe19) || /* Vertical forms */
38281
- (c >= 0xfe30 && c <= 0xfe6f) || /* CJK Compatibility Forms */
38282
- (c >= 0xff00 && c <= 0xff60) || /* Fullwidth Forms */
38283
- (c >= 0xffe0 && c <= 0xffe6) ||
38284
- (c >= 0x20000 && c <= 0x2fffd) ||
38285
- (c >= 0x30000 && c <= 0x3fffd))) return 2;
38286
- return 1;
38287
- }
38271
+ cmd.printInfo = cmd.info; // old name
38288
38272
 
38289
- function getLayerData(lyr, dataset) {
38273
+ function getLayerInfo(lyr, dataset) {
38290
38274
  var n = getFeatureCount(lyr);
38291
38275
  var o = {
38276
+ layer_name: lyr.name,
38292
38277
  geometry_type: lyr.geometry_type,
38293
38278
  feature_count: n,
38294
38279
  null_shape_count: 0,
@@ -38296,31 +38281,47 @@ ${svg}
38296
38281
  };
38297
38282
  if (lyr.shapes && lyr.shapes.length > 0) {
38298
38283
  o.null_shape_count = countNullShapes(lyr.shapes);
38299
- o.bbox =getLayerBounds(lyr, dataset.arcs).toArray();
38284
+ o.bbox = getLayerBounds(lyr, dataset.arcs).toArray();
38300
38285
  o.proj4 = getProjInfo(dataset);
38301
38286
  }
38287
+ o.source_file = getLayerSourceFile(lyr, dataset) || null;
38288
+ o.attribute_data = getAttributeTableInfo(lyr);
38302
38289
  return o;
38303
38290
  }
38304
38291
 
38305
- // TODO: consider polygons with zero area or other invalid geometries
38306
- function countNullShapes(shapes) {
38307
- var count = 0;
38308
- for (var i=0; i<shapes.length; i++) {
38309
- if (!shapes[i] || shapes[i].length === 0) count++;
38292
+ // i: (optional) record index
38293
+ function getAttributeTableInfo(lyr, i) {
38294
+ if (!lyr.data || lyr.data.size() === 0 || lyr.data.getFields().length === 0) {
38295
+ return null;
38310
38296
  }
38311
- return count;
38297
+ var fields = applyFieldOrder(lyr.data.getFields(), 'ascending');
38298
+ var valueName = i === undefined ? 'first_value' : 'value';
38299
+ return fields.map(function(fname) {
38300
+ return {
38301
+ field: fname,
38302
+ [valueName]: lyr.data.getReadOnlyRecordAt(i || 0)[fname]
38303
+ };
38304
+ });
38312
38305
  }
38313
38306
 
38314
- function countNullRecords(records) {
38315
- var count = 0;
38316
- for (var i=0; i<records.length; i++) {
38317
- if (!records[i]) count++;
38318
- }
38319
- return count;
38307
+ function formatInfo(arr) {
38308
+ var str = '';
38309
+ arr.forEach(function(info, i) {
38310
+ var title = 'Layer: ' + (info.layer_name || '[unnamed layer]');
38311
+ var tableStr = formatAttributeTableInfo(info.attribute_data);
38312
+ var tableWidth = measureLongestLine(tableStr);
38313
+ var ruleLen = Math.min(Math.max(title.length, tableWidth), MAX_RULE_LEN);
38314
+ str += '\n';
38315
+ str += utils.lpad('', ruleLen, '=') + '\n';
38316
+ str += title + '\n';
38317
+ str += utils.lpad('', ruleLen, '-') + '\n';
38318
+ str += formatLayerInfo(info);
38319
+ str += tableStr;
38320
+ });
38321
+ return str;
38320
38322
  }
38321
38323
 
38322
- function getLayerInfo(lyr, dataset) {
38323
- var data = getLayerData(lyr, dataset);
38324
+ function formatLayerInfo(data) {
38324
38325
  var str = '';
38325
38326
  str += "Type: " + (data.geometry_type || "tabular data") + "\n";
38326
38327
  str += utils.format("Records: %,d\n",data.feature_count);
@@ -38331,21 +38332,19 @@ ${svg}
38331
38332
  str += "Bounds: " + data.bbox.join(',') + "\n";
38332
38333
  str += "CRS: " + data.proj4 + "\n";
38333
38334
  }
38334
- str += "Source: " + (getLayerSourceFile(lyr, dataset) || 'n/a') + "\n";
38335
+ str += "Source: " + (data.source_file || 'n/a') + "\n";
38335
38336
  return str;
38336
38337
  }
38337
38338
 
38338
- function getAttributeTableInfo(lyr, i) {
38339
- if (!lyr.data || lyr.data.size() === 0 || lyr.data.getFields().length === 0) {
38340
- return "Attribute data: [none]\n";
38341
- }
38342
- return "\nAttribute data\n" + formatAttributeTable(lyr.data, i);
38343
- }
38344
-
38345
- function formatAttributeTable(data, i) {
38346
- var fields = applyFieldOrder(data.getFields(), 'ascending');
38347
- var vals = fields.map(function(fname) {
38348
- return data.getReadOnlyRecordAt(i || 0)[fname];
38339
+ function formatAttributeTableInfo(arr) {
38340
+ if (!arr) return "Attribute data: [none]\n";
38341
+ var header = "\nAttribute data\n";
38342
+ var valKey = 'first_value' in arr[0] ? 'first_value' : 'value';
38343
+ var vals = [];
38344
+ var fields = [];
38345
+ arr.forEach(function(o) {
38346
+ fields.push(o.field);
38347
+ vals.push(o[valKey]);
38349
38348
  });
38350
38349
  var maxIntegralChars = vals.reduce(function(max, val) {
38351
38350
  if (utils.isNumber(val)) {
@@ -38357,20 +38356,66 @@ ${svg}
38357
38356
  var col2Arr = vals.reduce(function(memo, val) {
38358
38357
  memo.push(formatTableValue(val, maxIntegralChars));
38359
38358
  return memo;
38360
- }, [i >= 0 ? 'Value' : 'First value']);
38359
+ }, [valKey == 'first_value' ? 'First value' : 'Value']);
38361
38360
  var col1Chars = maxChars(col1Arr);
38362
38361
  var col2Chars = maxChars(col2Arr);
38363
38362
  var sepStr = (utils.rpad('', col1Chars + 2, '-') + '+' +
38364
38363
  utils.rpad('', col2Chars + 2, '-')).substr(0, MAX_RULE_LEN);
38365
38364
  var sepLine = sepStr + '\n';
38366
- var table = sepLine;
38365
+ var table = '';
38367
38366
  col1Arr.forEach(function(col1, i) {
38368
38367
  var w = stringDisplayWidth(col1);
38369
38368
  table += ' ' + col1 + utils.rpad('', col1Chars - w, ' ') + ' | ' +
38370
38369
  col2Arr[i] + '\n';
38371
38370
  if (i === 0) table += sepLine; // separator after first line
38372
38371
  });
38373
- return table + sepLine;
38372
+ return header + sepLine + table + sepLine;
38373
+ }
38374
+
38375
+ function measureLongestLine(str) {
38376
+ return Math.max.apply(null, str.split('\n').map(function(line) {return stringDisplayWidth(line);}));
38377
+ }
38378
+
38379
+ function stringDisplayWidth(str) {
38380
+ var w = 0;
38381
+ for (var i = 0, n=str.length; i < n; i++) {
38382
+ w += charDisplayWidth(str.charCodeAt(i));
38383
+ }
38384
+ return w;
38385
+ }
38386
+
38387
+ // see https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
38388
+ // this is a simplified version, focusing on double-width CJK chars and ignoring nonprinting etc chars
38389
+ function charDisplayWidth(c) {
38390
+ if (c >= 0x1100 &&
38391
+ (c <= 0x115f || c == 0x2329 || c == 0x232a ||
38392
+ (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || /* CJK ... Yi */
38393
+ (c >= 0xac00 && c <= 0xd7a3) || /* Hangul Syllables */
38394
+ (c >= 0xf900 && c <= 0xfaff) || /* CJK Compatibility Ideographs */
38395
+ (c >= 0xfe10 && c <= 0xfe19) || /* Vertical forms */
38396
+ (c >= 0xfe30 && c <= 0xfe6f) || /* CJK Compatibility Forms */
38397
+ (c >= 0xff00 && c <= 0xff60) || /* Fullwidth Forms */
38398
+ (c >= 0xffe0 && c <= 0xffe6) ||
38399
+ (c >= 0x20000 && c <= 0x2fffd) ||
38400
+ (c >= 0x30000 && c <= 0x3fffd))) return 2;
38401
+ return 1;
38402
+ }
38403
+
38404
+ // TODO: consider polygons with zero area or other invalid geometries
38405
+ function countNullShapes(shapes) {
38406
+ var count = 0;
38407
+ for (var i=0; i<shapes.length; i++) {
38408
+ if (!shapes[i] || shapes[i].length === 0) count++;
38409
+ }
38410
+ return count;
38411
+ }
38412
+
38413
+ function countNullRecords(records) {
38414
+ var count = 0;
38415
+ for (var i=0; i<records.length; i++) {
38416
+ if (!records[i]) count++;
38417
+ }
38418
+ return count;
38374
38419
  }
38375
38420
 
38376
38421
  function maxChars(arr) {
@@ -38424,8 +38469,9 @@ ${svg}
38424
38469
 
38425
38470
  var Info = /*#__PURE__*/Object.freeze({
38426
38471
  __proto__: null,
38427
- getLayerData: getLayerData,
38472
+ getLayerInfo: getLayerInfo,
38428
38473
  getAttributeTableInfo: getAttributeTableInfo,
38474
+ formatAttributeTableInfo: formatAttributeTableInfo,
38429
38475
  formatTableValue: formatTableValue
38430
38476
  });
38431
38477
 
@@ -38477,7 +38523,7 @@ ${svg}
38477
38523
  function getFeatureInfo(id, lyr, arcs) {
38478
38524
  var msg = "Feature " + id + '\n';
38479
38525
  msg += getShapeInfo(id, lyr, arcs);
38480
- msg += getAttributeTableInfo(lyr, id);
38526
+ msg += formatAttributeTableInfo(getAttributeTableInfo(lyr, id));
38481
38527
  return msg;
38482
38528
  }
38483
38529
 
@@ -40589,7 +40635,7 @@ ${svg}
40589
40635
 
40590
40636
  function getRunCommandData(target) {
40591
40637
  var lyr = target.layers[0];
40592
- var data = getLayerData(lyr, target.dataset);
40638
+ var data = getLayerInfo(lyr, target.dataset);
40593
40639
  data.layer = lyr;
40594
40640
  data.dataset = target.dataset;
40595
40641
  return data;
@@ -42939,7 +42985,7 @@ ${svg}
42939
42985
  cmd.include(opts);
42940
42986
 
42941
42987
  } else if (name == 'info') {
42942
- cmd.printInfo(expandCommandTargets(targets));
42988
+ cmd.info(targets, opts);
42943
42989
 
42944
42990
  } else if (name == 'inlay') {
42945
42991
  outputLayers = cmd.inlay(targetLayers, source, targetDataset, opts);
@@ -43305,13 +43351,13 @@ ${svg}
43305
43351
  commands = runAndRemoveInfoCommands(commands);
43306
43352
  if (commands.length === 0) return done(null);
43307
43353
 
43308
- // add options to -i -o -join -clip -erase commands to bypass file i/o
43354
+ // add options to -i -o -join -clip -erase etc. commands to bypass file i/o
43309
43355
  // TODO: find a less kludgy solution
43310
43356
  commands.forEach(function(cmd) {
43311
43357
  if (commandTakesFileInput(cmd.name) && inputObj) {
43312
43358
  cmd.options.input = inputObj;
43313
43359
  }
43314
- if (cmd.name == 'o' && outputArr) {
43360
+ if (outputArr && (cmd.name == 'o' || cmd.name == 'info' && cmd.options.save_to)) {
43315
43361
  cmd.options.output = outputArr;
43316
43362
  }
43317
43363
  });
package/www/page.css CHANGED
@@ -292,9 +292,6 @@ div.error-box {
292
292
  font-size: 16px;
293
293
  }
294
294
 
295
- div.error-box > div {
296
-
297
- }
298
295
 
299
296
  /* --- Splash screen -------------- */
300
297
 
@@ -516,22 +513,26 @@ body.dragover #import-options-drop-area .drop-area {
516
513
  }
517
514
 
518
515
  .info-box h4 {
519
- font-size: 1em;
516
+ font-size: 1.085em;
520
517
  font-weight: normal;
521
- margin: 0 0 1px 0;
518
+ margin: 0 0 2px 0;
522
519
  }
523
520
 
524
521
  .info-box p {
525
522
  white-space: pre-line;
526
523
  margin: 0 0 7px 0;
524
+ line-height: 1.2;
525
+ font-size: 90%;
527
526
  }
528
527
 
529
528
  .option-menu {
530
529
  padding: 0 0 9px 0;
531
530
  }
532
531
 
533
- .option-menu input[type="radio"],
534
- .option-menu input[type="checkbox"]
532
+ /*.option-menu input[type="radio"],
533
+ .option-menu input[type="checkbox"] */
534
+ .info-box input[type="radio"],
535
+ .info-box input[type="checkbox"]
535
536
  {
536
537
  position: relative;
537
538
  top: 1px;
@@ -539,16 +540,16 @@ body.dragover #import-options-drop-area .drop-area {
539
540
  height: 12px;
540
541
  }
541
542
 
542
- .option-menu .tip-button {
543
+ .info-box .tip-button {
543
544
  margin-left: 12px;
544
545
  margin-top: 3px;
545
546
  }
546
547
 
547
- .option-menu input[type="checkbox"] {
548
+ .info-box input[type="checkbox"] {
548
549
  margin: 3px 5px 0 1px;
549
550
  }
550
551
 
551
- .option-menu input.radio {
552
+ .info-box input.radio {
552
553
  margin: 0 5px 0 0;
553
554
  }
554
555
 
@@ -1079,10 +1080,7 @@ img.close-btn:hover,
1079
1080
  height: 100%;
1080
1081
  }
1081
1082
 
1082
- .info-box p {
1083
- line-height: 1.2;
1084
- font-size: 90%;
1085
- }
1083
+
1086
1084
 
1087
1085
  .basemap-styles {
1088
1086
  margin: 0 -2px;
@@ -1212,6 +1210,13 @@ div.basemap-style-btn.active img {
1212
1210
  top: 23px;
1213
1211
  }
1214
1212
 
1213
+ #save-preference {
1214
+ display: none;
1215
+ position: relative;
1216
+ top: 1px;
1217
+ left: 5px;
1218
+ }
1219
+
1215
1220
  .nav-sub-menu {
1216
1221
  position: absolute;
1217
1222
  z-index: -1;