mapshaper 0.5.105 → 0.5.108

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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ v0.5.108
2
+ * Bug fixes
3
+ * Added file_exists() to -if/-elif expressions
4
+
5
+ v0.5.106
6
+ * More basemap fixes.
7
+
1
8
  v0.5.105
2
9
  * More basemap improvements.
3
10
 
package/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.5.105";
3
+ var VERSION = "0.5.108";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -7813,10 +7813,24 @@
7813
7813
  // a, b: two ring data objects (from getPathMetadata);
7814
7814
  function testRingInRing(a, b, arcs) {
7815
7815
  if (b.bounds.contains(a.bounds) === false) return false;
7816
- var p = arcs.getVertex(a.ids[0], 0); // test with first point in the ring
7816
+ // Don't test with first point -- this may return false if a hole intersects
7817
+ // the containing ring at the first vertex.
7818
+ // Instead, use the midpoint of the first segment
7819
+ var p = getFirstMidpoint(a.ids[0], arcs);
7820
+ //// test with first point in the ring
7821
+ // var p = arcs.getVertex(a.ids[0], 0);
7817
7822
  return geom.testPointInRing(p.x, p.y, b.ids, arcs) == 1;
7818
7823
  }
7819
7824
 
7825
+ function getFirstMidpoint(arcId, arcs) {
7826
+ var p1 = arcs.getVertex(arcId, 0);
7827
+ var p2 = arcs.getVertex(arcId, 1);
7828
+ return {
7829
+ x: (p1.x + p2.x) / 2,
7830
+ y: (p1.y + p2.y) / 2
7831
+ };
7832
+ }
7833
+
7820
7834
  // Bundle holes with their containing rings for Topo/GeoJSON polygon export.
7821
7835
  // Assumes outer rings are CW and inner (hole) rings are CCW, unless
7822
7836
  // the reverseWinding flag is set.
@@ -9525,9 +9539,6 @@
9525
9539
  return rec && (rec[name] === val);
9526
9540
  });
9527
9541
  };
9528
- obj.file_exists = function(name) {
9529
- return cli.isFile(name);
9530
- };
9531
9542
  return obj;
9532
9543
  }
9533
9544
 
@@ -18954,6 +18965,10 @@ ${svg}
18954
18965
  .option('json-path', {
18955
18966
  old_alias: 'json-subtree',
18956
18967
  describe: '[JSON] path to JSON input data; separator is /'
18968
+ })
18969
+ .option('single-part', {
18970
+ type: 'flag',
18971
+ // describe: '[GeoJSON] split multi-part features into single-part features'
18957
18972
  });
18958
18973
 
18959
18974
  parser.command('o')
@@ -22119,6 +22134,8 @@ ${svg}
22119
22134
  // TODO: improve so geometry_type option skips features instead of creating null geometries
22120
22135
  if (geom && geom.type == 'GeometryCollection') {
22121
22136
  GeoJSON.importComplexFeature(importer, geom, rec, opts);
22137
+ } else if (opts.single_part && isMultiPartGeometry(geom)) {
22138
+ GeoJSON.importMultiAsSingles(importer, geom, rec, opts);
22122
22139
  } else {
22123
22140
  GeoJSON.importSimpleFeature(importer, geom, rec, opts);
22124
22141
  }
@@ -22160,11 +22177,27 @@ ${svg}
22160
22177
  return Object.values(index);
22161
22178
  }
22162
22179
 
22180
+ function isMultiPartGeometry(geom) {
22181
+ return geom && geom.type && geom.type.indexOf('Multi') === 0;
22182
+ }
22183
+
22163
22184
  GeoJSON.importSimpleFeature = function(importer, geom, rec, opts) {
22164
22185
  importer.startShape(rec);
22165
22186
  GeoJSON.importSimpleGeometry(importer, geom, opts);
22166
22187
  };
22167
22188
 
22189
+ // Split a multi-part feature into several single features
22190
+ GeoJSON.importMultiAsSingles = function(importer, geom, rec, opts) {
22191
+ geom.coordinates.forEach(function(coords, i) {
22192
+ var geom2 = {
22193
+ type: geom.type.substr(5),
22194
+ coordinates: coords
22195
+ };
22196
+ var rec2 = i === 0 ? rec : copyRecord(rec);
22197
+ GeoJSON.importSimpleFeature(importer, geom2, rec2, opts);
22198
+ });
22199
+ };
22200
+
22168
22201
  GeoJSON.importSimpleGeometry = function(importer, geom, opts) {
22169
22202
  var type = geom ? geom.type : null;
22170
22203
  if (type === null) {
@@ -34614,14 +34647,22 @@ ${svg}
34614
34647
  return o;
34615
34648
  }
34616
34649
 
34617
- function compileLayerExpression(expr, lyr, dataset, opts) {
34618
- var proxy = getLayerProxy(lyr, dataset.arcs, opts);
34650
+ function compileIfCommandExpression(expr, catalog, targ, opts) {
34651
+ var ctx = getLayerProxy(targ.layer, targ.dataset.arcs, opts);
34619
34652
  var exprOpts = Object.assign({returns: true}, opts);
34620
34653
  var func = compileExpressionToFunction(expr, exprOpts);
34621
- var ctx = proxy;
34654
+
34655
+ ctx.layer_exists = function(name) {
34656
+ return !!catalog.findSingleLayer(name);
34657
+ };
34658
+
34659
+ ctx.file_exists = function(file) {
34660
+ return cli.isFile(file);
34661
+ };
34662
+
34622
34663
  return function() {
34623
34664
  try {
34624
- return func.call(proxy, {}, ctx);
34665
+ return func.call(ctx, {}, ctx);
34625
34666
  } catch(e) {
34626
34667
  // if (opts.quiet) throw e;
34627
34668
  stop(e.name, "in expression [" + expr + "]:", e.message);
@@ -34674,7 +34715,7 @@ ${svg}
34674
34715
  function testLayer(catalog, opts) {
34675
34716
  var targ = getTargetLayer(catalog, opts);
34676
34717
  if (opts.expression) {
34677
- return compileLayerExpression(opts.expression, targ.layer, targ.dataset, opts)();
34718
+ return compileIfCommandExpression(opts.expression, catalog, targ, opts)();
34678
34719
  }
34679
34720
  if (opts.empty) {
34680
34721
  return layerIsEmpty(targ.layer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.5.105",
3
+ "version": "0.5.108",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
@@ -6442,10 +6442,13 @@
6442
6442
 
6443
6443
  function CoordinatesDisplay(gui, ext, mouse) {
6444
6444
  var readout = gui.container.findChild('.coordinate-info').hide();
6445
- var enabled = false;
6446
6445
 
6447
- gui.model.on('select', function(e) {
6448
- enabled = !!e.layer.geometry_type; // no display on tabular layers
6446
+ function enabled() {
6447
+ var lyr = gui.map.getActiveLayer();
6448
+ return !!(lyr && lyr.layer.geometry_type);
6449
+ }
6450
+
6451
+ gui.model.on('update', function(e) {
6449
6452
  readout.hide();
6450
6453
  });
6451
6454
 
@@ -6466,7 +6469,7 @@
6466
6469
  mouse.on('leave', clearCoords);
6467
6470
 
6468
6471
  mouse.on('click', function(e) {
6469
- if (!enabled) return;
6472
+ if (!enabled()) return;
6470
6473
  GUI.selectElement(readout.node());
6471
6474
  });
6472
6475
 
@@ -6474,7 +6477,7 @@
6474
6477
  mouse.on('drag', onMouseChange, null, 10); // high priority so editor doesn't block propagation
6475
6478
 
6476
6479
  function onMouseChange(e) {
6477
- if (!enabled) return;
6480
+ if (!enabled()) return;
6478
6481
  if (isOverMap(e)) {
6479
6482
  displayCoords(gui.map.translatePixelCoords(e.x, e.y));
6480
6483
  } else {
@@ -6998,6 +7001,15 @@
6998
7001
  ext.zoomByPct(delta, e.x / ext.width(), e.y / ext.height());
6999
7002
  });
7000
7003
 
7004
+ function fixBounds(bbox) {
7005
+ if (bbox[0] > bbox[2]) {
7006
+ swapElements(bbox, 0, 2);
7007
+ }
7008
+ if (bbox[1] > bbox[3]) {
7009
+ swapElements(bbox, 1, 3);
7010
+ }
7011
+ }
7012
+
7001
7013
  function swapElements(arr, i, j) {
7002
7014
  var tmp = arr[i];
7003
7015
  arr[i] = arr[j];
@@ -7007,21 +7019,28 @@
7007
7019
  function getBoxData(e) {
7008
7020
  var pageBox = [e.pageX, e.pageY, dragStartEvt.pageX, dragStartEvt.pageY];
7009
7021
  var mapBox = [e.x, e.y, dragStartEvt.x, dragStartEvt.y];
7010
- var tmp;
7011
- if (pageBox[0] > pageBox[2]) {
7012
- swapElements(pageBox, 0, 2);
7013
- swapElements(mapBox, 0, 2);
7014
- }
7015
- if (pageBox[1] > pageBox[3]) {
7016
- swapElements(pageBox, 1, 3);
7017
- swapElements(mapBox, 1, 3);
7018
- }
7022
+ var displayBox = pixToCoords(mapBox);
7023
+ var dataBox = getBBoxCoords(gui.map.getActiveLayer(), displayBox);
7024
+ fixBounds(pageBox);
7025
+ fixBounds(mapBox);
7026
+ fixBounds(displayBox);
7027
+ fixBounds(dataBox);
7019
7028
  return {
7020
7029
  map_bbox: mapBox,
7021
- page_bbox: pageBox
7030
+ page_bbox: pageBox,
7031
+ // round coords, for nicer 'info' display
7032
+ // (rounded precision should be sub-pixel)
7033
+ map_display_bbox: internal.getRoundedCoords(displayBox, internal.getBoundsPrecisionForDisplay(displayBox)),
7034
+ map_data_bbox: internal.getRoundedCoords(dataBox, internal.getBoundsPrecisionForDisplay(dataBox))
7022
7035
  };
7023
7036
  }
7024
7037
 
7038
+ function pixToCoords(bbox) {
7039
+ var a = ext.translatePixelCoords(bbox[0], bbox[1]);
7040
+ var b = ext.translatePixelCoords(bbox[2], bbox[3]);
7041
+ return [a[0], b[1], b[0], a[1]];
7042
+ }
7043
+
7025
7044
  function disabled() {
7026
7045
  return !!gui.options.disableNavigation;
7027
7046
  }
@@ -7104,17 +7123,16 @@
7104
7123
  if (!_on) return;
7105
7124
  var b = e.page_bbox;
7106
7125
  box.show(b[0], b[1], b[2], b[3]);
7107
- updateSelection(e.map_bbox, true);
7126
+ updateSelection(e.map_data_bbox, true);
7108
7127
  });
7109
7128
 
7110
7129
  gui.on('box_drag_end', function(e) {
7111
7130
  if (!_on) return;
7112
7131
  box.hide();
7113
- updateSelection(e.map_bbox);
7132
+ updateSelection(e.map_data_bbox);
7114
7133
  });
7115
7134
 
7116
- function updateSelection(bboxPixels, transient) {
7117
- var bbox = bboxToCoords(bboxPixels);
7135
+ function updateSelection(bbox, transient) {
7118
7136
  var active = gui.model.getActiveLayer();
7119
7137
  var ids = internal.findShapesIntersectingBBox(bbox, active.layer, active.dataset.arcs);
7120
7138
  if (transient) {
@@ -7128,12 +7146,6 @@
7128
7146
  _on = true;
7129
7147
  }
7130
7148
 
7131
- function bboxToCoords(bbox) {
7132
- var a = ext.translatePixelCoords(bbox[0], bbox[1]);
7133
- var b = ext.translatePixelCoords(bbox[2], bbox[3]);
7134
- return [a[0], b[1], b[0], a[1]];
7135
- }
7136
-
7137
7149
  function turnOff() {
7138
7150
  reset();
7139
7151
  _on = false;
@@ -9337,7 +9349,7 @@
9337
9349
  var popup = gui.container.findChild('.box-tool-options');
9338
9350
  var coords = popup.findChild('.box-coords');
9339
9351
  var _on = false;
9340
- var bboxDisplayCoords, bboxPixels, bboxDataCoords;
9352
+ var bboxData;
9341
9353
 
9342
9354
  var infoBtn = new SimpleButton(popup.findChild('.info-btn')).on('click', function() {
9343
9355
  if (coords.visible()) hideCoords(); else showCoords();
@@ -9364,11 +9376,11 @@
9364
9376
  gui.enterMode('selection_tool');
9365
9377
  gui.interaction.setMode('selection');
9366
9378
  // kludge to pass bbox to the selection tool
9367
- gui.dispatchEvent('box_drag_end', {map_bbox: bboxPixels});
9379
+ gui.dispatchEvent('box_drag_end', bboxData);
9368
9380
  });
9369
9381
 
9370
9382
  new SimpleButton(popup.findChild('.clip-btn')).on('click', function() {
9371
- runCommand('-clip bbox2=' + bboxDataCoords.join(','));
9383
+ runCommand('-clip bbox=' + bboxData.map_data_bbox.join(','));
9372
9384
  });
9373
9385
 
9374
9386
  gui.addMode('box_tool', turnOn, turnOff);
@@ -9384,8 +9396,8 @@
9384
9396
  // Update the visible rectangle when the map view changes
9385
9397
  // (e.g. during zooming or panning)
9386
9398
  ext.on('change', function() {
9387
- if (!_on || !box.visible() || !bboxDisplayCoords) return;
9388
- var b = coordsToPix(bboxDisplayCoords);
9399
+ if (!_on || !box.visible() || !bboxData) return;
9400
+ var b = coordsToPix(bboxData.map_display_bbox);
9389
9401
  var pos = ext.position();
9390
9402
  var dx = pos.pageX,
9391
9403
  dy = pos.pageY;
@@ -9399,20 +9411,17 @@
9399
9411
 
9400
9412
  gui.on('box_drag', function(e) {
9401
9413
  var b = e.page_bbox;
9402
- bboxPixels = e.map_bbox;
9403
- bboxDisplayCoords = pixToCoords(bboxPixels);
9414
+ bboxData = e.data;
9404
9415
  if (_on || inZoomMode()) {
9405
9416
  box.show(b[0], b[1], b[2], b[3]);
9406
9417
  }
9407
9418
  });
9408
9419
 
9409
9420
  gui.on('box_drag_end', function(e) {
9410
- bboxPixels = e.map_bbox;
9411
- bboxDisplayCoords = pixToCoords(bboxPixels);
9412
- bboxDataCoords = getBBoxCoords(gui.map.getActiveLayer(), bboxDisplayCoords);
9421
+ bboxData = e.data;
9413
9422
  if (inZoomMode()) {
9414
9423
  box.hide();
9415
- nav.zoomToBbox(bboxPixels);
9424
+ nav.zoomToBbox(e.map_bbox);
9416
9425
  } else if (_on) {
9417
9426
  popup.show();
9418
9427
  }
@@ -9433,7 +9442,7 @@
9433
9442
 
9434
9443
  function showCoords() {
9435
9444
  El(infoBtn.node()).addClass('selected-btn');
9436
- coords.text(bboxDataCoords.join(','));
9445
+ coords.text(bboxData.map_data_bbox.join(','));
9437
9446
  coords.show();
9438
9447
  GUI.selectElement(coords.node());
9439
9448
  }
@@ -9462,15 +9471,6 @@
9462
9471
  hideCoords();
9463
9472
  }
9464
9473
 
9465
- function pixToCoords(bbox) {
9466
- var a = ext.translatePixelCoords(bbox[0], bbox[1]);
9467
- var b = ext.translatePixelCoords(bbox[2], bbox[3]);
9468
- var bbox2 = [a[0], b[1], b[0], a[1]];
9469
- // round coords, for nicer 'info' display
9470
- // (rounded precision should be sub-pixel)
9471
- return internal.getRoundedCoords(bbox2, internal.getBoundsPrecisionForDisplay(bbox2));
9472
- }
9473
-
9474
9474
  function coordsToPix(bbox) {
9475
9475
  var a = ext.translateCoords(bbox[0], bbox[1]);
9476
9476
  var b = ext.translateCoords(bbox[2], bbox[3]);
package/www/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.5.105";
3
+ var VERSION = "0.5.108";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -7813,10 +7813,24 @@
7813
7813
  // a, b: two ring data objects (from getPathMetadata);
7814
7814
  function testRingInRing(a, b, arcs) {
7815
7815
  if (b.bounds.contains(a.bounds) === false) return false;
7816
- var p = arcs.getVertex(a.ids[0], 0); // test with first point in the ring
7816
+ // Don't test with first point -- this may return false if a hole intersects
7817
+ // the containing ring at the first vertex.
7818
+ // Instead, use the midpoint of the first segment
7819
+ var p = getFirstMidpoint(a.ids[0], arcs);
7820
+ //// test with first point in the ring
7821
+ // var p = arcs.getVertex(a.ids[0], 0);
7817
7822
  return geom.testPointInRing(p.x, p.y, b.ids, arcs) == 1;
7818
7823
  }
7819
7824
 
7825
+ function getFirstMidpoint(arcId, arcs) {
7826
+ var p1 = arcs.getVertex(arcId, 0);
7827
+ var p2 = arcs.getVertex(arcId, 1);
7828
+ return {
7829
+ x: (p1.x + p2.x) / 2,
7830
+ y: (p1.y + p2.y) / 2
7831
+ };
7832
+ }
7833
+
7820
7834
  // Bundle holes with their containing rings for Topo/GeoJSON polygon export.
7821
7835
  // Assumes outer rings are CW and inner (hole) rings are CCW, unless
7822
7836
  // the reverseWinding flag is set.
@@ -9525,9 +9539,6 @@
9525
9539
  return rec && (rec[name] === val);
9526
9540
  });
9527
9541
  };
9528
- obj.file_exists = function(name) {
9529
- return cli.isFile(name);
9530
- };
9531
9542
  return obj;
9532
9543
  }
9533
9544
 
@@ -18954,6 +18965,10 @@ ${svg}
18954
18965
  .option('json-path', {
18955
18966
  old_alias: 'json-subtree',
18956
18967
  describe: '[JSON] path to JSON input data; separator is /'
18968
+ })
18969
+ .option('single-part', {
18970
+ type: 'flag',
18971
+ // describe: '[GeoJSON] split multi-part features into single-part features'
18957
18972
  });
18958
18973
 
18959
18974
  parser.command('o')
@@ -22119,6 +22134,8 @@ ${svg}
22119
22134
  // TODO: improve so geometry_type option skips features instead of creating null geometries
22120
22135
  if (geom && geom.type == 'GeometryCollection') {
22121
22136
  GeoJSON.importComplexFeature(importer, geom, rec, opts);
22137
+ } else if (opts.single_part && isMultiPartGeometry(geom)) {
22138
+ GeoJSON.importMultiAsSingles(importer, geom, rec, opts);
22122
22139
  } else {
22123
22140
  GeoJSON.importSimpleFeature(importer, geom, rec, opts);
22124
22141
  }
@@ -22160,11 +22177,27 @@ ${svg}
22160
22177
  return Object.values(index);
22161
22178
  }
22162
22179
 
22180
+ function isMultiPartGeometry(geom) {
22181
+ return geom && geom.type && geom.type.indexOf('Multi') === 0;
22182
+ }
22183
+
22163
22184
  GeoJSON.importSimpleFeature = function(importer, geom, rec, opts) {
22164
22185
  importer.startShape(rec);
22165
22186
  GeoJSON.importSimpleGeometry(importer, geom, opts);
22166
22187
  };
22167
22188
 
22189
+ // Split a multi-part feature into several single features
22190
+ GeoJSON.importMultiAsSingles = function(importer, geom, rec, opts) {
22191
+ geom.coordinates.forEach(function(coords, i) {
22192
+ var geom2 = {
22193
+ type: geom.type.substr(5),
22194
+ coordinates: coords
22195
+ };
22196
+ var rec2 = i === 0 ? rec : copyRecord(rec);
22197
+ GeoJSON.importSimpleFeature(importer, geom2, rec2, opts);
22198
+ });
22199
+ };
22200
+
22168
22201
  GeoJSON.importSimpleGeometry = function(importer, geom, opts) {
22169
22202
  var type = geom ? geom.type : null;
22170
22203
  if (type === null) {
@@ -34614,14 +34647,22 @@ ${svg}
34614
34647
  return o;
34615
34648
  }
34616
34649
 
34617
- function compileLayerExpression(expr, lyr, dataset, opts) {
34618
- var proxy = getLayerProxy(lyr, dataset.arcs, opts);
34650
+ function compileIfCommandExpression(expr, catalog, targ, opts) {
34651
+ var ctx = getLayerProxy(targ.layer, targ.dataset.arcs, opts);
34619
34652
  var exprOpts = Object.assign({returns: true}, opts);
34620
34653
  var func = compileExpressionToFunction(expr, exprOpts);
34621
- var ctx = proxy;
34654
+
34655
+ ctx.layer_exists = function(name) {
34656
+ return !!catalog.findSingleLayer(name);
34657
+ };
34658
+
34659
+ ctx.file_exists = function(file) {
34660
+ return cli.isFile(file);
34661
+ };
34662
+
34622
34663
  return function() {
34623
34664
  try {
34624
- return func.call(proxy, {}, ctx);
34665
+ return func.call(ctx, {}, ctx);
34625
34666
  } catch(e) {
34626
34667
  // if (opts.quiet) throw e;
34627
34668
  stop(e.name, "in expression [" + expr + "]:", e.message);
@@ -34674,7 +34715,7 @@ ${svg}
34674
34715
  function testLayer(catalog, opts) {
34675
34716
  var targ = getTargetLayer(catalog, opts);
34676
34717
  if (opts.expression) {
34677
- return compileLayerExpression(opts.expression, targ.layer, targ.dataset, opts)();
34718
+ return compileIfCommandExpression(opts.expression, catalog, targ, opts)();
34678
34719
  }
34679
34720
  if (opts.empty) {
34680
34721
  return layerIsEmpty(targ.layer);
package/www/page.css CHANGED
@@ -210,7 +210,7 @@ body {
210
210
  max-height: 160px;
211
211
  overflow: hidden;
212
212
  overflow-y: auto;
213
- margin-right: -13px;
213
+ margin-right: -3px;
214
214
  }
215
215
 
216
216
  .export-layer-list > div {
@@ -420,8 +420,8 @@ body.dragover #import-options-drop-area .drop-area {
420
420
  background: rgba(255, 255, 255, 0.4);
421
421
  border: 1px solid #999;
422
422
  padding: 0 3px;
423
- height: 17px;
424
- font-size: 12px;
423
+ height: 18px;
424
+ font-size: 13px;
425
425
  }
426
426
 
427
427
  #mshp-not-supported {
@@ -436,10 +436,10 @@ body.dragover #import-options-drop-area .drop-area {
436
436
  overflow-y: auto;
437
437
  }
438
438
 
439
- .dropped-file-list p {
439
+ #import-options .dropped-file-list p {
440
440
  line-height: 1;
441
441
  margin-bottom: 5px;
442
- font-size: 15px;
442
+ font-size: 14px;
443
443
  font-weight: bold;
444
444
  }
445
445
 
@@ -484,7 +484,7 @@ body.dragover #import-options-drop-area .drop-area {
484
484
  }
485
485
 
486
486
  .option-menu {
487
- padding: 0 0 7px 0;
487
+ padding: 0 0 9px 0;
488
488
  }
489
489
 
490
490
  .option-menu input {