mapshaper 0.7.20 → 0.7.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/mapshaper-gui CHANGED
@@ -14,6 +14,7 @@ var defaultPort = 5555,
14
14
  .option('-n, --name <name(s)>', 'rename input layer or layers')
15
15
  .option('-c, --commands <string>', 'console commands to run initially')
16
16
  .option('-t, --target <name>', 'name of layer to select initially')
17
+ .option('-w, --watch', 'watch input files and reload the browser on change')
17
18
  .addOption(new commander.Option('-b, --blurb <text>',
18
19
  'replace the default blurb on the import screen').hideHelp())
19
20
  .argument('[file...]', 'data files to load')
@@ -30,9 +31,26 @@ var defaultPort = 5555,
30
31
  port = parseInt(opts.port, 10) || defaultPort,
31
32
  dataFiles = expandShapefiles(program.args),
32
33
  probeCount = 0,
33
- sessionId = null;
34
+ sessionId = null,
35
+ sseClients = [],
36
+ reloadTimer = null;
34
37
  validateFiles(dataFiles);
35
38
 
39
+ if (opts.watch && dataFiles.length > 0) {
40
+ dataFiles.forEach(function(file) {
41
+ if (isUrl(file)) return;
42
+ fs.watch(file, function() {
43
+ // debounce: editors often write files in multiple steps
44
+ clearTimeout(reloadTimer);
45
+ reloadTimer = setTimeout(function() {
46
+ sseClients.forEach(function(res) {
47
+ try { res.write('event: reload\ndata: changed\n\n'); } catch(e) {}
48
+ });
49
+ }, 150);
50
+ });
51
+ });
52
+ }
53
+
36
54
  process.on('uncaughtException', function(err) {
37
55
  // added 'code' for Node.js v16
38
56
  if (err.errno === 'EADDRINUSE' || err.code === 'EADDRINUSE') {
@@ -99,6 +117,18 @@ function startServer(port) {
99
117
  serveFile(getDataFilePath(uri), response);
100
118
  } else if (uri.indexOf('/save') === 0) {
101
119
  saveContent(request, response);
120
+ } else if (uri == '/sse' && opts.watch) {
121
+ // Server-Sent Events endpoint for file-watch reload notifications
122
+ response.writeHead(200, {
123
+ 'Content-Type': 'text/event-stream',
124
+ 'Cache-Control': 'no-cache',
125
+ 'Connection': 'keep-alive'
126
+ });
127
+ response.write('data: connected\n\n');
128
+ sseClients.push(response);
129
+ request.on('close', function() {
130
+ sseClients = sseClients.filter(function(c) { return c !== response; });
131
+ });
102
132
  } else {
103
133
  // serve a file from the web root. Translate directory paths (any URI
104
134
  // ending in '/') to their index.html so that links like '/docs/' work
@@ -106,7 +136,21 @@ function startServer(port) {
106
136
  if (uri.endsWith('/')) {
107
137
  uri += 'index.html';
108
138
  }
109
- serveFile(getAssetFilePath(uri), response);
139
+ if (opts.watch && uri == '/index.html') {
140
+ // inject SSE reload script into index.html
141
+ var htmlPath = getAssetFilePath('/index.html');
142
+ fs.readFile(htmlPath, 'utf8', function(err, content) {
143
+ if (err) return serve404(response);
144
+ var script = '<script>(function(){' +
145
+ 'var es=new EventSource("/sse");' +
146
+ 'es.addEventListener("reload",function(){location.reload();});' +
147
+ '})();</script>';
148
+ content = content.replace('</body>', script + '\n</body>');
149
+ serveContent(content, response, 'text/html');
150
+ });
151
+ } else {
152
+ serveFile(getAssetFilePath(uri), response);
153
+ }
110
154
  }
111
155
  }).listen(port, 'localhost', function() {
112
156
  if (process.env.MAPSHAPER_GUI_NO_OPEN) return;
@@ -165,7 +209,7 @@ function serveContent(content, response, mimeType) {
165
209
  }
166
210
  response.setHeader('Cache-Control', 'no-cache');
167
211
  response.writeHead(200);
168
- response.write(content, "binary");
212
+ response.write(content, Buffer.isBuffer(content) ? undefined : "utf8");
169
213
  response.end();
170
214
  }
171
215
 
package/mapshaper.js CHANGED
@@ -7417,7 +7417,7 @@
7417
7417
  i = fw ? arcId : ~arcId,
7418
7418
  iter = _zz && _zlimit ? _filteredArcIter : _arcIter;
7419
7419
  if (i >= _nn.length) {
7420
- error("#getArcId() out-of-range arc id:", arcId);
7420
+ error("#getArcId() out-of-range arc id:", arcId, `(max id: ${_nn.length - 1})`);
7421
7421
  }
7422
7422
  return iter.init(_ii[i], _nn[i], fw, _zlimit);
7423
7423
  };
@@ -17446,9 +17446,14 @@
17446
17446
  var layers = layersArg.filter(getFeatureCount); // ignore empty layers
17447
17447
  var merged = {};
17448
17448
  opts = opts || {};
17449
+ if (!layers.length && layersArg.length > 1) {
17450
+ layers = layersArg; // merge all-empty layer sets
17451
+ }
17449
17452
  if (!layers.length) return null;
17450
17453
  if (layers.length == 1) {
17451
- message('Use the target= option to specify multiple layers for merging');
17454
+ if (layersArg.length == 1) {
17455
+ message('Use the target= option to specify multiple layers for merging');
17456
+ }
17452
17457
  return layers.concat();
17453
17458
  }
17454
17459
  merged.data = mergeDataFromLayers(layers, opts);
@@ -45286,23 +45291,23 @@ ${svg}
45286
45291
  profileEnd('clipLayers');
45287
45292
  return result;
45288
45293
  }
45294
+ if (!usingPathClip) {
45295
+ result = clipLayersByClipDataset(targetLayers, clipDataset, type, opts);
45296
+ profileEnd('clipLayers');
45297
+ return result;
45298
+ }
45289
45299
  profileStart('mergeLayersForOverlay');
45290
45300
  mergedDataset = mergeLayersForOverlay2(targetLayers, targetDataset, clipDataset);
45291
45301
  profileEnd('mergeLayersForOverlay');
45292
45302
  clipLyr = mergedDataset.layers[mergedDataset.layers.length-1];
45293
- if (usingPathClip) {
45294
- nodes = addIntersectionCuts(mergedDataset, opts);
45295
- noteDatasetWillChange(targetDataset, {operation: type, unit: 'arcs'});
45296
- targetDataset.arcs = mergedDataset.arcs;
45297
- markDatasetChanged(targetDataset, {operation: type, unit: 'arcs'});
45298
- profileStart('clipDissolvePolygonLayer2');
45299
- clipLyr = utils.defaults({data: null}, clipLyr);
45300
- clipLyr = dissolvePolygonLayer2(clipLyr, mergedDataset, {quiet: true, silent: true});
45301
- profileEnd('clipDissolvePolygonLayer2');
45302
-
45303
- } else {
45304
- nodes = new NodeCollection(mergedDataset.arcs);
45305
- }
45303
+ nodes = addIntersectionCuts(mergedDataset, opts);
45304
+ noteDatasetWillChange(targetDataset, {operation: type, unit: 'arcs'});
45305
+ targetDataset.arcs = mergedDataset.arcs;
45306
+ markDatasetChanged(targetDataset, {operation: type, unit: 'arcs'});
45307
+ profileStart('clipDissolvePolygonLayer2');
45308
+ clipLyr = utils.defaults({data: null}, clipLyr);
45309
+ clipLyr = dissolvePolygonLayer2(clipLyr, mergedDataset, {quiet: true, silent: true});
45310
+ profileEnd('clipDissolvePolygonLayer2');
45306
45311
 
45307
45312
  profileStart('clipLayersByLayer');
45308
45313
  result = clipLayersByLayer(targetLayers, clipLyr, nodes, type, opts);
@@ -45311,6 +45316,16 @@ ${svg}
45311
45316
  return result;
45312
45317
  }
45313
45318
 
45319
+ function clipLayersByClipDataset(targetLayers, clipDataset, type, opts) {
45320
+ var clipLyr = clipDataset.layers[0];
45321
+ var nodes = new NodeCollection(clipDataset.arcs);
45322
+ var result;
45323
+ profileStart('clipLayersByLayer');
45324
+ result = clipLayersByLayer(targetLayers, clipLyr, nodes, type, opts);
45325
+ profileEnd('clipLayersByLayer');
45326
+ return result;
45327
+ }
45328
+
45314
45329
  function clipRasterLayers(targetLayers, clipSrc, targetDataset, type, opts) {
45315
45330
  var clipDataset, clipBounds, bbox;
45316
45331
  if (type != 'clip') {
@@ -58299,8 +58314,7 @@ ${svg}
58299
58314
  // TODO: consider replacing old layers as they are generated, for gc
58300
58315
  replaceLayers(targetDataset, targetLayers, outputLayers);
58301
58316
  // some operations leave unreferenced arcs that should be cleaned up
58302
- if ((name == 'clip' || name == 'erase' || name == 'rectangle' ||
58303
- name == 'rectangles' || name == 'filter' && opts.cleanup) && !opts.no_cleanup) {
58317
+ if (commandNeedsArcDissolve(name, targetLayers, opts)) {
58304
58318
  dissolveArcs(targetDataset);
58305
58319
  }
58306
58320
  }
@@ -58347,7 +58361,15 @@ ${svg}
58347
58361
  });
58348
58362
  }
58349
58363
 
58350
- var version = "0.7.20";
58364
+ function commandNeedsArcDissolve(name, targetLayers, opts) {
58365
+ if (opts.no_cleanup) return false;
58366
+ if (name == 'clip' || name == 'erase') {
58367
+ return utils.some(targetLayers || [], layerHasPaths);
58368
+ }
58369
+ return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
58370
+ }
58371
+
58372
+ var version = "0.7.21";
58351
58373
 
58352
58374
  // Parse command line args into commands and run them
58353
58375
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.7.20",
3
+ "version": "0.7.21",
4
4
  "description": "A tool for editing geospatial data for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
@@ -17024,6 +17024,9 @@
17024
17024
  }
17025
17025
 
17026
17026
  function selectStyleFeature(id, e) {
17027
+ if (selectionIds.includes(id)) {
17028
+ return utils$1.difference(selectionIds, [id]);
17029
+ }
17027
17030
  return eventUsesAdditiveSelection(e) ?
17028
17031
  toggleId(id, selectionIds) :
17029
17032
  [id];
package/www/mapshaper.js CHANGED
@@ -7417,7 +7417,7 @@
7417
7417
  i = fw ? arcId : ~arcId,
7418
7418
  iter = _zz && _zlimit ? _filteredArcIter : _arcIter;
7419
7419
  if (i >= _nn.length) {
7420
- error("#getArcId() out-of-range arc id:", arcId);
7420
+ error("#getArcId() out-of-range arc id:", arcId, `(max id: ${_nn.length - 1})`);
7421
7421
  }
7422
7422
  return iter.init(_ii[i], _nn[i], fw, _zlimit);
7423
7423
  };
@@ -17446,9 +17446,14 @@
17446
17446
  var layers = layersArg.filter(getFeatureCount); // ignore empty layers
17447
17447
  var merged = {};
17448
17448
  opts = opts || {};
17449
+ if (!layers.length && layersArg.length > 1) {
17450
+ layers = layersArg; // merge all-empty layer sets
17451
+ }
17449
17452
  if (!layers.length) return null;
17450
17453
  if (layers.length == 1) {
17451
- message('Use the target= option to specify multiple layers for merging');
17454
+ if (layersArg.length == 1) {
17455
+ message('Use the target= option to specify multiple layers for merging');
17456
+ }
17452
17457
  return layers.concat();
17453
17458
  }
17454
17459
  merged.data = mergeDataFromLayers(layers, opts);
@@ -45286,23 +45291,23 @@ ${svg}
45286
45291
  profileEnd('clipLayers');
45287
45292
  return result;
45288
45293
  }
45294
+ if (!usingPathClip) {
45295
+ result = clipLayersByClipDataset(targetLayers, clipDataset, type, opts);
45296
+ profileEnd('clipLayers');
45297
+ return result;
45298
+ }
45289
45299
  profileStart('mergeLayersForOverlay');
45290
45300
  mergedDataset = mergeLayersForOverlay2(targetLayers, targetDataset, clipDataset);
45291
45301
  profileEnd('mergeLayersForOverlay');
45292
45302
  clipLyr = mergedDataset.layers[mergedDataset.layers.length-1];
45293
- if (usingPathClip) {
45294
- nodes = addIntersectionCuts(mergedDataset, opts);
45295
- noteDatasetWillChange(targetDataset, {operation: type, unit: 'arcs'});
45296
- targetDataset.arcs = mergedDataset.arcs;
45297
- markDatasetChanged(targetDataset, {operation: type, unit: 'arcs'});
45298
- profileStart('clipDissolvePolygonLayer2');
45299
- clipLyr = utils.defaults({data: null}, clipLyr);
45300
- clipLyr = dissolvePolygonLayer2(clipLyr, mergedDataset, {quiet: true, silent: true});
45301
- profileEnd('clipDissolvePolygonLayer2');
45302
-
45303
- } else {
45304
- nodes = new NodeCollection(mergedDataset.arcs);
45305
- }
45303
+ nodes = addIntersectionCuts(mergedDataset, opts);
45304
+ noteDatasetWillChange(targetDataset, {operation: type, unit: 'arcs'});
45305
+ targetDataset.arcs = mergedDataset.arcs;
45306
+ markDatasetChanged(targetDataset, {operation: type, unit: 'arcs'});
45307
+ profileStart('clipDissolvePolygonLayer2');
45308
+ clipLyr = utils.defaults({data: null}, clipLyr);
45309
+ clipLyr = dissolvePolygonLayer2(clipLyr, mergedDataset, {quiet: true, silent: true});
45310
+ profileEnd('clipDissolvePolygonLayer2');
45306
45311
 
45307
45312
  profileStart('clipLayersByLayer');
45308
45313
  result = clipLayersByLayer(targetLayers, clipLyr, nodes, type, opts);
@@ -45311,6 +45316,16 @@ ${svg}
45311
45316
  return result;
45312
45317
  }
45313
45318
 
45319
+ function clipLayersByClipDataset(targetLayers, clipDataset, type, opts) {
45320
+ var clipLyr = clipDataset.layers[0];
45321
+ var nodes = new NodeCollection(clipDataset.arcs);
45322
+ var result;
45323
+ profileStart('clipLayersByLayer');
45324
+ result = clipLayersByLayer(targetLayers, clipLyr, nodes, type, opts);
45325
+ profileEnd('clipLayersByLayer');
45326
+ return result;
45327
+ }
45328
+
45314
45329
  function clipRasterLayers(targetLayers, clipSrc, targetDataset, type, opts) {
45315
45330
  var clipDataset, clipBounds, bbox;
45316
45331
  if (type != 'clip') {
@@ -58299,8 +58314,7 @@ ${svg}
58299
58314
  // TODO: consider replacing old layers as they are generated, for gc
58300
58315
  replaceLayers(targetDataset, targetLayers, outputLayers);
58301
58316
  // some operations leave unreferenced arcs that should be cleaned up
58302
- if ((name == 'clip' || name == 'erase' || name == 'rectangle' ||
58303
- name == 'rectangles' || name == 'filter' && opts.cleanup) && !opts.no_cleanup) {
58317
+ if (commandNeedsArcDissolve(name, targetLayers, opts)) {
58304
58318
  dissolveArcs(targetDataset);
58305
58319
  }
58306
58320
  }
@@ -58347,7 +58361,15 @@ ${svg}
58347
58361
  });
58348
58362
  }
58349
58363
 
58350
- var version = "0.7.20";
58364
+ function commandNeedsArcDissolve(name, targetLayers, opts) {
58365
+ if (opts.no_cleanup) return false;
58366
+ if (name == 'clip' || name == 'erase') {
58367
+ return utils.some(targetLayers || [], layerHasPaths);
58368
+ }
58369
+ return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
58370
+ }
58371
+
58372
+ var version = "0.7.21";
58351
58373
 
58352
58374
  // Parse command line args into commands and run them
58353
58375
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.