mapshaper 0.5.88 → 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 CHANGED
@@ -9,6 +9,7 @@ var defaultPort = 5555,
9
9
  .option('-s, --direct-save', 'save files outside the browser\'s download folder')
10
10
  .option('-f, --force-save', 'allow overwriting input files with output files')
11
11
  .option('-a, --display-all', 'turn on visibility of all layers')
12
+ .option('-n, --name <name(s)>', 'rename input layer or layers')
12
13
  .option('-t, --target <name>', 'name of layer to select initially')
13
14
  .helpOption('-h, --help', 'show this help message')
14
15
  .version(require('../package.json').version)
@@ -29,7 +30,8 @@ var defaultPort = 5555,
29
30
  validateFiles(dataFiles);
30
31
 
31
32
  process.on('uncaughtException', function(err) {
32
- if (err.errno === 'EADDRINUSE') {
33
+ // added 'code' for Node.js v16
34
+ if (err.errno === 'EADDRINUSE' || err.code === 'EADDRINUSE') {
33
35
  // probe for an open port, unless user has specified a non-default port
34
36
  if (port == defaultPort && probeCount < 10) {
35
37
  probeCount++;
@@ -207,6 +209,7 @@ function getManifestJS(files, opts) {
207
209
  if (opts.directSave) o.allow_saving = true;
208
210
  if (opts.displayAll) o.display_all = true;
209
211
  if (opts.quickView) o.quick_view = true;
212
+ if (opts.name) o.name = opts.name;
210
213
  return "mapshaper.manifest = " + JSON.stringify(o) + ";\n";
211
214
  }
212
215
 
package/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.5.87";
3
+ var VERSION = "0.5.88";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -9350,16 +9350,12 @@
9350
9350
  var getters = {
9351
9351
  name: lyr.name,
9352
9352
  data: records,
9353
- type: lyr.geometry_type
9353
+ type: lyr.geometry_type,
9354
+ size: getFeatureCount(lyr),
9355
+ empty: getFeatureCount(lyr) === 0
9354
9356
  };
9355
9357
  addGetters(obj, getters);
9356
9358
  addBBoxGetter(obj, lyr, arcs);
9357
- obj.empty = function() {
9358
- return getFeatureCount(lyr) === 0;
9359
- };
9360
- obj.size = function() {
9361
- return getFeatureCount(lyr);
9362
- };
9363
9359
  return obj;
9364
9360
  }
9365
9361
 
@@ -20116,33 +20112,22 @@ ${svg}
20116
20112
  .option('target', targetOpt);
20117
20113
 
20118
20114
  parser.command('symbols')
20119
- // .describe('symbolize points as polygons, circles, stars or arrows')
20115
+ .describe('symbolize points as arrows, circles, stars, polygons, etc.')
20120
20116
  .option('type', {
20121
- describe: 'symbol type (e.g. star, polygon, circle, arrow)'
20122
- })
20123
- .option('scale', {
20124
- describe: 'scale symbols by a factor',
20125
- type: 'number'
20117
+ describe: 'symbol type (e.g. arrow, circle, square, star, polygon, ring)'
20126
20118
  })
20127
- .option('pixel-scale', {
20128
- describe: 'symbol scale in meters-per-pixel (see polygons option)',
20129
- type: 'number',
20119
+ .option('stroke', {})
20120
+ .option('stroke-width', {})
20121
+ .option('fill', {
20122
+ describe: 'symbol fill color'
20130
20123
  })
20131
20124
  .option('polygons', {
20132
20125
  describe: 'generate symbols as polygons instead of SVG objects',
20133
20126
  type: 'flag'
20134
20127
  })
20135
- .option('radius', {
20136
- describe: 'distance from center to farthest point on the symbol',
20137
- type: 'distance'
20138
- })
20139
- .option('sides', {
20140
- describe: 'number of sides of a polygon symbol',
20141
- type: 'number'
20142
- })
20143
- .option('orientation', {
20144
- // TODO: removed (replaced by flipped and rotated)
20145
- // describe: 'use orientation=b for a rotated or flipped orientation'
20128
+ .option('pixel-scale', {
20129
+ describe: 'set symbol scale in meters-per-pixel (for polygons option)',
20130
+ type: 'number',
20146
20131
  })
20147
20132
  // .option('flipped', {
20148
20133
  // type: 'flag',
@@ -20150,11 +20135,23 @@ ${svg}
20150
20135
  // })
20151
20136
  .option('rotated', {
20152
20137
  type: 'flag',
20153
- describe: 'symbol is rotated to a different orientation'
20138
+ describe: 'symbol is rotated to an alternate orientation'
20154
20139
  })
20155
20140
  .option('rotation', {
20156
20141
  describe: 'rotation of symbol in degrees'
20157
20142
  })
20143
+ .option('scale', {
20144
+ describe: 'scale symbols by a multiplier',
20145
+ type: 'number'
20146
+ })
20147
+ .option('radius', {
20148
+ describe: 'distance from center to farthest point on the symbol',
20149
+ type: 'distance'
20150
+ })
20151
+ .option('sides', {
20152
+ describe: '(polygon) number of sides of a (regular) polygon symbol',
20153
+ type: 'number'
20154
+ })
20158
20155
  .option('points', {
20159
20156
  describe: '(star) number of points'
20160
20157
  })
@@ -20172,7 +20169,7 @@ ${svg}
20172
20169
  })
20173
20170
  .option('direction', {
20174
20171
  old_alias: 'arrow-direction',
20175
- describe: '(arrow) angle off vertical (-90 = left-pointing)'
20172
+ describe: '(arrow) angle off of vertical (-90 = left-pointing)'
20176
20173
  })
20177
20174
  .option('head-angle', {
20178
20175
  old_alias: 'arrow-head-angle',
@@ -20207,17 +20204,12 @@ ${svg}
20207
20204
  })
20208
20205
  .option('min-stem-ratio', {
20209
20206
  old_alias: 'arrow-min-stem',
20210
- describe: '(arrow) min ratio of stem to total length',
20207
+ describe: '(arrow) minimum ratio of stem to total length',
20211
20208
  type: 'number'
20212
20209
  })
20213
20210
  .option('anchor', {
20214
20211
  describe: '(arrow) takes one of: start, middle, end (default is start)'
20215
20212
  })
20216
- .option('stroke', {})
20217
- .option('stroke-width', {})
20218
- .option('fill', {
20219
- describe: 'symbol fill color'
20220
- })
20221
20213
  .option('effect', {})
20222
20214
  // .option('where', whereOpt)
20223
20215
  .option('name', nameOpt)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.5.88",
3
+ "version": "0.5.89",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
@@ -233,6 +233,30 @@
233
233
  return parsed[0].options;
234
234
  };
235
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
+
236
260
  // @file: Zip file
237
261
  // @cb: function(err, <files>)
238
262
  //
@@ -1107,20 +1131,19 @@
1107
1131
 
1108
1132
  function ImportControl(gui, opts) {
1109
1133
  var model = gui.model;
1134
+ var initialImport = true;
1110
1135
  var importCount = 0;
1111
1136
  var importTotal = 0;
1112
1137
  var overQuickView = false;
1113
- var useQuickView = opts.quick_view; // may be set by mapshaper-gui
1114
1138
  var queuedFiles = [];
1115
1139
  var manifestFiles = opts.files || [];
1116
- var cachedFiles = {};
1117
1140
  var catalog;
1118
1141
 
1119
1142
  if (opts.catalog) {
1120
1143
  catalog = new CatalogControl(gui, opts.catalog, downloadFiles);
1121
1144
  }
1122
1145
 
1123
- new SimpleButton('#import-buttons .submit-btn').on('click', onSubmit);
1146
+ new SimpleButton('#import-buttons .submit-btn').on('click', importQueuedFiles);
1124
1147
  new SimpleButton('#import-buttons .cancel-btn').on('click', gui.clearMode);
1125
1148
  new DropControl(gui, 'body', receiveFiles);
1126
1149
  new FileChooser('#file-selection-btn', receiveFiles);
@@ -1128,7 +1151,7 @@
1128
1151
  new FileChooser('#add-file-btn', receiveFiles);
1129
1152
  initDropArea('#import-quick-drop', true);
1130
1153
  initDropArea('#import-drop');
1131
- gui.keyboard.onMenuSubmit(El('#import-options'), onSubmit);
1154
+ gui.keyboard.onMenuSubmit(El('#import-options'), importQueuedFiles);
1132
1155
 
1133
1156
  gui.addMode('import', turnOn, turnOff);
1134
1157
  gui.enterMode('import');
@@ -1140,6 +1163,10 @@
1140
1163
  }
1141
1164
  });
1142
1165
 
1166
+ function useQuickView() {
1167
+ return initialImport && (opts.quick_view || overQuickView);
1168
+ }
1169
+
1143
1170
  function initDropArea(el, isQuick) {
1144
1171
  var area = El(el)
1145
1172
  .on('dragleave', onout)
@@ -1157,15 +1184,19 @@
1157
1184
  }
1158
1185
  }
1159
1186
 
1160
- function findMatchingShp(filename) {
1161
- // use case-insensitive matching
1162
- var base = internal.getPathBase(filename).toLowerCase();
1163
- return model.getDatasets().filter(function(d) {
1164
- var fname = d.info.input_files && d.info.input_files[0] || "";
1165
- var ext = internal.getFileExtension(fname).toLowerCase();
1166
- var base2 = internal.getPathBase(fname).toLowerCase();
1167
- return base == base2 && ext == 'shp';
1168
- });
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();
1169
1200
  }
1170
1201
 
1171
1202
  function turnOn() {
@@ -1186,13 +1217,8 @@
1186
1217
  importCount = 0;
1187
1218
  }
1188
1219
  gui.clearProgressMessage();
1189
- useQuickView = false; // unset 'quick view' mode, if on
1190
- close();
1191
- }
1192
-
1193
- function close() {
1220
+ initialImport = false; // unset 'quick view' mode, if on
1194
1221
  clearQueuedFiles();
1195
- cachedFiles = {};
1196
1222
  }
1197
1223
 
1198
1224
  function onImportComplete() {
@@ -1226,34 +1252,6 @@
1226
1252
  }, []);
1227
1253
  }
1228
1254
 
1229
- // When a Shapefile component is at the head of the queue, move the entire
1230
- // Shapefile to the front of the queue, sorted in reverse alphabetical order,
1231
- // (a kludge), so .shp is read before .dbf and .prj
1232
- // (If a .dbf file is imported before a .shp, it becomes a separate dataset)
1233
- // TODO: import Shapefile parts without relying on this kludge
1234
- function sortQueue(queue) {
1235
- var nextFile = queue[0];
1236
- var basename, parts;
1237
- if (!isShapefilePart(nextFile.name)) {
1238
- return queue;
1239
- }
1240
- basename = internal.getFileBase(nextFile.name).toLowerCase();
1241
- parts = [];
1242
- queue = queue.filter(function(file) {
1243
- if (internal.getFileBase(file.name).toLowerCase() == basename) {
1244
- parts.push(file);
1245
- return false;
1246
- }
1247
- return true;
1248
- });
1249
- parts.sort(function(a, b) {
1250
- // Sorting on LC filename so Shapefiles with mixed-case
1251
- // extensions are sorted correctly
1252
- return a.name.toLowerCase() < b.name.toLowerCase() ? 1 : -1;
1253
- });
1254
- return parts.concat(queue);
1255
- }
1256
-
1257
1255
  function showQueuedFiles() {
1258
1256
  var list = gui.container.findChild('.dropped-file-list').empty();
1259
1257
  queuedFiles.forEach(function(f) {
@@ -1261,16 +1259,14 @@
1261
1259
  });
1262
1260
  }
1263
1261
 
1264
- function receiveFiles(files) {
1265
- var prevSize = queuedFiles.length;
1266
- useQuickView = useQuickView || overQuickView;
1267
- files = handleZipFiles(utils.toArray(files));
1268
- addFilesToQueue(files);
1262
+ async function receiveFiles(files) {
1263
+ // TODO: show importing message here?
1264
+ var expanded = await expandFiles(files);
1265
+ addFilesToQueue(expanded);
1269
1266
  if (queuedFiles.length === 0) return;
1270
1267
  gui.enterMode('import');
1271
-
1272
- if (useQuickView) {
1273
- onSubmit();
1268
+ if (useQuickView()) {
1269
+ importQueuedFiles();
1274
1270
  } else {
1275
1271
  gui.container.addClass('queued-files');
1276
1272
  El('#path-import-options').classed('hidden', !filesMayContainPaths(queuedFiles));
@@ -1278,25 +1274,51 @@
1278
1274
  }
1279
1275
  }
1280
1276
 
1281
- function filesMayContainPaths(files) {
1282
- return utils.some(files, function(f) {
1283
- var type = internal.guessInputFileType(f.name);
1284
- return type == 'shp' || type == 'json' || internal.isZipFile(f.name);
1285
- });
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;
1286
1288
  }
1287
1289
 
1288
- function onSubmit() {
1289
- gui.container.removeClass('queued-files');
1290
- gui.container.removeClass('splash-screen');
1291
- procNextQueuedFile();
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
+ }
1292
1301
  }
1293
1302
 
1294
- function addDataset(dataset) {
1295
- if (!datasetIsEmpty(dataset)) {
1296
- model.addDataset(dataset);
1297
- importCount++;
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);
1298
1309
  }
1299
- procNextQueuedFile();
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
+ });
1300
1322
  }
1301
1323
 
1302
1324
  function datasetIsEmpty(dataset) {
@@ -1305,41 +1327,27 @@
1305
1327
  });
1306
1328
  }
1307
1329
 
1308
- function procNextQueuedFile() {
1309
- if (queuedFiles.length === 0) {
1310
- gui.clearMode();
1311
- } else {
1312
- queuedFiles = sortQueue(queuedFiles);
1313
- readFile(queuedFiles.shift());
1314
- }
1315
- }
1316
1330
 
1317
1331
  // TODO: support .cpg
1318
1332
  function isShapefilePart(name) {
1319
1333
  return /\.(shp|shx|dbf|prj)$/i.test(name);
1320
1334
  }
1321
1335
 
1322
-
1323
1336
  function readImportOpts() {
1324
- if (useQuickView) return {};
1325
- var freeform = El('#import-options .advanced-options').node().value,
1326
- opts = GUI.parseFreeformOptions(freeform, 'i');
1327
- opts.no_repair = !El("#repair-intersections-opt").node().checked;
1328
- opts.snap = !!El("#snap-points-opt").node().checked;
1329
- return opts;
1330
- }
1331
-
1332
- // for CLI output
1333
- function readImportOptsAsString() {
1334
- if (useQuickView) return '';
1335
- var freeform = El('#import-options .advanced-options').node().value;
1336
- var opts = readImportOpts();
1337
- if (opts.snap) freeform = 'snap ' + freeform;
1338
- 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;
1339
1347
  }
1340
1348
 
1341
1349
  // @file a File object
1342
- function readFile(file) {
1350
+ async function readContentFileAsync(file, cb) {
1343
1351
  var name = file.name,
1344
1352
  reader = new FileReader(),
1345
1353
  useBinary = internal.isSupportedBinaryInputType(name) ||
@@ -1349,9 +1357,9 @@
1349
1357
 
1350
1358
  reader.addEventListener('loadend', function(e) {
1351
1359
  if (!reader.result) {
1352
- handleImportError("Web browser was unable to load the file.", name);
1360
+ cb(new Error());
1353
1361
  } else {
1354
- importFileContent(name, reader.result);
1362
+ cb(null, reader.result);
1355
1363
  }
1356
1364
  });
1357
1365
  if (useBinary) {
@@ -1362,91 +1370,6 @@
1362
1370
  }
1363
1371
  }
1364
1372
 
1365
- function importFileContent(fileName, content) {
1366
- var fileType = internal.guessInputType(fileName, content),
1367
- importOpts = readImportOpts(),
1368
- matches = findMatchingShp(fileName),
1369
- dataset, lyr;
1370
-
1371
- // Add dbf data to a previously imported .shp file with a matching name
1372
- // (.shp should have been queued before .dbf)
1373
- if (fileType == 'dbf' && matches.length > 0) {
1374
- // find an imported .shp layer that is missing attribute data
1375
- // (if multiple matches, try to use the most recently imported one)
1376
- dataset = matches.reduce(function(memo, d) {
1377
- if (!d.layers[0].data) {
1378
- memo = d;
1379
- }
1380
- return memo;
1381
- }, null);
1382
- if (dataset) {
1383
- lyr = dataset.layers[0];
1384
- lyr.data = new internal.ShapefileTable(content, importOpts.encoding);
1385
- if (lyr.shapes && lyr.data.size() != lyr.shapes.length) {
1386
- stop("Different number of records in .shp and .dbf files");
1387
- }
1388
- if (!lyr.geometry_type) {
1389
- // kludge: trigger display of table cells if .shp has null geometry
1390
- // TODO: test case if lyr is not the current active layer
1391
- model.updated({});
1392
- }
1393
- procNextQueuedFile();
1394
- return;
1395
- }
1396
- }
1397
-
1398
- if (fileType == 'shx') {
1399
- // save .shx for use when importing .shp
1400
- // (queue should be sorted so that .shx is processed before .shp)
1401
- cachedFiles[fileName.toLowerCase()] = {filename: fileName, content: content};
1402
- procNextQueuedFile();
1403
- return;
1404
- }
1405
-
1406
- // Add .prj file to previously imported .shp file
1407
- if (fileType == 'prj') {
1408
- matches.forEach(function(d) {
1409
- if (!d.info.prj) {
1410
- d.info.prj = content;
1411
- }
1412
- });
1413
- procNextQueuedFile();
1414
- return;
1415
- }
1416
-
1417
- importNewDataset(fileType, fileName, content, importOpts);
1418
- }
1419
-
1420
- function importNewDataset(fileType, fileName, content, importOpts) {
1421
- var size = content.byteLength || content.length, // ArrayBuffer or string
1422
- delay = 0;
1423
-
1424
- // show importing message if file is large
1425
- if (size > 4e7) {
1426
- gui.showProgressMessage('Importing');
1427
- delay = 35;
1428
- }
1429
- setTimeout(function() {
1430
- var dataset;
1431
- var input = {};
1432
- try {
1433
- input[fileType] = {filename: fileName, content: content};
1434
- if (fileType == 'shp') {
1435
- // shx file should already be cached, if it was added together with the shp
1436
- input.shx = cachedFiles[fileName.replace(/shp$/i, 'shx').toLowerCase()] || null;
1437
- }
1438
- dataset = internal.importContent(input, importOpts);
1439
- // save import options for use by repair control, etc.
1440
- dataset.info.import_options = importOpts;
1441
- gui.session.fileImported(fileName, readImportOptsAsString());
1442
- addDataset(dataset);
1443
-
1444
- } catch(e) {
1445
- handleImportError(e, fileName);
1446
- }
1447
- }, delay);
1448
- }
1449
-
1450
1373
  function handleImportError(e, fileName) {
1451
1374
  var msg = utils.isString(e) ? e : e.message;
1452
1375
  if (fileName) {
@@ -1457,34 +1380,6 @@
1457
1380
  console.error(e);
1458
1381
  }
1459
1382
 
1460
- function handleZipFiles(files) {
1461
- return files.filter(function(file) {
1462
- var isZip = internal.isZipFile(file.name);
1463
- if (isZip) {
1464
- importZipFile(file);
1465
- }
1466
- return !isZip;
1467
- });
1468
- }
1469
-
1470
- function importZipFile(file) {
1471
- // gui.showProgressMessage('Importing');
1472
- setTimeout(function() {
1473
- GUI.readZipFile(file, function(err, files) {
1474
- if (err) {
1475
- handleImportError(err, file.name);
1476
- } else {
1477
- // don't try to import .txt files from zip files
1478
- // (these would be parsed as dsv and throw errows)
1479
- files = files.filter(function(f) {
1480
- return !/\.txt$/i.test(f.name);
1481
- });
1482
- receiveFiles(files);
1483
- }
1484
- });
1485
- }, 35);
1486
- }
1487
-
1488
1383
  function prepFilesForDownload(names) {
1489
1384
  var items = names.map(function(name) {
1490
1385
  var isUrl = /:\/\//.test(name);
@@ -1530,37 +1425,107 @@
1530
1425
  });
1531
1426
  }
1532
1427
 
1533
- function downloadNextFile_v1(memo, item, next) {
1534
- var req = new XMLHttpRequest();
1535
- var blob;
1536
- req.responseType = 'blob';
1537
- req.addEventListener('load', function(e) {
1538
- if (req.status == 200) {
1539
- blob = req.response;
1540
- }
1541
- });
1542
- req.addEventListener('progress', function(e) {
1543
- if (!e.lengthComputable) return;
1544
- var pct = e.loaded / e.total;
1545
- 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
+ });
1546
1437
  });
1547
- req.addEventListener('loadend', function() {
1548
- var err;
1549
- if (req.status == 404) {
1550
- err = "Not&nbsp;found:&nbsp;" + item.name;
1551
- } else if (!blob) {
1552
- // Errors like DNS lookup failure, no CORS headers, no network connection
1553
- // all are status 0 - it seems impossible to show a more specific message
1554
- // actual reason is displayed on the console
1555
- err = "Error&nbsp;loading&nbsp;" + item.name + ". Possible causes include: wrong URL, no network connection, server not configured for cross-domain sharing (CORS).";
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');
1556
1504
  } else {
1557
- blob.name = item.basename;
1558
- memo.push(blob);
1505
+ // shapefile part without a .shp file
1506
+ d.group = null;
1559
1507
  }
1560
- next(err, memo);
1561
1508
  });
1562
- req.open('GET', item.url);
1563
- req.send();
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;
1564
1529
  }
1565
1530
  }
1566
1531
 
@@ -8452,6 +8417,7 @@
8452
8417
  opts.display_all = vars['display-all'] || vars.a || !!manifest.display_all;
8453
8418
  opts.quick_view = vars['quick-view'] || vars.q || !!manifest.quick_view;
8454
8419
  opts.target = vars.target || manifest.target || null;
8420
+ opts.name = vars.name || manifest.name || null;
8455
8421
  return opts;
8456
8422
  }
8457
8423
 
package/www/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.5.87";
3
+ var VERSION = "0.5.88";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -9350,16 +9350,12 @@
9350
9350
  var getters = {
9351
9351
  name: lyr.name,
9352
9352
  data: records,
9353
- type: lyr.geometry_type
9353
+ type: lyr.geometry_type,
9354
+ size: getFeatureCount(lyr),
9355
+ empty: getFeatureCount(lyr) === 0
9354
9356
  };
9355
9357
  addGetters(obj, getters);
9356
9358
  addBBoxGetter(obj, lyr, arcs);
9357
- obj.empty = function() {
9358
- return getFeatureCount(lyr) === 0;
9359
- };
9360
- obj.size = function() {
9361
- return getFeatureCount(lyr);
9362
- };
9363
9359
  return obj;
9364
9360
  }
9365
9361
 
@@ -20116,33 +20112,22 @@ ${svg}
20116
20112
  .option('target', targetOpt);
20117
20113
 
20118
20114
  parser.command('symbols')
20119
- // .describe('symbolize points as polygons, circles, stars or arrows')
20115
+ .describe('symbolize points as arrows, circles, stars, polygons, etc.')
20120
20116
  .option('type', {
20121
- describe: 'symbol type (e.g. star, polygon, circle, arrow)'
20122
- })
20123
- .option('scale', {
20124
- describe: 'scale symbols by a factor',
20125
- type: 'number'
20117
+ describe: 'symbol type (e.g. arrow, circle, square, star, polygon, ring)'
20126
20118
  })
20127
- .option('pixel-scale', {
20128
- describe: 'symbol scale in meters-per-pixel (see polygons option)',
20129
- type: 'number',
20119
+ .option('stroke', {})
20120
+ .option('stroke-width', {})
20121
+ .option('fill', {
20122
+ describe: 'symbol fill color'
20130
20123
  })
20131
20124
  .option('polygons', {
20132
20125
  describe: 'generate symbols as polygons instead of SVG objects',
20133
20126
  type: 'flag'
20134
20127
  })
20135
- .option('radius', {
20136
- describe: 'distance from center to farthest point on the symbol',
20137
- type: 'distance'
20138
- })
20139
- .option('sides', {
20140
- describe: 'number of sides of a polygon symbol',
20141
- type: 'number'
20142
- })
20143
- .option('orientation', {
20144
- // TODO: removed (replaced by flipped and rotated)
20145
- // describe: 'use orientation=b for a rotated or flipped orientation'
20128
+ .option('pixel-scale', {
20129
+ describe: 'set symbol scale in meters-per-pixel (for polygons option)',
20130
+ type: 'number',
20146
20131
  })
20147
20132
  // .option('flipped', {
20148
20133
  // type: 'flag',
@@ -20150,11 +20135,23 @@ ${svg}
20150
20135
  // })
20151
20136
  .option('rotated', {
20152
20137
  type: 'flag',
20153
- describe: 'symbol is rotated to a different orientation'
20138
+ describe: 'symbol is rotated to an alternate orientation'
20154
20139
  })
20155
20140
  .option('rotation', {
20156
20141
  describe: 'rotation of symbol in degrees'
20157
20142
  })
20143
+ .option('scale', {
20144
+ describe: 'scale symbols by a multiplier',
20145
+ type: 'number'
20146
+ })
20147
+ .option('radius', {
20148
+ describe: 'distance from center to farthest point on the symbol',
20149
+ type: 'distance'
20150
+ })
20151
+ .option('sides', {
20152
+ describe: '(polygon) number of sides of a (regular) polygon symbol',
20153
+ type: 'number'
20154
+ })
20158
20155
  .option('points', {
20159
20156
  describe: '(star) number of points'
20160
20157
  })
@@ -20172,7 +20169,7 @@ ${svg}
20172
20169
  })
20173
20170
  .option('direction', {
20174
20171
  old_alias: 'arrow-direction',
20175
- describe: '(arrow) angle off vertical (-90 = left-pointing)'
20172
+ describe: '(arrow) angle off of vertical (-90 = left-pointing)'
20176
20173
  })
20177
20174
  .option('head-angle', {
20178
20175
  old_alias: 'arrow-head-angle',
@@ -20207,17 +20204,12 @@ ${svg}
20207
20204
  })
20208
20205
  .option('min-stem-ratio', {
20209
20206
  old_alias: 'arrow-min-stem',
20210
- describe: '(arrow) min ratio of stem to total length',
20207
+ describe: '(arrow) minimum ratio of stem to total length',
20211
20208
  type: 'number'
20212
20209
  })
20213
20210
  .option('anchor', {
20214
20211
  describe: '(arrow) takes one of: start, middle, end (default is start)'
20215
20212
  })
20216
- .option('stroke', {})
20217
- .option('stroke-width', {})
20218
- .option('fill', {
20219
- describe: 'symbol fill color'
20220
- })
20221
20213
  .option('effect', {})
20222
20214
  // .option('where', whereOpt)
20223
20215
  .option('name', nameOpt)