mapshaper 0.6.19 → 0.6.21

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.
@@ -27,6 +27,7 @@
27
27
  get defaults () { return defaults; },
28
28
  get extend () { return extend; },
29
29
  get inherit () { return inherit; },
30
+ get promisify () { return promisify; },
30
31
  get reduceAsync () { return reduceAsync; },
31
32
  get merge () { return merge; },
32
33
  get difference () { return difference; },
@@ -84,6 +85,7 @@
84
85
  get formatter () { return formatter; },
85
86
  get wildcardToRegExp () { return wildcardToRegExp; },
86
87
  get createBuffer () { return createBuffer; },
88
+ get toBuffer () { return toBuffer; },
87
89
  get expandoBuffer () { return expandoBuffer; },
88
90
  get copyElements () { return copyElements; },
89
91
  get extendBuffer () { return extendBuffer; },
@@ -588,12 +590,12 @@
588
590
  if (arguments.length == 1) {
589
591
  return this.el.getAttribute(obj);
590
592
  }
591
- this.el.setAttribute(obj, value);
592
- // this.el[obj] = value;
593
+ if (value === null) {
594
+ this.el.removeAttribute(obj);
595
+ } else {
596
+ this.el.setAttribute(obj, value);
597
+ }
593
598
  }
594
- // else if (!value) {
595
- // Opts.copyAllParams(this.el, obj);
596
- // }
597
599
  return this;
598
600
  },
599
601
 
@@ -1342,13 +1344,6 @@
1342
1344
  var sessionId = getUniqId('session');
1343
1345
  var snapshotCount = 0;
1344
1346
 
1345
- window.addEventListener('beforeunload', async function() {
1346
- // delete snapshot data
1347
- // This is not ideal, because the data gets deleted even if the user
1348
- // cancels the page close... but there's no apparent good alternative
1349
- await finalCleanup();
1350
- });
1351
-
1352
1347
  function getUniqId(prefix) {
1353
1348
  return prefix + '_' + (Math.random() + 1).toString(36).substring(2,8);
1354
1349
  }
@@ -1358,29 +1353,43 @@
1358
1353
  }
1359
1354
 
1360
1355
  function SessionSnapshots(gui) {
1361
- if (!gui.options.saveControl) return;
1362
1356
  var _menuOpen = false;
1363
1357
  var _menuTimeout;
1364
- var btn = gui.buttons.addButton('#ribbon-icon').addClass('menu-btn save-btn');
1365
- var menu = El('div').addClass('nav-sub-menu save-menu').appendTo(btn.node());
1358
+ var btn, menu;
1366
1359
 
1367
- initialCleanup();
1360
+ init();
1368
1361
 
1369
- btn.on('mouseenter', function() {
1370
- btn.addClass('hover');
1371
- clearTimeout(_menuTimeout); // prevent timed closing
1372
- if (!_menuOpen) {
1373
- openMenu();
1374
- }
1375
- });
1362
+ async function init() {
1363
+ var enabled = await isStorageEnabled();
1364
+ if (!enabled) return;
1376
1365
 
1377
- btn.on('mouseleave', function() {
1378
- if (!_menuOpen) {
1379
- btn.removeClass('hover');
1380
- } else {
1381
- closeMenu(200);
1382
- }
1383
- });
1366
+ btn = gui.buttons.addButton('#ribbon-icon').addClass('menu-btn save-btn');
1367
+ menu = El('div').addClass('nav-sub-menu save-menu').appendTo(btn.node());
1368
+ await initialCleanup();
1369
+
1370
+ window.addEventListener('beforeunload', async function() {
1371
+ // delete snapshot data
1372
+ // This is not ideal, because the data gets deleted even if the user
1373
+ // cancels the page close... but there's no apparent good alternative
1374
+ await finalCleanup();
1375
+ });
1376
+
1377
+ btn.on('mouseenter', function() {
1378
+ btn.addClass('hover');
1379
+ clearTimeout(_menuTimeout); // prevent timed closing
1380
+ if (!_menuOpen) {
1381
+ openMenu();
1382
+ }
1383
+ });
1384
+
1385
+ btn.on('mouseleave', function() {
1386
+ if (!_menuOpen) {
1387
+ btn.removeClass('hover');
1388
+ } else {
1389
+ closeMenu(200);
1390
+ }
1391
+ });
1392
+ }
1384
1393
 
1385
1394
  async function renderMenu() {
1386
1395
  var snapshots = await fetchSnapshotList();
@@ -1389,7 +1398,7 @@
1389
1398
  addMenuLink({
1390
1399
  slug: 'stash',
1391
1400
  // label: 'save data snapshot',
1392
- label: 'take a snapshot',
1401
+ label: 'create a snapshot',
1393
1402
  action: saveSnapshot
1394
1403
  });
1395
1404
 
@@ -1407,7 +1416,7 @@
1407
1416
  var line = El('div').appendTo(menu).addClass('save-menu-item');
1408
1417
  El('span').appendTo(line).html(`<span class="save-item-label">#${item.number}</span> `);
1409
1418
  // show snapshot size
1410
- // El('span').appendTo(line).html(` <span class="save-item-size">${item.display_size}</span>`);
1419
+ El('span').appendTo(line).html(` <span class="save-item-size">${item.display_size}</span>`);
1411
1420
  El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
1412
1421
  await restoreSnapshotById(item.id, gui);
1413
1422
  closeMenu(100);
@@ -1461,7 +1470,7 @@
1461
1470
  }
1462
1471
 
1463
1472
  async function saveSnapshot(gui) {
1464
- var buf = captureSnapshot(gui);
1473
+ var buf = await captureSnapshot(gui);
1465
1474
  var entryId = String(++snapshotCount).padStart(3, '0');
1466
1475
  var snapshotId = sessionId + '_' + entryId; // e.g. session_d89fw_001
1467
1476
  var entry = {
@@ -1512,7 +1521,7 @@
1512
1521
  console.log('Snapshot not available:', id);
1513
1522
  return;
1514
1523
  }
1515
- var data = internal.unpackSession(buf);
1524
+ var data = await internal.unpackSession(buf);
1516
1525
  buf = null;
1517
1526
  gui.model.clear();
1518
1527
  importDatasets(data.datasets, gui);
@@ -1522,11 +1531,11 @@
1522
1531
  // Add datasets to the current project
1523
1532
  // TODO: figure out if interface data should be imported (e.g. should
1524
1533
  // visibility flag of imported layers be imported)
1525
- function importSessionData(buf, gui) {
1534
+ async function importSessionData(buf, gui) {
1526
1535
  if (buf instanceof ArrayBuffer) {
1527
1536
  buf = new Uint8Array(buf);
1528
1537
  }
1529
- var data = internal.unpackSession(buf);
1538
+ var data = await internal.unpackSession(buf);
1530
1539
  importDatasets(data.datasets, gui);
1531
1540
  }
1532
1541
 
@@ -1537,11 +1546,16 @@
1537
1546
  gui.model.updated({select: true, arc_count: true}); // arc_count to refresh display shapes
1538
1547
  }
1539
1548
 
1540
- function captureSnapshot(gui) {
1549
+ async function captureSnapshot(gui) {
1541
1550
  var datasets = gui.model.getDatasets();
1542
1551
  var lyr = gui.model.getActiveLayer().layer;
1552
+ // compact: true applies compression to vector coordinates, for ~30% reduction
1553
+ // in file size in a typical polygon or polyline file, but longer processing time
1554
+ var opts = {compact: false};
1543
1555
  lyr.active = true;
1544
- var obj = internal.exportDatasetsToPack(datasets);
1556
+ // console.time('msx');
1557
+ var obj = await internal.exportDatasetsToPack(datasets, opts);
1558
+ // console.timeEnd('msx')
1545
1559
  delete lyr.active;
1546
1560
  obj.gui = getGuiState(gui);
1547
1561
  return internal.pack(obj);
@@ -1597,7 +1611,7 @@
1597
1611
  }
1598
1612
  });
1599
1613
  // remove old indexed snapshots
1600
- return updateIndex(function(index) {
1614
+ await updateIndex(function(index) {
1601
1615
  index.snapshots = index.snapshots.filter(function(snap) {
1602
1616
  var msPerDay = 1000 * 60 * 60 * 24;
1603
1617
  var daysOld = (Date.now() - snap.created) / msPerDay;
@@ -1679,17 +1693,24 @@
1679
1693
  }
1680
1694
 
1681
1695
  function getSessionData() {
1682
- try {
1683
- var data = JSON.parse(window.localStorage.getItem('session_data'));
1684
- return data || [];
1685
- } catch(e) {}
1686
- return [];
1696
+ var data = JSON.parse(window.localStorage.getItem('session_data'));
1697
+ return data || [];
1687
1698
  }
1688
1699
 
1689
1700
  function setSessionData(arr) {
1690
1701
  window.localStorage.setItem('session_data', JSON.stringify(arr));
1691
1702
  }
1692
1703
 
1704
+ async function isStorageEnabled() {
1705
+ try {
1706
+ setSessionData(getSessionData());
1707
+ await updateIndex(function() {});
1708
+ return true;
1709
+ } catch(e) {
1710
+ return false;
1711
+ }
1712
+ }
1713
+
1693
1714
  // @cb function(<FileList>)
1694
1715
  function DropControl(gui, el, cb) {
1695
1716
  var area = El(el);
@@ -1773,16 +1794,16 @@
1773
1794
  .attr('multiple', 'multiple')
1774
1795
  .on('change', onchange);
1775
1796
 
1776
- function onchange(e) {
1797
+ async function onchange(e) {
1777
1798
  var files = e.target.files;
1778
1799
  // files may be undefined (e.g. if user presses 'cancel' after a file has been selected)
1779
1800
  if (files) {
1780
1801
  // disable the button while files are being processed
1781
1802
  btn.addClass('selected');
1782
1803
  input.attr('disabled', true);
1783
- cb(files);
1804
+ await cb(files);
1784
1805
  btn.removeClass('selected');
1785
- input.attr('disabled', false);
1806
+ input.attr('disabled', null);
1786
1807
  }
1787
1808
  }
1788
1809
  }
@@ -1940,7 +1961,7 @@
1940
1961
  }
1941
1962
  gui.enterMode('import');
1942
1963
  if (useQuickView()) {
1943
- importQueuedFiles();
1964
+ await importQueuedFiles();
1944
1965
  } else {
1945
1966
  gui.container.addClass('queued-files');
1946
1967
  El('#path-import-options').classed('hidden', !filesMayContainPaths(queuedFiles));
@@ -1983,7 +2004,7 @@
1983
2004
  await wait(35);
1984
2005
  }
1985
2006
  if (group[internal.PACKAGE_EXT]) {
1986
- importSessionData(group[internal.PACKAGE_EXT].content, gui);
2007
+ await importSessionData(group[internal.PACKAGE_EXT].content, gui);
1987
2008
  } else {
1988
2009
  importDataset(group, importOpts);
1989
2010
  }
@@ -2113,14 +2134,6 @@
2113
2134
  return new Promise(resolve => setTimeout(resolve, ms));
2114
2135
  }
2115
2136
 
2116
- function runAsync(fn, arg) {
2117
- return new Promise((resolve, reject) => {
2118
- fn(arg, function(err, data) {
2119
- return err ? reject(err) : resolve(data);
2120
- });
2121
- });
2122
- }
2123
-
2124
2137
  async function readKmzFile(file) {
2125
2138
  var files = await readZipFile(file);
2126
2139
  var name = files[0] && files[0].name;
@@ -2142,7 +2155,7 @@
2142
2155
  async function readZipFile(file) {
2143
2156
  // Async is up to twice as fast unzipping large files
2144
2157
  // var index = internal.unzipSync(file.content);
2145
- var index = await runAsync(internal.unzipAsync, file.content);
2158
+ var index = await utils$1.promisify(internal.unzipAsync)(file.content);
2146
2159
  return Object.keys(index).reduce(function(memo, filename) {
2147
2160
  if (!/\.txt$/i.test(filename)) {
2148
2161
  memo.push({
@@ -2176,7 +2189,7 @@
2176
2189
 
2177
2190
  async function readFileData(file) {
2178
2191
  try {
2179
- var content = await runAsync(readContentFileAsync, file);
2192
+ var content = await utils$1.promisify(readContentFileAsync)(file);
2180
2193
  return {
2181
2194
  content: content,
2182
2195
  name: file.name
@@ -3489,7 +3502,7 @@
3489
3502
  flags.arc_count || flags.snap || flags.affine || flags.points || flags['merge-layers'];
3490
3503
  if (intersectionsMayHaveChanged) {
3491
3504
  // delete any cached intersection data, to trigger re-calculation
3492
- e.dataset.info.intersections = null;
3505
+ e.dataset._intersections = null;
3493
3506
  updateAsync();
3494
3507
  } else if (flags.select) {
3495
3508
  // new active layer, but no editing commands were run -- use cached intersections (if available)
@@ -3538,11 +3551,11 @@
3538
3551
  XX = internal.findSegmentIntersections(arcs, opts);
3539
3552
  showBtn = XX.length > 0;
3540
3553
  } else { // no simplification
3541
- XX = dataset.info.intersections;
3554
+ XX = dataset._intersections;
3542
3555
  if (!XX) {
3543
3556
  // cache intersections at 0 simplification, to avoid recalculating
3544
3557
  // every time the simplification slider is set to 100% or the layer is selected at 100%
3545
- XX = dataset.info.intersections = internal.findSegmentIntersections(arcs, opts);
3558
+ XX = dataset._intersections = internal.findSegmentIntersections(arcs, opts);
3546
3559
  }
3547
3560
  showBtn = false;
3548
3561
  }
@@ -3559,7 +3572,7 @@
3559
3572
 
3560
3573
  function dismiss() {
3561
3574
  var dataset = model.getActiveLayer().dataset;
3562
- dataset.info.intersections = null;
3575
+ dataset._intersections = null;
3563
3576
  dataset.info.no_intersections = true;
3564
3577
  reset();
3565
3578
  }
@@ -3624,8 +3637,6 @@
3624
3637
  return layers.reverse();
3625
3638
  }
3626
3639
 
3627
- // import { groupLayersByDataset } from '../dataset/mapshaper-target-utils';
3628
-
3629
3640
  // Export buttons and their behavior
3630
3641
  var ExportControl = function(gui) {
3631
3642
  var model = gui.model;
@@ -3677,16 +3688,15 @@
3677
3688
  gui.clearMode();
3678
3689
  gui.showProgressMessage('Exporting');
3679
3690
  setTimeout(function() {
3680
- exportMenuSelection(layers, function(err) {
3681
- if (err) {
3682
- if (utils$1.isString(err)) {
3683
- gui.alert(err);
3684
- } else {
3685
- // stack seems to change if Error is logged directly
3686
- console.error(err.stack);
3687
- gui.alert('Export failed for an unknown reason');
3688
- }
3691
+ exportMenuSelection(layers).catch(function(err) {
3692
+ if (utils$1.isString(err)) {
3693
+ gui.alert(err);
3694
+ } else {
3695
+ // stack seems to change if Error is logged directly
3696
+ console.error(err.stack);
3697
+ gui.alert('Export failed for an unknown reason');
3689
3698
  }
3699
+ }).finally(function() {
3690
3700
  gui.clearProgressMessage();
3691
3701
  });
3692
3702
  }, 20);
@@ -3708,17 +3718,12 @@
3708
3718
  }
3709
3719
 
3710
3720
  // done: function(string|Error|null)
3711
- function exportMenuSelection(layers, done) {
3712
- var opts, files;
3713
- try {
3714
- opts = getExportOpts();
3721
+ async function exportMenuSelection(layers) {
3722
+ var opts = getExportOpts();
3715
3723
  // note: command line "target" option gets ignored
3716
- files = internal.exportTargetLayers(layers, opts);
3717
- gui.session.layersExported(getTargetLayerIds(), getExportOptsAsString());
3718
- } catch(e) {
3719
- return done(e);
3720
- }
3721
- internal.writeFiles(files, opts, done);
3724
+ var files = await internal.exportTargetLayers(layers, opts);
3725
+ gui.session.layersExported(getTargetLayerIds(), getExportOptsAsString());
3726
+ await utils$1.promisify(internal.writeFiles)(files, opts);
3722
3727
  }
3723
3728
 
3724
3729
  function initLayerItem(o, i) {
@@ -3838,7 +3843,7 @@
3838
3843
  }
3839
3844
 
3840
3845
  function initFormatMenu() {
3841
- var defaults = ['shapefile', 'geojson', 'topojson', 'json', 'dsv', 'kml', 'svg'];
3846
+ var defaults = ['shapefile', 'geojson', 'topojson', 'json', 'dsv', 'kml', 'svg', internal.PACKAGE_EXT];
3842
3847
  var formats = utils$1.uniq(defaults.concat(getInputFormats()));
3843
3848
  var items = formats.map(function(fmt) {
3844
3849
  return utils$1.format('<div><label><input type="radio" name="format" value="%s"' +
@@ -3966,7 +3971,7 @@
3966
3971
  clearClass('insert-below');
3967
3972
  dragTargetId = layerOrderSlug = null;
3968
3973
  if (dragging) {
3969
- render(); // in case menu changed...
3974
+ renderLayerList(); // in case menu changed...
3970
3975
  dragging = false;
3971
3976
  }
3972
3977
  }
@@ -4003,6 +4008,32 @@
4003
4008
  }
4004
4009
 
4005
4010
  function render() {
4011
+ renderLayerList();
4012
+ renderFileList();
4013
+ }
4014
+
4015
+ function renderFileList() {
4016
+ var list = el.findChild('.file-list');
4017
+ var files = [];
4018
+ list.empty();
4019
+ model.forEachLayer(function(lyr, dataset) {
4020
+ var file = internal.getLayerSourceFile(lyr, dataset);
4021
+ if (!file || files.includes(file)) return;
4022
+ files.push(file);
4023
+ var warnings = getWarnings(lyr, dataset);
4024
+ var html = '<div class="layer-item">';
4025
+ html += rowHTML('name', file);
4026
+ if (warnings) {
4027
+ // html += rowHTML('problems', warnings, 'layer-problems');
4028
+ html += rowHTML('', warnings, 'layer-problems');
4029
+ }
4030
+ html += '</div>';
4031
+ list.appendChild(El('div').html(html).firstChild());
4032
+ });
4033
+
4034
+ }
4035
+
4036
+ function renderLayerList() {
4006
4037
  var list = el.findChild('.layer-list');
4007
4038
  var uniqIds = {};
4008
4039
  var pinnableCount = 0;
@@ -4049,7 +4080,6 @@
4049
4080
  cache.cleanup();
4050
4081
 
4051
4082
  function renderLayer(lyr, dataset, opts) {
4052
- var warnings = getWarnings(lyr, dataset);
4053
4083
  var classes = 'layer-item';
4054
4084
  var entry, html;
4055
4085
 
@@ -4059,13 +4089,7 @@
4059
4089
 
4060
4090
  html = '<!-- ' + lyr.menu_id + '--><div class="' + classes + '">';
4061
4091
  html += rowHTML('name', '<span class="layer-name colored-text dot-underline">' + formatLayerNameForDisplay(lyr.name) + '</span>', 'row1');
4062
- if (opts.show_source) {
4063
- html += rowHTML('source file', describeSrc(lyr, dataset) || 'n/a');
4064
- }
4065
4092
  html += rowHTML('contents', describeLyr(lyr));
4066
- if (warnings) {
4067
- html += rowHTML('problems', warnings, 'layer-problems');
4068
- }
4069
4093
  html += '<img class="close-btn" draggable="false" src="images/close.png">';
4070
4094
  if (opts.pinnable) {
4071
4095
  html += '<img class="pin-btn unpinned" draggable="false" src="images/eye.png">';
@@ -4197,6 +4221,8 @@
4197
4221
  var file = internal.getLayerSourceFile(lyr, dataset);
4198
4222
  var missing = [];
4199
4223
  var msg;
4224
+ // show missing file warning for first layer in dataset
4225
+ // (assuming it represents the content of the original file)
4200
4226
  if (utils$1.endsWith(file, '.shp') && lyr == dataset.layers[0]) {
4201
4227
  if (!lyr.data) {
4202
4228
  missing.push('.dbf');
@@ -5316,6 +5342,27 @@
5316
5342
  targ.prototype.__super__ = f;
5317
5343
  }
5318
5344
 
5345
+ function promisify(asyncFn) {
5346
+ return function() {
5347
+ var args = toArray(arguments);
5348
+ return new Promise((resolve, reject) => {
5349
+ var cb = function(err, data) {
5350
+ if (err) reject(err);
5351
+ else resolve(data);
5352
+ };
5353
+ args.push(cb);
5354
+ asyncFn.apply(this, args);
5355
+ });
5356
+ };
5357
+ }
5358
+
5359
+ function runAsync(fn, arg) {
5360
+ return new Promise((resolve, reject) => {
5361
+ fn(arg, function(err, data) {
5362
+ return err ? reject(err) : resolve(data);
5363
+ });
5364
+ });
5365
+ }
5319
5366
 
5320
5367
  // Call @iter on each member of an array (similar to Array#reduce(iter))
5321
5368
  // iter: function(memo, item, callback)
@@ -6017,6 +6064,15 @@
6017
6064
  }
6018
6065
  }
6019
6066
 
6067
+ function toBuffer(src) {
6068
+ if (src instanceof B) return src;
6069
+ if (src instanceof ArrayBuffer) return B.from(src);
6070
+ if (src instanceof Uint8Array) {
6071
+ return B.from(src.buffer, src.byteOffset, src.byteLength);
6072
+ }
6073
+ error('Unexpected argument type');
6074
+ }
6075
+
6020
6076
  function expandoBuffer(constructor, rate) {
6021
6077
  var capacity = 0,
6022
6078
  k = rate >= 1 ? rate : 1.2,
@@ -11191,11 +11247,14 @@
11191
11247
  gui.buttons = new SidebarButtons(gui);
11192
11248
  gui.map = new MshpMap(gui);
11193
11249
  gui.interaction = new InteractionMode(gui);
11194
- gui.sessionSave = new SessionSnapshots(gui);
11195
11250
  gui.session = new SessionHistory(gui);
11196
11251
  gui.undo = new Undo(gui);
11197
11252
  gui.state = {};
11198
11253
 
11254
+ if (opts.saveControl) {
11255
+ new SessionSnapshots(gui);
11256
+ }
11257
+
11199
11258
  initModeRules(gui);
11200
11259
 
11201
11260
  gui.showProgressMessage = function(msg) {