mapshaper 0.6.75 → 0.6.76

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.
@@ -760,10 +760,7 @@
760
760
  //
761
761
  //El.prototype.__domevents = utils.arrayToIndex("click,mousedown,mousemove,mouseup".split(','));
762
762
  El.prototype.__on = El.prototype.on;
763
- El.prototype.on = function(type, func, ctx) {
764
- if (ctx) {
765
- error$1("[El#on()] Third argument no longer supported.");
766
- }
763
+ El.prototype.on = function(type, func) {
767
764
  if (this.constructor == El) {
768
765
  this.el.addEventListener(type, func);
769
766
  } else {
@@ -1369,10 +1366,12 @@
1369
1366
  init();
1370
1367
 
1371
1368
  async function init() {
1372
- var enabled = await isStorageEnabled();
1373
- if (!enabled) return;
1374
-
1375
1369
  btn = gui.buttons.addButton('#ribbon-icon').addClass('menu-btn save-btn');
1370
+ var enabled = await isStorageEnabled();
1371
+ if (!enabled) {
1372
+ btn.remove();
1373
+ return;
1374
+ }
1376
1375
  menu = El('div').addClass('nav-sub-menu save-menu').appendTo(btn.node());
1377
1376
  await initialCleanup();
1378
1377
 
@@ -1444,14 +1443,6 @@
1444
1443
  renderMenu();
1445
1444
  }).text('remove');
1446
1445
  });
1447
-
1448
- // if (snapshots.length >= 1) {
1449
- // addMenuLink({
1450
- // slug: 'clear',
1451
- // label: 'remove all',
1452
- // action: clearData
1453
- // });
1454
- // }
1455
1446
  }
1456
1447
 
1457
1448
  function addMenuLink(item) {
@@ -2554,7 +2545,7 @@
2554
2545
  // Convert a point from display CRS coordinates to data coordinates.
2555
2546
  // These are only different when using dynamic reprojection (basemap view).
2556
2547
  function translateDisplayPoint(lyr, p) {
2557
- return isProjectedLayer(lyr) ? lyr.invertPoint(p[0], p[1]) : p;
2548
+ return isProjectedLayer(lyr) ? lyr.gui.invertPoint(p[0], p[1]) : p;
2558
2549
  }
2559
2550
 
2560
2551
  // bbox: display coords
@@ -2571,7 +2562,7 @@
2571
2562
 
2572
2563
  function isProjectedLayer(lyr) {
2573
2564
  // TODO: could do some validation on the layer's contents
2574
- return !!(lyr.source && lyr.invertPoint);
2565
+ return !!lyr.gui.invertPoint;
2575
2566
  }
2576
2567
 
2577
2568
  var darkStroke = "#334",
@@ -2933,22 +2924,22 @@
2933
2924
  }
2934
2925
 
2935
2926
  function flattenArcs(lyr) {
2936
- lyr.source.dataset.arcs.flatten();
2927
+ lyr.gui.source.dataset.arcs.flatten();
2937
2928
  if (isProjectedLayer(lyr)) {
2938
- lyr.arcs.flatten();
2929
+ lyr.gui.displayArcs.flatten();
2939
2930
  }
2940
2931
  }
2941
2932
 
2942
2933
  function setZ(lyr, z) {
2943
- lyr.source.dataset.arcs.setRetainedInterval(z);
2934
+ lyr.gui.source.dataset.arcs.setRetainedInterval(z);
2944
2935
  if (isProjectedLayer(lyr)) {
2945
- lyr.arcs.setRetainedInterval(z);
2936
+ lyr.gui.displayArcs.setRetainedInterval(z);
2946
2937
  }
2947
2938
  }
2948
2939
 
2949
2940
  function updateZ(lyr) {
2950
- if (isProjectedLayer(lyr) && !lyr.source.dataset.arcs.isFlat()) {
2951
- lyr.arcs.setThresholds(lyr.source.dataset.arcs.getVertexData().zz);
2941
+ if (isProjectedLayer(lyr) && !lyr.gui.source.dataset.arcs.isFlat()) {
2942
+ lyr.gui.displayArcs.setThresholds(lyr.gui.source.dataset.arcs.getVertexData().zz);
2952
2943
  }
2953
2944
  }
2954
2945
 
@@ -2984,108 +2975,107 @@
2984
2975
  }
2985
2976
 
2986
2977
  function deleteLastPath(lyr) {
2987
- var arcId = lyr.arcs.size() - 1;
2988
- if (lyr.layer.data) {
2989
- lyr.layer.data.getRecords().pop();
2978
+ var arcId = lyr.gui.displayArcs.size() - 1;
2979
+ if (lyr.data) {
2980
+ lyr.data.getRecords().pop();
2990
2981
  }
2991
- var shp = lyr.layer.shapes.pop();
2992
- internal.deleteLastArc(lyr.arcs);
2982
+ var shp = lyr.shapes.pop();
2983
+ internal.deleteLastArc(lyr.gui.displayArcs);
2993
2984
  if (isProjectedLayer(lyr)) {
2994
- internal.deleteLastArc(lyr.source.dataset.arcs);
2985
+ internal.deleteLastArc(lyr.gui.source.dataset.arcs);
2995
2986
  }
2996
2987
  }
2997
2988
 
2998
2989
  // p1, p2: two points in source data CRS coords.
2999
2990
  function appendNewPath(lyr, p1, p2) {
3000
- var arcId = lyr.arcs.size();
3001
- internal.appendEmptyArc(lyr.arcs);
3002
- lyr.layer.shapes.push([[arcId]]);
2991
+ var arcId = lyr.gui.displayArcs.size();
2992
+ internal.appendEmptyArc(lyr.gui.displayArcs);
2993
+ lyr.shapes.push([[arcId]]);
3003
2994
  if (isProjectedLayer(lyr)) {
3004
- // lyr.source.layer.shapes.push([[arcId]]);
3005
- internal.appendEmptyArc(lyr.source.dataset.arcs);
2995
+ internal.appendEmptyArc(lyr.gui.source.dataset.arcs);
3006
2996
  }
3007
2997
  appendVertex$1(lyr, p1);
3008
2998
  appendVertex$1(lyr, p2);
3009
- appendNewDataRecord(lyr.layer);
2999
+ appendNewDataRecord(lyr);
3010
3000
  }
3011
3001
 
3012
3002
  // p: point in source data CRS coords.
3013
3003
  function insertVertex$1(lyr, id, p) {
3014
- internal.insertVertex(lyr.source.dataset.arcs, id, p);
3004
+ internal.insertVertex(lyr.gui.source.dataset.arcs, id, p);
3015
3005
  if (isProjectedLayer(lyr)) {
3016
- internal.insertVertex(lyr.arcs, id, lyr.projectPoint(p[0], p[1]));
3006
+ internal.insertVertex(lyr.gui.displayArcs, id, lyr.gui.projectPoint(p[0], p[1]));
3017
3007
  }
3018
3008
  }
3019
3009
 
3020
3010
  function appendVertex$1(lyr, p) {
3021
- var n = lyr.source.dataset.arcs.getPointCount();
3011
+ var n = lyr.gui.source.dataset.arcs.getPointCount();
3022
3012
  insertVertex$1(lyr, n, p);
3023
3013
  }
3024
3014
 
3025
3015
  // TODO: make sure we're not also removing an entire arc
3026
3016
  function deleteLastVertex(lyr) {
3027
- deleteVertex$1(lyr, lyr.arcs.getPointCount() - 1);
3017
+ deleteVertex$1(lyr, lyr.gui.displayArcs.getPointCount() - 1);
3028
3018
  }
3029
3019
 
3030
3020
  function deleteVertex$1(lyr, id) {
3031
- internal.deleteVertex(lyr.arcs, id);
3021
+ internal.deleteVertex(lyr.gui.displayArcs, id);
3032
3022
  if (isProjectedLayer(lyr)) {
3033
- internal.deleteVertex(lyr.source.dataset.arcs, id);
3023
+ internal.deleteVertex(lyr.gui.source.dataset.arcs, id);
3034
3024
  }
3035
3025
  }
3036
3026
 
3037
3027
  function getLastArcCoords(target) {
3038
- var arcId = target.source.dataset.arcs.size() - 1;
3039
- return internal.getUnfilteredArcCoords(arcId, target.source.dataset.arcs);
3028
+ var arcId = target.gui.source.dataset.arcs.size() - 1;
3029
+ return internal.getUnfilteredArcCoords(arcId, target.gui.source.dataset.arcs);
3040
3030
  }
3041
3031
 
3042
3032
  function getLastVertexCoords(target) {
3043
- var arcs = target.source.dataset.arcs;
3033
+ var arcs = target.gui.source.dataset.arcs;
3044
3034
  return internal.getVertexCoords(arcs.getPointCount() - 1, arcs);
3045
3035
  }
3046
3036
 
3047
3037
  function getLastArcLength(target) {
3048
- var arcId = target.source.dataset.arcs.size() - 1;
3049
- return internal.getUnfilteredArcLength(arcId, target.source.dataset.arcs);
3038
+ var arcId = target.gui.source.dataset.arcs.size() - 1;
3039
+ return internal.getUnfilteredArcLength(arcId, target.gui.source.dataset.arcs);
3050
3040
  }
3051
3041
 
3052
3042
  function getPointCoords(lyr, fid) {
3053
- return internal.cloneShape(lyr.source.layer.shapes[fid]);
3043
+ return internal.cloneShape(lyr.shapes[fid]);
3054
3044
  }
3055
3045
 
3056
3046
  function getVertexCoords(lyr, id) {
3057
- return lyr.source.dataset.arcs.getVertex2(id);
3047
+ return lyr.gui.source.dataset.arcs.getVertex2(id);
3058
3048
  }
3059
3049
 
3060
3050
  // set data coords (not display coords) of one or more vertices.
3061
3051
  function setVertexCoords(lyr, ids, dataPoint) {
3062
- internal.snapVerticesToPoint(ids, dataPoint, lyr.source.dataset.arcs, true);
3052
+ internal.snapVerticesToPoint(ids, dataPoint, lyr.gui.source.dataset.arcs);
3063
3053
  if (isProjectedLayer(lyr)) {
3064
- var p = lyr.projectPoint(dataPoint[0], dataPoint[1]);
3065
- internal.snapVerticesToPoint(ids, p, lyr.arcs, true);
3054
+ var p = lyr.gui.projectPoint(dataPoint[0], dataPoint[1]);
3055
+ internal.snapVerticesToPoint(ids, p, lyr.gui.displayArcs);
3066
3056
  }
3067
3057
  }
3068
3058
 
3069
3059
  // coords: [x, y] point in data CRS (not display CRS)
3070
3060
  function setPointCoords(lyr, fid, coords) {
3071
- lyr.source.layer.shapes[fid] = coords;
3061
+ lyr.shapes[fid] = coords;
3072
3062
  if (isProjectedLayer(lyr)) {
3073
- lyr.layer.shapes[fid] = projectPointCoords(coords, lyr.projectPoint);
3063
+ lyr.shapes[fid] = projectPointCoords(coords, lyr.gui.projectPoint);
3074
3064
  }
3075
3065
  }
3076
3066
 
3077
3067
  function updateVertexCoords(lyr, ids) {
3078
3068
  if (!isProjectedLayer(lyr)) return;
3079
- var p = lyr.arcs.getVertex2(ids[0]);
3080
- internal.snapVerticesToPoint(ids, lyr.invertPoint(p[0], p[1]), lyr.source.dataset.arcs, true);
3069
+ var p = lyr.gui.displayArcs.getVertex2(ids[0]);
3070
+ internal.snapVerticesToPoint(ids, lyr.gui.invertPoint(p[0], p[1]), lyr.gui.source.dataset.arcs);
3081
3071
  }
3082
3072
 
3083
3073
  function setRectangleCoords(lyr, ids, coords) {
3084
3074
  ids.forEach(function(id, i) {
3085
3075
  var p = coords[i];
3086
- internal.snapVerticesToPoint([id], p, lyr.source.dataset.arcs, true);
3076
+ internal.snapVerticesToPoint([id], p, lyr.gui.source.dataset.arcs);
3087
3077
  if (isProjectedLayer(lyr)) {
3088
- internal.snapVerticesToPoint([id], lyr.projectPoint(p[0], p[1]), lyr.arcs, true);
3078
+ internal.snapVerticesToPoint([id], lyr.gui.projectPoint(p[0], p[1]), lyr.gui.displayArcs);
3089
3079
  }
3090
3080
  });
3091
3081
  }
@@ -3093,8 +3083,8 @@
3093
3083
  // Update source data coordinates by projecting display coordinates
3094
3084
  function updatePointCoords(lyr, fid) {
3095
3085
  if (!isProjectedLayer(lyr)) return;
3096
- var displayShp = lyr.layer.shapes[fid];
3097
- lyr.source.layer.shapes[fid] = projectPointCoords(displayShp, lyr.invertPoint);
3086
+ var displayShp = lyr.shapes[fid];
3087
+ lyr.shapes[fid] = projectPointCoords(displayShp, lyr.gui.invertPoint);
3098
3088
  }
3099
3089
 
3100
3090
  function projectPointCoords(src, proj) {
@@ -4231,6 +4221,11 @@
4231
4221
  return layers.reverse();
4232
4222
  }
4233
4223
 
4224
+ async function saveFileContentToClipboard(content) {
4225
+ var str = utils$1.isString(content) ? content : content.toString();
4226
+ await navigator.clipboard.writeText(str);
4227
+ }
4228
+
4234
4229
  // Export buttons and their behavior
4235
4230
  var ExportControl = function(gui) {
4236
4231
  var model = gui.model;
@@ -4374,12 +4369,6 @@
4374
4369
  } else {
4375
4370
  await utils$1.promisify(internal.writeFiles)(files, opts);
4376
4371
  }
4377
-
4378
- }
4379
-
4380
- async function saveFileContentToClipboard(content) {
4381
- var str = utils$1.isString(content) ? content : content.toString();
4382
- await navigator.clipboard.writeText(str);
4383
4372
  }
4384
4373
 
4385
4374
  function initLayerItem(o, i) {
@@ -5397,19 +5386,23 @@
5397
5386
  function KeyboardEvents(gui) {
5398
5387
  var self = this;
5399
5388
  var shiftDown = false;
5389
+ var ctrlDown = false;
5400
5390
  document.addEventListener('keyup', function(e) {
5401
5391
  if (!GUI.isActiveInstance(gui)) return;
5402
5392
  if (e.keyCode == 16) shiftDown = false;
5393
+ if (e.keyCode == 17) ctrlDown = false;
5403
5394
  self.dispatchEvent('keyup', getEventData(e));
5404
5395
  });
5405
5396
 
5406
5397
  document.addEventListener('keydown', function(e) {
5407
5398
  if (!GUI.isActiveInstance(gui)) return;
5408
5399
  if (e.keyCode == 16) shiftDown = true;
5400
+ if (e.keyCode == 17) ctrlDown = true;
5409
5401
  self.dispatchEvent('keydown', getEventData(e));
5410
5402
  });
5411
5403
 
5412
5404
  this.shiftIsPressed = function() { return shiftDown; };
5405
+ this.ctrlIsPressed = function() { return ctrlDown; };
5413
5406
 
5414
5407
  this.onMenuSubmit = function(menuEl, cb) {
5415
5408
  gui.on('enter_key', function(e) {
@@ -5426,6 +5419,7 @@
5426
5419
  9: 'tab',
5427
5420
  13: 'enter',
5428
5421
  16: 'shift',
5422
+ 17: 'ctrl',
5429
5423
  27: 'esc',
5430
5424
  32: 'space',
5431
5425
  37: 'left',
@@ -5447,7 +5441,7 @@
5447
5441
  var menus = {
5448
5442
  standard: ['info', 'selection', 'box'],
5449
5443
  polygons: ['info', 'selection', 'box', 'drawing'],
5450
- rectangles: ['info', 'selection', 'box', 'rectangles'], // 'vertices'
5444
+ rectangles: ['info', 'selection', 'box', 'rectangles'],
5451
5445
  lines: ['info', 'selection', 'box' , 'drawing'],
5452
5446
  table: ['info', 'selection'],
5453
5447
  labels: ['info', 'selection', 'box', 'labels', 'location', 'add-points'],
@@ -5468,7 +5462,7 @@
5468
5462
  labels: 'position labels',
5469
5463
  location: 'drag points',
5470
5464
  vertices: 'edit vertices',
5471
- selection: 'select features',
5465
+ selection: 'selection tool',
5472
5466
  'add-points': 'add points',
5473
5467
  drawing: 'draw/reshape tool',
5474
5468
  rectangles: 'drag-to-resize',
@@ -7261,11 +7255,11 @@
7261
7255
 
7262
7256
  // featureFilter: optional test function, accepts feature id
7263
7257
  //
7264
- function getShapeHitTest(displayLayer, ext, interactionMode, featureFilter) {
7265
- var geoType = displayLayer.layer.geometry_type;
7258
+ function getShapeHitTest(layer, ext, interactionMode, featureFilter) {
7259
+ var geoType = layer.gui.displayLayer.geometry_type;
7266
7260
  var test;
7267
- if (geoType == 'point' && displayLayer.style.type == 'styled') {
7268
- test = getGraduatedCircleTest(getRadiusFunction(displayLayer.style));
7261
+ if (geoType == 'point' && layer.gui.style.type == 'styled') {
7262
+ test = getGraduatedCircleTest(getRadiusFunction(layer.gui.style));
7269
7263
  } else if (geoType == 'point') {
7270
7264
  test = pointTest;
7271
7265
  } else if (interactionMode == 'drawing' && geoType == 'polygon') {
@@ -7305,13 +7299,13 @@
7305
7299
  cand, hitId;
7306
7300
  for (var i=0; i<cands.length; i++) {
7307
7301
  cand = cands[i];
7308
- if (geom.testPointInPolygon(x, y, cand.shape, displayLayer.arcs)) {
7302
+ if (geom.testPointInPolygon(x, y, cand.shape, layer.gui.displayArcs)) {
7309
7303
  hits.push(cand);
7310
7304
  }
7311
7305
  }
7312
7306
  if (cands.length > 0 && hits.length === 0) {
7313
7307
  // secondary detection: proximity, if not inside a polygon
7314
- sortByDistance(x, y, cands, displayLayer.arcs);
7308
+ sortByDistance(x, y, cands, layer.gui.displayArcs);
7315
7309
  hits = pickNearestCandidates(cands, 0, maxDist);
7316
7310
  }
7317
7311
  return {
@@ -7336,7 +7330,7 @@
7336
7330
  bufPix = bufArg >= 0 ? bufArg : 0.05, // tiny threshold for hitting almost-identical lines
7337
7331
  bufDist = getZoomAdjustedHitBuffer(bufPix),
7338
7332
  cands = findHitCandidates(x, y, maxDist);
7339
- sortByDistance(x, y, cands, displayLayer.arcs);
7333
+ sortByDistance(x, y, cands, layer.gui.displayArcs);
7340
7334
  cands = pickNearestCandidates(cands, bufDist, maxDist);
7341
7335
  return {
7342
7336
  ids: utils$1.pluck(cands, 'id')
@@ -7377,7 +7371,7 @@
7377
7371
  hits = [];
7378
7372
 
7379
7373
  // inlining forEachPoint() does not not appreciably speed this up
7380
- internal.forEachPoint(displayLayer.layer.shapes, function(p, id) {
7374
+ internal.forEachPoint(layer.gui.displayLayer.shapes, function(p, id) {
7381
7375
  var dist = geom.distance2D(x, y, p[0], p[1]) * toPx;
7382
7376
  if (dist > hitThreshold) return;
7383
7377
  if (dist < hitThreshold && hitThreshold > bullseyeDist) {
@@ -7411,7 +7405,7 @@
7411
7405
  directHit = false,
7412
7406
  hitRadius = 0,
7413
7407
  hitDist;
7414
- internal.forEachPoint(displayLayer.layer.shapes, function(p, id) {
7408
+ internal.forEachPoint(layer.gui.displayLayer.shapes, function(p, id) {
7415
7409
  var distSq = geom.distanceSq(x, y, p[0], p[1]);
7416
7410
  var isHit = false;
7417
7411
  var isOver, isNear, r, d, rpix;
@@ -7451,11 +7445,11 @@
7451
7445
 
7452
7446
  // Returns array of shape ids for shapes that pass a buffered bounding-box test
7453
7447
  function findHitCandidates(x, y, dist) {
7454
- var arcs = displayLayer.arcs,
7448
+ var arcs = layer.gui.displayArcs,
7455
7449
  index = {},
7456
7450
  cands = [],
7457
7451
  bbox = [];
7458
- displayLayer.layer.shapes.forEach(function(shp, shpId) {
7452
+ layer.gui.displayLayer.shapes.forEach(function(shp, shpId) {
7459
7453
  var cand;
7460
7454
  if (featureFilter && !featureFilter(shpId)) {
7461
7455
  return;
@@ -7587,7 +7581,7 @@
7587
7581
 
7588
7582
  function getPointerHitTest(mapLayer, ext, interactionMode, featureFilter) {
7589
7583
  var shapeTest, targetLayer;
7590
- if (!mapLayer || !internal.layerHasGeometry(mapLayer.layer)) {
7584
+ if (!mapLayer || !internal.layerHasGeometry(mapLayer.gui?.displayLayer)) {
7591
7585
  return function() {return {ids: []};};
7592
7586
  }
7593
7587
  shapeTest = getShapeHitTest(mapLayer, ext, interactionMode, featureFilter);
@@ -7626,6 +7620,13 @@
7626
7620
  // pan navigation
7627
7621
  var priority = 2;
7628
7622
 
7623
+ mouse.on('contextmenu', function(e) {
7624
+ if (isOverMap(e)) {
7625
+ e.originalEvent.preventDefault();
7626
+ }
7627
+ triggerHitEvent('contextmenu', e);
7628
+ }, false);
7629
+
7629
7630
  // init keyboard controls for pinned features
7630
7631
  gui.keyboard.on('keydown', function(evt) {
7631
7632
  var e = evt.originalEvent;
@@ -7653,7 +7654,7 @@
7653
7654
  // to help protect against inadvertent deletion, don't delete
7654
7655
  // when console is open or a popup menu is open
7655
7656
  if (!gui.getMode() && !gui.consoleIsOpen()) {
7656
- internal.deleteFeatureById(targetLayer.layer, pinnedId());
7657
+ internal.deleteFeatureById(targetLayer, pinnedId());
7657
7658
  self.clearSelection();
7658
7659
  gui.model.updated({flags: 'filter'}); // signal map to update
7659
7660
  }
@@ -7696,6 +7697,7 @@
7696
7697
 
7697
7698
  function pinnable() {
7698
7699
  return clickable() && !selectable();
7700
+ // return clickable();
7699
7701
  }
7700
7702
 
7701
7703
  function draggable() {
@@ -7790,7 +7792,7 @@
7790
7792
 
7791
7793
  self.getTargetDataTable = function() {
7792
7794
  var targ = self.getHitTarget();
7793
- return targ && targ.layer.data || null;
7795
+ return targ?.data || null;
7794
7796
  };
7795
7797
 
7796
7798
  // get function for selecting next or prev feature within the current set of
@@ -7803,7 +7805,7 @@
7803
7805
 
7804
7806
  // diff: 1 or -1
7805
7807
  function advanceSelectedFeature(diff) {
7806
- var n = internal.getFeatureCount(targetLayer.layer);
7808
+ var n = internal.getFeatureCount(targetLayer);
7807
7809
  if (n < 2 || pinnedId() == -1) return;
7808
7810
  storedData.id = (pinnedId() + n + diff) % n;
7809
7811
  storedData.ids = [storedData.id];
@@ -7848,12 +7850,15 @@
7848
7850
 
7849
7851
  mouse.on('click', function(e) {
7850
7852
  if (!hitTest || !active) return;
7853
+ if (!eventIsEnabled('click')) return;
7854
+
7851
7855
  e.stopPropagation();
7852
7856
 
7853
7857
  // TODO: move pinning to inspection control?
7854
7858
  if (clickable()) {
7855
7859
  updateSelectionState(convertClickDataToSelectionData(hitTest(e)));
7856
7860
  }
7861
+
7857
7862
  triggerHitEvent('click', e);
7858
7863
  }, null, priority);
7859
7864
 
@@ -7872,6 +7877,7 @@
7872
7877
  }
7873
7878
  }, null, priority);
7874
7879
 
7880
+
7875
7881
  function targetIsRollover(target) {
7876
7882
  while (target.parentNode && target != target.parentNode) {
7877
7883
  if (target.className && String(target.className).indexOf('rollover') > -1) {
@@ -7910,6 +7916,19 @@
7910
7916
  return hitData;
7911
7917
  }
7912
7918
 
7919
+ function mergeSelectionModeHoverData(hitData) {
7920
+ if (hitData.ids.length === 0 || selectionIds.includes(hitData.ids[0])) {
7921
+ hitData.ids = selectionIds;
7922
+ hitData.pinned = storedData.pinned;
7923
+ } else {
7924
+ //
7925
+ }
7926
+
7927
+ // kludge to inhibit hover effect while dragging a box
7928
+ if (gui.keydown) hitData.id = -1;
7929
+ return hitData;
7930
+ }
7931
+
7913
7932
  function mergeHoverData(hitData) {
7914
7933
  if (storedData.pinned) {
7915
7934
  hitData.id = storedData.id;
@@ -7964,14 +7983,21 @@
7964
7983
  return true; // special case -- using hover for line drawing animation
7965
7984
  }
7966
7985
 
7986
+ if (type == 'click' && gui.keyboard.ctrlIsPressed()) {
7987
+ return false; // don't fire if context menu might open
7988
+ }
7989
+ if (type == 'click' && gui.contextMenu.isOpen()) {
7990
+ return false;
7991
+ }
7992
+ if (type == 'click' && interactionMode == 'drawing') {
7993
+ return true; // click events are triggered even if no shape is hit
7994
+ }
7995
+
7967
7996
  // ignore pointer events when no features are being hit
7968
7997
  // (don't block pan and other navigation when events aren't being used for editing)
7969
7998
  var hitId = self.getHitId();
7970
7999
  if (hitId == -1) return false;
7971
8000
 
7972
- if (type == 'click' && !clickable()) {
7973
- return false;
7974
- }
7975
8001
  if ((type == 'drag' || type == 'dragstart' || type == 'dragend') && !draggable()) {
7976
8002
  return false;
7977
8003
  }
@@ -8000,10 +8026,13 @@
8000
8026
  // evt: event data (may be a pointer event object, an ordinary object or null)
8001
8027
  function triggerHitEvent(type, evt) {
8002
8028
  var eventData = {
8003
- mode: interactionMode,
8004
- overMap: evt ? isOverMap(evt) : null,
8005
- originalEvent: evt ? evt : null
8029
+ mode: interactionMode
8006
8030
  };
8031
+ if (evt) {
8032
+ eventData.coordinates = translateDisplayPoint(targetLayer, ext.translatePixelCoords(evt.x, evt.y));
8033
+ eventData.originalEvent = evt;
8034
+ eventData.overMap = isOverMap(evt);
8035
+ }
8007
8036
  // Merge stored hit data into the event data
8008
8037
  utils$1.defaults(eventData, evt && evt.data || {}, storedData);
8009
8038
  // utils.extend(eventData, storedData);
@@ -8050,7 +8079,7 @@
8050
8079
 
8051
8080
  function enabled() {
8052
8081
  var lyr = gui.map.getActiveLayer();
8053
- return !!(lyr && lyr.layer.geometry_type);
8082
+ return !!(lyr && lyr.gui.displayLayer.geometry_type);
8054
8083
  }
8055
8084
 
8056
8085
  gui.model.on('update', function(e) {
@@ -8095,7 +8124,7 @@
8095
8124
  var p2 = gui.map.translatePixelCoords(ext.width(), 0);
8096
8125
  var bbox = p1.concat(p2);
8097
8126
  var decimals = internal.getBoundsPrecisionForDisplay(bbox);
8098
- var str = internal.getRoundedCoordString(p, decimals);
8127
+ var str = internal.getRoundedCoordString(p, decimals);
8099
8128
  readout.text(str).show();
8100
8129
  }
8101
8130
 
@@ -8376,6 +8405,9 @@
8376
8405
  element.addEventListener('mouseout', onAreaOut);
8377
8406
  element.addEventListener('mousedown', onAreaDown);
8378
8407
  element.addEventListener('dblclick', onAreaDblClick);
8408
+ document.addEventListener('contextmenu', function(e) {
8409
+ _self.dispatchEvent('contextmenu', procMouseEvent(e));
8410
+ });
8379
8411
 
8380
8412
  this.enable = function() {
8381
8413
  if (!_disabled) return;
@@ -8943,8 +8975,8 @@
8943
8975
  gui.on('interaction_mode_change', function(e) {
8944
8976
  if (e.mode === 'selection') {
8945
8977
  gui.enterMode('selection_tool');
8946
- } else if (gui.getMode() == 'selection_tool') {
8947
- gui.clearMode();
8978
+ } else if (_on) {
8979
+ turnOff();
8948
8980
  }
8949
8981
  });
8950
8982
 
@@ -9410,8 +9442,17 @@
9410
9442
  }
9411
9443
  });
9412
9444
 
9445
+ hit.on('contextmenu', function(e) {
9446
+ var target = hit.getHitTarget();
9447
+ if (!e.overMap || !target) return;
9448
+
9449
+ if (e.mode == 'drawing') return; // TODO: make special menu for this mode
9450
+ gui.contextMenu.open(e, hit.getHitTarget());
9451
+ });
9452
+
9413
9453
  hit.on('change', function(e) {
9414
9454
  if (!inspecting()) return;
9455
+ if (gui.keyboard.ctrlIsPressed()) return;
9415
9456
  var ids;
9416
9457
  if (e.mode == 'selection') {
9417
9458
  ids = e.pinned && e.ids || [];
@@ -9424,8 +9465,8 @@
9424
9465
  // id: Id of a feature in the active layer, or -1
9425
9466
  function inspect(id, ids, pin) {
9426
9467
  var target = hit.getHitTarget();
9427
- if ((id > -1 || ids && ids.length > 0) && inspecting() && target && target.layer) {
9428
- _popup.show(id, ids, target.layer, pin);
9468
+ if ((id > -1 || ids && ids.length > 0) && inspecting() && target && target) {
9469
+ _popup.show(id, ids, target, pin);
9429
9470
  } else {
9430
9471
  _popup.hide();
9431
9472
  }
@@ -9529,15 +9570,20 @@
9529
9570
 
9530
9571
  function initLabelDragging(gui, ext, hit) {
9531
9572
  var downEvt;
9532
- var activeId;
9573
+ var activeId = -1;
9574
+ var prevHitEvt;
9533
9575
  var activeRecord;
9534
9576
 
9535
- function active(e) {
9536
- return e.id > -1 && gui.interaction.getMode() == 'labels';
9577
+ function active() {
9578
+ return gui.interaction.getMode() == 'labels';
9579
+ }
9580
+
9581
+ function labelSelected(e) {
9582
+ return e.id > -1 && active();
9537
9583
  }
9538
9584
 
9539
9585
  hit.on('dragstart', function(e) {
9540
- if (!active(e)) return;
9586
+ if (!labelSelected(e)) return;
9541
9587
  var symNode = getSymbolTarget(e);
9542
9588
  var table = hit.getTargetDataTable();
9543
9589
  if (!symNode || !table) {
@@ -9550,8 +9596,25 @@
9550
9596
  gui.dispatchEvent('label_dragstart', {FID: activeId});
9551
9597
  });
9552
9598
 
9599
+ hit.on('change', function(e) {
9600
+ if (!active()) return;
9601
+ if (prevHitEvt) clearLabelHighlight(prevHitEvt);
9602
+ showLabelHighlight(e);
9603
+ prevHitEvt = e;
9604
+ });
9605
+
9606
+ function clearLabelHighlight(e) {
9607
+ var txt = getTextNode(getSymbolTarget(e));
9608
+ if (txt) txt.classList.remove('active-label');
9609
+ }
9610
+
9611
+ function showLabelHighlight(e) {
9612
+ var txt = getTextNode(getSymbolTarget(e));
9613
+ if (txt) txt.classList.add('active-label');
9614
+ }
9615
+
9553
9616
  hit.on('drag', function(e) {
9554
- if (!active(e) || activeId == -1) return;
9617
+ if (!labelSelected(e) || activeId == -1) return;
9555
9618
  if (e.id != activeId) {
9556
9619
  error$1("Mismatched hit ids:", e.id, activeId);
9557
9620
  }
@@ -9565,7 +9628,7 @@
9565
9628
  // to anchor point, for better placement when eventual display font is
9566
9629
  // different from mapshaper's font.
9567
9630
  // if (!isMultilineLabel(textNode)) {
9568
- autoUpdateTextAnchor(textNode, activeRecord, getDisplayCoordsById(activeId, hit.getHitTarget().layer, ext));
9631
+ autoUpdateTextAnchor(textNode, activeRecord, getDisplayCoordsById(activeId, hit.getHitTarget(), ext));
9569
9632
  // }
9570
9633
  updateNumber(activeRecord, 'dx', internal.roundToDigits(+activeRecord.dx, 3));
9571
9634
  updateNumber(activeRecord, 'dy', internal.roundToDigits(+activeRecord.dy, 3));
@@ -9575,7 +9638,7 @@
9575
9638
  });
9576
9639
 
9577
9640
  hit.on('dragend', function(e) {
9578
- if (!active(e) || activeId == -1) return;
9641
+ if (!labelSelected(e) || activeId == -1) return;
9579
9642
  gui.dispatchEvent('label_dragend', {FID: e.id});
9580
9643
  activeId = -1;
9581
9644
  activeRecord = null;
@@ -9596,19 +9659,25 @@
9596
9659
  }
9597
9660
 
9598
9661
  function getSymbolTarget(e) {
9599
- if (e.id > -1 === false || !e.container) return null;
9600
- return getSymbolNodeById(e.id, e.container);
9662
+ return e.id > -1 ? getSymbolNodeById(e.id) : null;
9601
9663
  }
9602
9664
 
9603
9665
  function getTextNode(symNode) {
9666
+ if (!symNode) return null;
9604
9667
  if (symNode.tagName == 'text') return symNode;
9605
9668
  return symNode.querySelector('text');
9606
9669
  }
9607
9670
 
9608
- function getSymbolNodeById(id, parent) {
9671
+ // function getSymbolNodeById_OLD(id, parent) {
9672
+ // var sel = '[data-id="' + id + '"]';
9673
+ // return parent.querySelector(sel);
9674
+ // }
9675
+
9676
+ function getSymbolNodeById(id) {
9609
9677
  // TODO: optimize selector
9610
9678
  var sel = '[data-id="' + id + '"]';
9611
- return parent.querySelector(sel);
9679
+ var activeLayer = hit.getHitTarget();
9680
+ return activeLayer.gui.svg_container.querySelector(sel);
9612
9681
  }
9613
9682
 
9614
9683
  // function getTextTarget2(e) {
@@ -9655,7 +9724,7 @@
9655
9724
  // fails when symbol includes a dot (<g><circle/><text/></g> structure)
9656
9725
  function updateSymbolNode(node, d, id) {
9657
9726
  var o = internal.svg.renderStyledLabel(d); // TODO: symbol support
9658
- var activeLayer = hit.getHitTarget().layer;
9727
+ var activeLayer = hit.getHitTarget();
9659
9728
  var xy = activeLayer.shapes[id][0];
9660
9729
  var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
9661
9730
  var node2;
@@ -9688,7 +9757,7 @@
9688
9757
  hit.on('drag', function(e) {
9689
9758
  if (!active(e)) return;
9690
9759
  // TODO: support multi points... get id of closest part to the pointer
9691
- var p = getPointCoordsById(e.id, symbolInfo.target.layer);
9760
+ var p = getPointCoordsById(e.id, symbolInfo.target);
9692
9761
  if (!p) return;
9693
9762
  var diff = translateDeltaDisplayCoords(e.dx, e.dy, ext);
9694
9763
  p[0] += diff[0];
@@ -9719,8 +9788,8 @@
9719
9788
  }
9720
9789
  }
9721
9790
 
9722
- // pointer thresholds for hovering near a vertex or segment midpoint
9723
- var HOVER_THRESHOLD$1 = 10;
9791
+ // pixel distance threshold for hovering near a vertex or segment midpoint
9792
+ var HOVER_THRESHOLD = 10;
9724
9793
 
9725
9794
  function initLineEditing(gui, ext, hit) {
9726
9795
  var hoverVertexInfo;
@@ -9746,7 +9815,7 @@
9746
9815
  }
9747
9816
 
9748
9817
  function polygonMode() {
9749
- return active() && hit.getHitTarget().layer.geometry_type == 'polygon';
9818
+ return active() && hit.getHitTarget().geometry_type == 'polygon';
9750
9819
  }
9751
9820
 
9752
9821
  function clearHoverVertex() {
@@ -9762,7 +9831,7 @@
9762
9831
  if (e.mode == 'drawing') {
9763
9832
  gui.enterMode('drawing_tool');
9764
9833
  } else if (active()) {
9765
- gui.clearMode();
9834
+ turnOff();
9766
9835
  }
9767
9836
  }, null, 10); // higher priority than hit control, so turnOff() has correct hit target
9768
9837
 
@@ -9789,27 +9858,10 @@
9789
9858
  appendVertex$1(target, e.p);
9790
9859
  }
9791
9860
  if (e.shapes) {
9792
- replaceShapes(e.shapes);
9861
+ replaceDrawnShapes(e.shapes);
9793
9862
  }
9794
9863
  });
9795
9864
 
9796
- function replaceShapes(shapes) {
9797
- var target = hit.getHitTarget();
9798
- var records = target.layer.data?.getRecords();
9799
- var prevLen = target.layer.shapes.length;
9800
- var newLen = initialShapeCount + shapes.length;
9801
- var recordCount = records?.length || 0;
9802
- target.layer.shapes = target.layer.shapes.slice(0, initialShapeCount).concat(shapes);
9803
- while (records && records.length > newLen) {
9804
- records.pop();
9805
- }
9806
- while (records && records.length < newLen) {
9807
- appendNewDataRecord(target.layer);
9808
- }
9809
- }
9810
-
9811
-
9812
-
9813
9865
  gui.on('undo_path_extend', function(e) {
9814
9866
  var target = hit.getHitTarget();
9815
9867
  if (drawing() && prevHoverEvent) {
@@ -9822,14 +9874,14 @@
9822
9874
  gui.undo.undo(); // remove the path
9823
9875
  }
9824
9876
  if (e.shapes) {
9825
- replaceShapes(e.shapes);
9877
+ replaceDrawnShapes(e.shapes);
9826
9878
  }
9827
9879
  });
9828
9880
 
9829
9881
  function turnOn() {
9830
9882
  var target = hit.getHitTarget();
9831
- initialArcCount = target.arcs.size();
9832
- initialShapeCount = target.layer.shapes.length;
9883
+ initialArcCount = target.gui.displayArcs.size();
9884
+ initialShapeCount = target.shapes.length;
9833
9885
  if (sessionCount === 0) {
9834
9886
  showInstructions();
9835
9887
  }
@@ -9845,6 +9897,12 @@
9845
9897
  non_blocking: true, max_width: '360px'});
9846
9898
  }
9847
9899
 
9900
+ function hideInstructions() {
9901
+ if (!alert) return;
9902
+ alert.close('fade');
9903
+ alert = null;
9904
+ }
9905
+
9848
9906
  function turnOff() {
9849
9907
  var removed = 0;
9850
9908
  finishCurrentPath();
@@ -9852,10 +9910,7 @@
9852
9910
  removed = removeOpenPolygons();
9853
9911
  }
9854
9912
  clearDrawingInfo();
9855
- if (alert) {
9856
- alert.close();
9857
- alert = null;
9858
- }
9913
+ hideInstructions();
9859
9914
  initialArcCount = -1;
9860
9915
  initialShapeCount = -1;
9861
9916
  if (gui.interaction.getMode() == 'drawing') {
@@ -9870,20 +9925,21 @@
9870
9925
  // returns number of removed shapes
9871
9926
  function removeOpenPolygons() {
9872
9927
  var target = hit.getHitTarget();
9873
- var arcs = target.source.dataset.arcs;
9874
- var n = target.layer.shapes.length;
9875
- // delete open paths (should only occur on single-arc shapes)
9928
+ var arcs = target.gui.source.dataset.arcs;
9929
+ var n = target.shapes.length;
9930
+ // delete open paths
9876
9931
  for (var i=initialShapeCount; i<n; i++) {
9877
- var shp = target.layer.shapes[i];
9878
- if (!geom.pathIsClosed(shp[0], arcs)) {
9879
- target.layer.shapes[i] = null;
9932
+ var shp = target.shapes[i];
9933
+ if (!geom.pathIsClosed(shp[0], arcs)) { // assume open paths have one arc
9934
+ target.shapes[i] = null;
9880
9935
  }
9881
9936
  }
9882
- // removes polygons with wrong winding order and null geometry
9883
- mapshaper.cmd.filterFeatures(target.layer, arcs, {remove_empty: true, quiet: true});
9884
- return n - target.layer.shapes.length;
9937
+ // removes features with wrong winding order or null geometry
9938
+ mapshaper.cmd.filterFeatures(target, arcs, {remove_empty: true, quiet: true});
9939
+ return n - target.shapes.length;
9885
9940
  }
9886
9941
 
9942
+ // updates display arcs and redraws all layers
9887
9943
  function fullRedraw() {
9888
9944
  gui.model.updated({arc_count: true});
9889
9945
  }
@@ -9895,13 +9951,25 @@
9895
9951
  prevClickEvent = prevHoverEvent = null;
9896
9952
  }
9897
9953
 
9954
+ hit.on('contextmenu', function(e) {
9955
+ if (!active() || drawing() || dragging()) return;
9956
+ var target = hit.getHitTarget();
9957
+ var vInfo = hoverVertexInfo;
9958
+ if (hoverVertexInfo?.type == 'vertex' && !vertexIsEndpoint(vInfo, target)) {
9959
+ e.deleteVertex = function() {
9960
+ deleteActiveVertex(e, vInfo);
9961
+ };
9962
+ }
9963
+ // don't allow copying of open paths as geojson in polygon mode
9964
+ gui.contextMenu.open(e, target);
9965
+ });
9966
+
9898
9967
  hit.on('dragstart', function(e) {
9899
9968
  if (!active() || drawing() || !hoverVertexInfo) return;
9900
- if (alert) {
9901
- alert.close('fade');
9902
- }
9969
+ hideInstructions();
9903
9970
  e.originalEvent.stopPropagation();
9904
9971
  _dragging = true;
9972
+ updateCursor();
9905
9973
  if (hoverVertexInfo.type == 'interpolated') {
9906
9974
  insertVertex$1(hit.getHitTarget(), hoverVertexInfo.i, hoverVertexInfo.point);
9907
9975
  hoverVertexInfo.ids = [hoverVertexInfo.i];
@@ -9915,11 +9983,10 @@
9915
9983
  var target = hit.getHitTarget();
9916
9984
  var p = ext.translatePixelCoords(e.x, e.y);
9917
9985
  if (gui.keyboard.shiftIsPressed()) {
9918
- internal.snapPointToArcEndpoint(p, hoverVertexInfo.ids, target.arcs);
9986
+ internal.snapPointToArcEndpoint(p, hoverVertexInfo.ids, target.gui.displayArcs);
9919
9987
  }
9920
- internal.snapVerticesToPoint(hoverVertexInfo.ids, p, target.arcs);
9988
+ internal.snapVerticesToPoint(hoverVertexInfo.ids, p, target.gui.displayArcs);
9921
9989
  hit.setHoverVertex(p, '');
9922
-
9923
9990
  // redrawing the whole map updates the data layer as well as the overlay layer
9924
9991
  // gui.dispatchEvent('map-needs-refresh');
9925
9992
  });
@@ -9927,9 +9994,9 @@
9927
9994
  hit.on('dragend', function(e) {
9928
9995
  if (!dragging()) return;
9929
9996
  _dragging = false;
9930
- // kludge to get dataset to recalculate internal bounding boxes
9931
9997
  var target = hit.getHitTarget();
9932
- target.arcs.transformPoints(function() {});
9998
+ // kludge to get dataset to recalculate internal bounding boxes
9999
+ target.gui.displayArcs.transformPoints(function() {});
9933
10000
  updateVertexCoords(target, hoverVertexInfo.ids);
9934
10001
  gui.dispatchEvent('vertex_dragend', hoverVertexInfo);
9935
10002
  gui.dispatchEvent('map-needs-refresh'); // redraw basemap
@@ -9953,6 +10020,7 @@
9953
10020
  // ... or the closest point along the segment (for adding a new vertex)
9954
10021
  hit.on('hover', function(e) {
9955
10022
  if (!active() || dragging()) return;
10023
+
9956
10024
  if (drawing()) {
9957
10025
  if (!e.overMap) {
9958
10026
  finishCurrentPath();
@@ -9975,6 +10043,7 @@
9975
10043
  } else {
9976
10044
  clearHoverVertex();
9977
10045
  }
10046
+ updateCursor();
9978
10047
  prevHoverEvent = e;
9979
10048
  }, null, 100);
9980
10049
 
@@ -9985,23 +10054,22 @@
9985
10054
  var p = pixToDataCoords(e.x, e.y);
9986
10055
  if (drawing()) {
9987
10056
  extendCurrentPath(hoverVertexInfo?.point || p);
9988
- // extendCurrentPath(p); // just extend to current mouse position (not hover vertex)
9989
10057
  } else if (gui.keyboard.shiftIsPressed()) {
9990
10058
  deleteActiveVertex(e);
9991
10059
  } else {
9992
10060
  startNewPath(p);
9993
- if (alert) {
9994
- alert.close('fade');
9995
- }
10061
+ hideInstructions();
10062
+ updateCursor();
9996
10063
  }
9997
10064
  prevClickEvent = e;
9998
10065
  });
9999
10066
 
10000
- // esc key finishes a path
10067
+ // esc or enter key finishes a path
10001
10068
  gui.keyboard.on('keydown', function(e) {
10002
10069
  if (active() && (e.keyName == 'esc' || e.keyName == 'enter')) {
10003
10070
  e.stopPropagation();
10004
10071
  finishCurrentPath();
10072
+ e.originalEvent.preventDefault(); // block console "enter"
10005
10073
  }
10006
10074
  }, null, 10);
10007
10075
 
@@ -10015,16 +10083,24 @@
10015
10083
  return dbl;
10016
10084
  }
10017
10085
 
10018
- function deleteActiveVertex(e) {
10019
- var info = findDraggableVertices(e);
10020
- if (!info) return;
10086
+ function updateCursor() {
10087
+ var useArrow = hoverVertexInfo && !hoverVertexInfo.extendable && !drawing();
10088
+ gui.container.findChild('.map-layers').classed('dragging', useArrow);
10089
+ }
10090
+
10091
+ function vertexIsEndpoint(info, target) {
10092
+ var vId = info.ids[0];
10093
+ return internal.vertexIsArcStart(vId, target.gui.displayArcs) ||
10094
+ internal.vertexIsArcEnd(vId, target.gui.displayArcs);
10095
+ }
10096
+
10097
+ // info: optional vertex info object
10098
+ function deleteActiveVertex(e, infoArg) {
10099
+ var info = infoArg || findDraggableVertices(e);
10100
+ if (!info || info.type != 'vertex') return;
10021
10101
  var vId = info.ids[0];
10022
10102
  var target = hit.getHitTarget();
10023
- if (internal.vertexIsArcStart(vId, target.arcs) ||
10024
- internal.vertexIsArcEnd(vId, target.arcs)) {
10025
- // TODO: support removing arc endpoints
10026
- return;
10027
- }
10103
+ if (vertexIsEndpoint(info, target)) return;
10028
10104
  gui.dispatchEvent('vertex_delete', {
10029
10105
  target: target,
10030
10106
  vertex_id: vId
@@ -10050,25 +10126,27 @@
10050
10126
  var dist2 = dist / Math.sqrt(2);
10051
10127
  var minDist = Infinity;
10052
10128
  var cands = [
10053
- {x: x0, y: y0 + dist},
10054
- {x: x0, y: y0 - dist},
10055
- {x: x0 + dist, y: y0},
10056
- {x: x0 - dist, y: y0},
10057
- {x: x0 + dist2, y: y0 + dist2},
10058
- {x: x0 + dist2, y: y0 - dist2},
10059
- {x: x0 - dist2, y: y0 + dist2},
10060
- {x: x0 - dist2, y: y0 - dist2}
10129
+ {dx: 0, dy: dist},
10130
+ {dx: 0, dy: -dist},
10131
+ {dx: dist, dy: 0},
10132
+ {dx: -dist, dy: 0},
10133
+ {dx: dist2, dy: dist2},
10134
+ {dx: dist2, dy: -dist2},
10135
+ {dx: -dist2, dy: dist2},
10136
+ {dx: -dist2, dy: -dist2}
10061
10137
  ];
10062
10138
  var snapped = cands.reduce(function(memo, cand) {
10063
- var dist = geom.distance2D(thisEvt.x, thisEvt.y, cand.x, cand.y);
10139
+ var dist = geom.distance2D(thisEvt.x, thisEvt.y, x0 + cand.dx, y0 + cand.dy);
10064
10140
  if (dist < minDist) {
10065
10141
  minDist = dist;
10066
10142
  return cand;
10067
10143
  }
10068
10144
  return memo;
10069
10145
  }, null);
10070
- thisEvt.x = snapped.x;
10071
- thisEvt.y = snapped.y;
10146
+ thisEvt.x = x0 + snapped.dx;
10147
+ thisEvt.y = y0 + snapped.dy;
10148
+
10149
+ return null;
10072
10150
  }
10073
10151
 
10074
10152
  function finishCurrentPath() {
@@ -10089,7 +10167,7 @@
10089
10167
  var p1 = hoverVertexInfo?.point || p2;
10090
10168
  appendNewPath(target, p1, p2);
10091
10169
  gui.dispatchEvent('path_add', {target, p1, p2});
10092
- drawingId = target.layer.shapes.length - 1;
10170
+ drawingId = target.shapes.length - 1;
10093
10171
  hit.setDrawingId(drawingId);
10094
10172
  }
10095
10173
 
@@ -10103,11 +10181,11 @@
10103
10181
  error$1('Defective path');
10104
10182
  }
10105
10183
  if (finish && polygonMode()) {
10106
- shapes1 = target.layer.shapes.slice(initialShapeCount);
10107
- shapes2 = tryToClosePath(shapes1);
10184
+ shapes1 = target.shapes.slice(initialShapeCount);
10185
+ shapes2 = convertClosedPaths(shapes1);
10108
10186
  }
10109
10187
  if (shapes2) {
10110
- replaceShapes(shapes2);
10188
+ replaceDrawnShapes(shapes2);
10111
10189
  gui.dispatchEvent('path_extend', {target, p, shapes1, shapes2});
10112
10190
  clearDrawingInfo();
10113
10191
  fullRedraw();
@@ -10117,13 +10195,27 @@
10117
10195
  gui.dispatchEvent('path_extend', {target, p});
10118
10196
  hit.triggerChangeEvent(); // trigger overlay redraw
10119
10197
  }
10198
+ }
10120
10199
 
10200
+ function replaceDrawnShapes(shapes) {
10201
+ var target = hit.getHitTarget();
10202
+ var records = target.data?.getRecords();
10203
+ var prevLen = target.shapes.length;
10204
+ var newLen = initialShapeCount + shapes.length;
10205
+ var recordCount = records?.length || 0;
10206
+ target.shapes = target.shapes.slice(0, initialShapeCount).concat(shapes);
10207
+ while (records && records.length > newLen) {
10208
+ records.pop();
10209
+ }
10210
+ while (records && records.length < newLen) {
10211
+ appendNewDataRecord(target);
10212
+ }
10121
10213
  }
10122
10214
 
10123
10215
  // p: [x, y] source data coordinates
10124
10216
  function updatePathEndpoint(p) {
10125
10217
  var target = hit.getHitTarget();
10126
- var i = target.arcs.getPointCount() - 1;
10218
+ var i = target.gui.displayArcs.getPointCount() - 1;
10127
10219
  if (hoverVertexInfo) {
10128
10220
  p = hoverVertexInfo.point; // snap to selected point
10129
10221
  }
@@ -10133,8 +10225,8 @@
10133
10225
 
10134
10226
  function findPathStartInfo(e) {
10135
10227
  var target = hit.getHitTarget();
10136
- var arcId = target.arcs.size() - 1;
10137
- var data = target.arcs.getVertexData();
10228
+ var arcId = target.gui.displayArcs.size() - 1;
10229
+ var data = target.gui.displayArcs.getVertexData();
10138
10230
  var i = data.ii[arcId];
10139
10231
  var x = data.xx[i];
10140
10232
  var y = data.yy[i];
@@ -10142,7 +10234,7 @@
10142
10234
  var dist = geom.distance2D(p[0], p[1], x, y);
10143
10235
  var pathLen = data.nn[arcId];
10144
10236
  var pixelDist = dist / ext.getPixelSize();
10145
- if (pixelDist > HOVER_THRESHOLD$1 || pathLen < 4) {
10237
+ if (pixelDist > HOVER_THRESHOLD || pathLen < 4) {
10146
10238
  return null;
10147
10239
  }
10148
10240
  var point = translateDisplayPoint(target, [x, y]);
@@ -10156,41 +10248,38 @@
10156
10248
  //
10157
10249
  function findDraggableVertices(e) {
10158
10250
  var target = hit.getHitTarget();
10159
- var shp = target.layer.shapes[e.id];
10251
+ var shp = target.shapes[e.id];
10160
10252
  var p = ext.translatePixelCoords(e.x, e.y);
10161
- var ids = internal.findNearestVertices(p, shp, target.arcs);
10162
- var p2 = target.arcs.getVertex2(ids[0]);
10253
+ var ids = internal.findNearestVertices(p, shp, target.gui.displayArcs);
10254
+ var p2 = target.gui.displayArcs.getVertex2(ids[0]);
10163
10255
  var dist = geom.distance2D(p[0], p[1], p2[0], p2[1]);
10164
10256
  var pixelDist = dist / ext.getPixelSize();
10165
- if (pixelDist > HOVER_THRESHOLD$1) {
10257
+ if (pixelDist > HOVER_THRESHOLD) {
10166
10258
  return null;
10167
10259
  }
10168
10260
  var point = getVertexCoords(target, ids[0]); // data coordinates
10169
10261
  // find out if the vertex is the endpoint of a single path
10170
10262
  // (which could be extended by a newly drawn path)
10171
10263
  var extendable = ids.length == 1 &&
10172
- internal.vertexIsArcEndpoint(ids[0], target.arcs);
10173
- var displayPoint = target.arcs.getVertex2(ids[0]);
10264
+ internal.vertexIsArcEndpoint(ids[0], target.gui.displayArcs);
10265
+ var displayPoint = target.gui.displayArcs.getVertex2(ids[0]);
10174
10266
  return {target, ids, extendable, point, displayPoint, type: 'vertex'};
10175
10267
  }
10176
10268
 
10269
+
10177
10270
  function findInterpolatedPoint(e) {
10178
10271
  var target = hit.getHitTarget();
10179
10272
  //// vertex insertion not supported with simplification
10180
10273
  // if (!target.arcs.isFlat()) return null;
10181
10274
  var p = ext.translatePixelCoords(e.x, e.y);
10182
10275
  var minDist = Infinity;
10183
- var shp = target.layer.shapes[e.id];
10276
+ var shp = target.shapes[e.id];
10184
10277
  var closest;
10185
- internal.forEachSegmentInShape(shp, target.arcs, function(i, j, xx, yy) {
10278
+ internal.forEachSegmentInShape(shp, target.gui.displayArcs, function(i, j, xx, yy) {
10186
10279
  var x1 = xx[i],
10187
10280
  y1 = yy[i],
10188
10281
  x2 = xx[j],
10189
10282
  y2 = yy[j],
10190
- // switching from midpoint to nearest point to the mouse
10191
- // cx = (x1 + x2) / 2,
10192
- // cy = (y1 + y2) / 2,
10193
- // p2 = [cx, cy],
10194
10283
  p2 = internal.findClosestPointOnSeg(p[0], p[1], x1, y1, x2, y2, 0),
10195
10284
  dist = geom.distance2D(p2[0], p2[1], p[0], p[1]);
10196
10285
  if (dist < minDist) {
@@ -10203,7 +10292,7 @@
10203
10292
  }
10204
10293
  });
10205
10294
 
10206
- if (closest.distance / ext.getPixelSize() > HOVER_THRESHOLD$1) {
10295
+ if (closest.distance / ext.getPixelSize() > HOVER_THRESHOLD) {
10207
10296
  return null;
10208
10297
  }
10209
10298
  closest.point = translateDisplayPoint(target, closest.displayPoint);
@@ -10212,49 +10301,39 @@
10212
10301
  return closest;
10213
10302
  }
10214
10303
 
10215
- // shapes: shapes that have been drawn in the current session
10216
- //
10217
- function tryToClosePath(shapes) {
10304
+ // Try to form polygon shapes from an array of path shapes
10305
+ // shapes: array of all shapes that have been drawn in the current session
10306
+ function convertClosedPaths(shapes) {
10218
10307
  var target = hit.getHitTarget();
10308
+ // try to convert paths to polygons
10309
+ // NOTE: added "no_cuts" option to prevent polygons function from modifying
10310
+ // arcs, which would break undo/redo and cause other problems
10219
10311
  var tmpLyr = {
10220
10312
  geometry_type: 'polyline',
10221
10313
  shapes: shapes.concat()
10222
10314
  };
10223
- // create a temp dataset containing tmp layer and original layers
10224
- // (so original arcs are retained)
10225
- var tmpDataset = Object.assign({}, target.source.dataset);
10226
- tmpDataset.layers = tmpDataset.layers.concat(tmpLyr);
10227
- // NOTE: added "no_cuts" option to prevent polygons function from modifying
10228
- // arcs, which would break undo/redo and cause other problems
10229
- var outputLyr = mapshaper.cmd.polygons([tmpLyr], tmpDataset, {no_cuts: true})[0];
10230
- var isOpenPath = getOpenPathTest(outputLyr.shapes);
10231
- var shapes2 = [];
10232
- shapes.forEach(function(shp) {
10233
- if (isOpenPath(shp)) {
10234
- shapes2.push(shp);
10235
- }
10236
- });
10237
- return shapes2.concat(outputLyr.shapes);
10315
+ var output = mapshaper.cmd.polygons([tmpLyr], target.gui.source.dataset, {no_cuts: true});
10316
+ var closedShapes = output[0].shapes;
10317
+
10318
+ // find paths that were not convertible to polygons
10319
+ var isOpenPath = getOpenPathTest(closedShapes);
10320
+ var openShapes = shapes.filter(function(shp) { return isOpenPath(shp); });
10321
+
10322
+ // retain both converted polygons and unconverted polylines
10323
+ return openShapes.concat(closedShapes);
10238
10324
  }
10239
10325
 
10240
10326
  // Returns a function for testing if a shape is an unclosed path, and doesn't
10241
10327
  // overlap with an array of polygon shapes
10242
- // polygons: array of polygon shapes
10243
- function getOpenPathTest(polygons) {
10244
- var arcs = [];
10245
- internal.forEachArcId(polygons, function(arcId) {
10246
- if (arcId < 0) arcId = ~arcId;
10247
- arcs.push(arcId);
10328
+ function getOpenPathTest(polygonShapes) {
10329
+ var polygonArcs = [];
10330
+ internal.forEachArcId(polygonShapes, function(arcId) {
10331
+ polygonArcs.push(internal.absArcId(arcId));
10248
10332
  });
10333
+
10249
10334
  return function(shp) {
10250
- // assume that compound shapes are already polygons
10251
- var isOpen = false;
10252
- if (shapeHasOneFwdArc(shp)) {
10253
- var arcId = shp[0][0];
10254
- if (arcId < 0) arcId = ~arcId;
10255
- isOpen = !arcs.includes(arcId);
10256
- }
10257
- return isOpen;
10335
+ // assume that any compound shape is a polygon
10336
+ return shapeHasOneFwdArc(shp) && !polygonArcs.includes(shp[0][0]);
10258
10337
  };
10259
10338
  }
10260
10339
 
@@ -10283,8 +10362,7 @@
10283
10362
  // x, y: pixel coordinates
10284
10363
  function addPoint(x, y) {
10285
10364
  var p = ext.translatePixelCoords(x, y);
10286
- var target = hit.getHitTarget();
10287
- var lyr = target.layer;
10365
+ var lyr = hit.getHitTarget();
10288
10366
  var fid = lyr.shapes.length;
10289
10367
  var d = appendNewDataRecord(lyr);
10290
10368
  if (d) {
@@ -10293,186 +10371,10 @@
10293
10371
  lyr.data.getRecords()[fid] = d;
10294
10372
  }
10295
10373
  lyr.shapes[fid] = [p];
10296
- updatePointCoords(target, fid);
10297
- }
10298
-
10299
-
10300
- }
10301
-
10302
- // pointer thresholds for hovering near a vertex or segment midpoint
10303
- var HOVER_THRESHOLD = 8;
10304
- var MIDPOINT_THRESHOLD = 11;
10305
-
10306
- function initVertexDragging(gui, ext, hit) {
10307
- var insertionPoint;
10308
- var dragInfo;
10309
-
10310
- function active() {
10311
- return gui.interaction.getMode() == 'vertices';
10312
- }
10313
-
10314
- function dragging() {
10315
- return active() && !!dragInfo;
10316
- }
10317
-
10318
- function setHoverVertex(id) {
10319
- var target = hit.getHitTarget();
10320
- hit.setHoverVertex(target.arcs.getVertex2(id));
10321
- }
10322
-
10323
- function clearHoverVertex() {
10324
- hit.clearHoverVertex();
10325
- }
10326
-
10327
- // return data on the nearest vertex (or identical vertices) to the pointer
10328
- // (if within a distance threshold)
10329
- //
10330
- function findDraggableVertices(e) {
10331
- var target = hit.getHitTarget();
10332
- var shp = target.layer.shapes[e.id];
10333
- var p = ext.translatePixelCoords(e.x, e.y);
10334
- var nearestIds = internal.findNearestVertices(p, shp, target.arcs);
10335
- var p2 = target.arcs.getVertex2(nearestIds[0]);
10336
- var dist = geom.distance2D(p[0], p[1], p2[0], p2[1]);
10337
- var pixelDist = dist / ext.getPixelSize();
10338
- if (pixelDist > HOVER_THRESHOLD) {
10339
- return null;
10340
- }
10341
- var points = nearestIds.map(function(i) {
10342
- return getVertexCoords(target, i); // data coordinates
10343
- });
10344
- return {
10345
- target: target,
10346
- ids: nearestIds,
10347
- points: points
10348
- };
10374
+ updatePointCoords(lyr, fid);
10349
10375
  }
10350
10376
 
10351
- function findVertexInsertionPoint(e) {
10352
- var target = hit.getHitTarget();
10353
- //// vertex insertion not supported with simplification
10354
- // if (!target.arcs.isFlat()) return null;
10355
- var p = ext.translatePixelCoords(e.x, e.y);
10356
- var midpoint = findNearestMidpoint(p, e.id, target);
10357
- if (!midpoint ||
10358
- midpoint.distance / ext.getPixelSize() > MIDPOINT_THRESHOLD) return null;
10359
- return midpoint;
10360
- }
10361
-
10362
- hit.on('dragstart', function(e) {
10363
- if (!active()) return;
10364
- if (insertionPoint) {
10365
- var target = hit.getHitTarget();
10366
- insertVertex$1(target, insertionPoint.i, insertionPoint.point);
10367
- dragInfo = {
10368
- target: target,
10369
- insertion: true,
10370
- ids: [insertionPoint.i],
10371
- points: [insertionPoint.point]
10372
- };
10373
- insertionPoint = null;
10374
- } else {
10375
- dragInfo = findDraggableVertices(e);
10376
- }
10377
- if (dragInfo) {
10378
- setHoverVertex(dragInfo.ids[0]);
10379
- }
10380
- });
10381
-
10382
- hit.on('drag', function(e) {
10383
- if (!dragging()) return;
10384
- var target = hit.getHitTarget();
10385
- var p = ext.translatePixelCoords(e.x, e.y);
10386
- if (gui.keyboard.shiftIsPressed()) {
10387
- internal.snapPointToArcEndpoint(p, dragInfo.ids, target.arcs);
10388
- }
10389
- internal.snapVerticesToPoint(dragInfo.ids, p, target.arcs);
10390
- setHoverVertex(dragInfo.ids[0]);
10391
- // redrawing the whole map updates the data layer as well as the overlay layer
10392
- // gui.dispatchEvent('map-needs-refresh');
10393
- });
10394
-
10395
- hit.on('dragend', function(e) {
10396
- if (!dragging()) return;
10397
- // kludge to get dataset to recalculate internal bounding boxes
10398
- hit.getHitTarget().arcs.transformPoints(function() {});
10399
- clearHoverVertex();
10400
- updateVertexCoords(dragInfo.target, dragInfo.ids);
10401
- gui.dispatchEvent('vertex_dragend', dragInfo);
10402
- gui.dispatchEvent('map-needs-refresh');
10403
- dragInfo = null;
10404
- });
10405
-
10406
- hit.on('dblclick', function(e) {
10407
- if (!active()) return;
10408
- var info = findDraggableVertices(e); // same selection criteria as for dragging
10409
- if (!info) return;
10410
- var target = hit.getHitTarget();
10411
- var vId = info.ids[0];
10412
- if (internal.vertexIsArcStart(vId, target.arcs) ||
10413
- internal.vertexIsArcEnd(vId, target.arcs)) {
10414
- // TODO: support removing arc endpoints
10415
- return;
10416
- }
10417
- gui.dispatchEvent('vertex_delete', {
10418
- target: target,
10419
- vertex_id: vId
10420
- });
10421
- deleteVertex$1(target, vId);
10422
- clearHoverVertex();
10423
- gui.dispatchEvent('map-needs-refresh');
10424
- });
10425
-
10426
- // highlight hit vertex in path edit mode
10427
- hit.on('hover', function(e) {
10428
- insertionPoint = null;
10429
- if (!active() || dragging()) return; // no hover effect while dragging
10430
- var info = findDraggableVertices(e);
10431
- if (info) {
10432
- // hovering near a vertex: highlight the vertex
10433
- setHoverVertex(info.ids[0]);
10434
- return;
10435
- }
10436
- // if hovering near a segment midpoint: show the midpoint and save midpoint info
10437
- insertionPoint = findVertexInsertionPoint(e);
10438
- if (insertionPoint) {
10439
- hit.setHoverVertex(insertionPoint.displayPoint);
10440
- } else {
10441
- // pointer is not over a vertex: clear any hover effect
10442
- clearHoverVertex();
10443
- }
10444
- }, null, 100);
10445
- }
10446
-
10447
10377
 
10448
- // Given a location @p (e.g. corresponding to the mouse pointer location),
10449
- // find the midpoint of two vertices on @shp suitable for inserting a new vertex
10450
- function findNearestMidpoint(p, fid, target) {
10451
- var arcs = target.arcs;
10452
- var shp = target.layer.shapes[fid];
10453
- var minDist = Infinity, v;
10454
- internal.forEachSegmentInShape(shp, arcs, function(i, j, xx, yy) {
10455
- var x1 = xx[i],
10456
- y1 = yy[i],
10457
- x2 = xx[j],
10458
- y2 = yy[j],
10459
- cx = (x1 + x2) / 2,
10460
- cy = (y1 + y2) / 2,
10461
- midpoint = [cx, cy],
10462
- dist = geom.distance2D(cx, cy, p[0], p[1]);
10463
- if (dist < minDist) {
10464
- minDist = dist;
10465
- v = {
10466
- i: (i < j ? i : j) + 1, // insertion point
10467
- segment: [i, j],
10468
- segmentLen: geom.distance2D(x1, y1, x2, y2),
10469
- displayPoint: midpoint,
10470
- point: translateDisplayPoint(target, midpoint),
10471
- distance: dist
10472
- };
10473
- }
10474
- });
10475
- return v || null;
10476
10378
  }
10477
10379
 
10478
10380
  function initInteractiveEditing(gui, ext, hit) {
@@ -10480,7 +10382,6 @@
10480
10382
  initPointDragging(gui, ext, hit);
10481
10383
  initPointDrawing(gui, ext, hit);
10482
10384
  initLineEditing(gui, ext, hit);
10483
- initVertexDragging(gui, ext, hit);
10484
10385
  }
10485
10386
 
10486
10387
  function MapExtent(_position) {
@@ -10854,47 +10755,46 @@
10854
10755
  }
10855
10756
 
10856
10757
  // TODO: consider moving this upstream
10857
- function getArcsForRendering(obj, ext) {
10858
- var dataset = obj.source.dataset;
10758
+ function getArcsForRendering(lyr, ext) {
10759
+ var dataset = lyr.gui.source.dataset;
10859
10760
  var sourceArcs = dataset.arcs;
10860
- if (obj.geographic && dataset.gui?.displayArcs) {
10761
+ if (lyr.gui.geographic && dataset.gui?.displayArcs) {
10861
10762
  return dataset.gui.displayArcs.getScaledArcs(ext);
10862
10763
  }
10863
- return obj.arcs;
10764
+ return lyr.gui.displayArcs;
10864
10765
  }
10865
10766
 
10866
- function drawOutlineLayerToCanvas(obj, canv, ext) {
10767
+ function drawOutlineLayerToCanvas(lyr, canv, ext) {
10867
10768
  var arcs;
10868
- var style = obj.style;
10769
+ var style = lyr.gui.style;
10770
+ var arcCounts = lyr.gui.arcCounts;
10869
10771
  var darkStyle = {strokeWidth: style.strokeWidth, strokeColor: style.strokeColors[1]},
10870
10772
  lightStyle = {strokeWidth: style.strokeWidth, strokeColor: style.strokeColors[0]};
10871
10773
  var filter;
10872
- if (internal.layerHasPaths(obj.layer)) {
10873
- if (!obj.arcCounts) {
10874
- obj.arcCounts = new Uint8Array(obj.arcs.size());
10875
- internal.countArcsInShapes(obj.layer.shapes, obj.arcCounts);
10774
+ if (internal.layerHasPaths(lyr.gui.displayLayer)) {
10775
+ if (!arcCounts) {
10776
+ arcCounts = lyr.gui.arcCounts = new Uint8Array(lyr.gui.displayArcs.size());
10777
+ internal.countArcsInShapes(lyr.gui.displayLayer.shapes, arcCounts);
10876
10778
  }
10877
- if (obj.arcCounts) {
10878
- arcs = getArcsForRendering(obj, ext);
10879
- if (lightStyle.strokeColor) {
10880
- filter = getArcFilter(arcs, ext, false, obj.arcCounts);
10881
- canv.drawArcs(arcs, lightStyle, filter);
10882
- }
10883
- if (darkStyle.strokeColor && obj.layer.geometry_type != 'point') {
10884
- filter = getArcFilter(arcs, ext, true, obj.arcCounts);
10885
- canv.drawArcs(arcs, darkStyle, filter);
10886
- }
10779
+ arcs = getArcsForRendering(lyr, ext);
10780
+ if (lightStyle.strokeColor) {
10781
+ filter = getArcFilter(arcs, ext, false, arcCounts);
10782
+ canv.drawArcs(arcs, lightStyle, filter);
10783
+ }
10784
+ if (darkStyle.strokeColor && lyr.gui.displayLayer.geometry_type != 'point') {
10785
+ filter = getArcFilter(arcs, ext, true, arcCounts);
10786
+ canv.drawArcs(arcs, darkStyle, filter);
10887
10787
  }
10888
10788
  }
10889
- if (obj.layer.geometry_type == 'point') {
10890
- canv.drawSquareDots(obj.layer.shapes, style);
10789
+ if (lyr.gui.displayLayer.geometry_type == 'point') {
10790
+ canv.drawSquareDots(lyr.gui.displayLayer.shapes, style);
10891
10791
  }
10892
10792
  }
10893
10793
 
10894
- function drawStyledLayerToCanvas(obj, canv, ext) {
10794
+ function drawStyledLayerToCanvas(lyr, canv, ext) {
10895
10795
  // TODO: add filter for out-of-view shapes
10896
- var style = obj.style;
10897
- var layer = obj.layer;
10796
+ var style = lyr.gui.style;
10797
+ var layer = lyr.gui.displayLayer;
10898
10798
  var arcs, filter;
10899
10799
  if (layer.geometry_type == 'point') {
10900
10800
  if (style.type == 'styled') {
@@ -10903,7 +10803,7 @@
10903
10803
  canv.drawSquareDots(layer.shapes, style);
10904
10804
  }
10905
10805
  } else {
10906
- arcs = getArcsForRendering(obj, ext);
10806
+ arcs = getArcsForRendering(lyr, ext);
10907
10807
  filter = getShapeFilter(arcs, ext);
10908
10808
  canv.drawStyledPaths(layer.shapes, arcs, style, filter);
10909
10809
  if (style.vertices) {
@@ -11497,12 +11397,12 @@
11497
11397
  }
11498
11398
  };
11499
11399
 
11500
- el.reposition = function(target, type) {
11400
+ el.reposition = function(lyr, type) {
11501
11401
  resize(ext);
11502
- reposition(target, type, ext);
11402
+ reposition(lyr, type, ext);
11503
11403
  };
11504
11404
 
11505
- el.drawLayer = function(target, type) {
11405
+ el.drawLayer = function(lyr, type) {
11506
11406
  var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
11507
11407
  var html = '';
11508
11408
  // generate a unique id so layer can be identified when symbols are repositioned
@@ -11510,24 +11410,25 @@
11510
11410
  var id = utils$1.getUniqueName();
11511
11411
  var classNames = [id, 'mapshaper-svg-layer', 'mapshaper-' + type + '-layer'];
11512
11412
  g.setAttribute('class', classNames.join(' '));
11513
- target.svg_id = id;
11413
+ lyr.gui.svg_id = id;
11414
+ lyr.gui.svg_container = g;
11514
11415
  resize(ext);
11515
11416
  if (type == 'label' || type == 'symbol') {
11516
- html = renderSymbols(target.layer, ext);
11417
+ html = renderSymbols(lyr.gui.displayLayer, ext);
11517
11418
  } else if (type == 'furniture') {
11518
- html = renderFurniture(target.layer, ext);
11419
+ html = renderFurniture(lyr.gui.displayLayer, ext);
11519
11420
  }
11520
11421
  g.innerHTML = html;
11521
11422
  svg.append(g);
11522
11423
 
11523
11424
  // prevent svg hit detection on inactive layers
11524
- if (!target.active) {
11425
+ if (!lyr.active) {
11525
11426
  g.style.pointerEvents = 'none';
11526
11427
  }
11527
11428
  };
11528
11429
 
11529
- function reposition(target, type, ext) {
11530
- var container = el.findChild('.' + target.svg_id);
11430
+ function reposition(lyr, type, ext) {
11431
+ var container = el.findChild('.' + lyr.gui.svg_id);
11531
11432
  if (!container || !container.node()) {
11532
11433
  console.error('[reposition] missing SVG container');
11533
11434
  return;
@@ -11535,9 +11436,9 @@
11535
11436
  var elements;
11536
11437
  if (type == 'symbol') {
11537
11438
  elements = El.findAll('.mapshaper-svg-symbol', container.node());
11538
- repositionSymbols(elements, target.layer, ext);
11439
+ repositionSymbols(elements, lyr.gui.displayLayer, ext);
11539
11440
  } else if (type == 'furniture') {
11540
- repositionFurniture(container.node(), target.layer, ext);
11441
+ repositionFurniture(container.node(), lyr.gui.displayLayer, ext);
11541
11442
  } else {
11542
11443
  // container.getElementsByTagName('text')
11543
11444
  error('Unsupported symbol type:', type);
@@ -11571,7 +11472,7 @@
11571
11472
  _svg.clear();
11572
11473
  }
11573
11474
  layers.forEach(function(lyr) {
11574
- var isSvgLayer = internal.layerHasSvgSymbols(lyr.layer) || internal.layerHasLabels(lyr.layer);
11475
+ var isSvgLayer = internal.layerHasSvgSymbols(lyr) || internal.layerHasLabels(lyr);
11575
11476
  if (isSvgLayer && !needSvgRedraw) {
11576
11477
  _svg.reposition(lyr, 'symbol');
11577
11478
  } else if (isSvgLayer) {
@@ -11620,7 +11521,7 @@
11620
11521
 
11621
11522
  function drawCanvasLayer(lyr, canv) {
11622
11523
  if (!lyr) return;
11623
- if (lyr.style.type == 'outline') {
11524
+ if (lyr.gui.style.type == 'outline') {
11624
11525
  drawOutlineLayerToCanvas(lyr, canv, ext);
11625
11526
  } else {
11626
11527
  drawStyledLayerToCanvas(lyr, canv, ext);
@@ -11688,8 +11589,8 @@
11688
11589
  gui.on('interaction_mode_change', function(e) {
11689
11590
  if (e.mode === 'box') {
11690
11591
  gui.enterMode('box_tool');
11691
- } else if (gui.getMode() == 'box_tool') {
11692
- gui.clearMode();
11592
+ } else if (_on) {
11593
+ turnOff();
11693
11594
  }
11694
11595
  });
11695
11596
 
@@ -11792,8 +11693,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11792
11693
  gui.on('interaction_mode_change', function(e) {
11793
11694
  if (e.mode === 'rectangles') {
11794
11695
  gui.enterMode('rectangle_tool');
11795
- } else if (gui.getMode() == 'rectangle_tool') {
11796
- gui.clearMode();
11696
+ } else if (_on) {
11697
+ turnOff();
11797
11698
  }
11798
11699
  });
11799
11700
 
@@ -11803,8 +11704,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11803
11704
  var id = e.id;
11804
11705
  if (e.id > -1 && e.pinned) {
11805
11706
  var target = hit.getHitTarget();
11806
- var path = target.layer.shapes[e.id][0];
11807
- var bbox = target.arcs.getSimpleShapeBounds(path).toArray();
11707
+ var path = target.shapes[e.id][0];
11708
+ var bbox = target.gui.displayArcs.getSimpleShapeBounds(path).toArray();
11808
11709
  box.setDataCoords(bbox);
11809
11710
  dragInfo = {
11810
11711
  id: e.id,
@@ -11812,7 +11713,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11812
11713
  ids: [],
11813
11714
  points: []
11814
11715
  };
11815
- var iter = target.arcs.getShapeIter(path);
11716
+ var iter = target.gui.displayArcs.getShapeIter(path);
11816
11717
  while (iter.hasNext()) {
11817
11718
  dragInfo.points.push([iter.x, iter.y]);
11818
11719
  dragInfo.ids.push(iter._arc.i);
@@ -11911,8 +11812,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11911
11812
  };
11912
11813
  }
11913
11814
 
11914
- function getDisplayLayerForTable(table) {
11915
- var n = table.size(),
11815
+ function getDisplayLayerForTable(tableArg) {
11816
+ var table = tableArg || new internal.DataTable(0),
11817
+ n = table.size(),
11916
11818
  cellWidth = 12,
11917
11819
  cellHeight = 5,
11918
11820
  gutter = 6,
@@ -11970,54 +11872,45 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11970
11872
  };
11971
11873
  }
11972
11874
 
11875
+ // lyr: a map layer with gui property
11973
11876
  // displayCRS: CRS to use for display, or null (which clears any current display CRS)
11974
- function projectDisplayLayer(lyr, displayCRS) {
11975
- var crsInfo = getDatasetCrsInfo(lyr.source.dataset);
11976
- var sourceCRS = crsInfo.crs;
11977
- var lyr2;
11978
- //if (!lyr.geographic || !sourceCRS) {
11979
- // let getDisplayLayer() handle case of unprojectable source
11980
- if (!lyr.geographic) {
11877
+ function projectLayerForDisplay(lyr, displayCRS) {
11878
+ var crsInfo = getDatasetCrsInfo(lyr.gui.source.dataset);
11879
+ var sourceCRS = crsInfo.crs || null; // let enhanceLayerForDisplay() handle null case
11880
+ if (!lyr.gui.geographic) {
11981
11881
  return;
11982
11882
  }
11983
- if (lyr.dynamic_crs && internal.crsAreEqual(sourceCRS, lyr.dynamic_crs)) {
11883
+ if (lyr.gui.dynamic_crs && internal.crsAreEqual(sourceCRS, lyr.gui.dynamic_crs)) {
11984
11884
  return;
11985
11885
  }
11986
- lyr2 = getDisplayLayer(lyr.source.layer, lyr.source.dataset, {crs: displayCRS});
11987
- // kludge: copy projection-related properties to original layer
11988
- lyr.dynamic_crs = lyr2.dynamic_crs;
11989
- lyr.layer = lyr2.layer;
11990
-
11991
- if (lyr.style && lyr.style.ids) {
11886
+ enhanceLayerForDisplay(lyr, lyr.gui.source.dataset, {crs: displayCRS});
11887
+ if (lyr.gui.style?.ids) {
11992
11888
  // re-apply layer filter
11993
- lyr.layer = filterLayerByIds(lyr.layer, lyr.style.ids);
11889
+ lyr.gui.displayLayer = filterLayerByIds(lyr.gui.displayLayer, lyr.gui.style.ids);
11994
11890
  }
11995
- lyr.invertPoint = lyr2.invertPoint;
11996
- lyr.projectPoint = lyr2.projectPoint;
11997
- lyr.bounds = lyr2.bounds;
11998
- lyr.arcs = lyr2.arcs;
11999
11891
  }
12000
11892
 
12001
11893
 
12002
- // Wrap a layer in an object along with information needed for rendering
12003
- function getDisplayLayer(layer, dataset, opts) {
12004
- var obj = {
12005
- layer: null,
12006
- arcs: null,
12007
- // display_arcs: null,
11894
+ // Supplement a layer with information needed for rendering
11895
+ function enhanceLayerForDisplay(layer, dataset, opts) {
11896
+ var gui = {
11897
+ empty: internal.getFeatureCount(layer) === 0,
11898
+ geographic: false,
11899
+ displayArcs: null,
11900
+ displayLayer: null,
11901
+ source: {dataset},
11902
+ bounds: null,
12008
11903
  style: null,
11904
+ dynamic_crs: null,
12009
11905
  invertPoint: null,
12010
- projectPoint: null,
12011
- source: {
12012
- layer: layer,
12013
- dataset: dataset
12014
- },
12015
- empty: internal.getFeatureCount(layer) === 0
11906
+ projectPoint: null
12016
11907
  };
12017
11908
 
12018
11909
  var displayCRS = opts.crs || null;
12019
- // display arcs may have been generated when another layer in the dataset was converted for display... re-use if available
12020
- var displayArcs = dataset.gui?.displayArcs || null;
11910
+ // display arcs may have been generated when another layer in the dataset
11911
+ // was converted for display... re-use if available
11912
+ var displayArcs = dataset.gui?.displayArcs;
11913
+ var unprojectable = false;
12021
11914
  var sourceCRS;
12022
11915
  var emptyArcs;
12023
11916
 
@@ -12025,8 +11918,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12025
11918
  var crsInfo = getDatasetCrsInfo(dataset);
12026
11919
  if (crsInfo.error) {
12027
11920
  // unprojectable dataset -- return empty layer
12028
- obj.empty = true;
12029
- obj.geographic = true;
11921
+ gui.unprojectable = true;
12030
11922
  } else {
12031
11923
  sourceCRS = crsInfo.crs;
12032
11924
  }
@@ -12035,7 +11927,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12035
11927
  // Assume that dataset.displayArcs is in the display CRS
12036
11928
  // (it must be deleted upstream if reprojection is needed)
12037
11929
  // if (!obj.empty && dataset.arcs && !displayArcs) {
12038
- if (dataset.arcs && !displayArcs) {
11930
+ if (dataset.arcs && !displayArcs && !gui.unprojectable) {
12039
11931
  // project arcs, if needed
12040
11932
  if (needReprojectionForDisplay(sourceCRS, displayCRS)) {
12041
11933
  displayArcs = projectArcsForDisplay(dataset.arcs, sourceCRS, displayCRS);
@@ -12049,46 +11941,42 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12049
11941
  }
12050
11942
 
12051
11943
  if (internal.layerHasFurniture(layer)) {
12052
- obj.layer = layer;
12053
- obj.tabular = true;
12054
11944
  // TODO: consider how to render furniture in GUI
12055
- // obj.furniture = true;
12056
- // obj.furniture_type = internal.getFurnitureLayerType(layer);
12057
11945
  // treating furniture layers (other than frame) as tabular for now,
12058
11946
  // so there is something to show if they are selected
12059
- // obj.tabular = obj.furniture_type != 'frame';
12060
- } else if (layer.geometry_type) {
12061
- obj.geographic = true;
12062
- obj.layer = layer;
12063
- obj.arcs = displayArcs;
12064
- } else if (!obj.empty) {
12065
- obj.tabular = true;
12066
- } else {
12067
- obj.layer = {shapes: []}; // ideally we should avoid empty layers
12068
11947
  }
12069
11948
 
12070
- if (obj.tabular) {
12071
- utils$1.extend(obj, getDisplayLayerForTable(layer.data));
11949
+ if (gui.unprojectable) {
11950
+ gui.displayLayer = {shapes: []}; // TODO: improve
11951
+ } else if (layer.geometry_type) {
11952
+ gui.geographic = true;
11953
+ gui.displayLayer = layer;
11954
+ gui.displayArcs = displayArcs;
11955
+ } else {
11956
+ var table = getDisplayLayerForTable(layer.data);
11957
+ gui.tabular = true;
11958
+ gui.displayLayer = table.layer;
11959
+ gui.displayArcs = table.arcs;
12072
11960
  }
12073
11961
 
12074
11962
  // dynamic reprojection (arcs were already reprojected above)
12075
- if (obj.geographic && needReprojectionForDisplay(sourceCRS, displayCRS)) {
12076
- obj.dynamic_crs = displayCRS;
12077
- obj.invertPoint = internal.getProjTransform2(displayCRS, sourceCRS);
12078
- obj.projectPoint = internal.getProjTransform2(sourceCRS, displayCRS);
11963
+ if (gui.geographic && needReprojectionForDisplay(sourceCRS, displayCRS)) {
11964
+ gui.dynamic_crs = displayCRS;
11965
+ gui.invertPoint = internal.getProjTransform2(displayCRS, sourceCRS);
11966
+ gui.projectPoint = internal.getProjTransform2(sourceCRS, displayCRS);
12079
11967
  if (internal.layerHasPoints(layer)) {
12080
- obj.layer = projectPointsForDisplay(layer, sourceCRS, displayCRS);
11968
+ gui.displayLayer = projectPointsForDisplay(layer, sourceCRS, displayCRS);
12081
11969
  } else if (internal.layerHasPaths(layer)) {
12082
11970
  emptyArcs = findEmptyArcs(displayArcs);
12083
11971
  if (emptyArcs.length > 0) {
12084
11972
  // Don't try to draw paths containing coordinates that failed to project
12085
- obj.layer = internal.filterPathLayerByArcIds(obj.layer, emptyArcs);
11973
+ gui.displayLayer = internal.filterPathLayerByArcIds(gui.displayLayer, emptyArcs);
12086
11974
  }
12087
11975
  }
12088
11976
  }
12089
11977
 
12090
- obj.bounds = getDisplayBounds(obj.layer, obj.arcs);
12091
- return obj;
11978
+ gui.bounds = getDisplayBounds(gui.displayLayer, gui.displayArcs);
11979
+ layer.gui = gui;
12092
11980
  }
12093
11981
 
12094
11982
 
@@ -12402,13 +12290,19 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12402
12290
 
12403
12291
  model.on('update', onUpdate);
12404
12292
 
12293
+ document.addEventListener('visibilitychange', function(e) {
12294
+ // refresh map when browser tab is re-activated (Chrome on mac has been
12295
+ // blanking the canvas after several other tabs are visited)
12296
+ if (document.visibilityState == 'visible') drawLayers();
12297
+ });
12405
12298
 
12406
12299
  // Update display of segment intersections
12407
12300
  this.setIntersectionLayer = function(lyr, dataset) {
12408
12301
  if (lyr == _intersectionLyr) return; // no change
12409
12302
  if (lyr) {
12410
- _intersectionLyr = getDisplayLayer(lyr, dataset, getDisplayOptions());
12411
- _intersectionLyr.style = getIntersectionStyle(_intersectionLyr.layer, getGlobalStyleOptions());
12303
+ enhanceLayerForDisplay(lyr, dataset, getDisplayOptions());
12304
+ _intersectionLyr = lyr;
12305
+ _intersectionLyr.gui.style = getIntersectionStyle(_intersectionLyr.gui.displayLayer, getGlobalStyleOptions());
12412
12306
  } else {
12413
12307
  _intersectionLyr = null;
12414
12308
  }
@@ -12437,9 +12331,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12437
12331
  };
12438
12332
 
12439
12333
  this.getDisplayCRS = function() {
12440
- if (!_activeLyr || !_activeLyr.geographic) return null;
12441
- if (_activeLyr.dynamic_crs) return _activeLyr.dynamic_crs;
12442
- var info = getDatasetCrsInfo(_activeLyr.source.dataset);
12334
+ if (!_activeLyr || !_activeLyr.gui.geographic) return null;
12335
+ if (_activeLyr.gui.dynamic_crs) return _activeLyr.gui.dynamic_crs;
12336
+ var info = getDatasetCrsInfo(_activeLyr.gui.source.dataset);
12443
12337
  return info.crs || null;
12444
12338
  };
12445
12339
 
@@ -12478,7 +12372,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12478
12372
 
12479
12373
  // Reproject all visible map layers
12480
12374
  getContentLayers().forEach(function(lyr) {
12481
- projectDisplayLayer(lyr, newCRS);
12375
+ projectLayerForDisplay(lyr, newCRS);
12482
12376
  });
12483
12377
 
12484
12378
  // kludge to make sure all layers have styles
@@ -12503,7 +12397,6 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12503
12397
  var prevLyr = _activeLyr || null;
12504
12398
  var fullBounds;
12505
12399
  var needReset;
12506
-
12507
12400
  if (!prevLyr) {
12508
12401
  initMap(); // first call
12509
12402
  }
@@ -12529,8 +12422,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12529
12422
  return;
12530
12423
  }
12531
12424
 
12532
- _activeLyr = getDisplayLayer(e.layer, e.dataset, getDisplayOptions());
12533
- _activeLyr.style = getActiveLayerStyle(_activeLyr.layer, getGlobalStyleOptions());
12425
+ enhanceLayerForDisplay(e.layer, e.dataset, getDisplayOptions());
12426
+ _activeLyr = e.layer;
12427
+ _activeLyr.gui.style = getActiveLayerStyle(_activeLyr.gui.displayLayer, getGlobalStyleOptions());
12534
12428
  _activeLyr.active = true;
12535
12429
 
12536
12430
  if (popupCanStayOpen(e.flags)) {
@@ -12544,7 +12438,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12544
12438
  updateVisibleMapLayers();
12545
12439
  fullBounds = calcFullBounds();
12546
12440
 
12547
- if (!prevLyr || prevLyr.tabular || _activeLyr.tabular) {
12441
+ if (!prevLyr || prevLyr.gui.tabular || _activeLyr.gui.tabular) {
12548
12442
  needReset = true;
12549
12443
  } else {
12550
12444
  needReset = mapNeedsReset(fullBounds, _ext.getFullBounds(), _ext.getBounds(), e.flags);
@@ -12587,12 +12481,11 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12587
12481
  }
12588
12482
 
12589
12483
  function updateOverlayLayer(e) {
12590
- var style = getOverlayStyle(_activeLyr.layer, e, getGlobalStyleOptions());
12484
+ var style = getOverlayStyle(_activeLyr.gui.displayLayer, e, getGlobalStyleOptions());
12591
12485
  if (style) {
12592
- _overlayLyr = utils$1.defaults({
12593
- layer: filterLayerByIds(_activeLyr.layer, style.ids),
12594
- style: style
12595
- }, _activeLyr);
12486
+ var displayLayer = filterLayerByIds(_activeLyr.gui.displayLayer, style.ids);
12487
+ var gui = Object.assign({}, _activeLyr.gui, {style, displayLayer});
12488
+ _overlayLyr = utils$1.defaults({gui}, _activeLyr);
12596
12489
  } else {
12597
12490
  _overlayLyr = null;
12598
12491
  }
@@ -12623,7 +12516,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12623
12516
  var b = new Bounds();
12624
12517
  var layers = getContentLayers();
12625
12518
  layers.forEach(function(lyr) {
12626
- b.mergeBounds(lyr.bounds);
12519
+ b.mergeBounds(lyr.gui.bounds);
12627
12520
  });
12628
12521
 
12629
12522
  if (!b.hasBounds()) {
@@ -12646,7 +12539,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12646
12539
  var widthPx = _ext.width();
12647
12540
  var marginPct = widthPx < 700 && 3.5 || widthPx < 800 && 3 || 2.5;
12648
12541
  if (isTableView()) {
12649
- var n = internal.getFeatureCount(_activeLyr.layer);
12542
+ var n = internal.getFeatureCount(_activeLyr);
12650
12543
  marginPct = n < 5 && 20 || n < 100 && 10 || 4;
12651
12544
  }
12652
12545
  b.scale(1 + marginPct / 100 * 2);
@@ -12658,7 +12551,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12658
12551
  }
12659
12552
 
12660
12553
  function isActiveLayer(lyr) {
12661
- return _activeLyr && lyr == _activeLyr.source.layer || false;
12554
+ return _activeLyr && lyr == _activeLyr || false;
12662
12555
  }
12663
12556
 
12664
12557
  function isVisibleLayer(lyr) {
@@ -12666,12 +12559,12 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12666
12559
  }
12667
12560
 
12668
12561
  function isTableView() {
12669
- return !!_activeLyr.tabular;
12562
+ return !!_activeLyr.gui.tabular;
12670
12563
  }
12671
12564
 
12672
12565
  function findFrameLayer() {
12673
12566
  return getVisibleMapLayers().find(function(lyr) {
12674
- return internal.isFrameLayer(lyr.layer, lyr.arcs);
12567
+ return internal.isFrameLayer(lyr.gui.displayLayer, lyr.gui.displayArcs);
12675
12568
  });
12676
12569
  }
12677
12570
 
@@ -12682,10 +12575,13 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12682
12575
 
12683
12576
  function getFrameData() {
12684
12577
  var lyr = findFrameLayer();
12685
- return lyr && internal.getFrameLayerData(lyr.layer, lyr.arcs) || null;
12578
+ return lyr && internal.getFrameLayerData(lyr, lyr.gui.displayArcs) || null;
12686
12579
  }
12687
12580
 
12688
12581
  function clearAllDisplayArcs() {
12582
+ model.forEachLayer(function(lyr) {
12583
+ if (lyr.gui) delete lyr.gui.arcCounts;
12584
+ });
12689
12585
  model.getDatasets().forEach(function(o) {
12690
12586
  delete o.gui;
12691
12587
  });
@@ -12698,7 +12594,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12698
12594
  if (isActiveLayer(o.layer)) {
12699
12595
  layers.push(_activeLyr);
12700
12596
  } else if (!isTableView()) {
12701
- layers.push(getDisplayLayer(o.layer, o.dataset, getDisplayOptions()));
12597
+ enhanceLayerForDisplay(o.layer, o.dataset, getDisplayOptions());
12598
+ layers.push(o.layer);
12702
12599
  }
12703
12600
  });
12704
12601
  _visibleLayers = layers;
@@ -12720,13 +12617,13 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12720
12617
  return findActiveLayer(layers);
12721
12618
  }
12722
12619
  return layers.filter(function(o) {
12723
- return !!o.geographic;
12620
+ return !!o.gui.geographic;
12724
12621
  });
12725
12622
  }
12726
12623
 
12727
12624
  function getDrawableContentLayers() {
12728
12625
  return getContentLayers().filter(function(lyr) {
12729
- if (isActiveLayer(lyr.layer) && lyr.layer.hidden) return false;
12626
+ if (isActiveLayer(lyr) && lyr.hidden) return false;
12730
12627
  return true;
12731
12628
  });
12732
12629
  }
@@ -12740,31 +12637,33 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12740
12637
 
12741
12638
  function updateLayerStyles(layers) {
12742
12639
  layers.forEach(function(mapLayer, i) {
12640
+ var style;
12743
12641
  if (mapLayer.active) {
12744
12642
  // regenerating active style everytime, to support style change when
12745
12643
  // switching between outline and preview modes.
12746
- mapLayer.style = getActiveLayerStyle(mapLayer.layer, getGlobalStyleOptions());
12747
- if (mapLayer.style.type != 'styled' && layers.length > 1 && mapLayer.style.strokeColors) {
12644
+ style = getActiveLayerStyle(mapLayer.gui.displayLayer, getGlobalStyleOptions());
12645
+ if (style.type != 'styled' && layers.length > 1 && style.strokeColors) {
12748
12646
  // kludge to hide ghosted layers when reference layers are present
12749
12647
  // TODO: consider never showing ghosted layers (which appear after
12750
12648
  // commands like dissolve and filter).
12751
- mapLayer.style = utils$1.defaults({
12752
- strokeColors: [null, mapLayer.style.strokeColors[1]]
12753
- }, mapLayer.style);
12649
+ style = utils$1.defaults({
12650
+ strokeColors: [null, style.strokeColors[1]]
12651
+ }, style);
12754
12652
  }
12755
12653
  } else {
12756
- if (mapLayer.layer == _activeLyr.layer) {
12654
+ if (mapLayer == _activeLyr) {
12757
12655
  console.error("Error: shared map layer");
12758
12656
  }
12759
- mapLayer.style = getReferenceLayerStyle(mapLayer.layer, getGlobalStyleOptions());
12657
+ style = getReferenceLayerStyle(mapLayer.gui.displayLayer, getGlobalStyleOptions());
12760
12658
  }
12659
+ mapLayer.gui.style = style;
12761
12660
  });
12762
12661
  }
12763
12662
 
12764
12663
  function sortMapLayers(layers) {
12765
12664
  layers.sort(function(a, b) {
12766
12665
  // assume that each layer has a menu_order (assigned by updateLayerStackOrder())
12767
- return a.source.layer.menu_order - b.source.layer.menu_order;
12666
+ return a.menu_order - b.menu_order;
12768
12667
  });
12769
12668
  }
12770
12669
 
@@ -12844,6 +12743,99 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12844
12743
  });
12845
12744
  }
12846
12745
 
12746
+ function ContextMenu() {
12747
+ var body = document.querySelector('body');
12748
+ var menu = El('div').addClass('contextmenu rollover').appendTo(body);
12749
+ var _open = false;
12750
+ var _openCount = 0;
12751
+ document.addEventListener('mousedown', close);
12752
+
12753
+ this.isOpen = function() {
12754
+ return _open;
12755
+ };
12756
+
12757
+ function close() {
12758
+ var count = _openCount;
12759
+ if (!_open) return;
12760
+ setTimeout(function() {
12761
+ if (count == _openCount) {
12762
+ menu.hide();
12763
+ _open = false;
12764
+ }
12765
+ }, 300);
12766
+ }
12767
+
12768
+ function addMenuItem(label, func) {
12769
+ El('div')
12770
+ .appendTo(menu)
12771
+ .addClass('contextmenu-item')
12772
+ .text(label)
12773
+ .on('click', func)
12774
+ .show();
12775
+ }
12776
+
12777
+ this.open = function(e, lyr) {
12778
+ _open = true;
12779
+ _openCount++;
12780
+ var rspace = body.clientWidth - e.pageX;
12781
+ var xoffs = 10;
12782
+ menu.empty().show();
12783
+ if (rspace > 150) {
12784
+ menu.css('left', e.pageX + xoffs + 'px');
12785
+ } else {
12786
+ menu.css('right', (body.clientWidth - e.pageX + xoffs) + 'px');
12787
+ }
12788
+ menu.css('top', (e.pageY - 15) + 'px');
12789
+
12790
+ // menu contents
12791
+ if (e.deleteVertex) {
12792
+ addMenuItem('Delete vertex', e.deleteVertex);
12793
+ }
12794
+ if (e.coordinates) {
12795
+ addCopyCoords();
12796
+ }
12797
+ if (e.ids?.length) {
12798
+ addMenuItem('Copy as GeoJSON', copyGeoJSON);
12799
+ }
12800
+
12801
+ function addCopyCoords() {
12802
+ var bbox = internal.getLayerBounds(lyr, lyr.gui.source.dataset.arcs).toArray();
12803
+ var p = internal.getRoundedCoords(e.coordinates, internal.getBoundsPrecisionForDisplay(bbox));
12804
+ var coordStr = p.join(',');
12805
+ addMenuItem(coordStr, function() {
12806
+ saveFileContentToClipboard(coordStr);
12807
+ });
12808
+ }
12809
+
12810
+ function copyGeoJSON() {
12811
+ var opts = {
12812
+ no_replace: true,
12813
+ ids: e.ids,
12814
+ quiet: true
12815
+ };
12816
+ var dataset = lyr.gui.source.dataset;
12817
+ var layer = mapshaper.cmd.filterFeatures(lyr, dataset.arcs, opts);
12818
+ // the drawing tool can send open paths with 'polygon' geometry type,
12819
+ // should be changed to 'polyline'
12820
+ if (layerHasOpenPaths(layer, dataset.arcs)) {
12821
+ layer.geometry_type = 'polyline';
12822
+ }
12823
+ var features = internal.exportLayerAsGeoJSON(layer, dataset, {rfc7946: true, prettify: true}, true, 'string');
12824
+ var str = internal.geojson.formatCollection({"type": "FeatureCollection"}, features);
12825
+ saveFileContentToClipboard(str);
12826
+ }
12827
+ };
12828
+ }
12829
+
12830
+
12831
+ function layerHasOpenPaths(layer, arcs) {
12832
+ var retn = false;
12833
+ internal.editShapes(layer.shapes, function(part) {
12834
+ if (!geom.pathIsClosed(part, arcs)) retn = true;
12835
+ });
12836
+ return retn;
12837
+ }
12838
+
12847
12839
  function GuiInstance(container, opts) {
12848
12840
  var gui = new ModeSwitcher();
12849
12841
  opts = utils$1.extend({
@@ -12862,15 +12854,15 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12862
12854
  gui.model = new Model(gui);
12863
12855
  gui.keyboard = new KeyboardEvents(gui);
12864
12856
  gui.buttons = new SidebarButtons(gui);
12865
- gui.map = new MshpMap(gui);
12866
- gui.interaction = new InteractionMode(gui);
12867
12857
  gui.session = new SessionHistory(gui);
12858
+ gui.contextMenu = new ContextMenu();
12868
12859
  gui.undo = new Undo(gui);
12869
- gui.state = {};
12870
-
12860
+ gui.map = new MshpMap(gui);
12871
12861
  if (opts.saveControl) {
12872
12862
  new SessionSnapshots(gui);
12873
12863
  }
12864
+ gui.interaction = new InteractionMode(gui);
12865
+ gui.state = {};
12874
12866
 
12875
12867
  var msgCount = 0;
12876
12868
  var clearMsg;