mapshaper 0.5.85 → 0.5.89
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/bin/mapshaper-gui +4 -1
- package/mapshaper.js +385 -141
- package/package.json +1 -1
- package/www/mapshaper-gui.js +476 -376
- package/www/mapshaper.js +385 -141
- package/www/nacis/Makefile +22 -0
- package/www/nacis/Makefile.txt +22 -0
- package/www/nacis/images/close.png +0 -0
- package/www/nacis/images/eye.png +0 -0
- package/www/nacis/images/eye2.png +0 -0
- package/www/nacis/index.html +262 -0
- package/www/nacis/manifest.js +27 -0
- package/www/nacis/map.svg +12308 -0
- package/CHANGELOG.md +0 -1288
package/www/mapshaper-gui.js
CHANGED
|
@@ -182,6 +182,10 @@
|
|
|
182
182
|
return (el && (el.tagName == 'INPUT' || el.contentEditable == 'true')) ? el : null;
|
|
183
183
|
};
|
|
184
184
|
|
|
185
|
+
GUI.textIsSelected = function() {
|
|
186
|
+
return !!GUI.getInputElement();
|
|
187
|
+
};
|
|
188
|
+
|
|
185
189
|
GUI.selectElement = function(el) {
|
|
186
190
|
var range = document.createRange(),
|
|
187
191
|
sel = window.getSelection();
|
|
@@ -229,6 +233,30 @@
|
|
|
229
233
|
return parsed[0].options;
|
|
230
234
|
};
|
|
231
235
|
|
|
236
|
+
// Convert an options object to a command line options string
|
|
237
|
+
// (used by gui-import-control.js)
|
|
238
|
+
// TODO: handle options with irregular string <-> object conversion
|
|
239
|
+
GUI.formatCommandOptions = function(o) {
|
|
240
|
+
var arr = [];
|
|
241
|
+
Object.keys(o).forEach(function(key) {
|
|
242
|
+
var name = key.replace(/_/g, '-');
|
|
243
|
+
var val = o[key];
|
|
244
|
+
var str;
|
|
245
|
+
// TODO: quote values that contain spaces
|
|
246
|
+
if (Array.isArray(val)) {
|
|
247
|
+
str = name + '=' + val.join(',');
|
|
248
|
+
} else if (val === true) {
|
|
249
|
+
str = name;
|
|
250
|
+
} else if (val === false) {
|
|
251
|
+
return;
|
|
252
|
+
} else {
|
|
253
|
+
str = name + '=' + val;
|
|
254
|
+
}
|
|
255
|
+
arr.push(str);
|
|
256
|
+
});
|
|
257
|
+
return arr.join(' ');
|
|
258
|
+
};
|
|
259
|
+
|
|
232
260
|
// @file: Zip file
|
|
233
261
|
// @cb: function(err, <files>)
|
|
234
262
|
//
|
|
@@ -1027,6 +1055,10 @@
|
|
|
1027
1055
|
var types = Array.from(e.clipboardData.types || []).join(',');
|
|
1028
1056
|
var items = Array.from(e.clipboardData.items || []);
|
|
1029
1057
|
var files;
|
|
1058
|
+
if (GUI.textIsSelected()) {
|
|
1059
|
+
// user is probably pasting text into an editable text field
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1030
1062
|
block(e);
|
|
1031
1063
|
// Browser compatibility (tested on MacOS only):
|
|
1032
1064
|
// Chrome and Safari: full support
|
|
@@ -1099,20 +1131,19 @@
|
|
|
1099
1131
|
|
|
1100
1132
|
function ImportControl(gui, opts) {
|
|
1101
1133
|
var model = gui.model;
|
|
1134
|
+
var initialImport = true;
|
|
1102
1135
|
var importCount = 0;
|
|
1103
1136
|
var importTotal = 0;
|
|
1104
1137
|
var overQuickView = false;
|
|
1105
|
-
var useQuickView = opts.quick_view; // may be set by mapshaper-gui
|
|
1106
1138
|
var queuedFiles = [];
|
|
1107
1139
|
var manifestFiles = opts.files || [];
|
|
1108
|
-
var cachedFiles = {};
|
|
1109
1140
|
var catalog;
|
|
1110
1141
|
|
|
1111
1142
|
if (opts.catalog) {
|
|
1112
1143
|
catalog = new CatalogControl(gui, opts.catalog, downloadFiles);
|
|
1113
1144
|
}
|
|
1114
1145
|
|
|
1115
|
-
new SimpleButton('#import-buttons .submit-btn').on('click',
|
|
1146
|
+
new SimpleButton('#import-buttons .submit-btn').on('click', importQueuedFiles);
|
|
1116
1147
|
new SimpleButton('#import-buttons .cancel-btn').on('click', gui.clearMode);
|
|
1117
1148
|
new DropControl(gui, 'body', receiveFiles);
|
|
1118
1149
|
new FileChooser('#file-selection-btn', receiveFiles);
|
|
@@ -1120,7 +1151,7 @@
|
|
|
1120
1151
|
new FileChooser('#add-file-btn', receiveFiles);
|
|
1121
1152
|
initDropArea('#import-quick-drop', true);
|
|
1122
1153
|
initDropArea('#import-drop');
|
|
1123
|
-
gui.keyboard.onMenuSubmit(El('#import-options'),
|
|
1154
|
+
gui.keyboard.onMenuSubmit(El('#import-options'), importQueuedFiles);
|
|
1124
1155
|
|
|
1125
1156
|
gui.addMode('import', turnOn, turnOff);
|
|
1126
1157
|
gui.enterMode('import');
|
|
@@ -1132,6 +1163,10 @@
|
|
|
1132
1163
|
}
|
|
1133
1164
|
});
|
|
1134
1165
|
|
|
1166
|
+
function useQuickView() {
|
|
1167
|
+
return initialImport && (opts.quick_view || overQuickView);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1135
1170
|
function initDropArea(el, isQuick) {
|
|
1136
1171
|
var area = El(el)
|
|
1137
1172
|
.on('dragleave', onout)
|
|
@@ -1149,15 +1184,19 @@
|
|
|
1149
1184
|
}
|
|
1150
1185
|
}
|
|
1151
1186
|
|
|
1152
|
-
function
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1187
|
+
async function importQueuedFiles() {
|
|
1188
|
+
gui.container.removeClass('queued-files');
|
|
1189
|
+
gui.container.removeClass('splash-screen');
|
|
1190
|
+
var files = queuedFiles;
|
|
1191
|
+
try {
|
|
1192
|
+
if (files.length > 0) {
|
|
1193
|
+
queuedFiles = [];
|
|
1194
|
+
await importFiles(files);
|
|
1195
|
+
}
|
|
1196
|
+
} catch(e) {
|
|
1197
|
+
console.error(e);
|
|
1198
|
+
}
|
|
1199
|
+
gui.clearMode();
|
|
1161
1200
|
}
|
|
1162
1201
|
|
|
1163
1202
|
function turnOn() {
|
|
@@ -1178,13 +1217,8 @@
|
|
|
1178
1217
|
importCount = 0;
|
|
1179
1218
|
}
|
|
1180
1219
|
gui.clearProgressMessage();
|
|
1181
|
-
|
|
1182
|
-
close();
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
function close() {
|
|
1220
|
+
initialImport = false; // unset 'quick view' mode, if on
|
|
1186
1221
|
clearQueuedFiles();
|
|
1187
|
-
cachedFiles = {};
|
|
1188
1222
|
}
|
|
1189
1223
|
|
|
1190
1224
|
function onImportComplete() {
|
|
@@ -1218,34 +1252,6 @@
|
|
|
1218
1252
|
}, []);
|
|
1219
1253
|
}
|
|
1220
1254
|
|
|
1221
|
-
// When a Shapefile component is at the head of the queue, move the entire
|
|
1222
|
-
// Shapefile to the front of the queue, sorted in reverse alphabetical order,
|
|
1223
|
-
// (a kludge), so .shp is read before .dbf and .prj
|
|
1224
|
-
// (If a .dbf file is imported before a .shp, it becomes a separate dataset)
|
|
1225
|
-
// TODO: import Shapefile parts without relying on this kludge
|
|
1226
|
-
function sortQueue(queue) {
|
|
1227
|
-
var nextFile = queue[0];
|
|
1228
|
-
var basename, parts;
|
|
1229
|
-
if (!isShapefilePart(nextFile.name)) {
|
|
1230
|
-
return queue;
|
|
1231
|
-
}
|
|
1232
|
-
basename = internal.getFileBase(nextFile.name).toLowerCase();
|
|
1233
|
-
parts = [];
|
|
1234
|
-
queue = queue.filter(function(file) {
|
|
1235
|
-
if (internal.getFileBase(file.name).toLowerCase() == basename) {
|
|
1236
|
-
parts.push(file);
|
|
1237
|
-
return false;
|
|
1238
|
-
}
|
|
1239
|
-
return true;
|
|
1240
|
-
});
|
|
1241
|
-
parts.sort(function(a, b) {
|
|
1242
|
-
// Sorting on LC filename so Shapefiles with mixed-case
|
|
1243
|
-
// extensions are sorted correctly
|
|
1244
|
-
return a.name.toLowerCase() < b.name.toLowerCase() ? 1 : -1;
|
|
1245
|
-
});
|
|
1246
|
-
return parts.concat(queue);
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
1255
|
function showQueuedFiles() {
|
|
1250
1256
|
var list = gui.container.findChild('.dropped-file-list').empty();
|
|
1251
1257
|
queuedFiles.forEach(function(f) {
|
|
@@ -1253,16 +1259,14 @@
|
|
|
1253
1259
|
});
|
|
1254
1260
|
}
|
|
1255
1261
|
|
|
1256
|
-
function receiveFiles(files) {
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
addFilesToQueue(files);
|
|
1262
|
+
async function receiveFiles(files) {
|
|
1263
|
+
// TODO: show importing message here?
|
|
1264
|
+
var expanded = await expandFiles(files);
|
|
1265
|
+
addFilesToQueue(expanded);
|
|
1261
1266
|
if (queuedFiles.length === 0) return;
|
|
1262
1267
|
gui.enterMode('import');
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
onSubmit();
|
|
1268
|
+
if (useQuickView()) {
|
|
1269
|
+
importQueuedFiles();
|
|
1266
1270
|
} else {
|
|
1267
1271
|
gui.container.addClass('queued-files');
|
|
1268
1272
|
El('#path-import-options').classed('hidden', !filesMayContainPaths(queuedFiles));
|
|
@@ -1270,25 +1274,51 @@
|
|
|
1270
1274
|
}
|
|
1271
1275
|
}
|
|
1272
1276
|
|
|
1273
|
-
function
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1277
|
+
async function expandFiles(files) {
|
|
1278
|
+
var files2 = [], expanded;
|
|
1279
|
+
for (var f of files) {
|
|
1280
|
+
if (internal.isZipFile(f.name)) {
|
|
1281
|
+
expanded = await readZipFile(f);
|
|
1282
|
+
files2 = files2.concat(expanded);
|
|
1283
|
+
} else {
|
|
1284
|
+
files2.push(f);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
return files2;
|
|
1278
1288
|
}
|
|
1279
1289
|
|
|
1280
|
-
function
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1290
|
+
async function importFiles(files) {
|
|
1291
|
+
var fileData = await readFiles(files);
|
|
1292
|
+
var importOpts = readImportOpts();
|
|
1293
|
+
var groups = groupFilesForImport(fileData, importOpts);
|
|
1294
|
+
for (var group of groups) {
|
|
1295
|
+
if (group.size > 4e7) {
|
|
1296
|
+
gui.showProgressMessage('Importing');
|
|
1297
|
+
await wait(35);
|
|
1298
|
+
}
|
|
1299
|
+
importDataset(group, importOpts);
|
|
1300
|
+
}
|
|
1284
1301
|
}
|
|
1285
1302
|
|
|
1286
|
-
function
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1303
|
+
function importDataset(group, importOpts) {
|
|
1304
|
+
var optStr = GUI.formatCommandOptions(importOpts);
|
|
1305
|
+
var dataset = internal.importContent(group, importOpts);
|
|
1306
|
+
if (datasetIsEmpty(dataset)) return;
|
|
1307
|
+
if (group.layername) {
|
|
1308
|
+
dataset.layers.forEach(lyr => lyr.name = group.layername);
|
|
1290
1309
|
}
|
|
1291
|
-
|
|
1310
|
+
// save import options for use by repair control, etc.
|
|
1311
|
+
dataset.info.import_options = importOpts;
|
|
1312
|
+
model.addDataset(dataset);
|
|
1313
|
+
importCount++;
|
|
1314
|
+
gui.session.fileImported(group.filename, optStr);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
function filesMayContainPaths(files) {
|
|
1318
|
+
return utils.some(files, function(f) {
|
|
1319
|
+
var type = internal.guessInputFileType(f.name);
|
|
1320
|
+
return type == 'shp' || type == 'json' || internal.isZipFile(f.name);
|
|
1321
|
+
});
|
|
1292
1322
|
}
|
|
1293
1323
|
|
|
1294
1324
|
function datasetIsEmpty(dataset) {
|
|
@@ -1297,41 +1327,27 @@
|
|
|
1297
1327
|
});
|
|
1298
1328
|
}
|
|
1299
1329
|
|
|
1300
|
-
function procNextQueuedFile() {
|
|
1301
|
-
if (queuedFiles.length === 0) {
|
|
1302
|
-
gui.clearMode();
|
|
1303
|
-
} else {
|
|
1304
|
-
queuedFiles = sortQueue(queuedFiles);
|
|
1305
|
-
readFile(queuedFiles.shift());
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
1330
|
|
|
1309
1331
|
// TODO: support .cpg
|
|
1310
1332
|
function isShapefilePart(name) {
|
|
1311
1333
|
return /\.(shp|shx|dbf|prj)$/i.test(name);
|
|
1312
1334
|
}
|
|
1313
1335
|
|
|
1314
|
-
|
|
1315
1336
|
function readImportOpts() {
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
if (useQuickView) return '';
|
|
1327
|
-
var freeform = El('#import-options .advanced-options').node().value;
|
|
1328
|
-
var opts = readImportOpts();
|
|
1329
|
-
if (opts.snap) freeform = 'snap ' + freeform;
|
|
1330
|
-
return freeform.trim();
|
|
1337
|
+
var importOpts;
|
|
1338
|
+
if (useQuickView()) {
|
|
1339
|
+
importOpts = {}; // default opts using quickview
|
|
1340
|
+
} else {
|
|
1341
|
+
var freeform = El('#import-options .advanced-options').node().value;
|
|
1342
|
+
importOpts = GUI.parseFreeformOptions(freeform, 'i');
|
|
1343
|
+
importOpts.no_repair = !El("#repair-intersections-opt").node().checked;
|
|
1344
|
+
importOpts.snap = !!El("#snap-points-opt").node().checked;
|
|
1345
|
+
}
|
|
1346
|
+
return importOpts;
|
|
1331
1347
|
}
|
|
1332
1348
|
|
|
1333
1349
|
// @file a File object
|
|
1334
|
-
function
|
|
1350
|
+
async function readContentFileAsync(file, cb) {
|
|
1335
1351
|
var name = file.name,
|
|
1336
1352
|
reader = new FileReader(),
|
|
1337
1353
|
useBinary = internal.isSupportedBinaryInputType(name) ||
|
|
@@ -1341,9 +1357,9 @@
|
|
|
1341
1357
|
|
|
1342
1358
|
reader.addEventListener('loadend', function(e) {
|
|
1343
1359
|
if (!reader.result) {
|
|
1344
|
-
|
|
1360
|
+
cb(new Error());
|
|
1345
1361
|
} else {
|
|
1346
|
-
|
|
1362
|
+
cb(null, reader.result);
|
|
1347
1363
|
}
|
|
1348
1364
|
});
|
|
1349
1365
|
if (useBinary) {
|
|
@@ -1354,91 +1370,6 @@
|
|
|
1354
1370
|
}
|
|
1355
1371
|
}
|
|
1356
1372
|
|
|
1357
|
-
function importFileContent(fileName, content) {
|
|
1358
|
-
var fileType = internal.guessInputType(fileName, content),
|
|
1359
|
-
importOpts = readImportOpts(),
|
|
1360
|
-
matches = findMatchingShp(fileName),
|
|
1361
|
-
dataset, lyr;
|
|
1362
|
-
|
|
1363
|
-
// Add dbf data to a previously imported .shp file with a matching name
|
|
1364
|
-
// (.shp should have been queued before .dbf)
|
|
1365
|
-
if (fileType == 'dbf' && matches.length > 0) {
|
|
1366
|
-
// find an imported .shp layer that is missing attribute data
|
|
1367
|
-
// (if multiple matches, try to use the most recently imported one)
|
|
1368
|
-
dataset = matches.reduce(function(memo, d) {
|
|
1369
|
-
if (!d.layers[0].data) {
|
|
1370
|
-
memo = d;
|
|
1371
|
-
}
|
|
1372
|
-
return memo;
|
|
1373
|
-
}, null);
|
|
1374
|
-
if (dataset) {
|
|
1375
|
-
lyr = dataset.layers[0];
|
|
1376
|
-
lyr.data = new internal.ShapefileTable(content, importOpts.encoding);
|
|
1377
|
-
if (lyr.shapes && lyr.data.size() != lyr.shapes.length) {
|
|
1378
|
-
stop("Different number of records in .shp and .dbf files");
|
|
1379
|
-
}
|
|
1380
|
-
if (!lyr.geometry_type) {
|
|
1381
|
-
// kludge: trigger display of table cells if .shp has null geometry
|
|
1382
|
-
// TODO: test case if lyr is not the current active layer
|
|
1383
|
-
model.updated({});
|
|
1384
|
-
}
|
|
1385
|
-
procNextQueuedFile();
|
|
1386
|
-
return;
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
if (fileType == 'shx') {
|
|
1391
|
-
// save .shx for use when importing .shp
|
|
1392
|
-
// (queue should be sorted so that .shx is processed before .shp)
|
|
1393
|
-
cachedFiles[fileName.toLowerCase()] = {filename: fileName, content: content};
|
|
1394
|
-
procNextQueuedFile();
|
|
1395
|
-
return;
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
// Add .prj file to previously imported .shp file
|
|
1399
|
-
if (fileType == 'prj') {
|
|
1400
|
-
matches.forEach(function(d) {
|
|
1401
|
-
if (!d.info.prj) {
|
|
1402
|
-
d.info.prj = content;
|
|
1403
|
-
}
|
|
1404
|
-
});
|
|
1405
|
-
procNextQueuedFile();
|
|
1406
|
-
return;
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
importNewDataset(fileType, fileName, content, importOpts);
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
function importNewDataset(fileType, fileName, content, importOpts) {
|
|
1413
|
-
var size = content.byteLength || content.length, // ArrayBuffer or string
|
|
1414
|
-
delay = 0;
|
|
1415
|
-
|
|
1416
|
-
// show importing message if file is large
|
|
1417
|
-
if (size > 4e7) {
|
|
1418
|
-
gui.showProgressMessage('Importing');
|
|
1419
|
-
delay = 35;
|
|
1420
|
-
}
|
|
1421
|
-
setTimeout(function() {
|
|
1422
|
-
var dataset;
|
|
1423
|
-
var input = {};
|
|
1424
|
-
try {
|
|
1425
|
-
input[fileType] = {filename: fileName, content: content};
|
|
1426
|
-
if (fileType == 'shp') {
|
|
1427
|
-
// shx file should already be cached, if it was added together with the shp
|
|
1428
|
-
input.shx = cachedFiles[fileName.replace(/shp$/i, 'shx').toLowerCase()] || null;
|
|
1429
|
-
}
|
|
1430
|
-
dataset = internal.importContent(input, importOpts);
|
|
1431
|
-
// save import options for use by repair control, etc.
|
|
1432
|
-
dataset.info.import_options = importOpts;
|
|
1433
|
-
gui.session.fileImported(fileName, readImportOptsAsString());
|
|
1434
|
-
addDataset(dataset);
|
|
1435
|
-
|
|
1436
|
-
} catch(e) {
|
|
1437
|
-
handleImportError(e, fileName);
|
|
1438
|
-
}
|
|
1439
|
-
}, delay);
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
1373
|
function handleImportError(e, fileName) {
|
|
1443
1374
|
var msg = utils.isString(e) ? e : e.message;
|
|
1444
1375
|
if (fileName) {
|
|
@@ -1449,34 +1380,6 @@
|
|
|
1449
1380
|
console.error(e);
|
|
1450
1381
|
}
|
|
1451
1382
|
|
|
1452
|
-
function handleZipFiles(files) {
|
|
1453
|
-
return files.filter(function(file) {
|
|
1454
|
-
var isZip = internal.isZipFile(file.name);
|
|
1455
|
-
if (isZip) {
|
|
1456
|
-
importZipFile(file);
|
|
1457
|
-
}
|
|
1458
|
-
return !isZip;
|
|
1459
|
-
});
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
function importZipFile(file) {
|
|
1463
|
-
// gui.showProgressMessage('Importing');
|
|
1464
|
-
setTimeout(function() {
|
|
1465
|
-
GUI.readZipFile(file, function(err, files) {
|
|
1466
|
-
if (err) {
|
|
1467
|
-
handleImportError(err, file.name);
|
|
1468
|
-
} else {
|
|
1469
|
-
// don't try to import .txt files from zip files
|
|
1470
|
-
// (these would be parsed as dsv and throw errows)
|
|
1471
|
-
files = files.filter(function(f) {
|
|
1472
|
-
return !/\.txt$/i.test(f.name);
|
|
1473
|
-
});
|
|
1474
|
-
receiveFiles(files);
|
|
1475
|
-
}
|
|
1476
|
-
});
|
|
1477
|
-
}, 35);
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
1383
|
function prepFilesForDownload(names) {
|
|
1481
1384
|
var items = names.map(function(name) {
|
|
1482
1385
|
var isUrl = /:\/\//.test(name);
|
|
@@ -1522,37 +1425,107 @@
|
|
|
1522
1425
|
});
|
|
1523
1426
|
}
|
|
1524
1427
|
|
|
1525
|
-
function
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
req.addEventListener('progress', function(e) {
|
|
1535
|
-
if (!e.lengthComputable) return;
|
|
1536
|
-
var pct = e.loaded / e.total;
|
|
1537
|
-
if (catalog) catalog.progress(pct);
|
|
1428
|
+
function wait(ms) {
|
|
1429
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
function runAsync(fn, arg) {
|
|
1433
|
+
return new Promise((resolve, reject) => {
|
|
1434
|
+
fn(arg, function(err, data) {
|
|
1435
|
+
return err ? reject(err) : resolve(data);
|
|
1436
|
+
});
|
|
1538
1437
|
});
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
async function readZipFile(file) {
|
|
1441
|
+
var files;
|
|
1442
|
+
await wait(35); // pause a beat so status message can display
|
|
1443
|
+
try {
|
|
1444
|
+
files = await runAsync(GUI.readZipFile, file);
|
|
1445
|
+
// don't try to import .txt files from zip files
|
|
1446
|
+
// (these would be parsed as dsv and throw errows)
|
|
1447
|
+
files = files.filter(function(f) {
|
|
1448
|
+
return !/\.txt$/i.test(f.name);
|
|
1449
|
+
});
|
|
1450
|
+
} catch(e) {
|
|
1451
|
+
handleImportError(e, file.name);
|
|
1452
|
+
files = [];
|
|
1453
|
+
}
|
|
1454
|
+
return files;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
async function readFileData(file) {
|
|
1458
|
+
try {
|
|
1459
|
+
var content = await runAsync(readContentFileAsync, file);
|
|
1460
|
+
return {
|
|
1461
|
+
content: content,
|
|
1462
|
+
size: content.byteLength || content.length, // ArrayBuffer or string
|
|
1463
|
+
name: file.name,
|
|
1464
|
+
basename: internal.getFileBase(file.name).toLowerCase(),
|
|
1465
|
+
type: internal.guessInputType(file.name, content)
|
|
1466
|
+
};
|
|
1467
|
+
} catch (e) {
|
|
1468
|
+
handleImportError("Web browser was unable to load the file.", file.name);
|
|
1469
|
+
}
|
|
1470
|
+
return null;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
async function readFiles(files) {
|
|
1474
|
+
var data = [], d;
|
|
1475
|
+
for (var file of files) {
|
|
1476
|
+
d = await readFileData(file);
|
|
1477
|
+
if (d) data.push(d);
|
|
1478
|
+
}
|
|
1479
|
+
return data;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
function groupFilesForImport(data, importOpts) {
|
|
1483
|
+
var names = importOpts.name ? [importOpts.name] : null;
|
|
1484
|
+
if (initialImport && opts.name) { // name from mapshaper-gui --name option
|
|
1485
|
+
names = opts.name.split(',');
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
function key(basename, type) {
|
|
1489
|
+
return basename + '.' + type;
|
|
1490
|
+
}
|
|
1491
|
+
function hasShp(basename) {
|
|
1492
|
+
var shpKey = key(basename, 'shp');
|
|
1493
|
+
return data.some(d => key(d.basename, d.type) == shpKey);
|
|
1494
|
+
}
|
|
1495
|
+
data.forEach(d => {
|
|
1496
|
+
if (d.type == 'shp' || !isShapefilePart(d.name)) {
|
|
1497
|
+
d.group = key(d.basename, d.type);
|
|
1498
|
+
d.filename = d.name;
|
|
1499
|
+
} else if (hasShp(d.basename)) {
|
|
1500
|
+
d.group = key(d.basename, 'shp');
|
|
1501
|
+
} else if (d.type == 'dbf') {
|
|
1502
|
+
d.filename = d.name;
|
|
1503
|
+
d.group = key(d.basename, 'dbf');
|
|
1548
1504
|
} else {
|
|
1549
|
-
|
|
1550
|
-
|
|
1505
|
+
// shapefile part without a .shp file
|
|
1506
|
+
d.group = null;
|
|
1551
1507
|
}
|
|
1552
|
-
next(err, memo);
|
|
1553
1508
|
});
|
|
1554
|
-
|
|
1555
|
-
|
|
1509
|
+
var index = {};
|
|
1510
|
+
var groups = [];
|
|
1511
|
+
data.forEach(d => {
|
|
1512
|
+
if (!d.group) return;
|
|
1513
|
+
var g = index[d.group];
|
|
1514
|
+
if (!g) {
|
|
1515
|
+
g = {};
|
|
1516
|
+
g.layername = names ? names[groups.length] || names[names.length - 1] : null;
|
|
1517
|
+
groups.push(g);
|
|
1518
|
+
index[d.group] = g;
|
|
1519
|
+
}
|
|
1520
|
+
g.size = (g.size || 0) + d.size; // accumulate size
|
|
1521
|
+
g[d.type] = {
|
|
1522
|
+
filename: d.name,
|
|
1523
|
+
content: d.content
|
|
1524
|
+
};
|
|
1525
|
+
// kludge: stash import name for session history
|
|
1526
|
+
if (d.filename) g.filename = d.filename;
|
|
1527
|
+
});
|
|
1528
|
+
return groups;
|
|
1556
1529
|
}
|
|
1557
1530
|
}
|
|
1558
1531
|
|
|
@@ -3395,7 +3368,7 @@
|
|
|
3395
3368
|
GUI.onClick(entry, function() {
|
|
3396
3369
|
var target = findLayerById(id);
|
|
3397
3370
|
// don't select if user is typing or dragging
|
|
3398
|
-
if (!GUI.
|
|
3371
|
+
if (!GUI.textIsSelected() && !dragging) {
|
|
3399
3372
|
gui.clearMode();
|
|
3400
3373
|
if (!map.isActiveLayer(target.layer)) {
|
|
3401
3374
|
model.selectLayer(target.layer, target.dataset);
|
|
@@ -3574,6 +3547,153 @@
|
|
|
3574
3547
|
}
|
|
3575
3548
|
}
|
|
3576
3549
|
|
|
3550
|
+
// import { cloneShape } from '../paths/mapshaper-shape-utils';
|
|
3551
|
+
// import { copyRecord } from '../datatable/mapshaper-data-utils';
|
|
3552
|
+
var snapVerticesToPoint$1 = internal.snapVerticesToPoint;
|
|
3553
|
+
var cloneShape = internal.cloneShape;
|
|
3554
|
+
var copyRecord = internal.copyRecord;
|
|
3555
|
+
|
|
3556
|
+
function Undo(gui) {
|
|
3557
|
+
var history, offset, stashedUndo;
|
|
3558
|
+
reset();
|
|
3559
|
+
|
|
3560
|
+
function reset() {
|
|
3561
|
+
history = [];
|
|
3562
|
+
stashedUndo = null;
|
|
3563
|
+
offset = 0;
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3566
|
+
|
|
3567
|
+
function isUndoEvt(e) {
|
|
3568
|
+
return (e.ctrlKey || e.metaKey) && !e.shiftKey && e.key == 'z';
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
function isRedoEvt(e) {
|
|
3572
|
+
return (e.ctrlKey || e.metaKey) && (e.shiftKey && e.key == 'z' || !e.shiftKey && e.key == 'y');
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
gui.keyboard.on('keydown', function(evt) {
|
|
3576
|
+
var e = evt.originalEvent,
|
|
3577
|
+
kc = e.keyCode;
|
|
3578
|
+
if (isUndoEvt(e)) {
|
|
3579
|
+
this.undo();
|
|
3580
|
+
e.stopPropagation();
|
|
3581
|
+
e.preventDefault();
|
|
3582
|
+
}
|
|
3583
|
+
if (isRedoEvt(e)) {
|
|
3584
|
+
this.redo();
|
|
3585
|
+
e.stopPropagation();
|
|
3586
|
+
e.preventDefault();
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3589
|
+
}, this, 10);
|
|
3590
|
+
|
|
3591
|
+
// undo/redo point/symbol dragging
|
|
3592
|
+
//
|
|
3593
|
+
gui.on('symbol_dragstart', function(e) {
|
|
3594
|
+
stashedUndo = this.makePointSetter(e.FID);
|
|
3595
|
+
}, this);
|
|
3596
|
+
|
|
3597
|
+
gui.on('symbol_dragend', function(e) {
|
|
3598
|
+
var redo = this.makePointSetter(e.FID);
|
|
3599
|
+
this.addHistoryState(stashedUndo, redo);
|
|
3600
|
+
}, this);
|
|
3601
|
+
|
|
3602
|
+
// undo/redo label dragging
|
|
3603
|
+
//
|
|
3604
|
+
gui.on('label_dragstart', function(e) {
|
|
3605
|
+
stashedUndo = this.makeDataSetter(e.FID);
|
|
3606
|
+
}, this);
|
|
3607
|
+
|
|
3608
|
+
gui.on('label_dragend', function(e) {
|
|
3609
|
+
var redo = this.makeDataSetter(e.FID);
|
|
3610
|
+
this.addHistoryState(stashedUndo, redo);
|
|
3611
|
+
}, this);
|
|
3612
|
+
|
|
3613
|
+
// undo/redo data editing
|
|
3614
|
+
// TODO: consider setting selected feature to the undo/redo target feature
|
|
3615
|
+
//
|
|
3616
|
+
gui.on('data_preupdate', function(e) {
|
|
3617
|
+
stashedUndo = this.makeDataSetter(e.FID);
|
|
3618
|
+
}, this);
|
|
3619
|
+
|
|
3620
|
+
gui.on('data_postupdate', function(e) {
|
|
3621
|
+
var redo = this.makeDataSetter(e.FID);
|
|
3622
|
+
this.addHistoryState(stashedUndo, redo);
|
|
3623
|
+
}, this);
|
|
3624
|
+
|
|
3625
|
+
// undo/redo vertex dragging
|
|
3626
|
+
gui.on('vertex_dragstart', function(e) {
|
|
3627
|
+
stashedUndo = this.makeVertexSetter(e.FID, e.vertex_ids);
|
|
3628
|
+
}, this);
|
|
3629
|
+
|
|
3630
|
+
gui.on('vertex_dragend', function(e) {
|
|
3631
|
+
var redo = this.makeVertexSetter(e.FID, e.vertex_ids);
|
|
3632
|
+
this.addHistoryState(stashedUndo, redo);
|
|
3633
|
+
}, this);
|
|
3634
|
+
|
|
3635
|
+
this.clear = function() {
|
|
3636
|
+
reset();
|
|
3637
|
+
};
|
|
3638
|
+
|
|
3639
|
+
this.makePointSetter = function(i) {
|
|
3640
|
+
var target = gui.model.getActiveLayer();
|
|
3641
|
+
var shp = cloneShape(target.layer.shapes[i]);
|
|
3642
|
+
return function() {
|
|
3643
|
+
target.layer.shapes[i] = shp;
|
|
3644
|
+
};
|
|
3645
|
+
};
|
|
3646
|
+
|
|
3647
|
+
this.makeDataSetter = function(id) {
|
|
3648
|
+
var target = gui.model.getActiveLayer();
|
|
3649
|
+
var rec = copyRecord(target.layer.data.getRecordAt(id));
|
|
3650
|
+
return function() {
|
|
3651
|
+
target.layer.data.getRecords()[id] = rec;
|
|
3652
|
+
gui.dispatchEvent('popup-needs-refresh');
|
|
3653
|
+
};
|
|
3654
|
+
};
|
|
3655
|
+
|
|
3656
|
+
this.makeVertexSetter = function(fid, ids) {
|
|
3657
|
+
var target = gui.model.getActiveLayer();
|
|
3658
|
+
var arcs = target.dataset.arcs;
|
|
3659
|
+
var p = internal.getVertexCoords(ids[0], arcs);
|
|
3660
|
+
return function() {
|
|
3661
|
+
snapVerticesToPoint$1(ids, p, arcs, true);
|
|
3662
|
+
};
|
|
3663
|
+
};
|
|
3664
|
+
|
|
3665
|
+
this.addHistoryState = function(undo, redo) {
|
|
3666
|
+
if (offset > 0) {
|
|
3667
|
+
history.splice(-offset);
|
|
3668
|
+
offset = 0;
|
|
3669
|
+
}
|
|
3670
|
+
history.push({undo, redo});
|
|
3671
|
+
};
|
|
3672
|
+
|
|
3673
|
+
this.undo = function() {
|
|
3674
|
+
var item = getHistoryItem();
|
|
3675
|
+
if (item) {
|
|
3676
|
+
offset++;
|
|
3677
|
+
item.undo();
|
|
3678
|
+
gui.dispatchEvent('map-needs-refresh');
|
|
3679
|
+
}
|
|
3680
|
+
};
|
|
3681
|
+
|
|
3682
|
+
this.redo = function() {
|
|
3683
|
+
if (offset <= 0) return;
|
|
3684
|
+
offset--;
|
|
3685
|
+
var item = getHistoryItem();
|
|
3686
|
+
item.redo();
|
|
3687
|
+
gui.dispatchEvent('map-needs-refresh');
|
|
3688
|
+
};
|
|
3689
|
+
|
|
3690
|
+
function getHistoryItem() {
|
|
3691
|
+
var item = history[history.length - offset - 1];
|
|
3692
|
+
return item || null;
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3577
3697
|
function SidebarButtons(gui) {
|
|
3578
3698
|
var root = gui.container.findChild('.mshp-main-map');
|
|
3579
3699
|
var buttons = El('div').addClass('nav-buttons').appendTo(root).hide();
|
|
@@ -3705,6 +3825,7 @@
|
|
|
3705
3825
|
|
|
3706
3826
|
var menus = {
|
|
3707
3827
|
standard: ['info', 'selection', 'data', 'box'],
|
|
3828
|
+
polygons: ['info', 'selection', 'data', 'box', 'vertices'],
|
|
3708
3829
|
lines: ['info', 'selection', 'data', 'box', 'vertices'],
|
|
3709
3830
|
table: ['info', 'selection', 'data'],
|
|
3710
3831
|
labels: ['info', 'selection', 'data', 'box', 'labels', 'location'],
|
|
@@ -3816,6 +3937,9 @@
|
|
|
3816
3937
|
if (internal.layerHasPaths(o.layer) && o.layer.geometry_type == 'polyline') {
|
|
3817
3938
|
return menus.lines;
|
|
3818
3939
|
}
|
|
3940
|
+
if (internal.layerHasPaths(o.layer) && o.layer.geometry_type == 'polygon') {
|
|
3941
|
+
return menus.polygons;
|
|
3942
|
+
}
|
|
3819
3943
|
return menus.standard;
|
|
3820
3944
|
}
|
|
3821
3945
|
|
|
@@ -3888,7 +4012,9 @@
|
|
|
3888
4012
|
}
|
|
3889
4013
|
|
|
3890
4014
|
function onModeChange() {
|
|
3891
|
-
|
|
4015
|
+
var mode = getInteractionMode();
|
|
4016
|
+
gui.state.interaction_mode = mode;
|
|
4017
|
+
gui.dispatchEvent('interaction_mode_change', {mode: mode});
|
|
3892
4018
|
}
|
|
3893
4019
|
|
|
3894
4020
|
// Update button highlight and selected menu item highlight (if any)
|
|
@@ -4319,7 +4445,7 @@
|
|
|
4319
4445
|
}
|
|
4320
4446
|
|
|
4321
4447
|
// ignore keypress if no feature is selected or user is editing text
|
|
4322
|
-
if (pinnedId() == -1 || GUI.
|
|
4448
|
+
if (pinnedId() == -1 || GUI.textIsSelected()) return;
|
|
4323
4449
|
|
|
4324
4450
|
if (e.keyCode == 37 || e.keyCode == 39) {
|
|
4325
4451
|
// L/R arrow keys
|
|
@@ -4477,6 +4603,7 @@
|
|
|
4477
4603
|
|
|
4478
4604
|
// Hits are re-detected on 'hover' (if hit detection is active)
|
|
4479
4605
|
mouse.on('hover', function(e) {
|
|
4606
|
+
handlePointerEvent(e);
|
|
4480
4607
|
if (storedData.pinned || !hitTest || !active) return;
|
|
4481
4608
|
if (e.hover && isOverMap(e)) {
|
|
4482
4609
|
// mouse is hovering directly over map area -- update hit detection
|
|
@@ -5521,7 +5648,9 @@
|
|
|
5521
5648
|
} else {
|
|
5522
5649
|
// field content has changed
|
|
5523
5650
|
strval = strval2;
|
|
5651
|
+
gui.dispatchEvent('data_preupdate', {FID: currId}); // for undo/redo
|
|
5524
5652
|
rec[key] = val2;
|
|
5653
|
+
gui.dispatchEvent('data_postupdate', {FID: currId});
|
|
5525
5654
|
input.value(strval);
|
|
5526
5655
|
setFieldClass(el, val2, type);
|
|
5527
5656
|
self.dispatchEvent('update', {field: key, value: val2, id: currId});
|
|
@@ -5737,6 +5866,8 @@
|
|
|
5737
5866
|
rec[key] = isString ? String(newVal) : newVal;
|
|
5738
5867
|
}
|
|
5739
5868
|
|
|
5869
|
+
var snapVerticesToPoint = internal.snapVerticesToPoint;
|
|
5870
|
+
|
|
5740
5871
|
function getDisplayCoordsById(id, layer, ext) {
|
|
5741
5872
|
var coords = getPointCoordsById(id, layer);
|
|
5742
5873
|
return ext.translateCoords(coords[0], coords[1]);
|
|
@@ -5757,7 +5888,7 @@
|
|
|
5757
5888
|
}
|
|
5758
5889
|
|
|
5759
5890
|
|
|
5760
|
-
function
|
|
5891
|
+
function InteractiveEditor(gui, ext, hit) {
|
|
5761
5892
|
// var targetTextNode; // text node currently being dragged
|
|
5762
5893
|
var dragging = false;
|
|
5763
5894
|
var activeRecord;
|
|
@@ -5819,6 +5950,7 @@
|
|
|
5819
5950
|
if (e.mode != 'labels') {
|
|
5820
5951
|
stopDragging();
|
|
5821
5952
|
}
|
|
5953
|
+
gui.undo.clear(); // TODO: put this elsewhere?
|
|
5822
5954
|
});
|
|
5823
5955
|
|
|
5824
5956
|
// down event on svg
|
|
@@ -5830,12 +5962,17 @@
|
|
|
5830
5962
|
// 3: on other text -> stop dragging, select new text
|
|
5831
5963
|
|
|
5832
5964
|
hit.on('dragstart', function(e) {
|
|
5833
|
-
if (
|
|
5834
|
-
|
|
5965
|
+
if (e.id >= 0 === false) return;
|
|
5966
|
+
if (labelEditingEnabled() && onLabelDragStart(e)) {
|
|
5967
|
+
triggerGlobalEvent('label_dragstart', e);
|
|
5968
|
+
startDragging();
|
|
5835
5969
|
} else if (locationEditingEnabled()) {
|
|
5836
|
-
|
|
5970
|
+
triggerGlobalEvent('symbol_dragstart', e);
|
|
5971
|
+
startDragging();
|
|
5837
5972
|
} else if (vertexEditingEnabled()) {
|
|
5838
5973
|
onVertexDragStart(e);
|
|
5974
|
+
triggerGlobalEvent('vertex_dragstart', e);
|
|
5975
|
+
startDragging();
|
|
5839
5976
|
}
|
|
5840
5977
|
});
|
|
5841
5978
|
|
|
@@ -5851,70 +5988,77 @@
|
|
|
5851
5988
|
|
|
5852
5989
|
hit.on('dragend', function(e) {
|
|
5853
5990
|
if (locationEditingEnabled()) {
|
|
5854
|
-
|
|
5991
|
+
triggerGlobalEvent('symbol_dragend', e);
|
|
5855
5992
|
stopDragging();
|
|
5856
5993
|
} else if (labelEditingEnabled()) {
|
|
5994
|
+
triggerGlobalEvent('label_dragend', e);
|
|
5857
5995
|
stopDragging();
|
|
5858
5996
|
} else if (vertexEditingEnabled()) {
|
|
5859
|
-
|
|
5997
|
+
// kludge to get dataset to recalculate internal bounding boxes
|
|
5998
|
+
hit.getHitTarget().arcs.transformPoints(function() {});
|
|
5999
|
+
triggerGlobalEvent('vertex_dragend', e);
|
|
5860
6000
|
stopDragging();
|
|
5861
6001
|
}
|
|
5862
6002
|
});
|
|
5863
6003
|
|
|
5864
6004
|
hit.on('click', function(e) {
|
|
5865
6005
|
if (labelEditingEnabled()) {
|
|
6006
|
+
var target = hit.getHitTarget();
|
|
5866
6007
|
onLabelClick(e);
|
|
5867
6008
|
}
|
|
5868
6009
|
});
|
|
5869
6010
|
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
6011
|
+
// TODO: highlight hit vertex in path edit mode
|
|
6012
|
+
if (false) hit.on('hover', function(e) {
|
|
6013
|
+
if (vertexEditingEnabled() && !dragging) {
|
|
6014
|
+
onVertexHover(e);
|
|
5874
6015
|
}
|
|
6016
|
+
}, null, 100);
|
|
6017
|
+
|
|
6018
|
+
function onVertexHover(e) {
|
|
6019
|
+
// hovering in vertex edit mode: find vertex insertion point
|
|
6020
|
+
var target = hit.getHitTarget();
|
|
6021
|
+
var shp = target.layer.shapes[e.id];
|
|
6022
|
+
var p = ext.translatePixelCoords(e.x, e.y);
|
|
6023
|
+
var o = internal.findInsertionPoint(p, shp, target.arcs, ext.getPixelSize());
|
|
5875
6024
|
}
|
|
5876
6025
|
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
6026
|
+
|
|
6027
|
+
function getVertexEventData(e) {
|
|
6028
|
+
return {
|
|
6029
|
+
FID: activeId,
|
|
6030
|
+
vertexIds: activeVertexIds
|
|
6031
|
+
};
|
|
5881
6032
|
}
|
|
5882
6033
|
|
|
5883
6034
|
function onLocationDrag(e) {
|
|
5884
6035
|
var lyr = hit.getHitTarget().layer;
|
|
5885
|
-
var p = getPointCoordsById(e.id,
|
|
6036
|
+
var p = getPointCoordsById(e.id, lyr);
|
|
5886
6037
|
if (!p) return;
|
|
5887
6038
|
var diff = translateDeltaDisplayCoords(e.dx, e.dy, ext);
|
|
5888
6039
|
p[0] += diff[0];
|
|
5889
6040
|
p[1] += diff[1];
|
|
5890
|
-
|
|
6041
|
+
triggerRedraw();
|
|
5891
6042
|
triggerGlobalEvent('symbol_drag', e);
|
|
5892
6043
|
}
|
|
5893
6044
|
|
|
5894
|
-
function
|
|
6045
|
+
function onVertexDragStart(e) {
|
|
5895
6046
|
var target = hit.getHitTarget();
|
|
6047
|
+
var shp = target.layer.shapes[e.id];
|
|
5896
6048
|
var p = ext.translatePixelCoords(e.x, e.y);
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
6049
|
+
activeVertexIds = internal.findNearestVertices(p, shp, target.arcs);
|
|
6050
|
+
activeId = e.id;
|
|
6051
|
+
}
|
|
6052
|
+
|
|
6053
|
+
function onVertexDrag(e) {
|
|
6054
|
+
var target = hit.getHitTarget();
|
|
5900
6055
|
if (!activeVertexIds) return; // ignore error condition
|
|
6056
|
+
var p = ext.translatePixelCoords(e.x, e.y);
|
|
5901
6057
|
if (gui.keyboard.shiftIsPressed()) {
|
|
5902
6058
|
internal.snapPointToArcEndpoint(p, activeVertexIds, target.arcs);
|
|
5903
6059
|
}
|
|
5904
|
-
activeVertexIds.
|
|
5905
|
-
|
|
5906
|
-
});
|
|
5907
|
-
self.dispatchEvent('location_change'); // signal map to redraw
|
|
5908
|
-
}
|
|
5909
|
-
|
|
5910
|
-
function onLocationDragEnd(e) {
|
|
5911
|
-
triggerGlobalEvent('symbol_dragend', e);
|
|
5912
|
-
}
|
|
5913
|
-
|
|
5914
|
-
function onVertexDragEnd(e) {
|
|
5915
|
-
// kludge to get dataset to recalculate internal bounding boxes
|
|
5916
|
-
hit.getHitTarget().arcs.transformPoints(function() {});
|
|
5917
|
-
activeVertexIds = null;
|
|
6060
|
+
snapVerticesToPoint(activeVertexIds, p, target.arcs);
|
|
6061
|
+
triggerRedraw();
|
|
5918
6062
|
}
|
|
5919
6063
|
|
|
5920
6064
|
function onLabelClick(e) {
|
|
@@ -5927,11 +6071,19 @@
|
|
|
5927
6071
|
}
|
|
5928
6072
|
}
|
|
5929
6073
|
|
|
6074
|
+
function triggerRedraw() {
|
|
6075
|
+
gui.dispatchEvent('map-needs-refresh');
|
|
6076
|
+
}
|
|
6077
|
+
|
|
5930
6078
|
function triggerGlobalEvent(type, e) {
|
|
5931
|
-
if (e.id >= 0)
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
6079
|
+
if (e.id >= 0 === false) return;
|
|
6080
|
+
var o = {
|
|
6081
|
+
FID: e.id,
|
|
6082
|
+
layer_name: hit.getHitTarget().layer.name,
|
|
6083
|
+
vertex_ids: activeVertexIds
|
|
6084
|
+
};
|
|
6085
|
+
// fire event to signal external editor that symbol coords have changed
|
|
6086
|
+
gui.dispatchEvent(type, o);
|
|
5935
6087
|
}
|
|
5936
6088
|
|
|
5937
6089
|
function getLabelRecordById(id) {
|
|
@@ -5953,11 +6105,11 @@
|
|
|
5953
6105
|
function onLabelDragStart(e) {
|
|
5954
6106
|
var textNode = getTextTarget3(e);
|
|
5955
6107
|
var table = hit.getTargetDataTable();
|
|
5956
|
-
if (!textNode || !table) return;
|
|
6108
|
+
if (!textNode || !table) return false;
|
|
5957
6109
|
activeId = e.id;
|
|
5958
6110
|
activeRecord = getLabelRecordById(activeId);
|
|
5959
|
-
dragging = true;
|
|
5960
6111
|
downEvt = e;
|
|
6112
|
+
return true;
|
|
5961
6113
|
}
|
|
5962
6114
|
|
|
5963
6115
|
function onLabelDrag(e) {
|
|
@@ -6006,88 +6158,17 @@
|
|
|
6006
6158
|
}
|
|
6007
6159
|
return el.tagName == 'text' ? el : null;
|
|
6008
6160
|
}
|
|
6161
|
+
}
|
|
6009
6162
|
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
// downEvt = e;
|
|
6013
|
-
// if (!textTarget) {
|
|
6014
|
-
// stopEditing();
|
|
6015
|
-
// } else if (!editing) {
|
|
6016
|
-
// // nop
|
|
6017
|
-
// } else if (textTarget == targetTextNode) {
|
|
6018
|
-
// startDragging();
|
|
6019
|
-
// } else {
|
|
6020
|
-
// startDragging();
|
|
6021
|
-
// editTextNode(textTarget);
|
|
6022
|
-
// }
|
|
6023
|
-
// });
|
|
6024
|
-
|
|
6025
|
-
// up event on svg
|
|
6026
|
-
// a: currently dragging text
|
|
6027
|
-
// -> stop dragging
|
|
6028
|
-
// b: clicked on a text feature
|
|
6029
|
-
// -> start editing it
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
// svg.addEventListener('mouseup', function(e) {
|
|
6033
|
-
// var textTarget = getTextTarget(e);
|
|
6034
|
-
// var isClick = isClickEvent(e, downEvt);
|
|
6035
|
-
// if (isClick && textTarget && textTarget == targetTextNode &&
|
|
6036
|
-
// activeRecord && isMultilineLabel(targetTextNode)) {
|
|
6037
|
-
// toggleTextAlign(targetTextNode, activeRecord);
|
|
6038
|
-
// updateSymbol();
|
|
6039
|
-
// }
|
|
6040
|
-
// if (dragging) {
|
|
6041
|
-
// stopDragging();
|
|
6042
|
-
// } else if (isClick && textTarget) {
|
|
6043
|
-
// editTextNode(textTarget);
|
|
6044
|
-
// }
|
|
6045
|
-
// });
|
|
6046
|
-
|
|
6047
|
-
// block dbl-click navigation when editing
|
|
6048
|
-
// mouse.on('dblclick', function(e) {
|
|
6049
|
-
// if (editing) e.stopPropagation();
|
|
6050
|
-
// }, null, eventPriority);
|
|
6051
|
-
|
|
6052
|
-
// mouse.on('dragstart', function(e) {
|
|
6053
|
-
// onLabelDrag(e);
|
|
6054
|
-
// }, null, eventPriority);
|
|
6055
|
-
|
|
6056
|
-
// mouse.on('drag', function(e) {
|
|
6057
|
-
// var scale = ext.getSymbolScale() || 1;
|
|
6058
|
-
// onLabelDrag(e);
|
|
6059
|
-
// if (!dragging || !activeRecord) return;
|
|
6060
|
-
// applyDelta(activeRecord, 'dx', e.dx / scale);
|
|
6061
|
-
// applyDelta(activeRecord, 'dy', e.dy / scale);
|
|
6062
|
-
// if (!isMultilineLabel(targetTextNode)) {
|
|
6063
|
-
// // update anchor position of single-line labels based on label position
|
|
6064
|
-
// // relative to anchor point, for better placement when eventual display font is
|
|
6065
|
-
// // different from mapshaper's font.
|
|
6066
|
-
// updateTextAnchor(targetTextNode, activeRecord);
|
|
6067
|
-
// }
|
|
6068
|
-
// // updateSymbol(targetTextNode, activeRecord);
|
|
6069
|
-
// targetTextNode = updateSymbol2(targetTextNode, activeRecord, activeId);
|
|
6070
|
-
// }, null, eventPriority);
|
|
6071
|
-
|
|
6072
|
-
// mouse.on('dragend', function(e) {
|
|
6073
|
-
// onLabelDrag(e);
|
|
6074
|
-
// stopDragging();
|
|
6075
|
-
// }, null, eventPriority);
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
// function onLabelDrag(e) {
|
|
6079
|
-
// if (dragging) {
|
|
6080
|
-
// e.stopPropagation();
|
|
6081
|
-
// }
|
|
6082
|
-
// }
|
|
6163
|
+
function startDragging() {
|
|
6164
|
+
dragging = true;
|
|
6083
6165
|
}
|
|
6084
6166
|
|
|
6085
6167
|
function stopDragging() {
|
|
6086
6168
|
dragging = false;
|
|
6087
6169
|
activeId = -1;
|
|
6088
6170
|
activeRecord = null;
|
|
6089
|
-
|
|
6090
|
-
// svg.removeAttribute('class');
|
|
6171
|
+
activeVertexIds = null;
|
|
6091
6172
|
}
|
|
6092
6173
|
|
|
6093
6174
|
function isClickEvent(up, down) {
|
|
@@ -6098,16 +6179,6 @@
|
|
|
6098
6179
|
return dist <= 4 && elapsed < 300;
|
|
6099
6180
|
}
|
|
6100
6181
|
|
|
6101
|
-
|
|
6102
|
-
// function deselectText(el) {
|
|
6103
|
-
// el.removeAttribute('class');
|
|
6104
|
-
// }
|
|
6105
|
-
|
|
6106
|
-
// function selectText(el) {
|
|
6107
|
-
// el.setAttribute('class', 'selected');
|
|
6108
|
-
// }
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
6182
|
}
|
|
6112
6183
|
|
|
6113
6184
|
var darkStroke = "#334",
|
|
@@ -6151,7 +6222,7 @@
|
|
|
6151
6222
|
dotSize: 2.5
|
|
6152
6223
|
}, polyline: {
|
|
6153
6224
|
strokeColor: black,
|
|
6154
|
-
strokeWidth: 2.5
|
|
6225
|
+
strokeWidth: 2.5,
|
|
6155
6226
|
}
|
|
6156
6227
|
},
|
|
6157
6228
|
unselectedHoverStyles = {
|
|
@@ -6465,6 +6536,7 @@
|
|
|
6465
6536
|
|
|
6466
6537
|
this.maxScale = maxScale;
|
|
6467
6538
|
|
|
6539
|
+
// Display scale, e.g. meters per pixel or degrees per pixel
|
|
6468
6540
|
this.getPixelSize = function() {
|
|
6469
6541
|
return 1 / this.getTransform().mx;
|
|
6470
6542
|
};
|
|
@@ -6656,7 +6728,10 @@
|
|
|
6656
6728
|
} else {
|
|
6657
6729
|
arcs = getArcsForRendering(obj, ext);
|
|
6658
6730
|
filter = getShapeFilter(arcs, ext);
|
|
6659
|
-
canv.
|
|
6731
|
+
canv.drawStyledPaths(layer.shapes, arcs, style, filter);
|
|
6732
|
+
if (style.vertices) {
|
|
6733
|
+
canv.drawVertices(layer.shapes, arcs, style, filter);
|
|
6734
|
+
}
|
|
6660
6735
|
}
|
|
6661
6736
|
}
|
|
6662
6737
|
|
|
@@ -6730,7 +6805,7 @@
|
|
|
6730
6805
|
|
|
6731
6806
|
/*
|
|
6732
6807
|
// Original function, not optimized
|
|
6733
|
-
_self.
|
|
6808
|
+
_self.drawStyledPaths = function(shapes, arcs, style) {
|
|
6734
6809
|
var startPath = getPathStart(_ext),
|
|
6735
6810
|
drawPath = getShapePencil(arcs, _ext),
|
|
6736
6811
|
styler = style.styler || null;
|
|
@@ -6743,8 +6818,28 @@
|
|
|
6743
6818
|
};
|
|
6744
6819
|
*/
|
|
6745
6820
|
|
|
6821
|
+
_self.drawVertices = function(shapes, arcs, style, filter) {
|
|
6822
|
+
var iter = new internal.ShapeIter(arcs);
|
|
6823
|
+
var t = getScaledTransform(_ext);
|
|
6824
|
+
var radius = (style.strokeWidth * 0.9 || 2.2) * GUI.getPixelRatio() * getScaledLineScale(_ext);
|
|
6825
|
+
var color = style.strokeColor || 'black';
|
|
6826
|
+
var shp;
|
|
6827
|
+
_ctx.beginPath();
|
|
6828
|
+
_ctx.fillStyle = color;
|
|
6829
|
+
for (var i=0; i<shapes.length; i++) {
|
|
6830
|
+
shp = shapes[i];
|
|
6831
|
+
if (!shp || filter && !filter(shp)) continue;
|
|
6832
|
+
iter.init(shp);
|
|
6833
|
+
while (iter.hasNext()) {
|
|
6834
|
+
drawCircle(iter.x * t.mx + t.bx, iter.y * t.my + t.by, radius, _ctx);
|
|
6835
|
+
}
|
|
6836
|
+
}
|
|
6837
|
+
_ctx.fill();
|
|
6838
|
+
_ctx.closePath();
|
|
6839
|
+
};
|
|
6840
|
+
|
|
6746
6841
|
// Optimized to draw paths in same-style batches (faster Canvas drawing)
|
|
6747
|
-
_self.
|
|
6842
|
+
_self.drawStyledPaths = function(shapes, arcs, style, filter) {
|
|
6748
6843
|
var styleIndex = {};
|
|
6749
6844
|
var batchSize = 1500;
|
|
6750
6845
|
var startPath = getPathStart(_ext, getScaledLineScale(_ext));
|
|
@@ -6872,7 +6967,7 @@
|
|
|
6872
6967
|
}
|
|
6873
6968
|
}
|
|
6874
6969
|
|
|
6875
|
-
// TODO: consider using
|
|
6970
|
+
// TODO: consider using drawStyledPaths(), which draws paths in batches
|
|
6876
6971
|
// for faster Canvas rendering. Downside: changes stacking order, which
|
|
6877
6972
|
// is bad if circles are graduated.
|
|
6878
6973
|
_self.drawPoints = function(shapes, style) {
|
|
@@ -7812,6 +7907,10 @@
|
|
|
7812
7907
|
_mouse.disable();
|
|
7813
7908
|
});
|
|
7814
7909
|
|
|
7910
|
+
gui.on('map-needs-refresh', function() {
|
|
7911
|
+
drawLayers();
|
|
7912
|
+
});
|
|
7913
|
+
|
|
7815
7914
|
model.on('update', onUpdate);
|
|
7816
7915
|
|
|
7817
7916
|
// Update display of segment intersections
|
|
@@ -7970,13 +8069,7 @@
|
|
|
7970
8069
|
});
|
|
7971
8070
|
}
|
|
7972
8071
|
|
|
7973
|
-
|
|
7974
|
-
_editor = new SymbolDragging2(gui, _ext, _hit);
|
|
7975
|
-
_editor.on('location_change', function(e) {
|
|
7976
|
-
// TODO: look into optimizing, so only changed symbol is redrawn
|
|
7977
|
-
drawLayers();
|
|
7978
|
-
});
|
|
7979
|
-
}
|
|
8072
|
+
_editor = new InteractiveEditor(gui, _ext, _hit);
|
|
7980
8073
|
|
|
7981
8074
|
_ext.on('change', function(e) {
|
|
7982
8075
|
if (e.reset) return; // don't need to redraw map here if extent has been reset
|
|
@@ -7996,6 +8089,10 @@
|
|
|
7996
8089
|
function updateOverlayLayer(e) {
|
|
7997
8090
|
var style = getOverlayStyle(_activeLyr.layer, e);
|
|
7998
8091
|
if (style) {
|
|
8092
|
+
// kludge to show vertices when editing path shapes
|
|
8093
|
+
if (gui.state.interaction_mode == 'vertices') {
|
|
8094
|
+
style.vertices = true;
|
|
8095
|
+
}
|
|
7999
8096
|
_overlayLyr = utils.defaults({
|
|
8000
8097
|
layer: filterLayerByIds(_activeLyr.layer, style.ids),
|
|
8001
8098
|
style: style
|
|
@@ -8235,6 +8332,8 @@
|
|
|
8235
8332
|
gui.map = new MshpMap(gui);
|
|
8236
8333
|
gui.interaction = new InteractionMode(gui);
|
|
8237
8334
|
gui.session = new SessionHistory(gui);
|
|
8335
|
+
gui.undo = new Undo(gui);
|
|
8336
|
+
gui.state = {};
|
|
8238
8337
|
|
|
8239
8338
|
gui.showProgressMessage = function(msg) {
|
|
8240
8339
|
if (!gui.progressMessage) {
|
|
@@ -8318,6 +8417,7 @@
|
|
|
8318
8417
|
opts.display_all = vars['display-all'] || vars.a || !!manifest.display_all;
|
|
8319
8418
|
opts.quick_view = vars['quick-view'] || vars.q || !!manifest.quick_view;
|
|
8320
8419
|
opts.target = vars.target || manifest.target || null;
|
|
8420
|
+
opts.name = vars.name || manifest.name || null;
|
|
8321
8421
|
return opts;
|
|
8322
8422
|
}
|
|
8323
8423
|
|