mapshaper 0.6.18 → 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.
- package/mapshaper.js +2743 -6507
- package/package.json +4 -3
- package/www/index.html +12 -7
- package/www/mapshaper-gui.js +783 -364
- package/www/mapshaper.js +2743 -6507
- package/www/modules.js +301 -1
- package/www/page.css +96 -14
package/www/mapshaper-gui.js
CHANGED
|
@@ -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; },
|
|
@@ -1106,6 +1107,609 @@
|
|
|
1106
1107
|
|
|
1107
1108
|
// utils.inherit(SimpleButton, EventDispatcher);
|
|
1108
1109
|
|
|
1110
|
+
function showPopupAlert(msg, title) {
|
|
1111
|
+
var self = {}, html = '';
|
|
1112
|
+
var _cancel, _close;
|
|
1113
|
+
var warningRxp = /^Warning: /;
|
|
1114
|
+
var el = El('div').appendTo('body').addClass('error-wrapper');
|
|
1115
|
+
var infoBox = El('div').appendTo(el).addClass('error-box info-box selectable');
|
|
1116
|
+
if (!title && warningRxp.test(msg)) {
|
|
1117
|
+
title = 'Warning';
|
|
1118
|
+
msg = msg.replace(warningRxp, '');
|
|
1119
|
+
}
|
|
1120
|
+
if (title) {
|
|
1121
|
+
html += `<div class="error-title">${title}</div>`;
|
|
1122
|
+
}
|
|
1123
|
+
html += `<p class="error-message">${msg}</p>`;
|
|
1124
|
+
El('div').appendTo(infoBox).addClass('close2-btn').on('click', function() {
|
|
1125
|
+
if (_cancel) _cancel();
|
|
1126
|
+
self.close();
|
|
1127
|
+
});
|
|
1128
|
+
El('div').appendTo(infoBox).addClass('error-content').html(html);
|
|
1129
|
+
|
|
1130
|
+
self.onCancel = function(cb) {
|
|
1131
|
+
_cancel = cb;
|
|
1132
|
+
return self;
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
self.onClose = function(cb) {
|
|
1136
|
+
_close = cb;
|
|
1137
|
+
return self;
|
|
1138
|
+
};
|
|
1139
|
+
|
|
1140
|
+
self.button = function(label, cb) {
|
|
1141
|
+
El('div')
|
|
1142
|
+
.addClass("btn dialog-btn alert-btn")
|
|
1143
|
+
.appendTo(infoBox)
|
|
1144
|
+
.html(label)
|
|
1145
|
+
.on('click', function() {
|
|
1146
|
+
self.close();
|
|
1147
|
+
cb();
|
|
1148
|
+
});
|
|
1149
|
+
return self;
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
self.close = function() {
|
|
1153
|
+
if (el) el.remove();
|
|
1154
|
+
if (_close) _close();
|
|
1155
|
+
el = _cancel = _close = null;
|
|
1156
|
+
};
|
|
1157
|
+
return self;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function AlertControl(gui) {
|
|
1161
|
+
var openAlert; // error popup
|
|
1162
|
+
var openPopup; // any popup
|
|
1163
|
+
|
|
1164
|
+
gui.addMode('alert', function() {}, closePopup);
|
|
1165
|
+
|
|
1166
|
+
gui.alert = function(str, title) {
|
|
1167
|
+
closePopup();
|
|
1168
|
+
openAlert = openPopup = showPopupAlert(str, title);
|
|
1169
|
+
// alert.button('close', gui.clearMode);
|
|
1170
|
+
openAlert.onClose(gui.clearMode);
|
|
1171
|
+
gui.enterMode('alert');
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
gui.message = function(str, title) {
|
|
1175
|
+
if (openPopup) return; // don't stomp on another popup
|
|
1176
|
+
openPopup = showPopupAlert(str, title);
|
|
1177
|
+
openPopup.onClose(function() {openPopup = null;});
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
function closePopup() {
|
|
1181
|
+
if (openPopup) openPopup.close();
|
|
1182
|
+
openPopup = openAlert = null;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function saveZipFile(zipfileName, files, done) {
|
|
1187
|
+
internal.zipAsync(files, function(err, buf) {
|
|
1188
|
+
if (err) {
|
|
1189
|
+
done(errorMessage(err));
|
|
1190
|
+
} else {
|
|
1191
|
+
saveBlobToLocalFile(zipfileName, new Blob([buf]), done);
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
function errorMessage(err) {
|
|
1196
|
+
var str = "Error creating Zip file";
|
|
1197
|
+
if (err.message) {
|
|
1198
|
+
str += ": " + err.message;
|
|
1199
|
+
}
|
|
1200
|
+
return str;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
function saveFilesToServer(paths, data, done) {
|
|
1205
|
+
var i = -1;
|
|
1206
|
+
next();
|
|
1207
|
+
function next(err) {
|
|
1208
|
+
i++;
|
|
1209
|
+
if (err) return done(err);
|
|
1210
|
+
if (i >= data.length) return done();
|
|
1211
|
+
saveBlobToServer(paths[i], new Blob([data[i]]), next);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
function saveBlobToServer(path, blob, done) {
|
|
1216
|
+
var q = '?file=' + encodeURIComponent(path);
|
|
1217
|
+
var url = window.location.origin + '/save' + q;
|
|
1218
|
+
window.fetch(url, {
|
|
1219
|
+
method: 'POST',
|
|
1220
|
+
credentials: 'include',
|
|
1221
|
+
body: blob
|
|
1222
|
+
}).then(function(resp) {
|
|
1223
|
+
if (resp.status == 400) {
|
|
1224
|
+
return resp.text();
|
|
1225
|
+
}
|
|
1226
|
+
}).then(function(err) {
|
|
1227
|
+
done(err);
|
|
1228
|
+
}).catch(function(resp) {
|
|
1229
|
+
done('connection to server was lost');
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
async function saveBlobToLocalFile(filename, blob, done) {
|
|
1234
|
+
done = done || function() {};
|
|
1235
|
+
if (window.showSaveFilePicker) {
|
|
1236
|
+
saveBlobToSelectedFile(filename, blob, done);
|
|
1237
|
+
} else {
|
|
1238
|
+
saveBlobToDownloadsFolder(filename, blob, done);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function showSaveDialog(filename, blob, done) {
|
|
1243
|
+
showPopupAlert(`Save ${filename} to:`)
|
|
1244
|
+
.button('selected folder', function() {
|
|
1245
|
+
saveBlobToSelectedFile(filename, blob, done);
|
|
1246
|
+
})
|
|
1247
|
+
.button('downloads', function() {
|
|
1248
|
+
saveBlobToDownloadsFolder(filename, blob, done);
|
|
1249
|
+
})
|
|
1250
|
+
.onCancel(done);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
async function saveBlobToSelectedFile(filename, blob, done) {
|
|
1254
|
+
// see: https://developer.chrome.com/articles/file-system-access/
|
|
1255
|
+
// note: saving multiple files to a directory using showDirectoryPicker()
|
|
1256
|
+
// does not work well (in Chrome). User is prompted for permission each time,
|
|
1257
|
+
// and some directories (like Downloads and Documents) are blocked.
|
|
1258
|
+
//
|
|
1259
|
+
var options = getSaveFileOptions(filename);
|
|
1260
|
+
var handle;
|
|
1261
|
+
try {
|
|
1262
|
+
handle = await window.showSaveFilePicker(options);
|
|
1263
|
+
var writable = await handle.createWritable();
|
|
1264
|
+
await writable.write(blob);
|
|
1265
|
+
await writable.close();
|
|
1266
|
+
} catch(e) {
|
|
1267
|
+
if (e.name == 'SecurityError') {
|
|
1268
|
+
// assuming this is a timeout error, with message like:
|
|
1269
|
+
// "Must be handling a user gesture to show a file picker."
|
|
1270
|
+
showSaveDialog(filename, blob, done);
|
|
1271
|
+
} else if (e.name == 'AbortError') {
|
|
1272
|
+
// fired if user clicks a cancel button (normal, no error message)
|
|
1273
|
+
// BUT: this kind of erro rmay also get fired when saving to a protected folder
|
|
1274
|
+
// (should show error message)
|
|
1275
|
+
done();
|
|
1276
|
+
} else {
|
|
1277
|
+
console.error(e.name, e.message, e);
|
|
1278
|
+
done('Save failed for an unknown reason');
|
|
1279
|
+
}
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
done();
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
function getSaveFileOptions(filename) {
|
|
1287
|
+
// see: https://wicg.github.io/file-system-access/#api-filepickeroptions
|
|
1288
|
+
var type = internal.guessInputFileType(filename);
|
|
1289
|
+
var ext = internal.getFileExtension(filename).toLowerCase();
|
|
1290
|
+
var accept = {};
|
|
1291
|
+
if (ext == 'kml') {
|
|
1292
|
+
accept['application/vnd.google-earth.kml+xml'] = ['.kml'];
|
|
1293
|
+
} else if (ext == 'svg') {
|
|
1294
|
+
accept['image/svg+xml'] = ['.svg'];
|
|
1295
|
+
} else if (ext == 'zip') {
|
|
1296
|
+
accept['application/zip'] == ['.zip'];
|
|
1297
|
+
} else if (type == 'text') {
|
|
1298
|
+
accept['text/csv'] = ['.csv', '.tsv', '.tab', '.txt'];
|
|
1299
|
+
} else if (type == 'json') {
|
|
1300
|
+
accept['application/json'] = ['.json', '.geojson', '.topojson'];
|
|
1301
|
+
} else {
|
|
1302
|
+
accept['application/octet-stream'] = ['.' + ext];
|
|
1303
|
+
}
|
|
1304
|
+
return {
|
|
1305
|
+
suggestedName: filename,
|
|
1306
|
+
// If startIn is given, Chrome will always start there
|
|
1307
|
+
// Default is to start in the previously selected dir (better)
|
|
1308
|
+
// // startIn: 'downloads', // or: desktop, documents, [file handle], [directory handle]
|
|
1309
|
+
types: [{
|
|
1310
|
+
description: 'Files',
|
|
1311
|
+
accept: accept
|
|
1312
|
+
}]
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
|
|
1317
|
+
function saveBlobToDownloadsFolder(filename, blob, done) {
|
|
1318
|
+
var anchor, blobUrl;
|
|
1319
|
+
try {
|
|
1320
|
+
blobUrl = URL.createObjectURL(blob);
|
|
1321
|
+
} catch(e) {
|
|
1322
|
+
done("Mapshaper can't export files from this browser. Try switching to Chrome or Firefox.");
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
anchor = El('a').attr('href', '#').appendTo('body').node();
|
|
1326
|
+
anchor.href = blobUrl;
|
|
1327
|
+
anchor.download = filename;
|
|
1328
|
+
var clickEvent = document.createEvent("MouseEvent");
|
|
1329
|
+
clickEvent.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false,
|
|
1330
|
+
false, false, false, 0, null);
|
|
1331
|
+
anchor.dispatchEvent(clickEvent);
|
|
1332
|
+
setTimeout(function() {
|
|
1333
|
+
// Revoke blob url to release memory; timeout needed in firefox
|
|
1334
|
+
URL.revokeObjectURL(blobUrl);
|
|
1335
|
+
anchor.parentNode.removeChild(anchor);
|
|
1336
|
+
done();
|
|
1337
|
+
}, 400);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
var idb = require('idb-keyval');
|
|
1341
|
+
// https://github.com/jakearchibald/idb
|
|
1342
|
+
// https://github.com/jakearchibald/idb-keyval
|
|
1343
|
+
var sessionId = getUniqId('session');
|
|
1344
|
+
var snapshotCount = 0;
|
|
1345
|
+
|
|
1346
|
+
function getUniqId(prefix) {
|
|
1347
|
+
return prefix + '_' + (Math.random() + 1).toString(36).substring(2,8);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
function isSnapshotId(str) {
|
|
1351
|
+
return /^session_/.test(str);
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
function SessionSnapshots(gui) {
|
|
1355
|
+
var _menuOpen = false;
|
|
1356
|
+
var _menuTimeout;
|
|
1357
|
+
var btn, menu;
|
|
1358
|
+
|
|
1359
|
+
init();
|
|
1360
|
+
|
|
1361
|
+
async function init() {
|
|
1362
|
+
var enabled = await isStorageEnabled();
|
|
1363
|
+
if (!enabled) return;
|
|
1364
|
+
|
|
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
|
+
}
|
|
1392
|
+
|
|
1393
|
+
async function renderMenu() {
|
|
1394
|
+
var snapshots = await fetchSnapshotList();
|
|
1395
|
+
|
|
1396
|
+
menu.empty();
|
|
1397
|
+
addMenuLink({
|
|
1398
|
+
slug: 'stash',
|
|
1399
|
+
// label: 'save data snapshot',
|
|
1400
|
+
label: 'take a snapshot',
|
|
1401
|
+
action: saveSnapshot
|
|
1402
|
+
});
|
|
1403
|
+
|
|
1404
|
+
// var available = await getAvailableStorage();
|
|
1405
|
+
// if (available) {
|
|
1406
|
+
// El('div').addClass('save-menu-entry').text(available + ' available').appendTo(menu);
|
|
1407
|
+
// }
|
|
1408
|
+
|
|
1409
|
+
// if (snapshots.length > 0) {
|
|
1410
|
+
// El('div').addClass('save-menu-entry').text('snapshots').appendTo(menu);
|
|
1411
|
+
// }
|
|
1412
|
+
|
|
1413
|
+
snapshots.forEach(function(item, i) {
|
|
1414
|
+
var id = i + 1;
|
|
1415
|
+
var line = El('div').appendTo(menu).addClass('save-menu-item');
|
|
1416
|
+
El('span').appendTo(line).html(`<span class="save-item-label">#${item.number}</span> `);
|
|
1417
|
+
// show snapshot size
|
|
1418
|
+
El('span').appendTo(line).html(` <span class="save-item-size">${item.display_size}</span>`);
|
|
1419
|
+
El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
|
|
1420
|
+
await restoreSnapshotById(item.id, gui);
|
|
1421
|
+
closeMenu(100);
|
|
1422
|
+
}).text('restore');
|
|
1423
|
+
El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
|
|
1424
|
+
var buf = await idb.get(item.id);
|
|
1425
|
+
saveBlobToLocalFile('mapshaper_snapshot.msx', new Blob([buf]));
|
|
1426
|
+
}).text('export');
|
|
1427
|
+
El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
|
|
1428
|
+
await removeSnapshotById(item.id);
|
|
1429
|
+
closeMenu(300);
|
|
1430
|
+
renderMenu();
|
|
1431
|
+
}).text('remove');
|
|
1432
|
+
});
|
|
1433
|
+
|
|
1434
|
+
// if (snapshots.length >= 1) {
|
|
1435
|
+
// addMenuLink({
|
|
1436
|
+
// slug: 'clear',
|
|
1437
|
+
// label: 'remove all',
|
|
1438
|
+
// action: clearData
|
|
1439
|
+
// });
|
|
1440
|
+
// }
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
function addMenuLink(item) {
|
|
1444
|
+
var line = El('div').appendTo(menu);
|
|
1445
|
+
var link = El('div').addClass('save-menu-link save-menu-entry').attr('data-name', item.slug).text(item.label).appendTo(line);
|
|
1446
|
+
link.on('click', async function(e) {
|
|
1447
|
+
await item.action(gui);
|
|
1448
|
+
e.stopPropagation();
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
function openMenu() {
|
|
1453
|
+
clearTimeout(_menuTimeout);
|
|
1454
|
+
if (!_menuOpen) {
|
|
1455
|
+
btn.addClass('open');
|
|
1456
|
+
_menuOpen = true;
|
|
1457
|
+
renderMenu();
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
function closeMenu(delay) {
|
|
1462
|
+
if (!_menuOpen) return;
|
|
1463
|
+
clearTimeout(_menuTimeout);
|
|
1464
|
+
_menuTimeout = setTimeout(function() {
|
|
1465
|
+
_menuOpen = false;
|
|
1466
|
+
btn.removeClass('open');
|
|
1467
|
+
btn.removeClass('hover');
|
|
1468
|
+
}, delay || 0);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
async function saveSnapshot(gui) {
|
|
1472
|
+
var buf = await captureSnapshot(gui);
|
|
1473
|
+
var entryId = String(++snapshotCount).padStart(3, '0');
|
|
1474
|
+
var snapshotId = sessionId + '_' + entryId; // e.g. session_d89fw_001
|
|
1475
|
+
var entry = {
|
|
1476
|
+
created: Date.now(),
|
|
1477
|
+
session: sessionId,
|
|
1478
|
+
id: snapshotId,
|
|
1479
|
+
name: snapshotCount + '.',
|
|
1480
|
+
number: snapshotCount,
|
|
1481
|
+
size: buf.length,
|
|
1482
|
+
display_size: formatSize(buf.length)
|
|
1483
|
+
};
|
|
1484
|
+
|
|
1485
|
+
await idb.set(entry.id, buf);
|
|
1486
|
+
await addToIndex(entry);
|
|
1487
|
+
renderMenu();
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
function formatSize(bytes) {
|
|
1492
|
+
var kb = Math.round(bytes / 1000);
|
|
1493
|
+
var mb = (bytes / 1e6).toFixed(1);
|
|
1494
|
+
if (kb < 990) {
|
|
1495
|
+
return kb + 'kB';
|
|
1496
|
+
}
|
|
1497
|
+
return mb + 'MB';
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
async function fetchSnapshotList() {
|
|
1501
|
+
await removeMissingSnapshots();
|
|
1502
|
+
var index = await fetchIndex();
|
|
1503
|
+
var snapshots = index.snapshots;
|
|
1504
|
+
snapshots = snapshots.filter(function(o) {return o.session == sessionId;});
|
|
1505
|
+
return snapshots.sort(function(a, b) {b.created > a.created;});
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
async function removeSnapshotById(id, gui) {
|
|
1509
|
+
await idb.del(id);
|
|
1510
|
+
return updateIndex(function(index) {
|
|
1511
|
+
index.snapshots = index.snapshots.filter(function(snap) {
|
|
1512
|
+
return snap.id != id;
|
|
1513
|
+
});
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
async function restoreSnapshotById(id, gui) {
|
|
1518
|
+
var buf = await idb.get(id);
|
|
1519
|
+
if (!buf) {
|
|
1520
|
+
console.log('Snapshot not available:', id);
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
var data = await internal.unpackSession(buf);
|
|
1524
|
+
buf = null;
|
|
1525
|
+
gui.model.clear();
|
|
1526
|
+
importDatasets(data.datasets, gui);
|
|
1527
|
+
gui.clearMode();
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
// Add datasets to the current project
|
|
1531
|
+
// TODO: figure out if interface data should be imported (e.g. should
|
|
1532
|
+
// visibility flag of imported layers be imported)
|
|
1533
|
+
async function importSessionData(buf, gui) {
|
|
1534
|
+
if (buf instanceof ArrayBuffer) {
|
|
1535
|
+
buf = new Uint8Array(buf);
|
|
1536
|
+
}
|
|
1537
|
+
var data = await internal.unpackSession(buf);
|
|
1538
|
+
importDatasets(data.datasets, gui);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
function importDatasets(datasets, gui) {
|
|
1542
|
+
gui.model.addDatasets(datasets);
|
|
1543
|
+
var target = findTargetLayer(datasets);
|
|
1544
|
+
gui.model.setDefaultTarget(target.layers, target.dataset);
|
|
1545
|
+
gui.model.updated({select: true, arc_count: true}); // arc_count to refresh display shapes
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
async function captureSnapshot(gui) {
|
|
1549
|
+
var datasets = gui.model.getDatasets();
|
|
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};
|
|
1554
|
+
lyr.active = true;
|
|
1555
|
+
// console.time('msx');
|
|
1556
|
+
var obj = await internal.exportDatasetsToPack(datasets, opts);
|
|
1557
|
+
// console.timeEnd('msx')
|
|
1558
|
+
delete lyr.active;
|
|
1559
|
+
obj.gui = getGuiState(gui);
|
|
1560
|
+
return internal.pack(obj);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
// TODO: capture gui state information to allow restoring more of the UI
|
|
1564
|
+
function getGuiState(gui) {
|
|
1565
|
+
return null;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
async function fetchIndex() {
|
|
1569
|
+
var index = await idb.get('msx_index');
|
|
1570
|
+
return index || {snapshots: []};
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
async function updateIndex(action) {
|
|
1574
|
+
return idb.update('msx_index', function(index) {
|
|
1575
|
+
if (!index || !Array.isArray(index.snapshots)) {
|
|
1576
|
+
index = {snapshots: []};
|
|
1577
|
+
}
|
|
1578
|
+
action(index);
|
|
1579
|
+
return index;
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
async function addToIndex(obj) {
|
|
1584
|
+
updateSessionData();
|
|
1585
|
+
return updateIndex(function(index) {
|
|
1586
|
+
index.snapshots.push(obj);
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
async function removeMissingSnapshots() {
|
|
1591
|
+
var keys = await idb.keys();
|
|
1592
|
+
return updateIndex(function(index) {
|
|
1593
|
+
index.snapshots = index.snapshots.filter(function(snap) {
|
|
1594
|
+
return keys.includes(snap.id);
|
|
1595
|
+
});
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
async function initialCleanup() {
|
|
1600
|
+
// (Safari workaround) remove any lingering data from past sessions
|
|
1601
|
+
if (getSessionData().length === 0) {
|
|
1602
|
+
await idb.clear();
|
|
1603
|
+
}
|
|
1604
|
+
// remove any snapshots that are not indexed
|
|
1605
|
+
var keys = await idb.keys();
|
|
1606
|
+
var indexedIds = (await fetchIndex()).snapshots.map(function(snap) {return snap.id;});
|
|
1607
|
+
keys.forEach(function(key) {
|
|
1608
|
+
if (isSnapshotId(key) && !indexedIds.includes(key)) {
|
|
1609
|
+
idb.del(key);
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1612
|
+
// remove old indexed snapshots
|
|
1613
|
+
await updateIndex(function(index) {
|
|
1614
|
+
index.snapshots = index.snapshots.filter(function(snap) {
|
|
1615
|
+
var msPerDay = 1000 * 60 * 60 * 24;
|
|
1616
|
+
var daysOld = (Date.now() - snap.created) / msPerDay;
|
|
1617
|
+
if (daysOld > 1) {
|
|
1618
|
+
if (keys.includes(snap.id)) idb.del(snap.id);
|
|
1619
|
+
return false;
|
|
1620
|
+
}
|
|
1621
|
+
return true;
|
|
1622
|
+
});
|
|
1623
|
+
return index;
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
async function getAvailableStorage() {
|
|
1628
|
+
var bytes;
|
|
1629
|
+
try {
|
|
1630
|
+
var estimate = await navigator.storage.estimate();
|
|
1631
|
+
bytes = (estimate.quota - estimate.usage);
|
|
1632
|
+
} catch(e) {
|
|
1633
|
+
return null;
|
|
1634
|
+
}
|
|
1635
|
+
var str = (bytes / 1e6).toFixed(1) + 'MB';
|
|
1636
|
+
if (str.length > 7) {
|
|
1637
|
+
str = (bytes / 1e9).toFixed(1) + 'GB';
|
|
1638
|
+
}
|
|
1639
|
+
if (str.length > 7) {
|
|
1640
|
+
str = (bytes / 1e12).toFixed(1) + 'TB';
|
|
1641
|
+
}
|
|
1642
|
+
if (parseFloat(str) >= 10) {
|
|
1643
|
+
str = str.replace(/\../, '');
|
|
1644
|
+
}
|
|
1645
|
+
return str;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
function findTargetLayer(datasets) {
|
|
1649
|
+
var target;
|
|
1650
|
+
datasets.forEach(function(dataset) {
|
|
1651
|
+
var lyr = dataset.layers.find(function(lyr) { return !!lyr.active; });
|
|
1652
|
+
if (lyr) target = {dataset: dataset, layers: [lyr]};
|
|
1653
|
+
});
|
|
1654
|
+
if (!target) {
|
|
1655
|
+
target = {dataset: datasets[0], layers: [datasets[0].layers[0]]};
|
|
1656
|
+
}
|
|
1657
|
+
return target;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
// Clean up snapshot data (called just before browser tab is closed)
|
|
1661
|
+
async function finalCleanup() {
|
|
1662
|
+
// When called on 'beforeunload', idb.clear() seems to complete
|
|
1663
|
+
// before tab is unloaded in Chrome and Firefox, but not in Safari.
|
|
1664
|
+
// Calling idb.del(key) to selectively delete data for the current session
|
|
1665
|
+
// does not seem to complete in any browser.
|
|
1666
|
+
// So we wait until the last open session is ending at this URL, and delete
|
|
1667
|
+
// data for all recently open sessions.
|
|
1668
|
+
//
|
|
1669
|
+
var sessions = getSessionData().filter(function(item) {
|
|
1670
|
+
// remove current session
|
|
1671
|
+
var daysOld = (Date.now() - item.timestamp) / (1000 * 60 * 60 * 24);
|
|
1672
|
+
if (item.session == sessionId) return false;
|
|
1673
|
+
// also remove any lingering old sessions (ordinarily this shouldn't be needed)
|
|
1674
|
+
if (daysOld > 1) return false;
|
|
1675
|
+
return true;
|
|
1676
|
+
});
|
|
1677
|
+
setSessionData(sessions);
|
|
1678
|
+
if (sessions.length === 0) {
|
|
1679
|
+
await idb.clear();
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
function updateSessionData() {
|
|
1684
|
+
// make sure the current session is added to the list of open sessions
|
|
1685
|
+
var sessions = getSessionData();
|
|
1686
|
+
if (sessions.find(o => o.session == sessionId)) return;
|
|
1687
|
+
var entry = {
|
|
1688
|
+
session: sessionId,
|
|
1689
|
+
timestamp: Date.now()
|
|
1690
|
+
};
|
|
1691
|
+
setSessionData(sessions.concat([entry]));
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
function getSessionData() {
|
|
1695
|
+
var data = JSON.parse(window.localStorage.getItem('session_data'));
|
|
1696
|
+
return data || [];
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
function setSessionData(arr) {
|
|
1700
|
+
window.localStorage.setItem('session_data', JSON.stringify(arr));
|
|
1701
|
+
}
|
|
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
|
+
|
|
1109
1713
|
// @cb function(<FileList>)
|
|
1110
1714
|
function DropControl(gui, el, cb) {
|
|
1111
1715
|
var area = El(el);
|
|
@@ -1391,18 +1995,24 @@
|
|
|
1391
1995
|
async function importFiles(fileData) {
|
|
1392
1996
|
var importOpts = readImportOpts();
|
|
1393
1997
|
var groups = groupFilesForImport(fileData, importOpts);
|
|
1998
|
+
var optStr = GUI.formatCommandOptions(importOpts);
|
|
1394
1999
|
fileData = null;
|
|
1395
2000
|
for (var group of groups) {
|
|
1396
2001
|
if (group.size > 4e7) {
|
|
1397
2002
|
gui.showProgressMessage('Importing');
|
|
1398
2003
|
await wait(35);
|
|
1399
2004
|
}
|
|
1400
|
-
|
|
2005
|
+
if (group[internal.PACKAGE_EXT]) {
|
|
2006
|
+
await importSessionData(group[internal.PACKAGE_EXT].content, gui);
|
|
2007
|
+
} else {
|
|
2008
|
+
importDataset(group, importOpts);
|
|
2009
|
+
}
|
|
2010
|
+
importCount++;
|
|
2011
|
+
gui.session.fileImported(group.filename, optStr);
|
|
1401
2012
|
}
|
|
1402
2013
|
}
|
|
1403
2014
|
|
|
1404
2015
|
function importDataset(group, importOpts) {
|
|
1405
|
-
var optStr = GUI.formatCommandOptions(importOpts);
|
|
1406
2016
|
var dataset = internal.importContent(group, importOpts);
|
|
1407
2017
|
if (datasetIsEmpty(dataset)) return;
|
|
1408
2018
|
if (group.layername) {
|
|
@@ -1411,8 +2021,6 @@
|
|
|
1411
2021
|
// save import options for use by repair control, etc.
|
|
1412
2022
|
dataset.info.import_options = importOpts;
|
|
1413
2023
|
model.addDataset(dataset);
|
|
1414
|
-
importCount++;
|
|
1415
|
-
gui.session.fileImported(group.filename, optStr);
|
|
1416
2024
|
}
|
|
1417
2025
|
|
|
1418
2026
|
function addEmptyLayer() {
|
|
@@ -1643,348 +2251,119 @@
|
|
|
1643
2251
|
content: d.content
|
|
1644
2252
|
};
|
|
1645
2253
|
// kludge: stash import name for session history
|
|
1646
|
-
if (d.filename) g.filename = d.filename;
|
|
1647
|
-
});
|
|
1648
|
-
return groups;
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
function draggable(ref) {
|
|
1653
|
-
var xdown, ydown;
|
|
1654
|
-
var el = El(ref),
|
|
1655
|
-
dragging = false,
|
|
1656
|
-
obj = new EventDispatcher();
|
|
1657
|
-
undraggable(el.node());
|
|
1658
|
-
el.on('mousedown', function(e) {
|
|
1659
|
-
xdown = e.pageX;
|
|
1660
|
-
ydown = e.pageY;
|
|
1661
|
-
window.addEventListener('mousemove', onmove);
|
|
1662
|
-
window.addEventListener('mouseup', onrelease);
|
|
1663
|
-
});
|
|
1664
|
-
|
|
1665
|
-
function onrelease(e) {
|
|
1666
|
-
window.removeEventListener('mousemove', onmove);
|
|
1667
|
-
window.removeEventListener('mouseup', onrelease);
|
|
1668
|
-
if (dragging) {
|
|
1669
|
-
dragging = false;
|
|
1670
|
-
obj.dispatchEvent('dragend');
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
function onmove(e) {
|
|
1675
|
-
if (!dragging) {
|
|
1676
|
-
dragging = true;
|
|
1677
|
-
obj.dispatchEvent('dragstart');
|
|
1678
|
-
}
|
|
1679
|
-
obj.dispatchEvent('drag', {dx: e.pageX - xdown, dy: e.pageY - ydown});
|
|
1680
|
-
}
|
|
1681
|
-
return obj;
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
function Slider(ref, opts) {
|
|
1685
|
-
var _self = this;
|
|
1686
|
-
var defaults = {
|
|
1687
|
-
space: 7
|
|
1688
|
-
};
|
|
1689
|
-
opts = utils$1.extend(defaults, opts);
|
|
1690
|
-
|
|
1691
|
-
var _pct = 0;
|
|
1692
|
-
var _track,
|
|
1693
|
-
_handle,
|
|
1694
|
-
_handleLeft = opts.space;
|
|
1695
|
-
|
|
1696
|
-
function size() {
|
|
1697
|
-
return _track ? _track.width() - opts.space * 2 : 0;
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
this.track = function(ref) {
|
|
1701
|
-
if (ref && !_track) {
|
|
1702
|
-
_track = El(ref);
|
|
1703
|
-
_handleLeft = _track.el.offsetLeft + opts.space;
|
|
1704
|
-
updateHandlePos();
|
|
1705
|
-
}
|
|
1706
|
-
return _track;
|
|
1707
|
-
};
|
|
1708
|
-
|
|
1709
|
-
this.handle = function(ref) {
|
|
1710
|
-
var startX;
|
|
1711
|
-
if (ref && !_handle) {
|
|
1712
|
-
_handle = El(ref);
|
|
1713
|
-
draggable(_handle)
|
|
1714
|
-
.on('drag', function(e) {
|
|
1715
|
-
setHandlePos(startX + e.dx);
|
|
1716
|
-
})
|
|
1717
|
-
.on('dragstart', function(e) {
|
|
1718
|
-
startX = position();
|
|
1719
|
-
_self.dispatchEvent('start');
|
|
1720
|
-
})
|
|
1721
|
-
.on('dragend', function(e) {
|
|
1722
|
-
_self.dispatchEvent('end');
|
|
1723
|
-
});
|
|
1724
|
-
updateHandlePos();
|
|
1725
|
-
}
|
|
1726
|
-
return _handle;
|
|
1727
|
-
};
|
|
1728
|
-
|
|
1729
|
-
function position() {
|
|
1730
|
-
return Math.round(_pct * size());
|
|
1731
|
-
}
|
|
1732
|
-
|
|
1733
|
-
this.pct = function(pct) {
|
|
1734
|
-
if (pct >= 0 && pct <= 1) {
|
|
1735
|
-
_pct = pct;
|
|
1736
|
-
updateHandlePos();
|
|
1737
|
-
}
|
|
1738
|
-
return _pct;
|
|
1739
|
-
};
|
|
1740
|
-
|
|
1741
|
-
function setHandlePos(x) {
|
|
1742
|
-
x = utils$1.clamp(x, 0, size());
|
|
1743
|
-
var pct = x / size();
|
|
1744
|
-
if (pct != _pct) {
|
|
1745
|
-
_pct = pct;
|
|
1746
|
-
_handle.css('left', _handleLeft + x);
|
|
1747
|
-
_self.dispatchEvent('change', {pct: _pct});
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
function updateHandlePos() {
|
|
1752
|
-
var x = _handleLeft + Math.round(position());
|
|
1753
|
-
if (_handle) _handle.css('left', x);
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
utils$1.inherit(Slider, EventDispatcher);
|
|
1758
|
-
|
|
1759
|
-
function showPopupAlert(msg, title) {
|
|
1760
|
-
var self = {}, html = '';
|
|
1761
|
-
var _cancel, _close;
|
|
1762
|
-
var warningRxp = /^Warning: /;
|
|
1763
|
-
var el = El('div').appendTo('body').addClass('error-wrapper');
|
|
1764
|
-
var infoBox = El('div').appendTo(el).addClass('error-box info-box selectable');
|
|
1765
|
-
if (!title && warningRxp.test(msg)) {
|
|
1766
|
-
title = 'Warning';
|
|
1767
|
-
msg = msg.replace(warningRxp, '');
|
|
1768
|
-
}
|
|
1769
|
-
if (title) {
|
|
1770
|
-
html += `<div class="error-title">${title}</div>`;
|
|
1771
|
-
}
|
|
1772
|
-
html += `<p class="error-message">${msg}</p>`;
|
|
1773
|
-
El('div').appendTo(infoBox).addClass('close2-btn').on('click', function() {
|
|
1774
|
-
if (_cancel) _cancel();
|
|
1775
|
-
self.close();
|
|
1776
|
-
});
|
|
1777
|
-
El('div').appendTo(infoBox).addClass('error-content').html(html);
|
|
1778
|
-
|
|
1779
|
-
self.onCancel = function(cb) {
|
|
1780
|
-
_cancel = cb;
|
|
1781
|
-
return self;
|
|
1782
|
-
};
|
|
1783
|
-
|
|
1784
|
-
self.onClose = function(cb) {
|
|
1785
|
-
_close = cb;
|
|
1786
|
-
return self;
|
|
1787
|
-
};
|
|
1788
|
-
|
|
1789
|
-
self.button = function(label, cb) {
|
|
1790
|
-
El('div')
|
|
1791
|
-
.addClass("btn dialog-btn alert-btn")
|
|
1792
|
-
.appendTo(infoBox)
|
|
1793
|
-
.html(label)
|
|
1794
|
-
.on('click', function() {
|
|
1795
|
-
self.close();
|
|
1796
|
-
cb();
|
|
1797
|
-
});
|
|
1798
|
-
return self;
|
|
1799
|
-
};
|
|
1800
|
-
|
|
1801
|
-
self.close = function() {
|
|
1802
|
-
if (el) el.remove();
|
|
1803
|
-
if (_close) _close();
|
|
1804
|
-
el = _cancel = _close = null;
|
|
1805
|
-
};
|
|
1806
|
-
return self;
|
|
1807
|
-
}
|
|
1808
|
-
|
|
1809
|
-
function AlertControl(gui) {
|
|
1810
|
-
var openAlert; // error popup
|
|
1811
|
-
var openPopup; // any popup
|
|
1812
|
-
|
|
1813
|
-
gui.addMode('alert', function() {}, closePopup);
|
|
1814
|
-
|
|
1815
|
-
gui.alert = function(str, title) {
|
|
1816
|
-
closePopup();
|
|
1817
|
-
openAlert = openPopup = showPopupAlert(str, title);
|
|
1818
|
-
// alert.button('close', gui.clearMode);
|
|
1819
|
-
openAlert.onClose(gui.clearMode);
|
|
1820
|
-
gui.enterMode('alert');
|
|
1821
|
-
};
|
|
1822
|
-
|
|
1823
|
-
gui.message = function(str, title) {
|
|
1824
|
-
if (openPopup) return; // don't stomp on another popup
|
|
1825
|
-
openPopup = showPopupAlert(str, title);
|
|
1826
|
-
openPopup.onClose(function() {openPopup = null;});
|
|
1827
|
-
};
|
|
1828
|
-
|
|
1829
|
-
function closePopup() {
|
|
1830
|
-
if (openPopup) openPopup.close();
|
|
1831
|
-
openPopup = openAlert = null;
|
|
2254
|
+
if (d.filename) g.filename = d.filename;
|
|
2255
|
+
});
|
|
2256
|
+
return groups;
|
|
1832
2257
|
}
|
|
1833
2258
|
}
|
|
1834
2259
|
|
|
1835
|
-
function
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
2260
|
+
function draggable(ref) {
|
|
2261
|
+
var xdown, ydown;
|
|
2262
|
+
var el = El(ref),
|
|
2263
|
+
dragging = false,
|
|
2264
|
+
obj = new EventDispatcher();
|
|
2265
|
+
undraggable(el.node());
|
|
2266
|
+
el.on('mousedown', function(e) {
|
|
2267
|
+
xdown = e.pageX;
|
|
2268
|
+
ydown = e.pageY;
|
|
2269
|
+
window.addEventListener('mousemove', onmove);
|
|
2270
|
+
window.addEventListener('mouseup', onrelease);
|
|
1842
2271
|
});
|
|
1843
2272
|
|
|
1844
|
-
function
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
2273
|
+
function onrelease(e) {
|
|
2274
|
+
window.removeEventListener('mousemove', onmove);
|
|
2275
|
+
window.removeEventListener('mouseup', onrelease);
|
|
2276
|
+
if (dragging) {
|
|
2277
|
+
dragging = false;
|
|
2278
|
+
obj.dispatchEvent('dragend');
|
|
1848
2279
|
}
|
|
1849
|
-
return str;
|
|
1850
2280
|
}
|
|
1851
|
-
}
|
|
1852
2281
|
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
if (i >= data.length) return done();
|
|
1860
|
-
saveBlobToServer(paths[i], new Blob([data[i]]), next);
|
|
2282
|
+
function onmove(e) {
|
|
2283
|
+
if (!dragging) {
|
|
2284
|
+
dragging = true;
|
|
2285
|
+
obj.dispatchEvent('dragstart');
|
|
2286
|
+
}
|
|
2287
|
+
obj.dispatchEvent('drag', {dx: e.pageX - xdown, dy: e.pageY - ydown});
|
|
1861
2288
|
}
|
|
2289
|
+
return obj;
|
|
1862
2290
|
}
|
|
1863
2291
|
|
|
1864
|
-
function
|
|
1865
|
-
var
|
|
1866
|
-
var
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
body: blob
|
|
1871
|
-
}).then(function(resp) {
|
|
1872
|
-
if (resp.status == 400) {
|
|
1873
|
-
return resp.text();
|
|
1874
|
-
}
|
|
1875
|
-
}).then(function(err) {
|
|
1876
|
-
done(err);
|
|
1877
|
-
}).catch(function(resp) {
|
|
1878
|
-
done('connection to server was lost');
|
|
1879
|
-
});
|
|
1880
|
-
}
|
|
2292
|
+
function Slider(ref, opts) {
|
|
2293
|
+
var _self = this;
|
|
2294
|
+
var defaults = {
|
|
2295
|
+
space: 7
|
|
2296
|
+
};
|
|
2297
|
+
opts = utils$1.extend(defaults, opts);
|
|
1881
2298
|
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
saveBlobToDownloadsFolder(filename, blob, done);
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
2299
|
+
var _pct = 0;
|
|
2300
|
+
var _track,
|
|
2301
|
+
_handle,
|
|
2302
|
+
_handleLeft = opts.space;
|
|
1889
2303
|
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
saveBlobToSelectedFile(filename, blob, done);
|
|
1894
|
-
})
|
|
1895
|
-
.button('downloads', function() {
|
|
1896
|
-
saveBlobToDownloadsFolder(filename, blob, done);
|
|
1897
|
-
})
|
|
1898
|
-
.onCancel(done);
|
|
1899
|
-
}
|
|
2304
|
+
function size() {
|
|
2305
|
+
return _track ? _track.width() - opts.space * 2 : 0;
|
|
2306
|
+
}
|
|
1900
2307
|
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
//
|
|
1907
|
-
var options = getSaveFileOptions(filename);
|
|
1908
|
-
var handle;
|
|
1909
|
-
try {
|
|
1910
|
-
handle = await window.showSaveFilePicker(options);
|
|
1911
|
-
var writable = await handle.createWritable();
|
|
1912
|
-
await writable.write(blob);
|
|
1913
|
-
await writable.close();
|
|
1914
|
-
} catch(e) {
|
|
1915
|
-
if (e.name == 'SecurityError') {
|
|
1916
|
-
// assuming this is a timeout error, with message like:
|
|
1917
|
-
// "Must be handling a user gesture to show a file picker."
|
|
1918
|
-
showSaveDialog(filename, blob, done);
|
|
1919
|
-
} else if (e.name == 'AbortError') {
|
|
1920
|
-
// fired if user clicks a cancel button (normal, no error message)
|
|
1921
|
-
// BUT: this kind of erro rmay also get fired when saving to a protected folder
|
|
1922
|
-
// (should show error message)
|
|
1923
|
-
done();
|
|
1924
|
-
} else {
|
|
1925
|
-
console.error(e.name, e.message, e);
|
|
1926
|
-
done('Save failed for an unknown reason');
|
|
2308
|
+
this.track = function(ref) {
|
|
2309
|
+
if (ref && !_track) {
|
|
2310
|
+
_track = El(ref);
|
|
2311
|
+
_handleLeft = _track.el.offsetLeft + opts.space;
|
|
2312
|
+
updateHandlePos();
|
|
1927
2313
|
}
|
|
1928
|
-
return;
|
|
1929
|
-
}
|
|
2314
|
+
return _track;
|
|
2315
|
+
};
|
|
1930
2316
|
|
|
1931
|
-
|
|
1932
|
-
|
|
2317
|
+
this.handle = function(ref) {
|
|
2318
|
+
var startX;
|
|
2319
|
+
if (ref && !_handle) {
|
|
2320
|
+
_handle = El(ref);
|
|
2321
|
+
draggable(_handle)
|
|
2322
|
+
.on('drag', function(e) {
|
|
2323
|
+
setHandlePos(startX + e.dx);
|
|
2324
|
+
})
|
|
2325
|
+
.on('dragstart', function(e) {
|
|
2326
|
+
startX = position();
|
|
2327
|
+
_self.dispatchEvent('start');
|
|
2328
|
+
})
|
|
2329
|
+
.on('dragend', function(e) {
|
|
2330
|
+
_self.dispatchEvent('end');
|
|
2331
|
+
});
|
|
2332
|
+
updateHandlePos();
|
|
2333
|
+
}
|
|
2334
|
+
return _handle;
|
|
2335
|
+
};
|
|
1933
2336
|
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
var type = internal.guessInputFileType(filename);
|
|
1937
|
-
var ext = internal.getFileExtension(filename).toLowerCase();
|
|
1938
|
-
var accept = {};
|
|
1939
|
-
if (ext == 'kml') {
|
|
1940
|
-
accept['application/vnd.google-earth.kml+xml'] = ['.kml'];
|
|
1941
|
-
} else if (ext == 'svg') {
|
|
1942
|
-
accept['image/svg+xml'] = ['.svg'];
|
|
1943
|
-
} else if (ext == 'zip') {
|
|
1944
|
-
accept['application/zip'] == ['.zip'];
|
|
1945
|
-
} else if (type == 'text') {
|
|
1946
|
-
accept['text/csv'] = ['.csv', '.tsv', '.tab', '.txt'];
|
|
1947
|
-
} else if (type == 'json') {
|
|
1948
|
-
accept['application/json'] = ['.json', '.geojson', '.topojson'];
|
|
1949
|
-
} else {
|
|
1950
|
-
accept['application/octet-stream'] = ['.' + ext];
|
|
2337
|
+
function position() {
|
|
2338
|
+
return Math.round(_pct * size());
|
|
1951
2339
|
}
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
accept: accept
|
|
1960
|
-
}]
|
|
2340
|
+
|
|
2341
|
+
this.pct = function(pct) {
|
|
2342
|
+
if (pct >= 0 && pct <= 1) {
|
|
2343
|
+
_pct = pct;
|
|
2344
|
+
updateHandlePos();
|
|
2345
|
+
}
|
|
2346
|
+
return _pct;
|
|
1961
2347
|
};
|
|
1962
|
-
}
|
|
1963
2348
|
|
|
2349
|
+
function setHandlePos(x) {
|
|
2350
|
+
x = utils$1.clamp(x, 0, size());
|
|
2351
|
+
var pct = x / size();
|
|
2352
|
+
if (pct != _pct) {
|
|
2353
|
+
_pct = pct;
|
|
2354
|
+
_handle.css('left', _handleLeft + x);
|
|
2355
|
+
_self.dispatchEvent('change', {pct: _pct});
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
1964
2358
|
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
blobUrl = URL.createObjectURL(blob);
|
|
1969
|
-
} catch(e) {
|
|
1970
|
-
done("Mapshaper can't export files from this browser. Try switching to Chrome or Firefox.");
|
|
1971
|
-
return;
|
|
2359
|
+
function updateHandlePos() {
|
|
2360
|
+
var x = _handleLeft + Math.round(position());
|
|
2361
|
+
if (_handle) _handle.css('left', x);
|
|
1972
2362
|
}
|
|
1973
|
-
anchor = El('a').attr('href', '#').appendTo('body').node();
|
|
1974
|
-
anchor.href = blobUrl;
|
|
1975
|
-
anchor.download = filename;
|
|
1976
|
-
var clickEvent = document.createEvent("MouseEvent");
|
|
1977
|
-
clickEvent.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false,
|
|
1978
|
-
false, false, false, 0, null);
|
|
1979
|
-
anchor.dispatchEvent(clickEvent);
|
|
1980
|
-
setTimeout(function() {
|
|
1981
|
-
// Revoke blob url to release memory; timeout needed in firefox
|
|
1982
|
-
URL.revokeObjectURL(blobUrl);
|
|
1983
|
-
anchor.parentNode.removeChild(anchor);
|
|
1984
|
-
done();
|
|
1985
|
-
}, 400);
|
|
1986
2363
|
}
|
|
1987
2364
|
|
|
2365
|
+
utils$1.inherit(Slider, EventDispatcher);
|
|
2366
|
+
|
|
1988
2367
|
// replace default error, stop and message functions
|
|
1989
2368
|
function setLoggingForGUI(gui) {
|
|
1990
2369
|
function stop() {
|
|
@@ -2488,7 +2867,7 @@
|
|
|
2488
2867
|
return copy;
|
|
2489
2868
|
}
|
|
2490
2869
|
|
|
2491
|
-
var wgs84 = internal.
|
|
2870
|
+
var wgs84 = internal.parseCrsString('wgs84');
|
|
2492
2871
|
var toWGS84 = internal.getProjTransform2(src, wgs84);
|
|
2493
2872
|
var fromWGS84 = internal.getProjTransform2(wgs84, dest);
|
|
2494
2873
|
|
|
@@ -2597,7 +2976,7 @@
|
|
|
2597
2976
|
function setDisplayProjection(gui, cmd) {
|
|
2598
2977
|
var arg = cmd.replace(/^projd[ ]*/, '');
|
|
2599
2978
|
if (arg) {
|
|
2600
|
-
gui.map.setDisplayCRS(internal.
|
|
2979
|
+
gui.map.setDisplayCRS(internal.parseCrsString(arg));
|
|
2601
2980
|
} else {
|
|
2602
2981
|
gui.map.setDisplayCRS(null);
|
|
2603
2982
|
}
|
|
@@ -3130,7 +3509,7 @@
|
|
|
3130
3509
|
flags.arc_count || flags.snap || flags.affine || flags.points || flags['merge-layers'];
|
|
3131
3510
|
if (intersectionsMayHaveChanged) {
|
|
3132
3511
|
// delete any cached intersection data, to trigger re-calculation
|
|
3133
|
-
e.dataset.
|
|
3512
|
+
e.dataset._intersections = null;
|
|
3134
3513
|
updateAsync();
|
|
3135
3514
|
} else if (flags.select) {
|
|
3136
3515
|
// new active layer, but no editing commands were run -- use cached intersections (if available)
|
|
@@ -3179,11 +3558,11 @@
|
|
|
3179
3558
|
XX = internal.findSegmentIntersections(arcs, opts);
|
|
3180
3559
|
showBtn = XX.length > 0;
|
|
3181
3560
|
} else { // no simplification
|
|
3182
|
-
XX = dataset.
|
|
3561
|
+
XX = dataset._intersections;
|
|
3183
3562
|
if (!XX) {
|
|
3184
3563
|
// cache intersections at 0 simplification, to avoid recalculating
|
|
3185
3564
|
// every time the simplification slider is set to 100% or the layer is selected at 100%
|
|
3186
|
-
XX = dataset.
|
|
3565
|
+
XX = dataset._intersections = internal.findSegmentIntersections(arcs, opts);
|
|
3187
3566
|
}
|
|
3188
3567
|
showBtn = false;
|
|
3189
3568
|
}
|
|
@@ -3200,7 +3579,7 @@
|
|
|
3200
3579
|
|
|
3201
3580
|
function dismiss() {
|
|
3202
3581
|
var dataset = model.getActiveLayer().dataset;
|
|
3203
|
-
dataset.
|
|
3582
|
+
dataset._intersections = null;
|
|
3204
3583
|
dataset.info.no_intersections = true;
|
|
3205
3584
|
reset();
|
|
3206
3585
|
}
|
|
@@ -3265,8 +3644,6 @@
|
|
|
3265
3644
|
return layers.reverse();
|
|
3266
3645
|
}
|
|
3267
3646
|
|
|
3268
|
-
// import { groupLayersByDataset } from '../dataset/mapshaper-target-utils';
|
|
3269
|
-
|
|
3270
3647
|
// Export buttons and their behavior
|
|
3271
3648
|
var ExportControl = function(gui) {
|
|
3272
3649
|
var model = gui.model;
|
|
@@ -3318,16 +3695,15 @@
|
|
|
3318
3695
|
gui.clearMode();
|
|
3319
3696
|
gui.showProgressMessage('Exporting');
|
|
3320
3697
|
setTimeout(function() {
|
|
3321
|
-
exportMenuSelection(layers
|
|
3322
|
-
if (err) {
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
gui.alert('Export failed for an unknown reason');
|
|
3329
|
-
}
|
|
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');
|
|
3330
3705
|
}
|
|
3706
|
+
}).finally(function() {
|
|
3331
3707
|
gui.clearProgressMessage();
|
|
3332
3708
|
});
|
|
3333
3709
|
}, 20);
|
|
@@ -3349,17 +3725,12 @@
|
|
|
3349
3725
|
}
|
|
3350
3726
|
|
|
3351
3727
|
// done: function(string|Error|null)
|
|
3352
|
-
function exportMenuSelection(layers
|
|
3353
|
-
var opts
|
|
3354
|
-
try {
|
|
3355
|
-
opts = getExportOpts();
|
|
3728
|
+
async function exportMenuSelection(layers) {
|
|
3729
|
+
var opts = getExportOpts();
|
|
3356
3730
|
// note: command line "target" option gets ignored
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
return done(e);
|
|
3361
|
-
}
|
|
3362
|
-
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);
|
|
3363
3734
|
}
|
|
3364
3735
|
|
|
3365
3736
|
function initLayerItem(o, i) {
|
|
@@ -3479,7 +3850,7 @@
|
|
|
3479
3850
|
}
|
|
3480
3851
|
|
|
3481
3852
|
function initFormatMenu() {
|
|
3482
|
-
var defaults = ['shapefile', 'geojson', 'topojson', 'json', 'dsv', 'kml', 'svg'];
|
|
3853
|
+
var defaults = ['shapefile', 'geojson', 'topojson', 'json', 'dsv', 'kml', 'svg', internal.PACKAGE_EXT];
|
|
3483
3854
|
var formats = utils$1.uniq(defaults.concat(getInputFormats()));
|
|
3484
3855
|
var items = formats.map(function(fmt) {
|
|
3485
3856
|
return utils$1.format('<div><label><input type="radio" name="format" value="%s"' +
|
|
@@ -3607,7 +3978,7 @@
|
|
|
3607
3978
|
clearClass('insert-below');
|
|
3608
3979
|
dragTargetId = layerOrderSlug = null;
|
|
3609
3980
|
if (dragging) {
|
|
3610
|
-
|
|
3981
|
+
renderLayerList(); // in case menu changed...
|
|
3611
3982
|
dragging = false;
|
|
3612
3983
|
}
|
|
3613
3984
|
}
|
|
@@ -3644,6 +4015,32 @@
|
|
|
3644
4015
|
}
|
|
3645
4016
|
|
|
3646
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() {
|
|
3647
4044
|
var list = el.findChild('.layer-list');
|
|
3648
4045
|
var uniqIds = {};
|
|
3649
4046
|
var pinnableCount = 0;
|
|
@@ -3690,7 +4087,6 @@
|
|
|
3690
4087
|
cache.cleanup();
|
|
3691
4088
|
|
|
3692
4089
|
function renderLayer(lyr, dataset, opts) {
|
|
3693
|
-
var warnings = getWarnings(lyr, dataset);
|
|
3694
4090
|
var classes = 'layer-item';
|
|
3695
4091
|
var entry, html;
|
|
3696
4092
|
|
|
@@ -3700,13 +4096,7 @@
|
|
|
3700
4096
|
|
|
3701
4097
|
html = '<!-- ' + lyr.menu_id + '--><div class="' + classes + '">';
|
|
3702
4098
|
html += rowHTML('name', '<span class="layer-name colored-text dot-underline">' + formatLayerNameForDisplay(lyr.name) + '</span>', 'row1');
|
|
3703
|
-
if (opts.show_source) {
|
|
3704
|
-
html += rowHTML('source file', describeSrc(lyr, dataset) || 'n/a');
|
|
3705
|
-
}
|
|
3706
4099
|
html += rowHTML('contents', describeLyr(lyr));
|
|
3707
|
-
if (warnings) {
|
|
3708
|
-
html += rowHTML('problems', warnings, 'layer-problems');
|
|
3709
|
-
}
|
|
3710
4100
|
html += '<img class="close-btn" draggable="false" src="images/close.png">';
|
|
3711
4101
|
if (opts.pinnable) {
|
|
3712
4102
|
html += '<img class="pin-btn unpinned" draggable="false" src="images/eye.png">';
|
|
@@ -3838,6 +4228,8 @@
|
|
|
3838
4228
|
var file = internal.getLayerSourceFile(lyr, dataset);
|
|
3839
4229
|
var missing = [];
|
|
3840
4230
|
var msg;
|
|
4231
|
+
// show missing file warning for first layer in dataset
|
|
4232
|
+
// (assuming it represents the content of the original file)
|
|
3841
4233
|
if (utils$1.endsWith(file, '.shp') && lyr == dataset.layers[0]) {
|
|
3842
4234
|
if (!lyr.data) {
|
|
3843
4235
|
missing.push('.dbf');
|
|
@@ -4320,7 +4712,8 @@
|
|
|
4320
4712
|
|
|
4321
4713
|
// Only render edit mode button/menu if this option is present
|
|
4322
4714
|
if (gui.options.inspectorControl) {
|
|
4323
|
-
|
|
4715
|
+
// use z-index so the menu is over other buttons
|
|
4716
|
+
btn = gui.buttons.addButton('#pointer-icon').addClass('menu-btn pointer-btn'),
|
|
4324
4717
|
menu = El('div').addClass('nav-sub-menu').appendTo(btn.node());
|
|
4325
4718
|
|
|
4326
4719
|
btn.on('mouseleave', function() {
|
|
@@ -4956,6 +5349,27 @@
|
|
|
4956
5349
|
targ.prototype.__super__ = f;
|
|
4957
5350
|
}
|
|
4958
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
|
+
}
|
|
4959
5373
|
|
|
4960
5374
|
// Call @iter on each member of an array (similar to Array#reduce(iter))
|
|
4961
5375
|
// iter: function(memo, item, callback)
|
|
@@ -5973,7 +6387,7 @@
|
|
|
5973
6387
|
return hits;
|
|
5974
6388
|
}
|
|
5975
6389
|
|
|
5976
|
-
|
|
6390
|
+
function vertexTest(x, y) {
|
|
5977
6391
|
var maxDist = getZoomAdjustedHitBuffer(25, 2),
|
|
5978
6392
|
cands = findHitCandidates(x, y, maxDist);
|
|
5979
6393
|
sortByDistance(x, y, cands, displayLayer.arcs);
|
|
@@ -10309,7 +10723,7 @@
|
|
|
10309
10723
|
}
|
|
10310
10724
|
|
|
10311
10725
|
if (!internal.isWebMercator(crs)) {
|
|
10312
|
-
gui.map.setDisplayCRS(internal.
|
|
10726
|
+
gui.map.setDisplayCRS(internal.parseCrsString('webmercator'));
|
|
10313
10727
|
}
|
|
10314
10728
|
var bbox = getLonLatBounds();
|
|
10315
10729
|
if (!checkBounds(bbox)) {
|
|
@@ -10427,7 +10841,7 @@
|
|
|
10427
10841
|
this.setDisplayCRS = function(crs) {
|
|
10428
10842
|
// TODO: update bounds of frame layer, if there is a frame layer
|
|
10429
10843
|
var oldCRS = this.getDisplayCRS();
|
|
10430
|
-
var newCRS = utils$1.isString(crs) ? internal.
|
|
10844
|
+
var newCRS = utils$1.isString(crs) ? internal.parseCrsString(crs) : crs;
|
|
10431
10845
|
// TODO: handle case that old and new CRS are the same
|
|
10432
10846
|
_dynamicCRS = newCRS;
|
|
10433
10847
|
if (!_activeLyr) return; // stop here if no layers have been selected
|
|
@@ -10484,12 +10898,12 @@
|
|
|
10484
10898
|
_activeLyr.style = getActiveStyle(_activeLyr.layer, gui.state.dark_basemap);
|
|
10485
10899
|
_activeLyr.active = true;
|
|
10486
10900
|
|
|
10487
|
-
if (e.flags.same_table) {
|
|
10901
|
+
if (e.flags.same_table && !e.flags.proj) {
|
|
10488
10902
|
// data may have changed; if popup is open, it needs to be refreshed
|
|
10489
10903
|
gui.dispatchEvent('popup-needs-refresh');
|
|
10490
10904
|
} else if (_hit) {
|
|
10491
|
-
_hit.setLayer(_activeLyr);
|
|
10492
10905
|
_hit.clearSelection();
|
|
10906
|
+
_hit.setLayer(_activeLyr);
|
|
10493
10907
|
}
|
|
10494
10908
|
|
|
10495
10909
|
updateVisibleMapLayers();
|
|
@@ -10818,6 +11232,7 @@
|
|
|
10818
11232
|
homeControl: true,
|
|
10819
11233
|
zoomControl: true,
|
|
10820
11234
|
inspectorControl: true,
|
|
11235
|
+
saveControl: true,
|
|
10821
11236
|
disableNavigation: false,
|
|
10822
11237
|
showMouseCoordinates: true,
|
|
10823
11238
|
focus: true
|
|
@@ -10834,6 +11249,10 @@
|
|
|
10834
11249
|
gui.undo = new Undo(gui);
|
|
10835
11250
|
gui.state = {};
|
|
10836
11251
|
|
|
11252
|
+
if (opts.saveControl) {
|
|
11253
|
+
new SessionSnapshots(gui);
|
|
11254
|
+
}
|
|
11255
|
+
|
|
10837
11256
|
initModeRules(gui);
|
|
10838
11257
|
|
|
10839
11258
|
gui.showProgressMessage = function(msg) {
|