mapshaper 0.6.79 → 0.6.81

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
@@ -1612,6 +1612,7 @@
1612
1612
  this.ymin -= halfHeight * ky;
1613
1613
  this.xmax += halfWidth * kx;
1614
1614
  this.ymax += halfHeight * ky;
1615
+ return this;
1615
1616
  };
1616
1617
 
1617
1618
  // Return a bounding box with the same extent as this one.
@@ -5003,7 +5004,7 @@
5003
5004
  if (isLatLngCRS(P)) {
5004
5005
  return xy.concat();
5005
5006
  }
5006
- proj = getProjTransform(P, parseCrsString('wgs84'));
5007
+ proj = getProjTransform2(P, parseCrsString('wgs84'));
5007
5008
  return proj(xy[0], xy[1]);
5008
5009
  }
5009
5010
 
@@ -26664,7 +26665,7 @@ ${svg}
26664
26665
  codepage = lookupCodePage(ldid),
26665
26666
  samples = getNonAsciiSamples(),
26666
26667
  only7bit = samples.length === 0,
26667
- encoding, msg;
26668
+ encoding;
26668
26669
 
26669
26670
  // First, check the ldid (language driver id) (an obsolete way to specify which
26670
26671
  // codepage to use for text encoding.)
@@ -26684,10 +26685,12 @@ ${svg}
26684
26685
  // As a last resort, try to guess the encoding:
26685
26686
  if (!encoding) {
26686
26687
  var info = detectEncoding(samples);
26688
+ var msg;
26687
26689
  encoding = info.encoding;
26688
26690
  if (info.confidence < 2) {
26689
- msg = 'Warning: Unable to auto-detect the DBF file text encoding with high confidence.';
26690
- msg += '\n\nDefaulting to: ' + encoding + (encoding in encodingNames ? ' (' + encodingNames[encoding] + ')' : '');
26691
+ msg = 'Warning: Unable to auto-detect the DBF file text encoding' +
26692
+ (info.confidence == 1 ? ' with high confidence' : '') + '.';
26693
+ msg += ' Defaulting to ' + encoding + (encoding in encodingNames ? ' (' + encodingNames[encoding] + ').' : '.');
26691
26694
  msg += '\n\nSample of how non-ascii text was imported:\n';
26692
26695
  if (runningInBrowser()) {
26693
26696
  msg += '<pre>' + formatStringsAsGrid(decodeSamples(encoding, samples), 50) + '</pre>';
@@ -45532,7 +45535,7 @@ ${svg}
45532
45535
  });
45533
45536
  }
45534
45537
 
45535
- var version = "0.6.79";
45538
+ var version = "0.6.81";
45536
45539
 
45537
45540
  // Parse command line args into commands and run them
45538
45541
  // 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.79",
3
+ "version": "0.6.81",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
package/www/index.html CHANGED
@@ -294,7 +294,6 @@ encoding=big5</span>. Click to see all options.</div></div></div></div>
294
294
  <div class="console-window"><div class="console-buffer selectable"></div></div>
295
295
  </div>
296
296
  <div class="mshp-main-map main-area map-area">
297
- <div class="coordinate-info colored-text selectable"></div>
298
297
  <div class="intersection-display">
299
298
  <span class="intersection-check text-btn colored-text">Check line intersections</span><span class="intersection-count">0 line intersections</span>
300
299
  <img class="close-btn" draggable="false" src="images/close.png">
@@ -2544,6 +2544,20 @@
2544
2544
  };
2545
2545
  }
2546
2546
 
2547
+ // p1: [x, y] coords
2548
+ // p2: [x, y] coords offset by 1x1 pixel
2549
+ function formatCoordsForDisplay(p1, p2) {
2550
+ var dx = Math.abs(p1[0] - p2[0]);
2551
+ var dy = Math.abs(p1[1] - p2[1]);
2552
+ var offs = (dx + dy) / 2;
2553
+ var decimals = 0;
2554
+ while (offs < 1 && decimals < 6) {
2555
+ offs *= 10;
2556
+ decimals++;
2557
+ }
2558
+ return [p1[0].toFixed(decimals), p1[1].toFixed(decimals)];
2559
+ }
2560
+
2547
2561
  // Convert a point from display CRS coordinates to data coordinates.
2548
2562
  // These are only different when using dynamic reprojection (basemap view).
2549
2563
  function translateDisplayPoint(lyr, p) {
@@ -2563,8 +2577,7 @@
2563
2577
  }
2564
2578
 
2565
2579
  function isProjectedLayer(lyr) {
2566
- // TODO: could do some validation on the layer's contents
2567
- return !!lyr.gui.invertPoint;
2580
+ return !!lyr?.gui.invertPoint;
2568
2581
  }
2569
2582
 
2570
2583
  var darkStroke = "#334",
@@ -2765,30 +2778,7 @@
2765
2778
 
2766
2779
 
2767
2780
  function getDefaultStyle(lyr, baseStyle) {
2768
- var style = Object.assign({}, baseStyle);
2769
- // reduce the dot size of large point layers
2770
- if (lyr.geometry_type == 'point' && style.dotSize > 0) {
2771
- style.dotSize *= getDotScale$1(lyr);
2772
- }
2773
- return style;
2774
- }
2775
-
2776
- function getDotScale$1(lyr) {
2777
- var topTier = 10000;
2778
- var n = countPoints(lyr.shapes, topTier); // short-circuit point counting above top threshold
2779
- var k = n < 200 && 4 || n < 2500 && 3 || n < topTier && 2 || 1;
2780
- return k;
2781
- }
2782
-
2783
- function countPoints(shapes, max) {
2784
- var count = 0;
2785
- var i, n, shp;
2786
- max = max || Infinity;
2787
- for (i=0, n=shapes.length; i<n && count<max; i++) {
2788
- shp = shapes[i];
2789
- count += shp ? shp.length : 0;
2790
- }
2791
- return count;
2781
+ return Object.assign({}, baseStyle);
2792
2782
  }
2793
2783
 
2794
2784
 
@@ -3232,6 +3222,58 @@
3232
3222
  return layers.reverse();
3233
3223
  }
3234
3224
 
3225
+
3226
+ function adjustPointSymbolSizes(layers, overlayLyr, ext) {
3227
+ var bbox = ext.getBounds().scale(1.5).toArray();
3228
+ var testInBounds = function(p) {
3229
+ return p[0] > bbox[0] && p[0] < bbox[2] && p[1] > bbox[1] && p[1] < bbox[3];
3230
+ };
3231
+ var topTier = 50000;
3232
+ var count = 0;
3233
+ layers = layers.filter(function(lyr) {
3234
+ return lyr.geometry_type == 'point' && lyr.gui.style.dotSize > 0;
3235
+ });
3236
+ layers.forEach(function(lyr) {
3237
+ // short-circuit point counting above top threshold
3238
+ count += countPoints(lyr.gui.displayLayer.shapes, topTier, testInBounds);
3239
+ });
3240
+ count = Math.min(topTier, count) || 1;
3241
+ var k = Math.pow(6 - utils$1.clamp(Math.log10(count), 1, 5), 1.3);
3242
+
3243
+ // zoom adjustments
3244
+ var mapScale = ext.scale();
3245
+ if (mapScale < 0.5) {
3246
+ k *= Math.pow(mapScale + 0.5, 0.35);
3247
+ } else if (mapScale > 1) {
3248
+ // scale faster at first
3249
+ k *= Math.pow(Math.min(mapScale, 4), 0.15);
3250
+ k *= Math.pow(mapScale, 0.05);
3251
+ }
3252
+
3253
+ // scale down when map is small
3254
+ var smallSide = Math.min(ext.width(), ext.height());
3255
+ k *= utils$1.clamp(smallSide / 500, 0.5, 1);
3256
+
3257
+ layers.forEach(function(lyr) {
3258
+ lyr.gui.style.dotScale = k;
3259
+ });
3260
+ if (overlayLyr && overlayLyr.geometry_type == 'point' && overlayLyr.gui.style.dotSize > 0) {
3261
+ overlayLyr.gui.style.dotScale = k;
3262
+ }
3263
+ }
3264
+
3265
+ function countPoints(shapes, max, filter) {
3266
+ var count = 0;
3267
+ var i, j, n, m, shp;
3268
+ for (i=0, n=shapes.length; i<n && count<max; i++) {
3269
+ shp = shapes[i];
3270
+ for (j=0, m=(shp ? shp.length : 0); j<m; j++) {
3271
+ count += filter(shp[j]) ? 1 : 0;
3272
+ }
3273
+ }
3274
+ return count;
3275
+ }
3276
+
3235
3277
  // lyr: a map layer with gui property
3236
3278
  // displayCRS: CRS to use for display, or null (which clears any current display CRS)
3237
3279
  function projectLayerForDisplay(lyr, displayCRS) {
@@ -3286,6 +3328,13 @@
3286
3328
  }
3287
3329
  }
3288
3330
 
3331
+ // make sure that every path layer has an associated arc collection
3332
+ // (if the layer is empty, its dataset may not have an arc collection).
3333
+ // this enables adding shapes using the drawing tools.
3334
+ if (!dataset.arcs && (layer.geometry_type == 'polygon' || layer.geometry_type == 'polyline')) {
3335
+ dataset.arcs = new internal.ArcCollection();
3336
+ }
3337
+
3289
3338
  // Assume that dataset.displayArcs is in the display CRS
3290
3339
  // (it must be deleted upstream if reprojection is needed)
3291
3340
  // if (!obj.empty && dataset.arcs && !displayArcs) {
@@ -3370,6 +3419,21 @@
3370
3419
  }
3371
3420
  }
3372
3421
 
3422
+ // Test if adding point p to a sequence of points (in pixel coords)
3423
+ // would result in a polyline that deviates from a straight line by
3424
+ // more than a given number of pixels
3425
+ //
3426
+ function pointExceedsTolerance(p, points, tolerance) {
3427
+ if (points.length < 2) return false;
3428
+ var p1 = points[0], p2, dist;
3429
+ for (var i=1; i<points.length; i++) {
3430
+ p2 = points[i];
3431
+ dist = Math.sqrt(geom.pointSegDistSq(p2[0], p2[1], p1[0], p1[1], p[0], p[1]));
3432
+ if (dist > tolerance) return true;
3433
+ }
3434
+ return false;
3435
+ }
3436
+
3373
3437
  function setZ(lyr, z) {
3374
3438
  lyr.gui.source.dataset.arcs.setRetainedInterval(z);
3375
3439
  if (isProjectedLayer(lyr)) {
@@ -3439,6 +3503,15 @@
3439
3503
  }
3440
3504
  }
3441
3505
 
3506
+ function deleteFeature(lyr, fid) {
3507
+ var records = lyr.data?.getRecords();
3508
+ if (records) records.splice(fid, 1);
3509
+ lyr.shapes.splice(fid, 1);
3510
+ if (isProjectedLayer(lyr) && lyr.shapes != lyr.gui.displayLayer.shapes) {
3511
+ lyr.gui.displayLayer.shapes.splice(fid, 1);
3512
+ }
3513
+ }
3514
+
3442
3515
  // p: one point in source data coords
3443
3516
  function appendNewPoint(lyr, p) {
3444
3517
  lyr.shapes.push([p]);
@@ -3452,12 +3525,7 @@
3452
3525
  }
3453
3526
 
3454
3527
  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
- }
3528
+ deleteFeature(lyr, fid);
3461
3529
  }
3462
3530
 
3463
3531
  function insertPoint(lyr, fid, shp, d) {
@@ -5620,7 +5688,6 @@
5620
5688
  gui.on('active', updateVisibility);
5621
5689
  gui.on('inactive', updateVisibility);
5622
5690
  gui.model.on('update', updateVisibility);
5623
- gui.on('mode', updateVisibility);
5624
5691
 
5625
5692
  // @iconRef: selector for an (svg) button icon
5626
5693
  this.addButton = function(iconRef) {
@@ -5649,10 +5716,7 @@
5649
5716
  };
5650
5717
 
5651
5718
  function updateVisibility() {
5652
- var noData = !gui.model.getActiveLayer();
5653
- if (GUI.isActiveInstance(gui) && !_hidden && !noData) {
5654
- buttons.show();
5655
- } else if (noData && gui.getMode() != 'import') {
5719
+ if (GUI.isActiveInstance(gui) && !_hidden) {
5656
5720
  buttons.show();
5657
5721
  } else {
5658
5722
  buttons.hide();
@@ -5746,29 +5810,37 @@
5746
5810
  var self = this;
5747
5811
  var shiftDown = false;
5748
5812
  var ctrlDown = false;
5813
+ var metaDown = false;
5814
+ var altDown = false;
5815
+
5816
+ function updateControlKeys(e) {
5817
+ shiftDown = e.shiftKey;
5818
+ ctrlDown = e.ctrlKey;
5819
+ metaDown = e.metaKey;
5820
+ altDown = e.altKey;
5821
+ }
5749
5822
  document.addEventListener('keyup', function(e) {
5750
5823
  if (!GUI.isActiveInstance(gui)) return;
5751
5824
  // this can fail to fire if keyup occurs over a context menu
5752
- shiftDown = e.shiftKey;
5753
- ctrlDown = e.ctrlKey;
5825
+ updateControlKeys(e);
5754
5826
  self.dispatchEvent('keyup', getEventData(e));
5755
5827
  });
5756
5828
 
5757
5829
  document.addEventListener('keydown', function(e) {
5758
5830
  if (!GUI.isActiveInstance(gui)) return;
5759
- shiftDown = e.shiftKey;
5760
- ctrlDown = e.ctrlKey;
5831
+ updateControlKeys(e);
5761
5832
  self.dispatchEvent('keydown', getEventData(e));
5762
5833
  });
5763
5834
 
5764
5835
  document.addEventListener('mousemove', function(e) {
5765
5836
  // refreshing these here to prevent problems when context menu opens
5766
- shiftDown = e.shiftKey;
5767
- ctrlDown = e.ctrlKey;
5837
+ updateControlKeys(e);
5768
5838
  });
5769
5839
 
5770
5840
  this.shiftIsPressed = function() { return shiftDown; };
5771
5841
  this.ctrlIsPressed = function() { return ctrlDown; };
5842
+ this.altIsPressed = function() { return altDown; };
5843
+ this.metaIsPressed = function() { return metaDown; };
5772
5844
 
5773
5845
  this.onMenuSubmit = function(menuEl, cb) {
5774
5846
  gui.on('enter_key', function(e) {
@@ -7999,7 +8071,9 @@
7999
8071
  return;
8000
8072
  }
8001
8073
  e.originalEvent.preventDefault();
8002
- if (!targetLayer) return; // TODO: enable menu on empty map
8074
+ if (!El('body').hasClass('map-view')) {
8075
+ return;
8076
+ }
8003
8077
  triggerHitEvent('contextmenu', e);
8004
8078
  }, false);
8005
8079
 
@@ -8423,7 +8497,9 @@
8423
8497
  mode: interactionMode
8424
8498
  };
8425
8499
  if (evt) {
8500
+ // data coordinates
8426
8501
  eventData.coordinates = translateDisplayPoint(targetLayer, ext.translatePixelCoords(evt.x, evt.y));
8502
+ eventData.display_coordinates = gui.map.pixelCoordsToDisplayCoords(evt.x, evt.y);
8427
8503
  eventData.originalEvent = evt;
8428
8504
  eventData.overMap = isOverMap(evt);
8429
8505
  }
@@ -8468,69 +8544,6 @@
8468
8544
  return self;
8469
8545
  }
8470
8546
 
8471
- function CoordinatesDisplay(gui, ext, mouse) {
8472
- var readout = gui.container.findChild('.coordinate-info').hide();
8473
-
8474
- function enabled() {
8475
- var lyr = gui.map.getActiveLayer();
8476
- return !!(lyr && lyr.gui.displayLayer.geometry_type);
8477
- }
8478
-
8479
- gui.model.on('update', function(e) {
8480
- readout.hide();
8481
- });
8482
-
8483
- readout.on('copy', function(e) {
8484
- // remove selection on copy (using timeout or else copy is cancelled)
8485
- setTimeout(function() {
8486
- window.getSelection().removeAllRanges();
8487
- }, 50);
8488
- });
8489
-
8490
- // clear coords when map pans
8491
- ext.on('change', function() {
8492
- clearCoords();
8493
- // shapes may change along with map scale
8494
- // target = lyr ? lyr.getDisplayLayer() : null;
8495
- });
8496
-
8497
- mouse.on('leave', clearCoords);
8498
-
8499
- mouse.on('click', function(e) {
8500
- if (!enabled()) return;
8501
- GUI.selectElement(readout.node());
8502
- });
8503
-
8504
- mouse.on('hover', onMouseChange);
8505
- mouse.on('drag', onMouseChange, null, 10); // high priority so editor doesn't block propagation
8506
-
8507
- function onMouseChange(e) {
8508
- if (!enabled()) return;
8509
- if (isOverMap(e)) {
8510
- displayCoords(gui.map.translatePixelCoords(e.x, e.y));
8511
- } else {
8512
- clearCoords();
8513
- }
8514
- }
8515
-
8516
- function displayCoords(p) {
8517
- var p1 = gui.map.translatePixelCoords(0, ext.height());
8518
- var p2 = gui.map.translatePixelCoords(ext.width(), 0);
8519
- var bbox = p1.concat(p2);
8520
- var decimals = internal.getBoundsPrecisionForDisplay(bbox);
8521
- var str = internal.getRoundedCoordString(p, decimals);
8522
- readout.text(str).show();
8523
- }
8524
-
8525
- function clearCoords() {
8526
- readout.hide();
8527
- }
8528
-
8529
- function isOverMap(e) {
8530
- return e.x >= 0 && e.y >= 0 && e.x < ext.width() && e.y < ext.height();
8531
- }
8532
- }
8533
-
8534
8547
  function getTimerFunction() {
8535
8548
  return typeof requestAnimationFrame == 'function' ?
8536
8549
  requestAnimationFrame : function(cb) {setTimeout(cb, 25);};
@@ -9079,7 +9092,8 @@
9079
9092
  if (!boxCoords) return null;
9080
9093
  var dataBox = getBBoxCoords(gui.map.getActiveLayer(), boxCoords);
9081
9094
  fixBounds(dataBox);
9082
- return internal.getRoundedCoords(dataBox, internal.getBoundsPrecisionForDisplay(dataBox));
9095
+ // return internal.getRoundedCoords(dataBox, internal.getBoundsPrecisionForDisplay(dataBox));
9096
+ return dataBox;
9083
9097
  };
9084
9098
 
9085
9099
  box.turnOn = function() {
@@ -9304,8 +9318,7 @@
9304
9318
 
9305
9319
  function useBoxZoom() {
9306
9320
  var mode = gui.getMode();
9307
- var disabled = ['selection_tool', 'box_tool', 'rectangle_tool'].includes(mode);
9308
- return !disabled;
9321
+ return !'selection_tool,box_tool,rectangle_tool,drawing_tool'.includes(mode);
9309
9322
  }
9310
9323
 
9311
9324
  function getBoxData(e) {
@@ -9837,12 +9850,18 @@
9837
9850
  });
9838
9851
 
9839
9852
  hit.on('contextmenu', function(e) {
9840
- var target = hit.getHitTarget();
9841
- if (!e.overMap || !target || e.mode == 'edit_lines' ||
9842
- e.mode == 'edit_polygons' || e.mode == 'edit_points') {
9853
+ if (!e.overMap || e.mode == 'edit_lines' || e.mode == 'edit_polygons' ||
9854
+ e.mode == 'edit_points') {
9843
9855
  return;
9844
9856
  }
9845
- gui.contextMenu.open(e, hit.getHitTarget());
9857
+ var target = hit.getHitTarget();
9858
+ if (e.ids.length == 1) {
9859
+ e.deleteFeature = function() {
9860
+ deleteFeature(target, e.ids[0]);
9861
+ gui.model.updated({filter:true});
9862
+ };
9863
+ }
9864
+ gui.contextMenu.open(e, target);
9846
9865
  });
9847
9866
 
9848
9867
  hit.on('change', function(e) {
@@ -10265,20 +10284,29 @@
10265
10284
  var drawingId = -1; // feature id of path being drawn
10266
10285
  var sessionCount = 0;
10267
10286
  var alert;
10287
+ var pencilPoints = [];
10268
10288
  var _dragging = false;
10269
10289
 
10270
10290
  function active() {
10271
10291
  return initialArcCount >= 0;
10272
10292
  }
10273
10293
 
10274
- function dragging() {
10294
+ function vertexDragging() {
10275
10295
  return _dragging;
10276
10296
  }
10277
10297
 
10278
- function drawing() {
10298
+ function pathDrawing() {
10279
10299
  return drawingId > -1;
10280
10300
  }
10281
10301
 
10302
+ function cmdKeyDown() {
10303
+ return gui.keyboard.altIsPressed() || gui.keyboard.metaIsPressed();
10304
+ }
10305
+
10306
+ function pencilIsActive() {
10307
+ return cmdKeyDown() && active() && !vertexDragging() && !!pencilPoints;
10308
+ }
10309
+
10282
10310
  function polygonMode() {
10283
10311
  return active() && hit.getHitTarget().geometry_type == 'polygon';
10284
10312
  }
@@ -10320,7 +10348,7 @@
10320
10348
 
10321
10349
  gui.on('redo_path_extend', function(e) {
10322
10350
  var target = hit.getHitTarget();
10323
- if (drawing() && prevHoverEvent) {
10351
+ if (pathDrawing() && prevHoverEvent) {
10324
10352
  updatePathEndpoint(e.p);
10325
10353
  appendVertex$1(target, pixToDataCoords(prevHoverEvent.x, prevHoverEvent.y));
10326
10354
  } else {
@@ -10333,7 +10361,7 @@
10333
10361
 
10334
10362
  gui.on('undo_path_extend', function(e) {
10335
10363
  var target = hit.getHitTarget();
10336
- if (drawing() && prevHoverEvent) {
10364
+ if (pathDrawing() && prevHoverEvent) {
10337
10365
  deleteLastVertex(target);
10338
10366
  updatePathEndpoint(pixToDataCoords(prevHoverEvent.x, prevHoverEvent.y));
10339
10367
  } else {
@@ -10360,11 +10388,11 @@
10360
10388
 
10361
10389
  function showInstructions() {
10362
10390
  var isMac = navigator.userAgent.includes('Mac');
10363
- var symbol = isMac ? '⌘' : '^';
10364
- var pathStr = polygonMode() ? 'closed paths' : 'paths';
10365
- var msg = `Instructions: Click on the map to draw ${pathStr}. Drag vertices to reshape a path. Type ${symbol}Z/${symbol}Y to undo/redo.`;
10391
+ var undoKey = isMac ? '⌘' : '^';
10392
+ var drawKey = isMac ? '' : 'Alt';
10393
+ var msg = `Instructions: Click to start a path or add a point. ${drawKey}-drag draws a path. Drag points to reshape. Type ${undoKey}Z/${undoKey}Y to undo/redo.`;
10366
10394
  alert = showPopupAlert(msg, null, {
10367
- non_blocking: true, max_width: '360px'});
10395
+ non_blocking: true, max_width: '388px'});
10368
10396
  }
10369
10397
 
10370
10398
  function hideInstructions() {
@@ -10424,7 +10452,7 @@
10424
10452
  }
10425
10453
 
10426
10454
  hit.on('contextmenu', function(e) {
10427
- if (!active() || drawing() || dragging()) return;
10455
+ if (!active() || pathDrawing() || vertexDragging()) return;
10428
10456
  var target = hit.getHitTarget();
10429
10457
  var vInfo = hoverVertexInfo;
10430
10458
  if (hoverVertexInfo?.type == 'vertex' && !vertexIsEndpoint(vInfo, target)) {
@@ -10437,7 +10465,7 @@
10437
10465
  });
10438
10466
 
10439
10467
  hit.on('dragstart', function(e) {
10440
- if (!active() || drawing() || !hoverVertexInfo) return;
10468
+ if (!active() || pathDrawing() || !hoverVertexInfo) return;
10441
10469
  hideInstructions();
10442
10470
  e.originalEvent.stopPropagation();
10443
10471
  _dragging = true;
@@ -10449,9 +10477,48 @@
10449
10477
  hit.setHoverVertex(hoverVertexInfo.displayPoint, hoverVertexInfo.type);
10450
10478
  });
10451
10479
 
10480
+ gui.map.getMouse().on('dragend', function(e) {
10481
+ pencilPoints = []; // re-enable pencil after closing a path
10482
+ });
10483
+
10484
+ gui.map.getMouse().on('drag', function(e) {
10485
+ if (!active() || !cmdKeyDown() || vertexDragging()) return;
10486
+ e.stopPropagation(); // prevent panning
10487
+ if (!pencilIsActive()) return;
10488
+ hoverVertexInfo = findPathStartInfo(e);
10489
+ var xy = [e.x, e.y];
10490
+ var p = pixToDataCoords(e.x, e.y);
10491
+ if (!pathDrawing()) {
10492
+ pencilPoints = [xy];
10493
+ startNewPath(p);
10494
+ } else if (pencilPoints.length == 0) {
10495
+ // start pencil-drawing when a path is started
10496
+ pencilPoints = [xy];
10497
+ extendCurrentPath(p);
10498
+ } else if (hoverVertexInfo && pencilPoints.length > 2) {
10499
+ // close path
10500
+ p = hoverVertexInfo.point;
10501
+ appendVertex$1(hit.getHitTarget(), p);
10502
+ extendCurrentPath(p);
10503
+ pencilPoints = null; // stop drawing
10504
+ } else if (pointExceedsTolerance(xy, pencilPoints, 1.5)) {
10505
+ xy = pencilPoints.pop();
10506
+ p = pixToDataCoords(xy[0], xy[1]);
10507
+ pencilPoints = [xy];
10508
+ extendCurrentPath(p);
10509
+ } else {
10510
+ // skip this point, update the hover line
10511
+ pencilPoints.push(xy);
10512
+ updatePathEndpoint(p);
10513
+ }
10514
+ }, null, 3); // higher priority than hit control
10515
+
10452
10516
  hit.on('drag', function(e) {
10453
- if (!dragging() || drawing()) return;
10517
+ if (!vertexDragging() || pathDrawing()) {
10518
+ return;
10519
+ }
10454
10520
  e.originalEvent.stopPropagation();
10521
+ // dragging a vertex
10455
10522
  var target = hit.getHitTarget();
10456
10523
  var p = ext.translatePixelCoords(e.x, e.y);
10457
10524
  if (gui.keyboard.shiftIsPressed()) {
@@ -10464,7 +10531,7 @@
10464
10531
  });
10465
10532
 
10466
10533
  hit.on('dragend', function(e) {
10467
- if (!dragging()) return;
10534
+ if (!vertexDragging()) return;
10468
10535
  _dragging = false;
10469
10536
  var target = hit.getHitTarget();
10470
10537
  // kludge to get dataset to recalculate internal bounding boxes
@@ -10481,7 +10548,7 @@
10481
10548
  if (!active()) return;
10482
10549
  // double click finishes a path
10483
10550
  // note: if the preceding 'click' finished the path, this does not fire
10484
- if (drawing()) {
10551
+ if (pathDrawing()) {
10485
10552
  finishCurrentPath();
10486
10553
  e.originalEvent.stopPropagation(); // prevent dblclick zoom
10487
10554
  return;
@@ -10491,9 +10558,9 @@
10491
10558
  // hover event highlights the nearest point in close proximity to the pointer
10492
10559
  // ... or the closest point along the segment (for adding a new vertex)
10493
10560
  hit.on('hover', function(e) {
10494
- if (!active() || dragging()) return;
10561
+ if (!active() || vertexDragging()) return;
10495
10562
 
10496
- if (drawing()) {
10563
+ if (pathDrawing()) {
10497
10564
  if (!e.overMap) {
10498
10565
  finishCurrentPath();
10499
10566
  return;
@@ -10507,7 +10574,7 @@
10507
10574
  // highlight nearby snappable vertex (the closest vertex on a nearby line,
10508
10575
  // or the first vertex of the current drawing path if not near a line)
10509
10576
  hoverVertexInfo = e.id >= 0 && findDraggableVertices(e) ||
10510
- drawing() && findPathStartInfo(e) ||
10577
+ pathDrawing() && findPathStartInfo(e) ||
10511
10578
  e.id >= 0 && findInterpolatedPoint(e);
10512
10579
  if (hoverVertexInfo) {
10513
10580
  // hovering near a vertex: highlight the vertex
@@ -10524,14 +10591,15 @@
10524
10591
  if (!active()) return;
10525
10592
  if (detectDoubleClick(e)) return; // ignore second click of a dblclick
10526
10593
  var p = pixToDataCoords(e.x, e.y);
10527
- if (drawing()) {
10594
+ if (pathDrawing()) {
10528
10595
  extendCurrentPath(hoverVertexInfo?.point || p);
10529
10596
  } else if (gui.keyboard.shiftIsPressed()) {
10530
10597
  deleteActiveVertex(e);
10598
+ } else if (hoverVertexInfo?.type == 'interpolated') {
10599
+ // don't start new path if hovering along a segment -- this is
10600
+ // likely to be an attempt to add a new vertex, not start a new path
10531
10601
  } else {
10532
10602
  startNewPath(p);
10533
- hideInstructions();
10534
- updateCursor();
10535
10603
  }
10536
10604
  prevClickEvent = e;
10537
10605
  });
@@ -10557,7 +10625,7 @@
10557
10625
 
10558
10626
  function updateCursor() {
10559
10627
  gui.container.findChild('.map-layers').classed('drawing', active());
10560
- var useArrow = hoverVertexInfo && !hoverVertexInfo.extendable && !drawing();
10628
+ var useArrow = hoverVertexInfo && !hoverVertexInfo.extendable && !pathDrawing();
10561
10629
  gui.container.findChild('.map-layers').classed('dragging', useArrow);
10562
10630
  }
10563
10631
 
@@ -10623,7 +10691,7 @@
10623
10691
  }
10624
10692
 
10625
10693
  function finishCurrentPath() {
10626
- if (!drawing()) return;
10694
+ if (!pathDrawing()) return;
10627
10695
  var target = hit.getHitTarget();
10628
10696
  if (getLastArcLength(target) <= 2) { // includes hover point
10629
10697
  deleteLastPath(target);
@@ -10642,6 +10710,8 @@
10642
10710
  gui.dispatchEvent('path_add', {target, p1, p2});
10643
10711
  drawingId = target.shapes.length - 1;
10644
10712
  hit.setDrawingId(drawingId);
10713
+ hideInstructions();
10714
+ updateCursor();
10645
10715
  }
10646
10716
 
10647
10717
  // p: [x, y] source data coordinates of new point on path
@@ -10662,7 +10732,6 @@
10662
10732
  gui.dispatchEvent('path_extend', {target, p, shapes1, shapes2});
10663
10733
  clearDrawingInfo();
10664
10734
  fullRedraw();
10665
-
10666
10735
  } else {
10667
10736
  appendVertex$1(target, p);
10668
10737
  gui.dispatchEvent('path_extend', {target, p});
@@ -10697,22 +10766,22 @@
10697
10766
  }
10698
10767
 
10699
10768
  function findPathStartInfo(e) {
10769
+ if (!pathDrawing()) return false;
10700
10770
  var target = hit.getHitTarget();
10701
10771
  var arcId = target.gui.displayArcs.size() - 1;
10702
- var data = target.gui.displayArcs.getVertexData();
10772
+ var p1 = ext.translatePixelCoords(e.x, e.y); // mouse coords
10773
+ var p2 = internal.getArcStartCoords(arcId, target.gui.displayArcs); // vertex coords
10774
+ var p3 = internal.getArcStartCoords(arcId, target.gui.source.dataset.arcs);
10775
+ var dist = geom.distance2D(p1[0], p1[1], p2[0], p2[1]);
10776
+ var data = target.gui.source.dataset.arcs.getVertexData();
10703
10777
  var i = data.ii[arcId];
10704
- var x = data.xx[i];
10705
- var y = data.yy[i];
10706
- var p = ext.translatePixelCoords(e.x, e.y);
10707
- var dist = geom.distance2D(p[0], p[1], x, y);
10708
10778
  var pathLen = data.nn[arcId];
10709
10779
  var pixelDist = dist / ext.getPixelSize();
10710
10780
  if (pixelDist > HOVER_THRESHOLD || pathLen < 4) {
10711
10781
  return null;
10712
10782
  }
10713
- var point = translateDisplayPoint(target, [x, y]);
10714
10783
  return {
10715
- target, ids: [i], extendable: false, point, displayPoint: [x, y], type: 'vertex'
10784
+ target, ids: [i], extendable: false, displayPoint: p2, point: p3, type: 'vertex'
10716
10785
  };
10717
10786
  }
10718
10787
 
@@ -10948,6 +11017,7 @@
10948
11017
  }
10949
11018
  };
10950
11019
 
11020
+ // translate display CRS coords to pixel coords
10951
11021
  this.translateCoords = function(x, y) {
10952
11022
  return this.getTransform().transform(x, y);
10953
11023
  };
@@ -10967,6 +11037,7 @@
10967
11037
  return bounds2.width() / _frame.width;
10968
11038
  };
10969
11039
 
11040
+ // convert pixel coords (0,0 is top left corner of map) to display CRS coords
10970
11041
  this.translatePixelCoords = function(x, y) {
10971
11042
  return this.getTransform().invert().transform(x, y);
10972
11043
  };
@@ -11429,8 +11500,7 @@
11429
11500
 
11430
11501
  _self.drawSquareDots = function(shapes, style) {
11431
11502
  var t = getScaledTransform(_ext),
11432
- scaleRatio = getDotScale(_ext),
11433
- size = Math.round((style.dotSize || 1) * scaleRatio),
11503
+ size = getDotSize(style),
11434
11504
  styler = style.styler || null,
11435
11505
  xmax = _canvas.width + size,
11436
11506
  ymax = _canvas.height + size,
@@ -11450,7 +11520,7 @@
11450
11520
  for (i=0, n=shapes.length; i<n; i++) {
11451
11521
  if (styler !== null) { // e.g. selected points
11452
11522
  styler(style, i);
11453
- size = style.dotSize * scaleRatio;
11523
+ size = getDotSize(style);
11454
11524
  if (style.dotColor != color) {
11455
11525
  color = style.dotColor;
11456
11526
  _ctx.fillStyle = color;
@@ -11597,25 +11667,16 @@
11597
11667
  return s;
11598
11668
  }
11599
11669
 
11600
- function getDotScale(ext) {
11601
- var smallSide = Math.min(ext.width(), ext.height());
11602
- var mapScale = ext.scale();
11603
- // reduce size on smaller screens
11604
- var j = smallSide < 200 && 0.5 || smallSide < 400 && 0.75 || 1;
11605
- // grow dots as map zooms in
11606
- var k = 1;
11607
- if (mapScale < 0.5) {
11608
- k = Math.pow(mapScale + 0.5, 0.35);
11609
- } else if (mapScale > 1) {
11610
- // scale faster at first, so small dots in large datasets
11611
- // become easily visible and clickable after zooming in a bit
11612
- k *= Math.pow(Math.min(mapScale, 10), 0.3);
11613
- k *= Math.pow(mapScale, 0.1);
11614
- }
11615
- // grow pixels more slowly on retina displays (to reduce number of pixels to
11616
- // draw for large point datasets when slightly zoomed in)
11617
- var l = Math.pow(GUI.getPixelRatio(), 0.8);
11618
- return j * k * l;
11670
+ function getDotSize(style) {
11671
+ var size = style.dotSize || 1;
11672
+ // TODO: improve
11673
+ var scale = style.dotScale || 1;
11674
+ size += (scale - 1) / 2;
11675
+ size *= Math.pow(scale, 0.3);
11676
+
11677
+ // shrink dots slightly on retina displays, to adjust for greater clarity
11678
+ // and reduce number of pixels to draw on large datasets.
11679
+ return Math.round(Math.pow(GUI.getPixelRatio(), 0.8) * size);
11619
11680
  }
11620
11681
 
11621
11682
  function getScaledTransform(ext) {
@@ -12483,9 +12544,6 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12483
12544
 
12484
12545
  _basemap = new Basemap(gui, _ext);
12485
12546
 
12486
- if (gui.options.showMouseCoordinates) {
12487
- new CoordinatesDisplay(gui, _ext, _mouse);
12488
- }
12489
12547
  _mouse.disable(); // wait for gui.focus() to activate mouse events
12490
12548
 
12491
12549
  model.on('select', function(e) {
@@ -12537,16 +12595,27 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12537
12595
  return internal.toLngLat(p, _dynamicCRS);
12538
12596
  };
12539
12597
 
12540
- this.getCenterLngLat = function() {
12541
- var bounds = _ext.getBounds();
12598
+ this.pixelCoordsToDisplayCoords = function(x, y) {
12599
+ var p1 = _ext.translatePixelCoords(x, y);
12600
+ var p2 = _ext.translatePixelCoords(x+1, y+1);
12542
12601
  var crs = this.getDisplayCRS();
12543
- // TODO: handle case where active layer is a frame layer
12544
- if (!bounds.hasBounds() || !crs) {
12545
- return null;
12602
+ if (crs) {
12603
+ p1 = internal.toLngLat(p1, crs) || p1;
12604
+ p2 = internal.toLngLat(p2, crs) || p2;
12546
12605
  }
12547
- return internal.toLngLat([bounds.centerX(), bounds.centerY()], crs);
12606
+ return formatCoordsForDisplay(p1, p2);
12548
12607
  };
12549
12608
 
12609
+ // this.getCenterLngLat = function() {
12610
+ // var bounds = _ext.getBounds();
12611
+ // var crs = this.getDisplayCRS();
12612
+ // // TODO: handle case where active layer is a frame layer
12613
+ // if (!bounds.hasBounds() || !crs) {
12614
+ // return null;
12615
+ // }
12616
+ // return internal.toLngLat([bounds.centerX(), bounds.centerY()], crs);
12617
+ // };
12618
+
12550
12619
  this.getDisplayCRS = function() {
12551
12620
  if (!_activeLyr) {
12552
12621
  return _dynamicCRS || internal.parseCrsString('wgs84');
@@ -12613,7 +12682,6 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12613
12682
  _ext.setFullBounds(calcFullBounds());
12614
12683
  _ext.resize();
12615
12684
  _renderer = new LayerRenderer(gui, el);
12616
- gui.buttons.show();
12617
12685
 
12618
12686
  if (opts.inspectorControl) {
12619
12687
  _hit = new HitControl(gui, _ext, _mouse),
@@ -12675,10 +12743,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12675
12743
  }
12676
12744
 
12677
12745
  if (e.layer) {
12678
- enhanceLayerForDisplay(e.layer, e.dataset, getDisplayOptions());
12679
- _activeLyr = e.layer;
12680
- _activeLyr.gui.style = getActiveLayerStyle(_activeLyr.gui.displayLayer, getGlobalStyleOptions());
12681
- _activeLyr.active = true;
12746
+ _activeLyr = initActiveLayer(e.layer, e.dataset);
12682
12747
  } else {
12683
12748
  _activeLyr = null;
12684
12749
  }
@@ -12716,10 +12781,12 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12716
12781
 
12717
12782
 
12718
12783
  function updateOverlayLayer(e) {
12719
- var style = getOverlayStyle(_activeLyr.gui.displayLayer, e, getGlobalStyleOptions());
12784
+ var style = !_activeLyr?.gui?.style ? null :
12785
+ getOverlayStyle(_activeLyr.gui.displayLayer, e, getGlobalStyleOptions());
12720
12786
  if (style) {
12721
12787
  var displayLayer = filterLayerByIds(_activeLyr.gui.displayLayer, style.ids);
12722
12788
  var gui = Object.assign({}, _activeLyr.gui, {style, displayLayer});
12789
+ style.dotScale = _activeLyr.gui.style.dotScale;
12723
12790
  _overlayLyr = utils$1.defaults({gui}, _activeLyr);
12724
12791
  } else {
12725
12792
  _overlayLyr = null;
@@ -12863,6 +12930,17 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12863
12930
  });
12864
12931
  }
12865
12932
 
12933
+ function initActiveLayer(lyr, dataset) {
12934
+ enhanceLayerForDisplay(lyr, dataset, getDisplayOptions());
12935
+ lyr.gui.style = getActiveLayerStyle(lyr.gui.displayLayer, getGlobalStyleOptions());
12936
+ lyr.active = true;
12937
+ getVisibleMapLayers().forEach(function(lyr) {
12938
+ delete lyr.active;
12939
+ });
12940
+ lyr.active = true;
12941
+ return lyr;
12942
+ }
12943
+
12866
12944
  function getDrawableFurnitureLayers(layers) {
12867
12945
  if (!isPreviewView()) return [];
12868
12946
  return getVisibleMapLayers().filter(function(o) {
@@ -12929,6 +13007,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12929
13007
  updateLayerStyles(contentLayers);
12930
13008
  updateLayerStackOrder(model.getLayers());// update menu_order property of all layers
12931
13009
  }
13010
+ adjustPointSymbolSizes(contentLayers, _overlayLyr, _ext);
12932
13011
  sortMapLayers(contentLayers);
12933
13012
  if (_intersectionLyr) {
12934
13013
  contentLayers = contentLayers.concat(_intersectionLyr);
@@ -12997,7 +13076,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12997
13076
  menu.hide();
12998
13077
  _open = false;
12999
13078
  }
13000
- }, 300);
13079
+ }, 200);
13001
13080
  }
13002
13081
 
13003
13082
  function addMenuItem(label, func) {
@@ -13010,7 +13089,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13010
13089
  }
13011
13090
 
13012
13091
  this.open = function(e, lyr) {
13013
- if (!lyr || !lyr.gui.geographic) return;
13092
+ if (lyr && !lyr.gui.geographic) return; // no popup for tabular data
13014
13093
  _open = true;
13015
13094
  _openCount++;
13016
13095
  var rspace = body.clientWidth - e.pageX;
@@ -13026,8 +13105,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13026
13105
  menu.css('top', (e.pageY - 15) + 'px');
13027
13106
 
13028
13107
  // menu contents
13029
- if (e.coordinates) {
13030
- addCopyCoords();
13108
+ //
13109
+ if (e.display_coordinates) {
13110
+ addCoords(e.display_coordinates);
13031
13111
  }
13032
13112
  if (e.deleteVertex) {
13033
13113
  addMenuItem('Delete vertex', e.deleteVertex);
@@ -13038,10 +13118,16 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13038
13118
  if (e.ids?.length) {
13039
13119
  addMenuItem('Copy as GeoJSON', copyGeoJSON);
13040
13120
  }
13121
+ if (e.deleteFeature) {
13122
+ addMenuItem(getDeleteLabel(), e.deleteFeature);
13123
+ }
13124
+
13125
+ function getDeleteLabel() {
13126
+ return 'Delete ' + (lyr.geometry_type == 'point' ? 'point' : 'shape');
13127
+ }
13041
13128
 
13042
- function addCopyCoords() {
13043
- var bbox = internal.getLayerBounds(lyr, lyr.gui.source.dataset.arcs).toArray();
13044
- var coordStr = internal.getRoundedCoordString(e.coordinates, internal.getBoundsPrecisionForDisplay(bbox));
13129
+ function addCoords(p) {
13130
+ var coordStr = p[0] + ',' + p[1];
13045
13131
  var displayStr = '• &nbsp;' + coordStr.replace(/-/g, '–').replace(',', ', ');
13046
13132
  addMenuItem(displayStr, function() {
13047
13133
  saveFileContentToClipboard(coordStr);
@@ -13086,7 +13172,6 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13086
13172
  inspectorControl: true,
13087
13173
  saveControl: true,
13088
13174
  disableNavigation: false,
13089
- showMouseCoordinates: true,
13090
13175
  focus: true
13091
13176
  }, opts);
13092
13177
 
package/www/mapshaper.js CHANGED
@@ -1612,6 +1612,7 @@
1612
1612
  this.ymin -= halfHeight * ky;
1613
1613
  this.xmax += halfWidth * kx;
1614
1614
  this.ymax += halfHeight * ky;
1615
+ return this;
1615
1616
  };
1616
1617
 
1617
1618
  // Return a bounding box with the same extent as this one.
@@ -5003,7 +5004,7 @@
5003
5004
  if (isLatLngCRS(P)) {
5004
5005
  return xy.concat();
5005
5006
  }
5006
- proj = getProjTransform(P, parseCrsString('wgs84'));
5007
+ proj = getProjTransform2(P, parseCrsString('wgs84'));
5007
5008
  return proj(xy[0], xy[1]);
5008
5009
  }
5009
5010
 
@@ -26664,7 +26665,7 @@ ${svg}
26664
26665
  codepage = lookupCodePage(ldid),
26665
26666
  samples = getNonAsciiSamples(),
26666
26667
  only7bit = samples.length === 0,
26667
- encoding, msg;
26668
+ encoding;
26668
26669
 
26669
26670
  // First, check the ldid (language driver id) (an obsolete way to specify which
26670
26671
  // codepage to use for text encoding.)
@@ -26684,10 +26685,12 @@ ${svg}
26684
26685
  // As a last resort, try to guess the encoding:
26685
26686
  if (!encoding) {
26686
26687
  var info = detectEncoding(samples);
26688
+ var msg;
26687
26689
  encoding = info.encoding;
26688
26690
  if (info.confidence < 2) {
26689
- msg = 'Warning: Unable to auto-detect the DBF file text encoding with high confidence.';
26690
- msg += '\n\nDefaulting to: ' + encoding + (encoding in encodingNames ? ' (' + encodingNames[encoding] + ')' : '');
26691
+ msg = 'Warning: Unable to auto-detect the DBF file text encoding' +
26692
+ (info.confidence == 1 ? ' with high confidence' : '') + '.';
26693
+ msg += ' Defaulting to ' + encoding + (encoding in encodingNames ? ' (' + encodingNames[encoding] + ').' : '.');
26691
26694
  msg += '\n\nSample of how non-ascii text was imported:\n';
26692
26695
  if (runningInBrowser()) {
26693
26696
  msg += '<pre>' + formatStringsAsGrid(decodeSamples(encoding, samples), 50) + '</pre>';
@@ -45532,7 +45535,7 @@ ${svg}
45532
45535
  });
45533
45536
  }
45534
45537
 
45535
- var version = "0.6.79";
45538
+ var version = "0.6.81";
45536
45539
 
45537
45540
  // Parse command line args into commands and run them
45538
45541
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
package/www/page.css CHANGED
@@ -151,21 +151,6 @@ body.map-view {
151
151
  height: 29px;
152
152
  }
153
153
 
154
- .basemap-on .coordinate-info {
155
- left: 100px;
156
- }
157
-
158
- .coordinate-info {
159
- z-index: 1;
160
- position: absolute;
161
- bottom: 7px;
162
- left: 7px;
163
- padding: 2px 5px 2px 5px;
164
- font-size: 11px;
165
- pointer-events: none;
166
- background: rgba(255,255,255,0.4);
167
- }
168
-
169
154
  .mapshaper-logo {
170
155
  font-weight: bold;
171
156
  font-size: 17px;
@@ -348,12 +333,10 @@ div.alert-box {
348
333
  border-radius: 9px;
349
334
  font-size: 14px;
350
335
  line-height: 1.5;
351
- /* color: #555;*/
352
336
  padding: 9px 11px 6px 11px;
353
337
  margin: 9px -1px 3px -1px;
354
338
  min-height: 120px;
355
339
  background: #f8fdff;
356
- ;
357
340
  }
358
341
 
359
342
  .catalog-mode .file-catalog {