mapshaper 0.5.104 → 0.5.107

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,13 @@
1
+ v0.5.107
2
+ * Bug fixes
3
+ * Added file_exists() to -if/-elif expressions
4
+
5
+ v0.5.106
6
+ * More basemap fixes.
7
+
8
+ v0.5.105
9
+ * More basemap improvements.
10
+
1
11
  v0.5.104
2
12
  * Improvements to the basemap feature.
3
13
 
package/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.5.104";
3
+ var VERSION = "0.5.103";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -1737,6 +1737,16 @@
1737
1737
 
1738
1738
  // Iterate over each [x,y] point in a layer
1739
1739
  // shapes: one layer's "shapes" array
1740
+ function forEachPoint_(shapes, cb) {
1741
+ var i, n, j, m, shp;
1742
+ for (i=0, n=shapes.length; i<n; i++) {
1743
+ shp = shapes[i];
1744
+ for (j=0, m=shp ? shp.length : 0; j<m; j++) {
1745
+ cb(shp[j], i);
1746
+ }
1747
+ }
1748
+ }
1749
+
1740
1750
  function forEachPoint(shapes, cb) {
1741
1751
  var i, n, j, m, shp;
1742
1752
  for (i=0, n=shapes.length; i<n; i++) {
@@ -1753,6 +1763,7 @@
1753
1763
  getPointBounds: getPointBounds$1,
1754
1764
  getPointFeatureBounds: getPointFeatureBounds,
1755
1765
  getPointsInLayer: getPointsInLayer,
1766
+ forEachPoint_: forEachPoint_,
1756
1767
  forEachPoint: forEachPoint
1757
1768
  });
1758
1769
 
@@ -7813,10 +7824,24 @@
7813
7824
  // a, b: two ring data objects (from getPathMetadata);
7814
7825
  function testRingInRing(a, b, arcs) {
7815
7826
  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
7827
+ // Don't test with first point -- this may return false if a hole intersects
7828
+ // the containing ring at the first vertex.
7829
+ // Instead, use the midpoint of the first segment
7830
+ var p = getFirstMidpoint(a.ids[0], arcs);
7831
+ //// test with first point in the ring
7832
+ // var p = arcs.getVertex(a.ids[0], 0);
7817
7833
  return geom.testPointInRing(p.x, p.y, b.ids, arcs) == 1;
7818
7834
  }
7819
7835
 
7836
+ function getFirstMidpoint(arcId, arcs) {
7837
+ var p1 = arcs.getVertex(arcId, 0);
7838
+ var p2 = arcs.getVertex(arcId, 1);
7839
+ return {
7840
+ x: (p1.x + p2.x) / 2,
7841
+ y: (p1.y + p2.y) / 2
7842
+ };
7843
+ }
7844
+
7820
7845
  // Bundle holes with their containing rings for Topo/GeoJSON polygon export.
7821
7846
  // Assumes outer rings are CW and inner (hole) rings are CCW, unless
7822
7847
  // the reverseWinding flag is set.
@@ -9525,9 +9550,6 @@
9525
9550
  return rec && (rec[name] === val);
9526
9551
  });
9527
9552
  };
9528
- obj.file_exists = function(name) {
9529
- return cli.isFile(name);
9530
- };
9531
9553
  return obj;
9532
9554
  }
9533
9555
 
@@ -9935,6 +9957,11 @@
9935
9957
  ctx.$ = i >= 0 ? getFeatureById(i) : layerOnlyProxy;
9936
9958
  ctx._ = ctx; // provide access to functions when masked by variable names
9937
9959
  ctx.d = rec || null; // expose data properties a la d3 (also exposed as this.properties)
9960
+ if (opts.record_alias) {
9961
+ // this is used to expose source table record as "source" in -join calc=
9962
+ // expresions.
9963
+ ctx[opts.record_alias] = ctx.d;
9964
+ }
9938
9965
  try {
9939
9966
  val = func.call(ctx.$, rec, ctx);
9940
9967
  } catch(e) {
@@ -18954,6 +18981,10 @@ ${svg}
18954
18981
  .option('json-path', {
18955
18982
  old_alias: 'json-subtree',
18956
18983
  describe: '[JSON] path to JSON input data; separator is /'
18984
+ })
18985
+ .option('single-part', {
18986
+ type: 'flag',
18987
+ // describe: '[GeoJSON] split multi-part features into single-part features'
18957
18988
  });
18958
18989
 
18959
18990
  parser.command('o')
@@ -22119,6 +22150,8 @@ ${svg}
22119
22150
  // TODO: improve so geometry_type option skips features instead of creating null geometries
22120
22151
  if (geom && geom.type == 'GeometryCollection') {
22121
22152
  GeoJSON.importComplexFeature(importer, geom, rec, opts);
22153
+ } else if (opts.single_part && isMultiPartGeometry(geom)) {
22154
+ GeoJSON.importMultiAsSingles(importer, geom, rec, opts);
22122
22155
  } else {
22123
22156
  GeoJSON.importSimpleFeature(importer, geom, rec, opts);
22124
22157
  }
@@ -22160,11 +22193,27 @@ ${svg}
22160
22193
  return Object.values(index);
22161
22194
  }
22162
22195
 
22196
+ function isMultiPartGeometry(geom) {
22197
+ return geom && geom.type && geom.type.indexOf('Multi') === 0;
22198
+ }
22199
+
22163
22200
  GeoJSON.importSimpleFeature = function(importer, geom, rec, opts) {
22164
22201
  importer.startShape(rec);
22165
22202
  GeoJSON.importSimpleGeometry(importer, geom, opts);
22166
22203
  };
22167
22204
 
22205
+ // Split a multi-part feature into several single features
22206
+ GeoJSON.importMultiAsSingles = function(importer, geom, rec, opts) {
22207
+ geom.coordinates.forEach(function(coords, i) {
22208
+ var geom2 = {
22209
+ type: geom.type.substr(5),
22210
+ coordinates: coords
22211
+ };
22212
+ var rec2 = i === 0 ? rec : copyRecord(rec);
22213
+ GeoJSON.importSimpleFeature(importer, geom2, rec2, opts);
22214
+ });
22215
+ };
22216
+
22168
22217
  GeoJSON.importSimpleGeometry = function(importer, geom, opts) {
22169
22218
  var type = geom ? geom.type : null;
22170
22219
  if (type === null) {
@@ -34614,19 +34663,33 @@ ${svg}
34614
34663
  return o;
34615
34664
  }
34616
34665
 
34617
- function compileLayerExpression(expr, lyr, dataset, opts) {
34618
- var proxy = getLayerProxy(lyr, dataset.arcs, opts);
34666
+ function compileIfCommandExpression(expr, catalog, target, opts) {
34667
+ var ctx = {};
34668
+
34669
+ if (target && target.layer) {
34670
+ ctx = getLayerProxy(target.layer, target.dataset.arcs, opts);
34671
+ }
34672
+
34673
+ ctx.file_exists = function(name) {
34674
+ return cli.isFile(name);
34675
+ };
34676
+
34677
+ ctx.layer_exists = function(name) {
34678
+ var lyr = catalog.findSingleLayer(name);
34679
+ return !!lyr;
34680
+ };
34681
+
34619
34682
  var exprOpts = Object.assign({returns: true}, opts);
34620
34683
  var func = compileExpressionToFunction(expr, exprOpts);
34621
- var ctx = proxy;
34622
34684
  return function() {
34623
34685
  try {
34624
- return func.call(proxy, {}, ctx);
34686
+ return func.call(ctx, {}, ctx);
34625
34687
  } catch(e) {
34626
34688
  // if (opts.quiet) throw e;
34627
34689
  stop(e.name, "in expression [" + expr + "]:", e.message);
34628
34690
  }
34629
34691
  };
34692
+
34630
34693
  }
34631
34694
 
34632
34695
  function skipCommand(cmdName) {
@@ -34674,7 +34737,7 @@ ${svg}
34674
34737
  function testLayer(catalog, opts) {
34675
34738
  var targ = getTargetLayer(catalog, opts);
34676
34739
  if (opts.expression) {
34677
- return compileLayerExpression(opts.expression, targ.layer, targ.dataset, opts)();
34740
+ return compileIfCommandExpression(opts.expression, catalog, targ, opts)();
34678
34741
  }
34679
34742
  if (opts.empty) {
34680
34743
  return layerIsEmpty(targ.layer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.5.104",
3
+ "version": "0.5.107",
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;
@@ -8386,24 +8398,32 @@
8386
8398
  function recenter(cx, cy, scale, data) {
8387
8399
  scale = scale ? limitScale(scale) : _scale;
8388
8400
  if (cx == _cx && cy == _cy && scale == _scale) return;
8401
+ navigate(cx, cy, scale);
8402
+ onChange(data);
8403
+ }
8404
+
8405
+ function navigate(cx, cy, scale) {
8389
8406
  if (_strictBounds) {
8390
- scale = Math.max(1, scale);
8407
+ var full = fillOut(_contentBounds);
8408
+ var minScale = full.height() / _strictBounds.height();
8409
+ if (scale < minScale) {
8410
+ var dx = cx - _cx;
8411
+ cx = _cx + dx * (minScale - _scale) / (scale - _scale);
8412
+ scale = minScale;
8413
+ }
8414
+ var dist = full.height() / 2 / scale;
8415
+ var ymax = _strictBounds.ymax - dist;
8416
+ var ymin = _strictBounds.ymin + dist;
8417
+ if (cy > ymax ) {
8418
+ cy = ymax;
8419
+ }
8420
+ if (cy < ymin) {
8421
+ cy = ymin;
8422
+ }
8391
8423
  }
8392
8424
  _cx = cx;
8393
8425
  _cy = cy;
8394
8426
  _scale = scale;
8395
- limitExtent();
8396
- onChange(data);
8397
- }
8398
-
8399
- function limitExtent() {
8400
- if (!_strictBounds) return;
8401
- if (_scale < 1) _scale = 1;
8402
- var dist = _strictBounds.height() / 2 / _scale;
8403
- var ymax = _strictBounds.ymax - dist;
8404
- var ymin = _strictBounds.ymin + dist;
8405
- if (_cy > ymax) _cy = ymax;
8406
- if (_cy < ymin) _cy = ymin;
8407
8427
  }
8408
8428
 
8409
8429
  function onChange(data) {
@@ -8473,7 +8493,6 @@
8473
8493
  function fitIn(b, b2) {
8474
8494
  // only fitting vertical extent
8475
8495
  // (currently only used in basemap view to enforce Mapbox's vertical limits)
8476
-
8477
8496
  if (b.height() > b2.height()) {
8478
8497
  b.scale(b2.height() / b.height());
8479
8498
  }
@@ -9330,7 +9349,7 @@
9330
9349
  var popup = gui.container.findChild('.box-tool-options');
9331
9350
  var coords = popup.findChild('.box-coords');
9332
9351
  var _on = false;
9333
- var bboxDisplayCoords, bboxPixels, bboxDataCoords;
9352
+ var bboxData;
9334
9353
 
9335
9354
  var infoBtn = new SimpleButton(popup.findChild('.info-btn')).on('click', function() {
9336
9355
  if (coords.visible()) hideCoords(); else showCoords();
@@ -9357,11 +9376,11 @@
9357
9376
  gui.enterMode('selection_tool');
9358
9377
  gui.interaction.setMode('selection');
9359
9378
  // kludge to pass bbox to the selection tool
9360
- gui.dispatchEvent('box_drag_end', {map_bbox: bboxPixels});
9379
+ gui.dispatchEvent('box_drag_end', bboxData);
9361
9380
  });
9362
9381
 
9363
9382
  new SimpleButton(popup.findChild('.clip-btn')).on('click', function() {
9364
- runCommand('-clip bbox2=' + bboxDataCoords.join(','));
9383
+ runCommand('-clip bbox=' + bboxData.map_data_bbox.join(','));
9365
9384
  });
9366
9385
 
9367
9386
  gui.addMode('box_tool', turnOn, turnOff);
@@ -9377,8 +9396,8 @@
9377
9396
  // Update the visible rectangle when the map view changes
9378
9397
  // (e.g. during zooming or panning)
9379
9398
  ext.on('change', function() {
9380
- if (!_on || !box.visible() || !bboxDisplayCoords) return;
9381
- var b = coordsToPix(bboxDisplayCoords);
9399
+ if (!_on || !box.visible() || !bboxData) return;
9400
+ var b = coordsToPix(bboxData.map_display_bbox);
9382
9401
  var pos = ext.position();
9383
9402
  var dx = pos.pageX,
9384
9403
  dy = pos.pageY;
@@ -9392,20 +9411,17 @@
9392
9411
 
9393
9412
  gui.on('box_drag', function(e) {
9394
9413
  var b = e.page_bbox;
9395
- bboxPixels = e.map_bbox;
9396
- bboxDisplayCoords = pixToCoords(bboxPixels);
9414
+ bboxData = e.data;
9397
9415
  if (_on || inZoomMode()) {
9398
9416
  box.show(b[0], b[1], b[2], b[3]);
9399
9417
  }
9400
9418
  });
9401
9419
 
9402
9420
  gui.on('box_drag_end', function(e) {
9403
- bboxPixels = e.map_bbox;
9404
- bboxDisplayCoords = pixToCoords(bboxPixels);
9405
- bboxDataCoords = getBBoxCoords(gui.map.getActiveLayer(), bboxDisplayCoords);
9421
+ bboxData = e.data;
9406
9422
  if (inZoomMode()) {
9407
9423
  box.hide();
9408
- nav.zoomToBbox(bboxPixels);
9424
+ nav.zoomToBbox(e.map_bbox);
9409
9425
  } else if (_on) {
9410
9426
  popup.show();
9411
9427
  }
@@ -9426,7 +9442,7 @@
9426
9442
 
9427
9443
  function showCoords() {
9428
9444
  El(infoBtn.node()).addClass('selected-btn');
9429
- coords.text(bboxDataCoords.join(','));
9445
+ coords.text(bboxData.map_data_bbox.join(','));
9430
9446
  coords.show();
9431
9447
  GUI.selectElement(coords.node());
9432
9448
  }
@@ -9455,15 +9471,6 @@
9455
9471
  hideCoords();
9456
9472
  }
9457
9473
 
9458
- function pixToCoords(bbox) {
9459
- var a = ext.translatePixelCoords(bbox[0], bbox[1]);
9460
- var b = ext.translatePixelCoords(bbox[2], bbox[3]);
9461
- var bbox2 = [a[0], b[1], b[0], a[1]];
9462
- // round coords, for nicer 'info' display
9463
- // (rounded precision should be sub-pixel)
9464
- return internal.getRoundedCoords(bbox2, internal.getBoundsPrecisionForDisplay(bbox2));
9465
- }
9466
-
9467
9474
  function coordsToPix(bbox) {
9468
9475
  var a = ext.translateCoords(bbox[0], bbox[1]);
9469
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.104";
3
+ var VERSION = "0.5.103";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -1737,6 +1737,16 @@
1737
1737
 
1738
1738
  // Iterate over each [x,y] point in a layer
1739
1739
  // shapes: one layer's "shapes" array
1740
+ function forEachPoint_(shapes, cb) {
1741
+ var i, n, j, m, shp;
1742
+ for (i=0, n=shapes.length; i<n; i++) {
1743
+ shp = shapes[i];
1744
+ for (j=0, m=shp ? shp.length : 0; j<m; j++) {
1745
+ cb(shp[j], i);
1746
+ }
1747
+ }
1748
+ }
1749
+
1740
1750
  function forEachPoint(shapes, cb) {
1741
1751
  var i, n, j, m, shp;
1742
1752
  for (i=0, n=shapes.length; i<n; i++) {
@@ -1753,6 +1763,7 @@
1753
1763
  getPointBounds: getPointBounds$1,
1754
1764
  getPointFeatureBounds: getPointFeatureBounds,
1755
1765
  getPointsInLayer: getPointsInLayer,
1766
+ forEachPoint_: forEachPoint_,
1756
1767
  forEachPoint: forEachPoint
1757
1768
  });
1758
1769
 
@@ -7813,10 +7824,24 @@
7813
7824
  // a, b: two ring data objects (from getPathMetadata);
7814
7825
  function testRingInRing(a, b, arcs) {
7815
7826
  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
7827
+ // Don't test with first point -- this may return false if a hole intersects
7828
+ // the containing ring at the first vertex.
7829
+ // Instead, use the midpoint of the first segment
7830
+ var p = getFirstMidpoint(a.ids[0], arcs);
7831
+ //// test with first point in the ring
7832
+ // var p = arcs.getVertex(a.ids[0], 0);
7817
7833
  return geom.testPointInRing(p.x, p.y, b.ids, arcs) == 1;
7818
7834
  }
7819
7835
 
7836
+ function getFirstMidpoint(arcId, arcs) {
7837
+ var p1 = arcs.getVertex(arcId, 0);
7838
+ var p2 = arcs.getVertex(arcId, 1);
7839
+ return {
7840
+ x: (p1.x + p2.x) / 2,
7841
+ y: (p1.y + p2.y) / 2
7842
+ };
7843
+ }
7844
+
7820
7845
  // Bundle holes with their containing rings for Topo/GeoJSON polygon export.
7821
7846
  // Assumes outer rings are CW and inner (hole) rings are CCW, unless
7822
7847
  // the reverseWinding flag is set.
@@ -9525,9 +9550,6 @@
9525
9550
  return rec && (rec[name] === val);
9526
9551
  });
9527
9552
  };
9528
- obj.file_exists = function(name) {
9529
- return cli.isFile(name);
9530
- };
9531
9553
  return obj;
9532
9554
  }
9533
9555
 
@@ -9935,6 +9957,11 @@
9935
9957
  ctx.$ = i >= 0 ? getFeatureById(i) : layerOnlyProxy;
9936
9958
  ctx._ = ctx; // provide access to functions when masked by variable names
9937
9959
  ctx.d = rec || null; // expose data properties a la d3 (also exposed as this.properties)
9960
+ if (opts.record_alias) {
9961
+ // this is used to expose source table record as "source" in -join calc=
9962
+ // expresions.
9963
+ ctx[opts.record_alias] = ctx.d;
9964
+ }
9938
9965
  try {
9939
9966
  val = func.call(ctx.$, rec, ctx);
9940
9967
  } catch(e) {
@@ -18954,6 +18981,10 @@ ${svg}
18954
18981
  .option('json-path', {
18955
18982
  old_alias: 'json-subtree',
18956
18983
  describe: '[JSON] path to JSON input data; separator is /'
18984
+ })
18985
+ .option('single-part', {
18986
+ type: 'flag',
18987
+ // describe: '[GeoJSON] split multi-part features into single-part features'
18957
18988
  });
18958
18989
 
18959
18990
  parser.command('o')
@@ -22119,6 +22150,8 @@ ${svg}
22119
22150
  // TODO: improve so geometry_type option skips features instead of creating null geometries
22120
22151
  if (geom && geom.type == 'GeometryCollection') {
22121
22152
  GeoJSON.importComplexFeature(importer, geom, rec, opts);
22153
+ } else if (opts.single_part && isMultiPartGeometry(geom)) {
22154
+ GeoJSON.importMultiAsSingles(importer, geom, rec, opts);
22122
22155
  } else {
22123
22156
  GeoJSON.importSimpleFeature(importer, geom, rec, opts);
22124
22157
  }
@@ -22160,11 +22193,27 @@ ${svg}
22160
22193
  return Object.values(index);
22161
22194
  }
22162
22195
 
22196
+ function isMultiPartGeometry(geom) {
22197
+ return geom && geom.type && geom.type.indexOf('Multi') === 0;
22198
+ }
22199
+
22163
22200
  GeoJSON.importSimpleFeature = function(importer, geom, rec, opts) {
22164
22201
  importer.startShape(rec);
22165
22202
  GeoJSON.importSimpleGeometry(importer, geom, opts);
22166
22203
  };
22167
22204
 
22205
+ // Split a multi-part feature into several single features
22206
+ GeoJSON.importMultiAsSingles = function(importer, geom, rec, opts) {
22207
+ geom.coordinates.forEach(function(coords, i) {
22208
+ var geom2 = {
22209
+ type: geom.type.substr(5),
22210
+ coordinates: coords
22211
+ };
22212
+ var rec2 = i === 0 ? rec : copyRecord(rec);
22213
+ GeoJSON.importSimpleFeature(importer, geom2, rec2, opts);
22214
+ });
22215
+ };
22216
+
22168
22217
  GeoJSON.importSimpleGeometry = function(importer, geom, opts) {
22169
22218
  var type = geom ? geom.type : null;
22170
22219
  if (type === null) {
@@ -34614,19 +34663,33 @@ ${svg}
34614
34663
  return o;
34615
34664
  }
34616
34665
 
34617
- function compileLayerExpression(expr, lyr, dataset, opts) {
34618
- var proxy = getLayerProxy(lyr, dataset.arcs, opts);
34666
+ function compileIfCommandExpression(expr, catalog, target, opts) {
34667
+ var ctx = {};
34668
+
34669
+ if (target && target.layer) {
34670
+ ctx = getLayerProxy(target.layer, target.dataset.arcs, opts);
34671
+ }
34672
+
34673
+ ctx.file_exists = function(name) {
34674
+ return cli.isFile(name);
34675
+ };
34676
+
34677
+ ctx.layer_exists = function(name) {
34678
+ var lyr = catalog.findSingleLayer(name);
34679
+ return !!lyr;
34680
+ };
34681
+
34619
34682
  var exprOpts = Object.assign({returns: true}, opts);
34620
34683
  var func = compileExpressionToFunction(expr, exprOpts);
34621
- var ctx = proxy;
34622
34684
  return function() {
34623
34685
  try {
34624
- return func.call(proxy, {}, ctx);
34686
+ return func.call(ctx, {}, ctx);
34625
34687
  } catch(e) {
34626
34688
  // if (opts.quiet) throw e;
34627
34689
  stop(e.name, "in expression [" + expr + "]:", e.message);
34628
34690
  }
34629
34691
  };
34692
+
34630
34693
  }
34631
34694
 
34632
34695
  function skipCommand(cmdName) {
@@ -34674,7 +34737,7 @@ ${svg}
34674
34737
  function testLayer(catalog, opts) {
34675
34738
  var targ = getTargetLayer(catalog, opts);
34676
34739
  if (opts.expression) {
34677
- return compileLayerExpression(opts.expression, targ.layer, targ.dataset, opts)();
34740
+ return compileIfCommandExpression(opts.expression, catalog, targ, opts)();
34678
34741
  }
34679
34742
  if (opts.empty) {
34680
34743
  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 {