mapshaper 0.5.106 → 0.5.109

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.109
2
+ * Support setting linejoin and linecap for individual SVG features.
3
+
4
+ v0.5.108
5
+ * Bug fixes
6
+ * Added file_exists() to -if/-elif expressions
7
+
1
8
  v0.5.106
2
9
  * More basemap fixes.
3
10
 
package/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.5.106";
3
+ var VERSION = "0.5.109";
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
 
@@ -13923,7 +13934,7 @@
13923
13934
 
13924
13935
  var propertiesBySymbolType = {
13925
13936
  polygon: utils.arrayToIndex(commonProperties.concat('fill', 'fill-pattern')),
13926
- polyline: utils.arrayToIndex(commonProperties),
13937
+ polyline: utils.arrayToIndex(commonProperties.concat('stroke-linecap', 'stroke-linejoin')),
13927
13938
  point: utils.arrayToIndex(commonProperties.concat('fill', 'r')),
13928
13939
  label: utils.arrayToIndex(commonProperties.concat(
13929
13940
  'fill,r,font-family,font-size,text-anchor,font-weight,font-style,letter-spacing,dominant-baseline'.split(',')))
@@ -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.106",
3
+ "version": "0.5.109",
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];
@@ -7009,14 +7021,10 @@
7009
7021
  var mapBox = [e.x, e.y, dragStartEvt.x, dragStartEvt.y];
7010
7022
  var displayBox = pixToCoords(mapBox);
7011
7023
  var dataBox = getBBoxCoords(gui.map.getActiveLayer(), displayBox);
7012
- if (pageBox[0] > pageBox[2]) {
7013
- swapElements(pageBox, 0, 2);
7014
- swapElements(mapBox, 0, 2);
7015
- }
7016
- if (pageBox[1] > pageBox[3]) {
7017
- swapElements(pageBox, 1, 3);
7018
- swapElements(mapBox, 1, 3);
7019
- }
7024
+ fixBounds(pageBox);
7025
+ fixBounds(mapBox);
7026
+ fixBounds(displayBox);
7027
+ fixBounds(dataBox);
7020
7028
  return {
7021
7029
  map_bbox: mapBox,
7022
7030
  page_bbox: pageBox,
@@ -9372,7 +9380,7 @@
9372
9380
  });
9373
9381
 
9374
9382
  new SimpleButton(popup.findChild('.clip-btn')).on('click', function() {
9375
- runCommand('-clip bbox2=' + bboxData.map_data_bbox.join(','));
9383
+ runCommand('-clip bbox=' + bboxData.map_data_bbox.join(','));
9376
9384
  });
9377
9385
 
9378
9386
  gui.addMode('box_tool', turnOn, turnOff);
package/www/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.5.106";
3
+ var VERSION = "0.5.109";
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
 
@@ -13923,7 +13934,7 @@
13923
13934
 
13924
13935
  var propertiesBySymbolType = {
13925
13936
  polygon: utils.arrayToIndex(commonProperties.concat('fill', 'fill-pattern')),
13926
- polyline: utils.arrayToIndex(commonProperties),
13937
+ polyline: utils.arrayToIndex(commonProperties.concat('stroke-linecap', 'stroke-linejoin')),
13927
13938
  point: utils.arrayToIndex(commonProperties.concat('fill', 'r')),
13928
13939
  label: utils.arrayToIndex(commonProperties.concat(
13929
13940
  'fill,r,font-family,font-size,text-anchor,font-weight,font-style,letter-spacing,dominant-baseline'.split(',')))
@@ -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 {