mapshaper 0.6.19 → 0.6.20

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; },
@@ -1342,13 +1343,6 @@
1342
1343
  var sessionId = getUniqId('session');
1343
1344
  var snapshotCount = 0;
1344
1345
 
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
1346
  function getUniqId(prefix) {
1353
1347
  return prefix + '_' + (Math.random() + 1).toString(36).substring(2,8);
1354
1348
  }
@@ -1358,29 +1352,43 @@
1358
1352
  }
1359
1353
 
1360
1354
  function SessionSnapshots(gui) {
1361
- if (!gui.options.saveControl) return;
1362
1355
  var _menuOpen = false;
1363
1356
  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());
1357
+ var btn, menu;
1366
1358
 
1367
- initialCleanup();
1359
+ init();
1368
1360
 
1369
- btn.on('mouseenter', function() {
1370
- btn.addClass('hover');
1371
- clearTimeout(_menuTimeout); // prevent timed closing
1372
- if (!_menuOpen) {
1373
- openMenu();
1374
- }
1375
- });
1361
+ async function init() {
1362
+ var enabled = await isStorageEnabled();
1363
+ if (!enabled) return;
1376
1364
 
1377
- btn.on('mouseleave', function() {
1378
- if (!_menuOpen) {
1379
- btn.removeClass('hover');
1380
- } else {
1381
- closeMenu(200);
1382
- }
1383
- });
1365
+ btn = gui.buttons.addButton('#ribbon-icon').addClass('menu-btn save-btn');
1366
+ menu = El('div').addClass('nav-sub-menu save-menu').appendTo(btn.node());
1367
+ await initialCleanup();
1368
+
1369
+ window.addEventListener('beforeunload', async function() {
1370
+ // delete snapshot data
1371
+ // This is not ideal, because the data gets deleted even if the user
1372
+ // cancels the page close... but there's no apparent good alternative
1373
+ await finalCleanup();
1374
+ });
1375
+
1376
+ btn.on('mouseenter', function() {
1377
+ btn.addClass('hover');
1378
+ clearTimeout(_menuTimeout); // prevent timed closing
1379
+ if (!_menuOpen) {
1380
+ openMenu();
1381
+ }
1382
+ });
1383
+
1384
+ btn.on('mouseleave', function() {
1385
+ if (!_menuOpen) {
1386
+ btn.removeClass('hover');
1387
+ } else {
1388
+ closeMenu(200);
1389
+ }
1390
+ });
1391
+ }
1384
1392
 
1385
1393
  async function renderMenu() {
1386
1394
  var snapshots = await fetchSnapshotList();
@@ -1407,7 +1415,7 @@
1407
1415
  var line = El('div').appendTo(menu).addClass('save-menu-item');
1408
1416
  El('span').appendTo(line).html(`<span class="save-item-label">#${item.number}</span> `);
1409
1417
  // show snapshot size
1410
- // El('span').appendTo(line).html(` <span class="save-item-size">${item.display_size}</span>`);
1418
+ El('span').appendTo(line).html(` <span class="save-item-size">${item.display_size}</span>`);
1411
1419
  El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
1412
1420
  await restoreSnapshotById(item.id, gui);
1413
1421
  closeMenu(100);
@@ -1461,7 +1469,7 @@
1461
1469
  }
1462
1470
 
1463
1471
  async function saveSnapshot(gui) {
1464
- var buf = captureSnapshot(gui);
1472
+ var buf = await captureSnapshot(gui);
1465
1473
  var entryId = String(++snapshotCount).padStart(3, '0');
1466
1474
  var snapshotId = sessionId + '_' + entryId; // e.g. session_d89fw_001
1467
1475
  var entry = {
@@ -1512,7 +1520,7 @@
1512
1520
  console.log('Snapshot not available:', id);
1513
1521
  return;
1514
1522
  }
1515
- var data = internal.unpackSession(buf);
1523
+ var data = await internal.unpackSession(buf);
1516
1524
  buf = null;
1517
1525
  gui.model.clear();
1518
1526
  importDatasets(data.datasets, gui);
@@ -1522,11 +1530,11 @@
1522
1530
  // Add datasets to the current project
1523
1531
  // TODO: figure out if interface data should be imported (e.g. should
1524
1532
  // visibility flag of imported layers be imported)
1525
- function importSessionData(buf, gui) {
1533
+ async function importSessionData(buf, gui) {
1526
1534
  if (buf instanceof ArrayBuffer) {
1527
1535
  buf = new Uint8Array(buf);
1528
1536
  }
1529
- var data = internal.unpackSession(buf);
1537
+ var data = await internal.unpackSession(buf);
1530
1538
  importDatasets(data.datasets, gui);
1531
1539
  }
1532
1540
 
@@ -1537,11 +1545,16 @@
1537
1545
  gui.model.updated({select: true, arc_count: true}); // arc_count to refresh display shapes
1538
1546
  }
1539
1547
 
1540
- function captureSnapshot(gui) {
1548
+ async function captureSnapshot(gui) {
1541
1549
  var datasets = gui.model.getDatasets();
1542
1550
  var lyr = gui.model.getActiveLayer().layer;
1551
+ // compact: true applies compression to vector coordinates, for ~30% reduction
1552
+ // in file size in a typical polygon or polyline file, but longer processing time
1553
+ var opts = {compact: false};
1543
1554
  lyr.active = true;
1544
- var obj = internal.exportDatasetsToPack(datasets);
1555
+ // console.time('msx');
1556
+ var obj = await internal.exportDatasetsToPack(datasets, opts);
1557
+ // console.timeEnd('msx')
1545
1558
  delete lyr.active;
1546
1559
  obj.gui = getGuiState(gui);
1547
1560
  return internal.pack(obj);
@@ -1597,7 +1610,7 @@
1597
1610
  }
1598
1611
  });
1599
1612
  // remove old indexed snapshots
1600
- return updateIndex(function(index) {
1613
+ await updateIndex(function(index) {
1601
1614
  index.snapshots = index.snapshots.filter(function(snap) {
1602
1615
  var msPerDay = 1000 * 60 * 60 * 24;
1603
1616
  var daysOld = (Date.now() - snap.created) / msPerDay;
@@ -1679,17 +1692,24 @@
1679
1692
  }
1680
1693
 
1681
1694
  function getSessionData() {
1682
- try {
1683
- var data = JSON.parse(window.localStorage.getItem('session_data'));
1684
- return data || [];
1685
- } catch(e) {}
1686
- return [];
1695
+ var data = JSON.parse(window.localStorage.getItem('session_data'));
1696
+ return data || [];
1687
1697
  }
1688
1698
 
1689
1699
  function setSessionData(arr) {
1690
1700
  window.localStorage.setItem('session_data', JSON.stringify(arr));
1691
1701
  }
1692
1702
 
1703
+ async function isStorageEnabled() {
1704
+ try {
1705
+ setSessionData(getSessionData());
1706
+ await updateIndex(function() {});
1707
+ return true;
1708
+ } catch(e) {
1709
+ return false;
1710
+ }
1711
+ }
1712
+
1693
1713
  // @cb function(<FileList>)
1694
1714
  function DropControl(gui, el, cb) {
1695
1715
  var area = El(el);
@@ -1983,7 +2003,7 @@
1983
2003
  await wait(35);
1984
2004
  }
1985
2005
  if (group[internal.PACKAGE_EXT]) {
1986
- importSessionData(group[internal.PACKAGE_EXT].content, gui);
2006
+ await importSessionData(group[internal.PACKAGE_EXT].content, gui);
1987
2007
  } else {
1988
2008
  importDataset(group, importOpts);
1989
2009
  }
@@ -3489,7 +3509,7 @@
3489
3509
  flags.arc_count || flags.snap || flags.affine || flags.points || flags['merge-layers'];
3490
3510
  if (intersectionsMayHaveChanged) {
3491
3511
  // delete any cached intersection data, to trigger re-calculation
3492
- e.dataset.info.intersections = null;
3512
+ e.dataset._intersections = null;
3493
3513
  updateAsync();
3494
3514
  } else if (flags.select) {
3495
3515
  // new active layer, but no editing commands were run -- use cached intersections (if available)
@@ -3538,11 +3558,11 @@
3538
3558
  XX = internal.findSegmentIntersections(arcs, opts);
3539
3559
  showBtn = XX.length > 0;
3540
3560
  } else { // no simplification
3541
- XX = dataset.info.intersections;
3561
+ XX = dataset._intersections;
3542
3562
  if (!XX) {
3543
3563
  // cache intersections at 0 simplification, to avoid recalculating
3544
3564
  // 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);
3565
+ XX = dataset._intersections = internal.findSegmentIntersections(arcs, opts);
3546
3566
  }
3547
3567
  showBtn = false;
3548
3568
  }
@@ -3559,7 +3579,7 @@
3559
3579
 
3560
3580
  function dismiss() {
3561
3581
  var dataset = model.getActiveLayer().dataset;
3562
- dataset.info.intersections = null;
3582
+ dataset._intersections = null;
3563
3583
  dataset.info.no_intersections = true;
3564
3584
  reset();
3565
3585
  }
@@ -3624,8 +3644,6 @@
3624
3644
  return layers.reverse();
3625
3645
  }
3626
3646
 
3627
- // import { groupLayersByDataset } from '../dataset/mapshaper-target-utils';
3628
-
3629
3647
  // Export buttons and their behavior
3630
3648
  var ExportControl = function(gui) {
3631
3649
  var model = gui.model;
@@ -3677,16 +3695,15 @@
3677
3695
  gui.clearMode();
3678
3696
  gui.showProgressMessage('Exporting');
3679
3697
  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
- }
3698
+ exportMenuSelection(layers).catch(function(err) {
3699
+ if (utils$1.isString(err)) {
3700
+ gui.alert(err);
3701
+ } else {
3702
+ // stack seems to change if Error is logged directly
3703
+ console.error(err.stack);
3704
+ gui.alert('Export failed for an unknown reason');
3689
3705
  }
3706
+ }).finally(function() {
3690
3707
  gui.clearProgressMessage();
3691
3708
  });
3692
3709
  }, 20);
@@ -3708,17 +3725,12 @@
3708
3725
  }
3709
3726
 
3710
3727
  // done: function(string|Error|null)
3711
- function exportMenuSelection(layers, done) {
3712
- var opts, files;
3713
- try {
3714
- opts = getExportOpts();
3728
+ async function exportMenuSelection(layers) {
3729
+ var opts = getExportOpts();
3715
3730
  // 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);
3731
+ var files = await internal.exportTargetLayers(layers, opts);
3732
+ gui.session.layersExported(getTargetLayerIds(), getExportOptsAsString());
3733
+ await utils$1.promisify(internal.writeFiles)(files, opts);
3722
3734
  }
3723
3735
 
3724
3736
  function initLayerItem(o, i) {
@@ -3838,7 +3850,7 @@
3838
3850
  }
3839
3851
 
3840
3852
  function initFormatMenu() {
3841
- var defaults = ['shapefile', 'geojson', 'topojson', 'json', 'dsv', 'kml', 'svg'];
3853
+ var defaults = ['shapefile', 'geojson', 'topojson', 'json', 'dsv', 'kml', 'svg', internal.PACKAGE_EXT];
3842
3854
  var formats = utils$1.uniq(defaults.concat(getInputFormats()));
3843
3855
  var items = formats.map(function(fmt) {
3844
3856
  return utils$1.format('<div><label><input type="radio" name="format" value="%s"' +
@@ -3966,7 +3978,7 @@
3966
3978
  clearClass('insert-below');
3967
3979
  dragTargetId = layerOrderSlug = null;
3968
3980
  if (dragging) {
3969
- render(); // in case menu changed...
3981
+ renderLayerList(); // in case menu changed...
3970
3982
  dragging = false;
3971
3983
  }
3972
3984
  }
@@ -4003,6 +4015,32 @@
4003
4015
  }
4004
4016
 
4005
4017
  function render() {
4018
+ renderLayerList();
4019
+ renderFileList();
4020
+ }
4021
+
4022
+ function renderFileList() {
4023
+ var list = el.findChild('.file-list');
4024
+ var files = [];
4025
+ list.empty();
4026
+ model.forEachLayer(function(lyr, dataset) {
4027
+ var file = internal.getLayerSourceFile(lyr, dataset);
4028
+ if (!file || files.includes(file)) return;
4029
+ files.push(file);
4030
+ var warnings = getWarnings(lyr, dataset);
4031
+ var html = '<div class="layer-item">';
4032
+ html += rowHTML('name', file);
4033
+ if (warnings) {
4034
+ // html += rowHTML('problems', warnings, 'layer-problems');
4035
+ html += rowHTML('', warnings, 'layer-problems');
4036
+ }
4037
+ html += '</div>';
4038
+ list.appendChild(El('div').html(html).firstChild());
4039
+ });
4040
+
4041
+ }
4042
+
4043
+ function renderLayerList() {
4006
4044
  var list = el.findChild('.layer-list');
4007
4045
  var uniqIds = {};
4008
4046
  var pinnableCount = 0;
@@ -4049,7 +4087,6 @@
4049
4087
  cache.cleanup();
4050
4088
 
4051
4089
  function renderLayer(lyr, dataset, opts) {
4052
- var warnings = getWarnings(lyr, dataset);
4053
4090
  var classes = 'layer-item';
4054
4091
  var entry, html;
4055
4092
 
@@ -4059,13 +4096,7 @@
4059
4096
 
4060
4097
  html = '<!-- ' + lyr.menu_id + '--><div class="' + classes + '">';
4061
4098
  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
4099
  html += rowHTML('contents', describeLyr(lyr));
4066
- if (warnings) {
4067
- html += rowHTML('problems', warnings, 'layer-problems');
4068
- }
4069
4100
  html += '<img class="close-btn" draggable="false" src="images/close.png">';
4070
4101
  if (opts.pinnable) {
4071
4102
  html += '<img class="pin-btn unpinned" draggable="false" src="images/eye.png">';
@@ -4197,6 +4228,8 @@
4197
4228
  var file = internal.getLayerSourceFile(lyr, dataset);
4198
4229
  var missing = [];
4199
4230
  var msg;
4231
+ // show missing file warning for first layer in dataset
4232
+ // (assuming it represents the content of the original file)
4200
4233
  if (utils$1.endsWith(file, '.shp') && lyr == dataset.layers[0]) {
4201
4234
  if (!lyr.data) {
4202
4235
  missing.push('.dbf');
@@ -5316,6 +5349,27 @@
5316
5349
  targ.prototype.__super__ = f;
5317
5350
  }
5318
5351
 
5352
+ function promisify(asyncFn) {
5353
+ return function() {
5354
+ var args = toArray(arguments);
5355
+ return new Promise((resolve, reject) => {
5356
+ var cb = function(err, data) {
5357
+ if (err) reject(err);
5358
+ else resolve(data);
5359
+ };
5360
+ args.push(cb);
5361
+ asyncFn.apply(this, args);
5362
+ });
5363
+ };
5364
+ }
5365
+
5366
+ function runAsync(fn, arg) {
5367
+ return new Promise((resolve, reject) => {
5368
+ fn(arg, function(err, data) {
5369
+ return err ? reject(err) : resolve(data);
5370
+ });
5371
+ });
5372
+ }
5319
5373
 
5320
5374
  // Call @iter on each member of an array (similar to Array#reduce(iter))
5321
5375
  // iter: function(memo, item, callback)
@@ -11191,11 +11245,14 @@
11191
11245
  gui.buttons = new SidebarButtons(gui);
11192
11246
  gui.map = new MshpMap(gui);
11193
11247
  gui.interaction = new InteractionMode(gui);
11194
- gui.sessionSave = new SessionSnapshots(gui);
11195
11248
  gui.session = new SessionHistory(gui);
11196
11249
  gui.undo = new Undo(gui);
11197
11250
  gui.state = {};
11198
11251
 
11252
+ if (opts.saveControl) {
11253
+ new SessionSnapshots(gui);
11254
+ }
11255
+
11199
11256
  initModeRules(gui);
11200
11257
 
11201
11258
  gui.showProgressMessage = function(msg) {