mapshaper 0.6.77 → 0.6.79

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/mapshaper.js CHANGED
@@ -4278,6 +4278,10 @@
4278
4278
  return data;
4279
4279
  }
4280
4280
 
4281
+ function layerHasAttributeData(lyr) {
4282
+ return lyr.data && lyr.data.getFields().length > 0;
4283
+ }
4284
+
4281
4285
  function layerHasNonNullData(lyr) {
4282
4286
  return lyr.data && getFirstNonEmptyRecord(lyr.data.getRecords()) ? true : false;
4283
4287
  }
@@ -4551,6 +4555,7 @@
4551
4555
  __proto__: null,
4552
4556
  insertFieldValues: insertFieldValues,
4553
4557
  getLayerDataTable: getLayerDataTable,
4558
+ layerHasAttributeData: layerHasAttributeData,
4554
4559
  layerHasNonNullData: layerHasNonNullData,
4555
4560
  layerHasGeometry: layerHasGeometry,
4556
4561
  layerIsGeometric: layerIsGeometric,
@@ -18025,11 +18030,10 @@
18025
18030
  opts = utils.extend({}, opts);
18026
18031
  opts.rfc7946 = !opts.gj2008; // use RFC 7946 as the default
18027
18032
  var extension = opts.extension || "json";
18028
- var layerGroups, warn;
18033
+ var layerGroups;
18029
18034
 
18030
18035
  if (opts.rfc7946) {
18031
- warn = getRFC7946Warnings(dataset);
18032
- if (warn) message(warn);
18036
+ warnIfNotWgs84(dataset);
18033
18037
  }
18034
18038
 
18035
18039
  if (opts.file) {
@@ -18120,14 +18124,12 @@
18120
18124
  return feat;
18121
18125
  }
18122
18126
 
18123
- function getRFC7946Warnings(dataset) {
18127
+ function warnIfNotWgs84(dataset) {
18124
18128
  var P = getDatasetCRS(dataset);
18125
- var str;
18126
- if (!P || !isLatLngCRS(P)) {
18127
- str = 'RFC 7946 warning: non-WGS84 GeoJSON output.';
18128
- if (P) str += ' Tip: use "-proj wgs84" to convert.';
18129
- }
18130
- return str;
18129
+ if (P && isLatLngCRS(P) || datasetIsEmpty(dataset)) return;
18130
+ var str = 'RFC 7946 warning: non-WGS84 GeoJSON output.';
18131
+ if (P) str += ' Tip: use "-proj wgs84" to convert.';
18132
+ message(str);
18131
18133
  }
18132
18134
 
18133
18135
  function getDatasetBbox(dataset, rfc7946) {
@@ -18397,7 +18399,7 @@
18397
18399
  'default': GeoJSON,
18398
18400
  exportGeoJSON: exportGeoJSON,
18399
18401
  exportLayerAsGeoJSON: exportLayerAsGeoJSON,
18400
- getRFC7946Warnings: getRFC7946Warnings,
18402
+ warnIfNotWgs84: warnIfNotWgs84,
18401
18403
  getDatasetBbox: getDatasetBbox,
18402
18404
  exportDatasetAsGeoJSON: exportDatasetAsGeoJSON,
18403
18405
  preserveOriginalCRS: preserveOriginalCRS,
@@ -45530,7 +45532,7 @@ ${svg}
45530
45532
  });
45531
45533
  }
45532
45534
 
45533
- var version = "0.6.77";
45535
+ var version = "0.6.79";
45534
45536
 
45535
45537
  // Parse command line args into commands and run them
45536
45538
  // 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.6.77",
3
+ "version": "0.6.79",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
@@ -1738,12 +1738,12 @@
1738
1738
  var btn = el.findChild('.btn').on('click', function() {
1739
1739
  var nameStr = name.node().value.trim();
1740
1740
  var type = el.findChild('input:checked').node().value;
1741
- addLayer(gui, nameStr, type);
1741
+ addEmptyLayer(gui, nameStr, type);
1742
1742
  popup.close();
1743
1743
  });
1744
1744
  }
1745
1745
 
1746
- function addLayer(gui, name, type) {
1746
+ function addEmptyLayer(gui, name, type) {
1747
1747
  var targ = gui.model.getActiveLayer();
1748
1748
  var crsInfo = targ && internal.getDatasetCrsInfo(targ.dataset);
1749
1749
  var dataset = {
@@ -2716,7 +2716,8 @@
2716
2716
  if (opts.interactionMode == 'vertices') {
2717
2717
  return getVertexStyle(baseLyr, o);
2718
2718
  }
2719
- if (opts.interactionMode == 'drawing') {
2719
+ if (opts.interactionMode == 'edit_lines' ||
2720
+ opts.interactionMode == 'edit_polygons') {
2720
2721
  return getLineEditingStyle(o);
2721
2722
  }
2722
2723
  var geomType = baseLyr.geometry_type;
@@ -3450,6 +3451,25 @@
3450
3451
  }
3451
3452
  }
3452
3453
 
3454
+ function deletePoint(lyr, fid) {
3455
+ var records = lyr.data?.getRecords();
3456
+ lyr.shapes.splice(fid, 1);
3457
+ if (records) records.splice(fid, 1);
3458
+ if (isProjectedLayer(lyr)) {
3459
+ lyr.gui.displayLayer.shapes.splice(fid, 1);
3460
+ }
3461
+ }
3462
+
3463
+ function insertPoint(lyr, fid, shp, d) {
3464
+ var records = lyr.data?.getRecords();
3465
+ if (records) records.splice(fid, 0, d);
3466
+ lyr.shapes.splice(fid, 0, shp);
3467
+ if (isProjectedLayer(lyr)) {
3468
+ var shp2 = projectPointCoords(shp, lyr.gui.projectPoint);
3469
+ lyr.gui.displayLayer.shapes.splice(fid, 0, shp2);
3470
+ }
3471
+ }
3472
+
3453
3473
  function deleteLastPoint(lyr) {
3454
3474
  if (lyr.data) {
3455
3475
  lyr.data.getRecords().pop();
@@ -3501,10 +3521,6 @@
3501
3521
  return internal.getUnfilteredArcLength(arcId, target.gui.source.dataset.arcs);
3502
3522
  }
3503
3523
 
3504
- function getPointCoords(lyr, fid) {
3505
- return internal.cloneShape(lyr.shapes[fid]);
3506
- }
3507
-
3508
3524
  function getVertexCoords(lyr, id) {
3509
3525
  return lyr.gui.source.dataset.arcs.getVertex2(id);
3510
3526
  }
@@ -3518,14 +3534,6 @@
3518
3534
  }
3519
3535
  }
3520
3536
 
3521
- // coords: [x, y] point in data CRS (not display CRS)
3522
- function setPointCoords(lyr, fid, coords) {
3523
- lyr.shapes[fid] = coords;
3524
- if (isProjectedLayer(lyr)) {
3525
- lyr.shapes[fid] = projectPointCoords(coords, lyr.gui.projectPoint);
3526
- }
3527
- }
3528
-
3529
3537
  function updateVertexCoords(lyr, ids) {
3530
3538
  if (!isProjectedLayer(lyr)) return;
3531
3539
  var p = lyr.gui.displayArcs.getVertex2(ids[0]);
@@ -3545,10 +3553,31 @@
3545
3553
  // Update source data coordinates by projecting display coordinates
3546
3554
  function updatePointCoords(lyr, fid) {
3547
3555
  if (!isProjectedLayer(lyr)) return;
3548
- var displayShp = lyr.shapes[fid];
3556
+ var displayShp = lyr.gui.displayLayer.shapes[fid];
3549
3557
  lyr.shapes[fid] = projectPointCoords(displayShp, lyr.gui.invertPoint);
3550
3558
  }
3551
3559
 
3560
+ // coords: [[x, y]] point in data CRS (not display CRS)
3561
+ function setPointCoords(lyr, fid, coords) {
3562
+ lyr.shapes[fid] = coords;
3563
+ if (isProjectedLayer(lyr)) {
3564
+ lyr.gui.displayLayer.shapes[fid] = projectPointCoords(coords, lyr.gui.projectPoint);
3565
+ }
3566
+ }
3567
+
3568
+ // return an [[x, y]] point in data CRS
3569
+ function getPointCoords(lyr, fid) {
3570
+ var coords = lyr.geometry_type == 'point' && lyr.shapes[fid];
3571
+ if (!coords || coords.length != 1) {
3572
+ return null;
3573
+ }
3574
+ return internal.cloneShape(coords);
3575
+ }
3576
+
3577
+ // export function getPointCoords(lyr, fid) {
3578
+ // return internal.cloneShape(lyr.shapes[fid]);
3579
+ // }
3580
+
3552
3581
  function projectPointCoords(src, proj) {
3553
3582
  var dest = [], p;
3554
3583
  for (var i=0; i<src.length; i++) {
@@ -5512,6 +5541,16 @@
5512
5541
  addHistoryState(undo, redo);
5513
5542
  });
5514
5543
 
5544
+ gui.on('point_delete', function(e) {
5545
+ var redo = function() {
5546
+ deletePoint(e.data.target, e.fid);
5547
+ };
5548
+ var undo = function() {
5549
+ insertPoint(e.data.target, e.fid, e.coords, e.d);
5550
+ };
5551
+ addHistoryState(undo, redo);
5552
+ });
5553
+
5515
5554
  gui.on('path_add', function(e) {
5516
5555
  var redo = function() {
5517
5556
  gui.dispatchEvent('redo_path_add', {p1: e.p1, p2: e.p2});
@@ -5581,6 +5620,7 @@
5581
5620
  gui.on('active', updateVisibility);
5582
5621
  gui.on('inactive', updateVisibility);
5583
5622
  gui.model.on('update', updateVisibility);
5623
+ gui.on('mode', updateVisibility);
5584
5624
 
5585
5625
  // @iconRef: selector for an (svg) button icon
5586
5626
  this.addButton = function(iconRef) {
@@ -5609,7 +5649,10 @@
5609
5649
  };
5610
5650
 
5611
5651
  function updateVisibility() {
5612
- if (GUI.isActiveInstance(gui) && !_hidden && !!gui.model.getActiveLayer()) {
5652
+ var noData = !gui.model.getActiveLayer();
5653
+ if (GUI.isActiveInstance(gui) && !_hidden && !noData) {
5654
+ buttons.show();
5655
+ } else if (noData && gui.getMode() != 'import') {
5613
5656
  buttons.show();
5614
5657
  } else {
5615
5658
  buttons.hide();
@@ -5706,19 +5749,24 @@
5706
5749
  document.addEventListener('keyup', function(e) {
5707
5750
  if (!GUI.isActiveInstance(gui)) return;
5708
5751
  // this can fail to fire if keyup occurs over a context menu
5709
- // if (e.keyCode == 16) shiftDown = false;
5710
5752
  shiftDown = e.shiftKey;
5711
- if (e.keyCode == 17) ctrlDown = false;
5753
+ ctrlDown = e.ctrlKey;
5712
5754
  self.dispatchEvent('keyup', getEventData(e));
5713
5755
  });
5714
5756
 
5715
5757
  document.addEventListener('keydown', function(e) {
5716
5758
  if (!GUI.isActiveInstance(gui)) return;
5717
5759
  shiftDown = e.shiftKey;
5718
- if (e.keyCode == 17) ctrlDown = true;
5760
+ ctrlDown = e.ctrlKey;
5719
5761
  self.dispatchEvent('keydown', getEventData(e));
5720
5762
  });
5721
5763
 
5764
+ document.addEventListener('mousemove', function(e) {
5765
+ // refreshing these here to prevent problems when context menu opens
5766
+ shiftDown = e.shiftKey;
5767
+ ctrlDown = e.ctrlKey;
5768
+ });
5769
+
5722
5770
  this.shiftIsPressed = function() { return shiftDown; };
5723
5771
  this.ctrlIsPressed = function() { return ctrlDown; };
5724
5772
 
@@ -5758,12 +5806,13 @@
5758
5806
 
5759
5807
  var menus = {
5760
5808
  standard: ['info', 'selection', 'box'],
5761
- polygons: ['info', 'selection', 'box', 'drawing'],
5809
+ empty: ['edit_points', 'edit_lines', 'edit_polygons'],
5810
+ polygons: ['info', 'selection', 'box', 'edit_polygons'],
5762
5811
  rectangles: ['info', 'selection', 'box', 'rectangles'],
5763
- lines: ['info', 'selection', 'box' , 'drawing'],
5812
+ lines: ['info', 'selection', 'box' , 'edit_lines'],
5764
5813
  table: ['info', 'selection'],
5765
- labels: ['info', 'selection', 'box', 'labels', 'location', 'add-points'],
5766
- points: ['info', 'selection', 'box', 'location', 'add-points']
5814
+ labels: ['info', 'selection', 'box', 'labels', 'edit_points'],
5815
+ points: ['info', 'selection', 'box', 'edit_points'] // , 'add-points'
5767
5816
  };
5768
5817
 
5769
5818
  var prompts = {
@@ -5778,11 +5827,12 @@
5778
5827
  box: 'shift-drag box tool',
5779
5828
  data: 'edit attributes',
5780
5829
  labels: 'position labels',
5781
- location: 'drag points',
5830
+ edit_points: 'draw/edit points',
5831
+ edit_lines: 'draw/edit lines',
5832
+ edit_polygons: 'draw/edit polygons',
5782
5833
  vertices: 'edit vertices',
5783
5834
  selection: 'selection tool',
5784
5835
  'add-points': 'add points',
5785
- drawing: 'draw/reshape tool',
5786
5836
  rectangles: 'drag-to-resize',
5787
5837
  off: 'turn off'
5788
5838
  };
@@ -5791,6 +5841,7 @@
5791
5841
 
5792
5842
  // state variables
5793
5843
  var _editMode = 'off';
5844
+ var _prevMode;
5794
5845
  var _menuOpen = false;
5795
5846
 
5796
5847
  // Only render edit mode button/menu if this option is present
@@ -5838,11 +5889,11 @@
5838
5889
  };
5839
5890
 
5840
5891
  this.modeUsesHitDetection = function(mode) {
5841
- return ['info', 'selection', 'data', 'labels', 'location', 'vertices', 'rectangles', 'drawing'].includes(mode);
5892
+ return ['info', 'selection', 'data', 'labels', 'edit_points', 'vertices', 'rectangles', 'edit_lines', 'edit_polygons'].includes(mode);
5842
5893
  };
5843
5894
 
5844
5895
  this.modeUsesPopup = function(mode) {
5845
- return ['info', 'selection', 'data', 'box', 'labels', 'location', 'rectangles'].includes(mode);
5896
+ return ['info', 'selection', 'data', 'box', 'labels', 'edit_points', 'rectangles'].includes(mode);
5846
5897
  };
5847
5898
 
5848
5899
  this.getMode = getInteractionMode;
@@ -5869,7 +5920,7 @@
5869
5920
  function getAvailableModes() {
5870
5921
  var o = gui.model.getActiveLayer();
5871
5922
  if (!o || !o.layer) {
5872
- return menus.standard; // TODO: more sensible handling of missing layer
5923
+ return menus.empty; // TODO: more sensible handling of missing layer
5873
5924
  }
5874
5925
  if (!o.layer.geometry_type) {
5875
5926
  return menus.table;
@@ -5952,6 +6003,7 @@
5952
6003
  var changed = mode != _editMode;
5953
6004
  if (changed) {
5954
6005
  menu.classed('active', mode != 'off');
6006
+ _prevMode = _editMode;
5955
6007
  _editMode = mode;
5956
6008
  onModeChange();
5957
6009
  updateArrowButton();
@@ -5962,7 +6014,7 @@
5962
6014
  function onModeChange() {
5963
6015
  var mode = getInteractionMode();
5964
6016
  gui.state.interaction_mode = mode;
5965
- gui.dispatchEvent('interaction_mode_change', {mode: mode});
6017
+ gui.dispatchEvent('interaction_mode_change', {mode: mode, prev_mode: _prevMode});
5966
6018
  }
5967
6019
 
5968
6020
  // Update button highlight and selected menu item highlight (if any)
@@ -7580,11 +7632,11 @@
7580
7632
  test = getGraduatedCircleTest(getRadiusFunction(layer.gui.style));
7581
7633
  } else if (geoType == 'point') {
7582
7634
  test = pointTest;
7583
- } else if (interactionMode == 'drawing' && geoType == 'polygon') {
7635
+ } else if (interactionMode == 'edit_polygons') {
7584
7636
  test = polygonVertexTest;
7585
7637
  } else if (
7586
7638
  interactionMode == 'vertices' ||
7587
- interactionMode == 'drawing') {
7639
+ interactionMode == 'edit_lines') {
7588
7640
  test = vertexTest;
7589
7641
  } else if (geoType == 'polyline') {
7590
7642
  test = polylineTest;
@@ -7684,7 +7736,8 @@
7684
7736
 
7685
7737
  function pointTest(x, y) {
7686
7738
  var bullseyeDist = 2, // hit all points w/in 2 px
7687
- hitThreshold = 25,
7739
+ // use small threshold when adding points
7740
+ hitThreshold = interactionMode == 'edit_points' ? 12 : 25,
7688
7741
  toPx = ext.getTransform().mx,
7689
7742
  hits = [];
7690
7743
 
@@ -7899,7 +7952,9 @@
7899
7952
 
7900
7953
  function getPointerHitTest(mapLayer, ext, interactionMode, featureFilter) {
7901
7954
  var shapeTest, targetLayer;
7902
- if (!mapLayer || !internal.layerHasGeometry(mapLayer.gui?.displayLayer)) {
7955
+ // need hit test on empty layers, in case we are drawing shapes
7956
+ // if (!mapLayer || !internal.layerHasGeometry(mapLayer.gui?.displayLayer)) {
7957
+ if (!mapLayer || !mapLayer.gui?.displayLayer.geometry_type) {
7903
7958
  return function() {return {ids: []};};
7904
7959
  }
7905
7960
  shapeTest = getShapeHitTest(mapLayer, ext, interactionMode, featureFilter);
@@ -8021,20 +8076,29 @@
8021
8076
  }
8022
8077
 
8023
8078
  function draggable() {
8024
- return interactionMode == 'vertices' || interactionMode == 'location' ||
8025
- interactionMode == 'labels' || interactionMode == 'drawing';
8079
+ return interactionMode == 'vertices' || interactionMode == 'edit_points' ||
8080
+ interactionMode == 'labels' || interactionMode == 'edit_lines' ||
8081
+ interactionMode == 'edit_polygons';
8026
8082
  }
8027
8083
 
8028
8084
  function clickable() {
8029
8085
  // click used to pin popup and select features
8030
8086
  return interactionMode == 'data' || interactionMode == 'info' ||
8031
- interactionMode == 'selection' || interactionMode == 'rectangles';
8087
+ interactionMode == 'selection' || interactionMode == 'rectangles' ||
8088
+ interactionMode == 'edit_points';
8032
8089
  }
8033
8090
 
8034
8091
  self.getHitId = function() {
8035
8092
  return hitTest ? storedData.id : -1;
8036
8093
  };
8037
8094
 
8095
+ self.setHitId = function(id) {
8096
+ if (storedData.id == id) return;
8097
+ storedData.id = id;
8098
+ storedData.ids = id == -1 ? [] : [id];
8099
+ triggerHitEvent('change');
8100
+ };
8101
+
8038
8102
  // Get a reference to the active layer, so listeners to hit events can interact
8039
8103
  // with data and shapes
8040
8104
  self.getHitTarget = function() {
@@ -8169,9 +8233,9 @@
8169
8233
 
8170
8234
 
8171
8235
  mouse.on('click', function(e) {
8236
+ var pinned = storedData.pinned;
8172
8237
  if (!hitTest || !active) return;
8173
8238
  if (!eventIsEnabled('click')) return;
8174
-
8175
8239
  e.stopPropagation();
8176
8240
 
8177
8241
  // TODO: move pinning to inspection control?
@@ -8179,6 +8243,11 @@
8179
8243
  updateSelectionState(convertClickDataToSelectionData(hitTest(e)));
8180
8244
  }
8181
8245
 
8246
+ if (pinned && interactionMode == 'edit_points') {
8247
+ // kludge: intercept the click event if popup is turning off, so
8248
+ // a new point doesn't get made
8249
+ return;
8250
+ }
8182
8251
  triggerHitEvent('click', e);
8183
8252
  }, null, priority);
8184
8253
 
@@ -8306,11 +8375,15 @@
8306
8375
  if (type == 'click' && gui.contextMenu.isOpen()) {
8307
8376
  return false;
8308
8377
  }
8309
- if (type == 'click' && interactionMode == 'drawing') {
8378
+ if (type == 'click' &&
8379
+ (interactionMode == 'edit_lines' || interactionMode == 'edit_polygons')) {
8310
8380
  return true; // click events are triggered even if no shape is hit
8311
8381
  }
8312
-
8313
- if (interactionMode == 'drawing' && (type == 'hover' || type == 'dblclick')) {
8382
+ if (type == 'click' && interactionMode == 'edit_points') {
8383
+ return true;
8384
+ }
8385
+ if ((interactionMode == 'edit_lines' || interactionMode == 'edit_polygons') &&
8386
+ (type == 'hover' || type == 'dblclick')) {
8314
8387
  return true; // special case -- using hover for line drawing animation
8315
8388
  }
8316
8389
 
@@ -8330,7 +8403,7 @@
8330
8403
  }
8331
8404
 
8332
8405
  function possiblyStopPropagation(e) {
8333
- if (interactionMode == 'drawing') {
8406
+ if (interactionMode == 'edit_lines' || interactionMode == 'edit_polygons') {
8334
8407
  // handled conditionally in the control
8335
8408
  return;
8336
8409
  }
@@ -9527,7 +9600,7 @@
9527
9600
  });
9528
9601
 
9529
9602
  self.show = function(id, ids, lyr, pinned, edit) {
9530
- var singleEdit = edit || pinned && gui.interaction.getMode() == 'data';
9603
+ var singleEdit = edit || pinned && !internal.layerHasAttributeData(lyr);
9531
9604
  var multiEdit = pinned && gui.interaction.getMode() == 'selection';
9532
9605
  var maxHeight = parent.node().clientHeight - 36;
9533
9606
 
@@ -9759,16 +9832,16 @@
9759
9832
  gui.session.dataValueUpdated(e.ids, e.field, e.value);
9760
9833
  // Refresh the display if a style variable has been changed interactively
9761
9834
  if (internal.isSupportedSvgStyleProperty(e.field)) {
9762
- // drawLayers();
9763
9835
  gui.dispatchEvent('map-needs-refresh');
9764
9836
  }
9765
9837
  });
9766
9838
 
9767
9839
  hit.on('contextmenu', function(e) {
9768
9840
  var target = hit.getHitTarget();
9769
- if (!e.overMap || !target) return;
9770
-
9771
- if (e.mode == 'drawing') return; // TODO: make special menu for this mode
9841
+ if (!e.overMap || !target || e.mode == 'edit_lines' ||
9842
+ e.mode == 'edit_polygons' || e.mode == 'edit_points') {
9843
+ return;
9844
+ }
9772
9845
  gui.contextMenu.open(e, hit.getHitTarget());
9773
9846
  });
9774
9847
 
@@ -10060,15 +10133,86 @@
10060
10133
  }
10061
10134
  }
10062
10135
 
10063
- function initPointDragging(gui, ext, hit) {
10064
- var symbolInfo;
10136
+ function initPointEditing(gui, ext, hit) {
10137
+ var instructionsShown = false;
10138
+ var symbolInfo, alert;
10065
10139
  function active(e) {
10066
- return e.id > -1 && gui.interaction.getMode() == 'location';
10140
+ return gui.interaction.getMode() == 'edit_points';
10067
10141
  }
10068
10142
 
10069
- hit.on('dragstart', function(e) {
10143
+ function overPoint(e) {
10144
+ return active(e) && e.id > -1;
10145
+ }
10146
+
10147
+ function hideInstructions() {
10148
+ if (!alert) return;
10149
+ alert.close('fade');
10150
+ alert = null;
10151
+ }
10152
+
10153
+ function showInstructions() {
10154
+ var isMac = navigator.userAgent.includes('Mac');
10155
+ var symbol = isMac ? '⌘' : '^';
10156
+ var msg = `Instructions: Click on the map to add points. Move points by dragging. Type ${symbol}Z/${symbol}Y to undo/redo.`;
10157
+ alert = showPopupAlert(msg, null, { non_blocking: true, max_width: '360px'});
10158
+ }
10159
+
10160
+ gui.on('interaction_mode_change', function(e) {
10161
+ if (e.mode == 'edit_points' && !gui.model.getActiveLayer()) {
10162
+ addEmptyLayer(gui, undefined, 'point');
10163
+ } else if (e.prev_mode == 'edit_points') {
10164
+ hideInstructions();
10165
+ gui.container.findChild('.map-layers').classed('add-points', false);
10166
+ }
10167
+ if (e.mode == 'edit_points' && !instructionsShown) {
10168
+ instructionsShown = true;
10169
+ showInstructions();
10170
+ }
10171
+ });
10172
+
10173
+ hit.on('contextmenu', function(e) {
10070
10174
  if (!active(e)) return;
10071
10175
  var target = hit.getHitTarget();
10176
+ var id = e.id;
10177
+ if (id > -1) {
10178
+ e.deletePoint = function() {
10179
+ removePoint(target, id);
10180
+ };
10181
+ }
10182
+ gui.contextMenu.open(e, target);
10183
+ });
10184
+
10185
+ function removePoint(target, id) {
10186
+ var d = target.data ? target.data.getRecords()[id] : null;
10187
+ var coords = target.shapes[id];
10188
+ deletePoint(target, id);
10189
+ gui.dispatchEvent('point_delete', {coords, d, target, fid: id});
10190
+ gui.dispatchEvent('map-needs-refresh');
10191
+ hit.setHitId(-1);
10192
+ }
10193
+
10194
+ hit.on('click', function(e) {
10195
+ if (overPoint(e) || !active(e)) return;
10196
+ hideInstructions();
10197
+
10198
+ // add point
10199
+ var p = pixToDataCoords(e.x, e.y);
10200
+ var target = hit.getHitTarget();
10201
+ appendNewPoint(target, p);
10202
+ gui.dispatchEvent('point_add', {p, target});
10203
+ gui.dispatchEvent('map-needs-refresh');
10204
+ hit.setHitId(target.shapes.length - 1); // highlight new point
10205
+ });
10206
+
10207
+ hit.on('change', function(e) {
10208
+ if (!active(e)) return;
10209
+ gui.container.findChild('.map-layers').classed('add-points', !overPoint(e));
10210
+ });
10211
+
10212
+ hit.on('dragstart', function(e) {
10213
+ if (!overPoint(e)) return;
10214
+ hideInstructions();
10215
+ var target = hit.getHitTarget();
10072
10216
  symbolInfo = {
10073
10217
  FID: e.id,
10074
10218
  startCoords: getPointCoords(target, e.id),
@@ -10077,37 +10221,36 @@
10077
10221
  });
10078
10222
 
10079
10223
  hit.on('drag', function(e) {
10080
- if (!active(e)) return;
10224
+ if (!overPoint(e)) return;
10081
10225
  // TODO: support multi points... get id of closest part to the pointer
10082
- var p = getPointCoordsById(e.id, symbolInfo.target);
10083
- if (!p) return;
10226
+ // var p = getPointCoordsById(e.id, symbolInfo.target);
10227
+ var id = symbolInfo.FID;
10228
+ var shp = symbolInfo.target.gui.displayLayer.shapes[id];
10229
+ if (!shp) return;
10084
10230
  var diff = translateDeltaDisplayCoords(e.dx, e.dy, ext);
10085
- p[0] += diff[0];
10086
- p[1] += diff[1];
10231
+ shp[0][0] += diff[0];
10232
+ shp[0][1] += diff[1];
10087
10233
  gui.dispatchEvent('map-needs-refresh');
10088
10234
  });
10089
10235
 
10090
10236
  hit.on('dragend', function(e) {
10091
- if (!active(e) || !symbolInfo ) return;
10092
- updatePointCoords(symbolInfo.target, e.id);
10237
+ if (!overPoint(e) || !symbolInfo ) return;
10238
+ updatePointCoords(symbolInfo.target, symbolInfo.FID);
10093
10239
  symbolInfo.endCoords = getPointCoords(symbolInfo.target, e.id);
10094
10240
  gui.dispatchEvent('symbol_dragend', symbolInfo);
10095
10241
  symbolInfo = null;
10096
10242
  });
10097
10243
 
10244
+ function pixToDataCoords(x, y) {
10245
+ var target = hit.getHitTarget();
10246
+ return translateDisplayPoint(target, ext.translatePixelCoords(x, y));
10247
+ }
10248
+
10098
10249
  function translateDeltaDisplayCoords(dx, dy, ext) {
10099
10250
  var a = ext.translatePixelCoords(0, 0);
10100
10251
  var b = ext.translatePixelCoords(dx, dy);
10101
10252
  return [b[0] - a[0], b[1] - a[1]];
10102
10253
  }
10103
-
10104
- function getPointCoordsById(id, layer) {
10105
- var coords = layer && layer.geometry_type == 'point' && layer.shapes[id];
10106
- if (!coords || coords.length != 1) {
10107
- return null;
10108
- }
10109
- return coords[0];
10110
- }
10111
10254
  }
10112
10255
 
10113
10256
  // pixel distance threshold for hovering near a vertex or segment midpoint
@@ -10148,13 +10291,17 @@
10148
10291
  gui.addMode('drawing_tool', turnOn, turnOff);
10149
10292
 
10150
10293
  gui.on('interaction_mode_change', function(e) {
10151
- gui.container.findChild('.map-layers').classed('drawing', e.mode == 'drawing');
10152
- var prevMode = gui.getMode();
10153
- if (e.mode == 'drawing') {
10294
+ if (e.mode == 'edit_lines' || e.mode == 'edit_polygons') {
10295
+ if (!gui.model.getActiveLayer()) {
10296
+ addEmptyLayer(gui, undefined, e.mode == 'edit_lines' ? 'polyline' : 'polygon');
10297
+ }
10154
10298
  gui.enterMode('drawing_tool');
10299
+ } else if (gui.getMode() == 'drawing_tool') {
10300
+ gui.clearMode();
10155
10301
  } else if (active()) {
10156
10302
  turnOff();
10157
10303
  }
10304
+ updateCursor();
10158
10305
  }, null, 10); // higher priority than hit control, so turnOff() has correct hit target
10159
10306
 
10160
10307
  gui.on('redo_path_add', function(e) {
@@ -10201,6 +10348,7 @@
10201
10348
  });
10202
10349
 
10203
10350
  function turnOn() {
10351
+ if (active()) return;
10204
10352
  var target = hit.getHitTarget();
10205
10353
  initialArcCount = target.gui.displayArcs.size();
10206
10354
  initialShapeCount = target.shapes.length;
@@ -10227,6 +10375,7 @@
10227
10375
 
10228
10376
  function turnOff() {
10229
10377
  var removed = 0;
10378
+ var mode = gui.interaction.getMode();
10230
10379
  finishCurrentPath();
10231
10380
  if (polygonMode()) {
10232
10381
  removed = removeOpenPolygons();
@@ -10235,10 +10384,11 @@
10235
10384
  hideInstructions();
10236
10385
  initialArcCount = -1;
10237
10386
  initialShapeCount = -1;
10238
- if (gui.interaction.getMode() == 'drawing') {
10387
+ if (mode == 'edit_lines' || mode == 'edit_polygons') {
10239
10388
  // mode change was not initiated by interactive menu -- turn off interactivity
10240
10389
  gui.interaction.turnOff();
10241
10390
  }
10391
+ updateCursor();
10242
10392
  if (removed > 0) {
10243
10393
  fullRedraw();
10244
10394
  }
@@ -10406,6 +10556,7 @@
10406
10556
  }
10407
10557
 
10408
10558
  function updateCursor() {
10559
+ gui.container.findChild('.map-layers').classed('drawing', active());
10409
10560
  var useArrow = hoverVertexInfo && !hoverVertexInfo.extendable && !drawing();
10410
10561
  gui.container.findChild('.map-layers').classed('dragging', useArrow);
10411
10562
  }
@@ -10664,38 +10815,9 @@
10664
10815
  }
10665
10816
  }
10666
10817
 
10667
- function initPointDrawing(gui, ext, hit) {
10668
- var mouse = gui.map.getMouse();
10669
-
10670
- gui.on('interaction_mode_change', function(e) {
10671
- gui.container.findChild('.map-layers').classed('add-points', e.mode === 'add-points');
10672
- });
10673
-
10674
- function active() {
10675
- return gui.interaction.getMode() == 'add-points';
10676
- }
10677
-
10678
- mouse.on('click', function(e) {
10679
- if (!active()) return;
10680
- var p = pixToDataCoords(e.x, e.y);
10681
- var target = hit.getHitTarget();
10682
- appendNewPoint(target, p);
10683
- gui.dispatchEvent('point_add', {p, target});
10684
- gui.dispatchEvent('map-needs-refresh');
10685
- });
10686
-
10687
-
10688
- function pixToDataCoords(x, y) {
10689
- var target = hit.getHitTarget();
10690
- return translateDisplayPoint(target, ext.translatePixelCoords(x, y));
10691
- }
10692
-
10693
- }
10694
-
10695
10818
  function initInteractiveEditing(gui, ext, hit) {
10696
10819
  initLabelDragging(gui, ext, hit);
10697
- initPointDragging(gui, ext, hit);
10698
- initPointDrawing(gui, ext, hit);
10820
+ initPointEditing(gui, ext, hit);
10699
10821
  initLineEditing(gui, ext, hit);
10700
10822
  }
10701
10823
 
@@ -12133,8 +12255,10 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12133
12255
  if (activeStyle) {
12134
12256
  turnOffBasemap();
12135
12257
  updateButtons();
12258
+ closeMenu();
12136
12259
  }
12137
12260
  });
12261
+
12138
12262
  fadeBtn.on('click', function() {
12139
12263
  if (faded) {
12140
12264
  mapEl.css('opacity', 1);
@@ -12161,11 +12285,19 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12161
12285
  showBasemap(style);
12162
12286
  }
12163
12287
  updateButtons();
12288
+ closeMenu();
12164
12289
  });
12165
12290
  btn.appendTo(list);
12166
12291
  });
12167
12292
  }
12168
12293
 
12294
+ // close and turn off mode
12295
+ function closeMenu() {
12296
+ setTimeout(function() {
12297
+ gui.clearMode();
12298
+ }, 200);
12299
+ }
12300
+
12169
12301
  function turnOffBasemap() {
12170
12302
  activeStyle = null;
12171
12303
  gui.map.setDisplayCRS(null);
@@ -12900,6 +13032,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12900
13032
  if (e.deleteVertex) {
12901
13033
  addMenuItem('Delete vertex', e.deleteVertex);
12902
13034
  }
13035
+ if (e.deletePoint) {
13036
+ addMenuItem('Delete point', e.deletePoint);
13037
+ }
12903
13038
  if (e.ids?.length) {
12904
13039
  addMenuItem('Copy as GeoJSON', copyGeoJSON);
12905
13040
  }
@@ -12923,7 +13058,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12923
13058
  var layer = mapshaper.cmd.filterFeatures(lyr, dataset.arcs, opts);
12924
13059
  // the drawing tool can send open paths with 'polygon' geometry type,
12925
13060
  // should be changed to 'polyline'
12926
- if (layerHasOpenPaths(layer, dataset.arcs)) {
13061
+ if (layer.geometry_type == 'polygon' && layerHasOpenPaths(layer, dataset.arcs)) {
12927
13062
  layer.geometry_type = 'polyline';
12928
13063
  }
12929
13064
  var features = internal.exportLayerAsGeoJSON(layer, dataset, {rfc7946: true, prettify: true}, true, 'string');
package/www/mapshaper.js CHANGED
@@ -4278,6 +4278,10 @@
4278
4278
  return data;
4279
4279
  }
4280
4280
 
4281
+ function layerHasAttributeData(lyr) {
4282
+ return lyr.data && lyr.data.getFields().length > 0;
4283
+ }
4284
+
4281
4285
  function layerHasNonNullData(lyr) {
4282
4286
  return lyr.data && getFirstNonEmptyRecord(lyr.data.getRecords()) ? true : false;
4283
4287
  }
@@ -4551,6 +4555,7 @@
4551
4555
  __proto__: null,
4552
4556
  insertFieldValues: insertFieldValues,
4553
4557
  getLayerDataTable: getLayerDataTable,
4558
+ layerHasAttributeData: layerHasAttributeData,
4554
4559
  layerHasNonNullData: layerHasNonNullData,
4555
4560
  layerHasGeometry: layerHasGeometry,
4556
4561
  layerIsGeometric: layerIsGeometric,
@@ -18025,11 +18030,10 @@
18025
18030
  opts = utils.extend({}, opts);
18026
18031
  opts.rfc7946 = !opts.gj2008; // use RFC 7946 as the default
18027
18032
  var extension = opts.extension || "json";
18028
- var layerGroups, warn;
18033
+ var layerGroups;
18029
18034
 
18030
18035
  if (opts.rfc7946) {
18031
- warn = getRFC7946Warnings(dataset);
18032
- if (warn) message(warn);
18036
+ warnIfNotWgs84(dataset);
18033
18037
  }
18034
18038
 
18035
18039
  if (opts.file) {
@@ -18120,14 +18124,12 @@
18120
18124
  return feat;
18121
18125
  }
18122
18126
 
18123
- function getRFC7946Warnings(dataset) {
18127
+ function warnIfNotWgs84(dataset) {
18124
18128
  var P = getDatasetCRS(dataset);
18125
- var str;
18126
- if (!P || !isLatLngCRS(P)) {
18127
- str = 'RFC 7946 warning: non-WGS84 GeoJSON output.';
18128
- if (P) str += ' Tip: use "-proj wgs84" to convert.';
18129
- }
18130
- return str;
18129
+ if (P && isLatLngCRS(P) || datasetIsEmpty(dataset)) return;
18130
+ var str = 'RFC 7946 warning: non-WGS84 GeoJSON output.';
18131
+ if (P) str += ' Tip: use "-proj wgs84" to convert.';
18132
+ message(str);
18131
18133
  }
18132
18134
 
18133
18135
  function getDatasetBbox(dataset, rfc7946) {
@@ -18397,7 +18399,7 @@
18397
18399
  'default': GeoJSON,
18398
18400
  exportGeoJSON: exportGeoJSON,
18399
18401
  exportLayerAsGeoJSON: exportLayerAsGeoJSON,
18400
- getRFC7946Warnings: getRFC7946Warnings,
18402
+ warnIfNotWgs84: warnIfNotWgs84,
18401
18403
  getDatasetBbox: getDatasetBbox,
18402
18404
  exportDatasetAsGeoJSON: exportDatasetAsGeoJSON,
18403
18405
  preserveOriginalCRS: preserveOriginalCRS,
@@ -45530,7 +45532,7 @@ ${svg}
45530
45532
  });
45531
45533
  }
45532
45534
 
45533
- var version = "0.6.77";
45535
+ var version = "0.6.79";
45534
45536
 
45535
45537
  // Parse command line args into commands and run them
45536
45538
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
package/www/page.css CHANGED
@@ -59,9 +59,7 @@ body.map-view {
59
59
 
60
60
 
61
61
  .selectable,.selectable * {
62
- user-select: default;
63
- -webkit-user-select: text;
64
- -moz-user-select: text;
62
+ user-select: text;
65
63
  cursor: text;
66
64
  }
67
65
 
@@ -345,17 +343,17 @@ div.alert-box {
345
343
  }
346
344
 
347
345
  .mini-drop-area {
348
- border: 1.5px dashed #bbb;
349
- max-width: 240px;
346
+ border: 1.5px dashed #999;
347
+ max-width: 245px;
350
348
  border-radius: 9px;
351
- font-size: 15px;
349
+ font-size: 14px;
352
350
  line-height: 1.5;
353
- color: #888;
354
- /* background-color: #FFFEF8;*/
355
- padding: 6px 4px 7px 7px;
356
- padding: 11px 14px;
351
+ /* color: #555;*/
352
+ padding: 9px 11px 6px 11px;
357
353
  margin: 9px -1px 3px -1px;
358
354
  min-height: 120px;
355
+ background: #f8fdff;
356
+ ;
359
357
  }
360
358
 
361
359
  .catalog-mode .file-catalog {
@@ -526,6 +524,10 @@ div.alert-box {
526
524
  /* border: 1px solid #aaa; */
527
525
  }
528
526
 
527
+ .info-box label {
528
+ cursor: pointer;
529
+ }
530
+
529
531
  .sidebar-buttons {
530
532
  display: none;
531
533
  position: absolute;