mapshaper 0.6.74 → 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 {
@@ -1144,10 +1141,18 @@
1144
1141
  return self;
1145
1142
  };
1146
1143
 
1147
- self.close = function() {
1148
- if (el) el.remove();
1144
+ self.close = function(action) {
1145
+ var ms = 0;
1146
+ var _el = el;
1147
+ if (action == 'fade' && _el) {
1148
+ ms = 1000;
1149
+ _el.addClass('fade-out');
1150
+ }
1149
1151
  if (_close) _close();
1150
1152
  el = _cancel = _close = null;
1153
+ setTimeout(function() {
1154
+ if (_el) _el.remove();
1155
+ }, ms);
1151
1156
  };
1152
1157
  return self;
1153
1158
  }
@@ -1361,10 +1366,12 @@
1361
1366
  init();
1362
1367
 
1363
1368
  async function init() {
1364
- var enabled = await isStorageEnabled();
1365
- if (!enabled) return;
1366
-
1367
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
+ }
1368
1375
  menu = El('div').addClass('nav-sub-menu save-menu').appendTo(btn.node());
1369
1376
  await initialCleanup();
1370
1377
 
@@ -1436,14 +1443,6 @@
1436
1443
  renderMenu();
1437
1444
  }).text('remove');
1438
1445
  });
1439
-
1440
- // if (snapshots.length >= 1) {
1441
- // addMenuLink({
1442
- // slug: 'clear',
1443
- // label: 'remove all',
1444
- // action: clearData
1445
- // });
1446
- // }
1447
1446
  }
1448
1447
 
1449
1448
  function addMenuLink(item) {
@@ -1753,6 +1752,9 @@
1753
1752
  }],
1754
1753
  info: {}
1755
1754
  };
1755
+ if (type == 'polygon' || type == 'point') {
1756
+ dataset.arcs = new internal.ArcCollection();
1757
+ }
1756
1758
  if (crsInfo) {
1757
1759
  internal.setDatasetCrsInfo(dataset, crsInfo);
1758
1760
  }
@@ -2426,7 +2428,10 @@
2426
2428
  }
2427
2429
 
2428
2430
  function error() {
2429
- stop.apply(null, utils$1.toArray(arguments));
2431
+ var msg = GUI.formatMessageArgs(arguments);
2432
+ console.error(msg);
2433
+ gui.alert('An unkown error occured');
2434
+ throw new Error(msg);
2430
2435
  }
2431
2436
 
2432
2437
  function message() {
@@ -2540,7 +2545,7 @@
2540
2545
  // Convert a point from display CRS coordinates to data coordinates.
2541
2546
  // These are only different when using dynamic reprojection (basemap view).
2542
2547
  function translateDisplayPoint(lyr, p) {
2543
- return isProjectedLayer(lyr) ? lyr.invertPoint(p[0], p[1]) : p;
2548
+ return isProjectedLayer(lyr) ? lyr.gui.invertPoint(p[0], p[1]) : p;
2544
2549
  }
2545
2550
 
2546
2551
  // bbox: display coords
@@ -2557,7 +2562,7 @@
2557
2562
 
2558
2563
  function isProjectedLayer(lyr) {
2559
2564
  // TODO: could do some validation on the layer's contents
2560
- return !!(lyr.source && lyr.invertPoint);
2565
+ return !!lyr.gui.invertPoint;
2561
2566
  }
2562
2567
 
2563
2568
  var darkStroke = "#334",
@@ -2807,7 +2812,7 @@
2807
2812
  strokeWidth: 1.2,
2808
2813
  vertices: true,
2809
2814
  vertex_overlay_color: o.hit_type == 'vertex' ? violet : black,
2810
- vertex_overlay_scale: o.hit_type == 'vertex' ? 2.4 : 1.7,
2815
+ vertex_overlay_scale: o.hit_type == 'vertex' ? 2.5 : 2,
2811
2816
  vertex_overlay: o.hit_coordinates || null,
2812
2817
  selected_points: o.selected_points || null,
2813
2818
  fillColor: null
@@ -2919,22 +2924,22 @@
2919
2924
  }
2920
2925
 
2921
2926
  function flattenArcs(lyr) {
2922
- lyr.source.dataset.arcs.flatten();
2927
+ lyr.gui.source.dataset.arcs.flatten();
2923
2928
  if (isProjectedLayer(lyr)) {
2924
- lyr.arcs.flatten();
2929
+ lyr.gui.displayArcs.flatten();
2925
2930
  }
2926
2931
  }
2927
2932
 
2928
2933
  function setZ(lyr, z) {
2929
- lyr.source.dataset.arcs.setRetainedInterval(z);
2934
+ lyr.gui.source.dataset.arcs.setRetainedInterval(z);
2930
2935
  if (isProjectedLayer(lyr)) {
2931
- lyr.arcs.setRetainedInterval(z);
2936
+ lyr.gui.displayArcs.setRetainedInterval(z);
2932
2937
  }
2933
2938
  }
2934
2939
 
2935
2940
  function updateZ(lyr) {
2936
- if (isProjectedLayer(lyr) && !lyr.source.dataset.arcs.isFlat()) {
2937
- 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);
2938
2943
  }
2939
2944
  }
2940
2945
 
@@ -2970,103 +2975,107 @@
2970
2975
  }
2971
2976
 
2972
2977
  function deleteLastPath(lyr) {
2973
- var arcId = lyr.arcs.size() - 1;
2974
- if (lyr.layer.data) {
2975
- lyr.layer.data.getRecords().pop();
2978
+ var arcId = lyr.gui.displayArcs.size() - 1;
2979
+ if (lyr.data) {
2980
+ lyr.data.getRecords().pop();
2976
2981
  }
2977
- var shp = lyr.layer.shapes.pop();
2978
- internal.deleteLastArc(lyr.arcs);
2982
+ var shp = lyr.shapes.pop();
2983
+ internal.deleteLastArc(lyr.gui.displayArcs);
2979
2984
  if (isProjectedLayer(lyr)) {
2980
- internal.deleteLastArc(lyr.source.dataset.arcs);
2985
+ internal.deleteLastArc(lyr.gui.source.dataset.arcs);
2981
2986
  }
2982
2987
  }
2983
2988
 
2984
2989
  // p1, p2: two points in source data CRS coords.
2985
2990
  function appendNewPath(lyr, p1, p2) {
2986
- var arcId = lyr.arcs.size();
2987
- internal.appendEmptyArc(lyr.arcs);
2988
- lyr.layer.shapes.push([[arcId]]);
2991
+ var arcId = lyr.gui.displayArcs.size();
2992
+ internal.appendEmptyArc(lyr.gui.displayArcs);
2993
+ lyr.shapes.push([[arcId]]);
2989
2994
  if (isProjectedLayer(lyr)) {
2990
- // lyr.source.layer.shapes.push([[arcId]]);
2991
- internal.appendEmptyArc(lyr.source.dataset.arcs);
2995
+ internal.appendEmptyArc(lyr.gui.source.dataset.arcs);
2992
2996
  }
2993
2997
  appendVertex$1(lyr, p1);
2994
2998
  appendVertex$1(lyr, p2);
2995
- appendNewDataRecord(lyr.layer);
2999
+ appendNewDataRecord(lyr);
2996
3000
  }
2997
3001
 
2998
3002
  // p: point in source data CRS coords.
2999
3003
  function insertVertex$1(lyr, id, p) {
3000
- internal.insertVertex(lyr.source.dataset.arcs, id, p);
3004
+ internal.insertVertex(lyr.gui.source.dataset.arcs, id, p);
3001
3005
  if (isProjectedLayer(lyr)) {
3002
- 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]));
3003
3007
  }
3004
3008
  }
3005
3009
 
3006
3010
  function appendVertex$1(lyr, p) {
3007
- var n = lyr.source.dataset.arcs.getPointCount();
3011
+ var n = lyr.gui.source.dataset.arcs.getPointCount();
3008
3012
  insertVertex$1(lyr, n, p);
3009
3013
  }
3010
3014
 
3011
3015
  // TODO: make sure we're not also removing an entire arc
3012
3016
  function deleteLastVertex(lyr) {
3013
- deleteVertex$1(lyr, lyr.arcs.getPointCount() - 1);
3017
+ deleteVertex$1(lyr, lyr.gui.displayArcs.getPointCount() - 1);
3014
3018
  }
3015
3019
 
3016
3020
  function deleteVertex$1(lyr, id) {
3017
- internal.deleteVertex(lyr.arcs, id);
3021
+ internal.deleteVertex(lyr.gui.displayArcs, id);
3018
3022
  if (isProjectedLayer(lyr)) {
3019
- internal.deleteVertex(lyr.source.dataset.arcs, id);
3023
+ internal.deleteVertex(lyr.gui.source.dataset.arcs, id);
3020
3024
  }
3021
3025
  }
3022
3026
 
3023
3027
  function getLastArcCoords(target) {
3024
- var arcId = target.source.dataset.arcs.size() - 1;
3025
- 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);
3030
+ }
3031
+
3032
+ function getLastVertexCoords(target) {
3033
+ var arcs = target.gui.source.dataset.arcs;
3034
+ return internal.getVertexCoords(arcs.getPointCount() - 1, arcs);
3026
3035
  }
3027
3036
 
3028
3037
  function getLastArcLength(target) {
3029
- var arcId = target.source.dataset.arcs.size() - 1;
3030
- 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);
3031
3040
  }
3032
3041
 
3033
3042
  function getPointCoords(lyr, fid) {
3034
- return internal.cloneShape(lyr.source.layer.shapes[fid]);
3043
+ return internal.cloneShape(lyr.shapes[fid]);
3035
3044
  }
3036
3045
 
3037
3046
  function getVertexCoords(lyr, id) {
3038
- return lyr.source.dataset.arcs.getVertex2(id);
3047
+ return lyr.gui.source.dataset.arcs.getVertex2(id);
3039
3048
  }
3040
3049
 
3041
3050
  // set data coords (not display coords) of one or more vertices.
3042
3051
  function setVertexCoords(lyr, ids, dataPoint) {
3043
- internal.snapVerticesToPoint(ids, dataPoint, lyr.source.dataset.arcs, true);
3052
+ internal.snapVerticesToPoint(ids, dataPoint, lyr.gui.source.dataset.arcs);
3044
3053
  if (isProjectedLayer(lyr)) {
3045
- var p = lyr.projectPoint(dataPoint[0], dataPoint[1]);
3046
- 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);
3047
3056
  }
3048
3057
  }
3049
3058
 
3050
3059
  // coords: [x, y] point in data CRS (not display CRS)
3051
3060
  function setPointCoords(lyr, fid, coords) {
3052
- lyr.source.layer.shapes[fid] = coords;
3061
+ lyr.shapes[fid] = coords;
3053
3062
  if (isProjectedLayer(lyr)) {
3054
- lyr.layer.shapes[fid] = projectPointCoords(coords, lyr.projectPoint);
3063
+ lyr.shapes[fid] = projectPointCoords(coords, lyr.gui.projectPoint);
3055
3064
  }
3056
3065
  }
3057
3066
 
3058
3067
  function updateVertexCoords(lyr, ids) {
3059
3068
  if (!isProjectedLayer(lyr)) return;
3060
- var p = lyr.arcs.getVertex2(ids[0]);
3061
- 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);
3062
3071
  }
3063
3072
 
3064
3073
  function setRectangleCoords(lyr, ids, coords) {
3065
3074
  ids.forEach(function(id, i) {
3066
3075
  var p = coords[i];
3067
- internal.snapVerticesToPoint([id], p, lyr.source.dataset.arcs, true);
3076
+ internal.snapVerticesToPoint([id], p, lyr.gui.source.dataset.arcs);
3068
3077
  if (isProjectedLayer(lyr)) {
3069
- 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);
3070
3079
  }
3071
3080
  });
3072
3081
  }
@@ -3074,8 +3083,8 @@
3074
3083
  // Update source data coordinates by projecting display coordinates
3075
3084
  function updatePointCoords(lyr, fid) {
3076
3085
  if (!isProjectedLayer(lyr)) return;
3077
- var displayShp = lyr.layer.shapes[fid];
3078
- lyr.source.layer.shapes[fid] = projectPointCoords(displayShp, lyr.invertPoint);
3086
+ var displayShp = lyr.shapes[fid];
3087
+ lyr.shapes[fid] = projectPointCoords(displayShp, lyr.gui.invertPoint);
3079
3088
  }
3080
3089
 
3081
3090
  function projectPointCoords(src, proj) {
@@ -4212,6 +4221,11 @@
4212
4221
  return layers.reverse();
4213
4222
  }
4214
4223
 
4224
+ async function saveFileContentToClipboard(content) {
4225
+ var str = utils$1.isString(content) ? content : content.toString();
4226
+ await navigator.clipboard.writeText(str);
4227
+ }
4228
+
4215
4229
  // Export buttons and their behavior
4216
4230
  var ExportControl = function(gui) {
4217
4231
  var model = gui.model;
@@ -4355,12 +4369,6 @@
4355
4369
  } else {
4356
4370
  await utils$1.promisify(internal.writeFiles)(files, opts);
4357
4371
  }
4358
-
4359
- }
4360
-
4361
- async function saveFileContentToClipboard(content) {
4362
- var str = utils$1.isString(content) ? content : content.toString();
4363
- await navigator.clipboard.writeText(str);
4364
4372
  }
4365
4373
 
4366
4374
  function initLayerItem(o, i) {
@@ -4920,13 +4928,10 @@
4920
4928
  return internal.getLayerSourceFile(lyr, dataset);
4921
4929
  }
4922
4930
 
4923
-
4924
4931
  function isPinnable(lyr) {
4925
- return internal.layerHasGeometry(lyr) || internal.layerHasFurniture(lyr);
4932
+ return internal.layerIsGeometric(lyr) || internal.layerHasFurniture(lyr);
4926
4933
  }
4927
4934
 
4928
-
4929
-
4930
4935
  function rowHTML(c1, c2, cname) {
4931
4936
  return utils$1.format('<div class="row%s"><div class="col1">%s</div>' +
4932
4937
  '<div class="col2">%s</div></div>', cname ? ' ' + cname : '', c1, c2);
@@ -5205,10 +5210,10 @@
5205
5210
 
5206
5211
  gui.on('path_extend', function(e) {
5207
5212
  var redo = function() {
5208
- gui.dispatchEvent('redo_path_extend', {p: e.p});
5213
+ gui.dispatchEvent('redo_path_extend', {p: e.p, shapes: e.shapes2});
5209
5214
  };
5210
5215
  var undo = function() {
5211
- gui.dispatchEvent('undo_path_extend');
5216
+ gui.dispatchEvent('undo_path_extend', {shapes: e.shapes1});
5212
5217
  };
5213
5218
  addHistoryState(undo, redo);
5214
5219
  });
@@ -5381,19 +5386,23 @@
5381
5386
  function KeyboardEvents(gui) {
5382
5387
  var self = this;
5383
5388
  var shiftDown = false;
5389
+ var ctrlDown = false;
5384
5390
  document.addEventListener('keyup', function(e) {
5385
5391
  if (!GUI.isActiveInstance(gui)) return;
5386
5392
  if (e.keyCode == 16) shiftDown = false;
5393
+ if (e.keyCode == 17) ctrlDown = false;
5387
5394
  self.dispatchEvent('keyup', getEventData(e));
5388
5395
  });
5389
5396
 
5390
5397
  document.addEventListener('keydown', function(e) {
5391
5398
  if (!GUI.isActiveInstance(gui)) return;
5392
5399
  if (e.keyCode == 16) shiftDown = true;
5400
+ if (e.keyCode == 17) ctrlDown = true;
5393
5401
  self.dispatchEvent('keydown', getEventData(e));
5394
5402
  });
5395
5403
 
5396
5404
  this.shiftIsPressed = function() { return shiftDown; };
5405
+ this.ctrlIsPressed = function() { return ctrlDown; };
5397
5406
 
5398
5407
  this.onMenuSubmit = function(menuEl, cb) {
5399
5408
  gui.on('enter_key', function(e) {
@@ -5410,6 +5419,7 @@
5410
5419
  9: 'tab',
5411
5420
  13: 'enter',
5412
5421
  16: 'shift',
5422
+ 17: 'ctrl',
5413
5423
  27: 'esc',
5414
5424
  32: 'space',
5415
5425
  37: 'left',
@@ -5431,7 +5441,7 @@
5431
5441
  var menus = {
5432
5442
  standard: ['info', 'selection', 'box'],
5433
5443
  polygons: ['info', 'selection', 'box', 'drawing'],
5434
- rectangles: ['info', 'selection', 'box', 'rectangles'], // 'vertices'
5444
+ rectangles: ['info', 'selection', 'box', 'rectangles'],
5435
5445
  lines: ['info', 'selection', 'box' , 'drawing'],
5436
5446
  table: ['info', 'selection'],
5437
5447
  labels: ['info', 'selection', 'box', 'labels', 'location', 'add-points'],
@@ -5452,7 +5462,7 @@
5452
5462
  labels: 'position labels',
5453
5463
  location: 'drag points',
5454
5464
  vertices: 'edit vertices',
5455
- selection: 'select features',
5465
+ selection: 'selection tool',
5456
5466
  'add-points': 'add points',
5457
5467
  drawing: 'draw/reshape tool',
5458
5468
  rectangles: 'drag-to-resize',
@@ -5543,22 +5553,23 @@
5543
5553
  if (!o || !o.layer) {
5544
5554
  return menus.standard; // TODO: more sensible handling of missing layer
5545
5555
  }
5546
- if (!internal.layerHasGeometry(o.layer)) {
5556
+ if (!o.layer.geometry_type) {
5547
5557
  return menus.table;
5548
5558
  }
5549
5559
  if (internal.layerHasLabels(o.layer)) {
5550
5560
  return menus.labels;
5551
5561
  }
5552
- if (internal.layerHasPoints(o.layer)) {
5562
+ if (o.layer.geometry_type == 'point') {
5553
5563
  return menus.points;
5554
5564
  }
5555
- if (internal.layerHasPaths(o.layer) && o.layer.geometry_type == 'polyline') {
5565
+ if (o.layer.geometry_type == 'polyline') {
5556
5566
  return menus.lines;
5557
5567
  }
5558
- if (internal.layerHasPaths(o.layer) && o.layer.geometry_type == 'polygon') {
5568
+ if (o.layer.geometry_type == 'polygon') {
5559
5569
  return internal.layerOnlyHasRectangles(o.layer, o.dataset.arcs) ?
5560
5570
  menus.rectangles : menus.polygons;
5561
5571
  }
5572
+
5562
5573
  return menus.standard;
5563
5574
  }
5564
5575
 
@@ -5908,13 +5919,13 @@
5908
5919
  }
5909
5920
 
5910
5921
  // Format an array of (preferably short) strings in columns for console logging.
5911
- function formatStringsAsGrid(arr) {
5922
+ function formatStringsAsGrid(arr, width) {
5912
5923
  // TODO: variable column width
5913
5924
  var longest = arr.reduce(function(len, str) {
5914
5925
  return Math.max(len, str.length);
5915
5926
  }, 0),
5916
5927
  colWidth = longest + 2,
5917
- perLine = Math.floor(80 / colWidth) || 1;
5928
+ perLine = Math.floor((width || 80) / colWidth) || 1;
5918
5929
  return arr.reduce(function(memo, name, i) {
5919
5930
  var col = i % perLine;
5920
5931
  if (i > 0 && col === 0) memo += '\n';
@@ -6575,7 +6586,7 @@
6575
6586
  }
6576
6587
 
6577
6588
  function getGenericComparator(asc) {
6578
- asc = asc !== false;
6589
+ asc = asc !== false && asc != 'descending'; // ascending is the default
6579
6590
  return function(a, b) {
6580
6591
  var retn = 0;
6581
6592
  if (b == null) {
@@ -7244,11 +7255,11 @@
7244
7255
 
7245
7256
  // featureFilter: optional test function, accepts feature id
7246
7257
  //
7247
- function getShapeHitTest(displayLayer, ext, interactionMode, featureFilter) {
7248
- var geoType = displayLayer.layer.geometry_type;
7258
+ function getShapeHitTest(layer, ext, interactionMode, featureFilter) {
7259
+ var geoType = layer.gui.displayLayer.geometry_type;
7249
7260
  var test;
7250
- if (geoType == 'point' && displayLayer.style.type == 'styled') {
7251
- test = getGraduatedCircleTest(getRadiusFunction(displayLayer.style));
7261
+ if (geoType == 'point' && layer.gui.style.type == 'styled') {
7262
+ test = getGraduatedCircleTest(getRadiusFunction(layer.gui.style));
7252
7263
  } else if (geoType == 'point') {
7253
7264
  test = pointTest;
7254
7265
  } else if (interactionMode == 'drawing' && geoType == 'polygon') {
@@ -7288,13 +7299,13 @@
7288
7299
  cand, hitId;
7289
7300
  for (var i=0; i<cands.length; i++) {
7290
7301
  cand = cands[i];
7291
- if (geom.testPointInPolygon(x, y, cand.shape, displayLayer.arcs)) {
7302
+ if (geom.testPointInPolygon(x, y, cand.shape, layer.gui.displayArcs)) {
7292
7303
  hits.push(cand);
7293
7304
  }
7294
7305
  }
7295
7306
  if (cands.length > 0 && hits.length === 0) {
7296
7307
  // secondary detection: proximity, if not inside a polygon
7297
- sortByDistance(x, y, cands, displayLayer.arcs);
7308
+ sortByDistance(x, y, cands, layer.gui.displayArcs);
7298
7309
  hits = pickNearestCandidates(cands, 0, maxDist);
7299
7310
  }
7300
7311
  return {
@@ -7319,7 +7330,7 @@
7319
7330
  bufPix = bufArg >= 0 ? bufArg : 0.05, // tiny threshold for hitting almost-identical lines
7320
7331
  bufDist = getZoomAdjustedHitBuffer(bufPix),
7321
7332
  cands = findHitCandidates(x, y, maxDist);
7322
- sortByDistance(x, y, cands, displayLayer.arcs);
7333
+ sortByDistance(x, y, cands, layer.gui.displayArcs);
7323
7334
  cands = pickNearestCandidates(cands, bufDist, maxDist);
7324
7335
  return {
7325
7336
  ids: utils$1.pluck(cands, 'id')
@@ -7360,7 +7371,7 @@
7360
7371
  hits = [];
7361
7372
 
7362
7373
  // inlining forEachPoint() does not not appreciably speed this up
7363
- internal.forEachPoint(displayLayer.layer.shapes, function(p, id) {
7374
+ internal.forEachPoint(layer.gui.displayLayer.shapes, function(p, id) {
7364
7375
  var dist = geom.distance2D(x, y, p[0], p[1]) * toPx;
7365
7376
  if (dist > hitThreshold) return;
7366
7377
  if (dist < hitThreshold && hitThreshold > bullseyeDist) {
@@ -7394,7 +7405,7 @@
7394
7405
  directHit = false,
7395
7406
  hitRadius = 0,
7396
7407
  hitDist;
7397
- internal.forEachPoint(displayLayer.layer.shapes, function(p, id) {
7408
+ internal.forEachPoint(layer.gui.displayLayer.shapes, function(p, id) {
7398
7409
  var distSq = geom.distanceSq(x, y, p[0], p[1]);
7399
7410
  var isHit = false;
7400
7411
  var isOver, isNear, r, d, rpix;
@@ -7434,11 +7445,11 @@
7434
7445
 
7435
7446
  // Returns array of shape ids for shapes that pass a buffered bounding-box test
7436
7447
  function findHitCandidates(x, y, dist) {
7437
- var arcs = displayLayer.arcs,
7448
+ var arcs = layer.gui.displayArcs,
7438
7449
  index = {},
7439
7450
  cands = [],
7440
7451
  bbox = [];
7441
- displayLayer.layer.shapes.forEach(function(shp, shpId) {
7452
+ layer.gui.displayLayer.shapes.forEach(function(shp, shpId) {
7442
7453
  var cand;
7443
7454
  if (featureFilter && !featureFilter(shpId)) {
7444
7455
  return;
@@ -7570,7 +7581,7 @@
7570
7581
 
7571
7582
  function getPointerHitTest(mapLayer, ext, interactionMode, featureFilter) {
7572
7583
  var shapeTest, targetLayer;
7573
- if (!mapLayer || !internal.layerHasGeometry(mapLayer.layer)) {
7584
+ if (!mapLayer || !internal.layerHasGeometry(mapLayer.gui?.displayLayer)) {
7574
7585
  return function() {return {ids: []};};
7575
7586
  }
7576
7587
  shapeTest = getShapeHitTest(mapLayer, ext, interactionMode, featureFilter);
@@ -7609,6 +7620,13 @@
7609
7620
  // pan navigation
7610
7621
  var priority = 2;
7611
7622
 
7623
+ mouse.on('contextmenu', function(e) {
7624
+ if (isOverMap(e)) {
7625
+ e.originalEvent.preventDefault();
7626
+ }
7627
+ triggerHitEvent('contextmenu', e);
7628
+ }, false);
7629
+
7612
7630
  // init keyboard controls for pinned features
7613
7631
  gui.keyboard.on('keydown', function(evt) {
7614
7632
  var e = evt.originalEvent;
@@ -7636,7 +7654,7 @@
7636
7654
  // to help protect against inadvertent deletion, don't delete
7637
7655
  // when console is open or a popup menu is open
7638
7656
  if (!gui.getMode() && !gui.consoleIsOpen()) {
7639
- internal.deleteFeatureById(targetLayer.layer, pinnedId());
7657
+ internal.deleteFeatureById(targetLayer, pinnedId());
7640
7658
  self.clearSelection();
7641
7659
  gui.model.updated({flags: 'filter'}); // signal map to update
7642
7660
  }
@@ -7679,6 +7697,7 @@
7679
7697
 
7680
7698
  function pinnable() {
7681
7699
  return clickable() && !selectable();
7700
+ // return clickable();
7682
7701
  }
7683
7702
 
7684
7703
  function draggable() {
@@ -7773,7 +7792,7 @@
7773
7792
 
7774
7793
  self.getTargetDataTable = function() {
7775
7794
  var targ = self.getHitTarget();
7776
- return targ && targ.layer.data || null;
7795
+ return targ?.data || null;
7777
7796
  };
7778
7797
 
7779
7798
  // get function for selecting next or prev feature within the current set of
@@ -7786,7 +7805,7 @@
7786
7805
 
7787
7806
  // diff: 1 or -1
7788
7807
  function advanceSelectedFeature(diff) {
7789
- var n = internal.getFeatureCount(targetLayer.layer);
7808
+ var n = internal.getFeatureCount(targetLayer);
7790
7809
  if (n < 2 || pinnedId() == -1) return;
7791
7810
  storedData.id = (pinnedId() + n + diff) % n;
7792
7811
  storedData.ids = [storedData.id];
@@ -7831,12 +7850,15 @@
7831
7850
 
7832
7851
  mouse.on('click', function(e) {
7833
7852
  if (!hitTest || !active) return;
7853
+ if (!eventIsEnabled('click')) return;
7854
+
7834
7855
  e.stopPropagation();
7835
7856
 
7836
7857
  // TODO: move pinning to inspection control?
7837
7858
  if (clickable()) {
7838
7859
  updateSelectionState(convertClickDataToSelectionData(hitTest(e)));
7839
7860
  }
7861
+
7840
7862
  triggerHitEvent('click', e);
7841
7863
  }, null, priority);
7842
7864
 
@@ -7855,6 +7877,7 @@
7855
7877
  }
7856
7878
  }, null, priority);
7857
7879
 
7880
+
7858
7881
  function targetIsRollover(target) {
7859
7882
  while (target.parentNode && target != target.parentNode) {
7860
7883
  if (target.className && String(target.className).indexOf('rollover') > -1) {
@@ -7893,6 +7916,19 @@
7893
7916
  return hitData;
7894
7917
  }
7895
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
+
7896
7932
  function mergeHoverData(hitData) {
7897
7933
  if (storedData.pinned) {
7898
7934
  hitData.id = storedData.id;
@@ -7947,14 +7983,21 @@
7947
7983
  return true; // special case -- using hover for line drawing animation
7948
7984
  }
7949
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
+
7950
7996
  // ignore pointer events when no features are being hit
7951
7997
  // (don't block pan and other navigation when events aren't being used for editing)
7952
7998
  var hitId = self.getHitId();
7953
7999
  if (hitId == -1) return false;
7954
8000
 
7955
- if (type == 'click' && !clickable()) {
7956
- return false;
7957
- }
7958
8001
  if ((type == 'drag' || type == 'dragstart' || type == 'dragend') && !draggable()) {
7959
8002
  return false;
7960
8003
  }
@@ -7983,10 +8026,13 @@
7983
8026
  // evt: event data (may be a pointer event object, an ordinary object or null)
7984
8027
  function triggerHitEvent(type, evt) {
7985
8028
  var eventData = {
7986
- mode: interactionMode,
7987
- overMap: evt ? isOverMap(evt) : null,
7988
- originalEvent: evt ? evt : null
8029
+ mode: interactionMode
7989
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
+ }
7990
8036
  // Merge stored hit data into the event data
7991
8037
  utils$1.defaults(eventData, evt && evt.data || {}, storedData);
7992
8038
  // utils.extend(eventData, storedData);
@@ -8033,7 +8079,7 @@
8033
8079
 
8034
8080
  function enabled() {
8035
8081
  var lyr = gui.map.getActiveLayer();
8036
- return !!(lyr && lyr.layer.geometry_type);
8082
+ return !!(lyr && lyr.gui.displayLayer.geometry_type);
8037
8083
  }
8038
8084
 
8039
8085
  gui.model.on('update', function(e) {
@@ -8078,7 +8124,7 @@
8078
8124
  var p2 = gui.map.translatePixelCoords(ext.width(), 0);
8079
8125
  var bbox = p1.concat(p2);
8080
8126
  var decimals = internal.getBoundsPrecisionForDisplay(bbox);
8081
- var str = internal.getRoundedCoordString(p, decimals);
8127
+ var str = internal.getRoundedCoordString(p, decimals);
8082
8128
  readout.text(str).show();
8083
8129
  }
8084
8130
 
@@ -8359,6 +8405,9 @@
8359
8405
  element.addEventListener('mouseout', onAreaOut);
8360
8406
  element.addEventListener('mousedown', onAreaDown);
8361
8407
  element.addEventListener('dblclick', onAreaDblClick);
8408
+ document.addEventListener('contextmenu', function(e) {
8409
+ _self.dispatchEvent('contextmenu', procMouseEvent(e));
8410
+ });
8362
8411
 
8363
8412
  this.enable = function() {
8364
8413
  if (!_disabled) return;
@@ -8926,8 +8975,8 @@
8926
8975
  gui.on('interaction_mode_change', function(e) {
8927
8976
  if (e.mode === 'selection') {
8928
8977
  gui.enterMode('selection_tool');
8929
- } else if (gui.getMode() == 'selection_tool') {
8930
- gui.clearMode();
8978
+ } else if (_on) {
8979
+ turnOff();
8931
8980
  }
8932
8981
  });
8933
8982
 
@@ -9393,8 +9442,17 @@
9393
9442
  }
9394
9443
  });
9395
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
+
9396
9453
  hit.on('change', function(e) {
9397
9454
  if (!inspecting()) return;
9455
+ if (gui.keyboard.ctrlIsPressed()) return;
9398
9456
  var ids;
9399
9457
  if (e.mode == 'selection') {
9400
9458
  ids = e.pinned && e.ids || [];
@@ -9407,8 +9465,8 @@
9407
9465
  // id: Id of a feature in the active layer, or -1
9408
9466
  function inspect(id, ids, pin) {
9409
9467
  var target = hit.getHitTarget();
9410
- if ((id > -1 || ids && ids.length > 0) && inspecting() && target && target.layer) {
9411
- _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);
9412
9470
  } else {
9413
9471
  _popup.hide();
9414
9472
  }
@@ -9512,15 +9570,20 @@
9512
9570
 
9513
9571
  function initLabelDragging(gui, ext, hit) {
9514
9572
  var downEvt;
9515
- var activeId;
9573
+ var activeId = -1;
9574
+ var prevHitEvt;
9516
9575
  var activeRecord;
9517
9576
 
9518
- function active(e) {
9519
- 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();
9520
9583
  }
9521
9584
 
9522
9585
  hit.on('dragstart', function(e) {
9523
- if (!active(e)) return;
9586
+ if (!labelSelected(e)) return;
9524
9587
  var symNode = getSymbolTarget(e);
9525
9588
  var table = hit.getTargetDataTable();
9526
9589
  if (!symNode || !table) {
@@ -9533,8 +9596,25 @@
9533
9596
  gui.dispatchEvent('label_dragstart', {FID: activeId});
9534
9597
  });
9535
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
+
9536
9616
  hit.on('drag', function(e) {
9537
- if (!active(e) || activeId == -1) return;
9617
+ if (!labelSelected(e) || activeId == -1) return;
9538
9618
  if (e.id != activeId) {
9539
9619
  error$1("Mismatched hit ids:", e.id, activeId);
9540
9620
  }
@@ -9548,7 +9628,7 @@
9548
9628
  // to anchor point, for better placement when eventual display font is
9549
9629
  // different from mapshaper's font.
9550
9630
  // if (!isMultilineLabel(textNode)) {
9551
- autoUpdateTextAnchor(textNode, activeRecord, getDisplayCoordsById(activeId, hit.getHitTarget().layer, ext));
9631
+ autoUpdateTextAnchor(textNode, activeRecord, getDisplayCoordsById(activeId, hit.getHitTarget(), ext));
9552
9632
  // }
9553
9633
  updateNumber(activeRecord, 'dx', internal.roundToDigits(+activeRecord.dx, 3));
9554
9634
  updateNumber(activeRecord, 'dy', internal.roundToDigits(+activeRecord.dy, 3));
@@ -9558,7 +9638,7 @@
9558
9638
  });
9559
9639
 
9560
9640
  hit.on('dragend', function(e) {
9561
- if (!active(e) || activeId == -1) return;
9641
+ if (!labelSelected(e) || activeId == -1) return;
9562
9642
  gui.dispatchEvent('label_dragend', {FID: e.id});
9563
9643
  activeId = -1;
9564
9644
  activeRecord = null;
@@ -9579,19 +9659,25 @@
9579
9659
  }
9580
9660
 
9581
9661
  function getSymbolTarget(e) {
9582
- if (e.id > -1 === false || !e.container) return null;
9583
- return getSymbolNodeById(e.id, e.container);
9662
+ return e.id > -1 ? getSymbolNodeById(e.id) : null;
9584
9663
  }
9585
9664
 
9586
9665
  function getTextNode(symNode) {
9666
+ if (!symNode) return null;
9587
9667
  if (symNode.tagName == 'text') return symNode;
9588
9668
  return symNode.querySelector('text');
9589
9669
  }
9590
9670
 
9591
- 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) {
9592
9677
  // TODO: optimize selector
9593
9678
  var sel = '[data-id="' + id + '"]';
9594
- return parent.querySelector(sel);
9679
+ var activeLayer = hit.getHitTarget();
9680
+ return activeLayer.gui.svg_container.querySelector(sel);
9595
9681
  }
9596
9682
 
9597
9683
  // function getTextTarget2(e) {
@@ -9638,7 +9724,7 @@
9638
9724
  // fails when symbol includes a dot (<g><circle/><text/></g> structure)
9639
9725
  function updateSymbolNode(node, d, id) {
9640
9726
  var o = internal.svg.renderStyledLabel(d); // TODO: symbol support
9641
- var activeLayer = hit.getHitTarget().layer;
9727
+ var activeLayer = hit.getHitTarget();
9642
9728
  var xy = activeLayer.shapes[id][0];
9643
9729
  var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
9644
9730
  var node2;
@@ -9671,7 +9757,7 @@
9671
9757
  hit.on('drag', function(e) {
9672
9758
  if (!active(e)) return;
9673
9759
  // TODO: support multi points... get id of closest part to the pointer
9674
- var p = getPointCoordsById(e.id, symbolInfo.target.layer);
9760
+ var p = getPointCoordsById(e.id, symbolInfo.target);
9675
9761
  if (!p) return;
9676
9762
  var diff = translateDeltaDisplayCoords(e.dx, e.dy, ext);
9677
9763
  p[0] += diff[0];
@@ -9702,20 +9788,22 @@
9702
9788
  }
9703
9789
  }
9704
9790
 
9705
- // pointer thresholds for hovering near a vertex or segment midpoint
9706
- var HOVER_THRESHOLD$1 = 10;
9791
+ // pixel distance threshold for hovering near a vertex or segment midpoint
9792
+ var HOVER_THRESHOLD = 10;
9707
9793
 
9708
9794
  function initLineEditing(gui, ext, hit) {
9709
9795
  var hoverVertexInfo;
9710
9796
  var prevClickEvent;
9711
9797
  var prevHoverEvent;
9712
9798
  var initialArcCount = -1;
9799
+ var initialShapeCount = -1;
9713
9800
  var drawingId = -1; // feature id of path being drawn
9801
+ var sessionCount = 0;
9714
9802
  var alert;
9715
9803
  var _dragging = false;
9716
9804
 
9717
9805
  function active() {
9718
- return !!alert;
9806
+ return initialArcCount >= 0;
9719
9807
  }
9720
9808
 
9721
9809
  function dragging() {
@@ -9727,7 +9815,7 @@
9727
9815
  }
9728
9816
 
9729
9817
  function polygonMode() {
9730
- return active() && hit.getHitTarget().layer.geometry_type == 'polygon';
9818
+ return active() && hit.getHitTarget().geometry_type == 'polygon';
9731
9819
  }
9732
9820
 
9733
9821
  function clearHoverVertex() {
@@ -9743,7 +9831,7 @@
9743
9831
  if (e.mode == 'drawing') {
9744
9832
  gui.enterMode('drawing_tool');
9745
9833
  } else if (active()) {
9746
- gui.clearMode();
9834
+ turnOff();
9747
9835
  }
9748
9836
  }, null, 10); // higher priority than hit control, so turnOff() has correct hit target
9749
9837
 
@@ -9753,7 +9841,7 @@
9753
9841
  appendNewPath(target, e.p1, e.p2);
9754
9842
  deleteLastVertex(target); // second vertex is a placeholder
9755
9843
  gui.undo.redo(); // add next vertex in the path
9756
- gui.model.updated({arc_count: true});
9844
+ fullRedraw();
9757
9845
  });
9758
9846
 
9759
9847
  gui.on('undo_path_add', function(e) {
@@ -9769,6 +9857,9 @@
9769
9857
  } else {
9770
9858
  appendVertex$1(target, e.p);
9771
9859
  }
9860
+ if (e.shapes) {
9861
+ replaceDrawnShapes(e.shapes);
9862
+ }
9772
9863
  });
9773
9864
 
9774
9865
  gui.on('undo_path_extend', function(e) {
@@ -9782,75 +9873,74 @@
9782
9873
  if (getLastArcLength(target) < 2) {
9783
9874
  gui.undo.undo(); // remove the path
9784
9875
  }
9876
+ if (e.shapes) {
9877
+ replaceDrawnShapes(e.shapes);
9878
+ }
9785
9879
  });
9786
9880
 
9787
9881
  function turnOn() {
9788
9882
  var target = hit.getHitTarget();
9789
- var pathStr = polygonMode() ? 'closed paths' : 'paths';
9883
+ initialArcCount = target.gui.displayArcs.size();
9884
+ initialShapeCount = target.shapes.length;
9885
+ if (sessionCount === 0) {
9886
+ showInstructions();
9887
+ }
9888
+ sessionCount++;
9889
+ }
9890
+
9891
+ function showInstructions() {
9790
9892
  var isMac = navigator.userAgent.includes('Mac');
9791
9893
  var symbol = isMac ? '⌘' : '^';
9894
+ var pathStr = polygonMode() ? 'closed paths' : 'paths';
9792
9895
  var msg = `Instructions: Click on the map to draw ${pathStr}. Drag vertices to reshape a path. Type ${symbol}Z/${symbol}Y to undo/redo.`;
9793
- initialArcCount = hit.getHitTarget().arcs.size();
9794
- alert = showPopupAlert(msg, null, {non_blocking: true, max_width: '360px'});
9896
+ alert = showPopupAlert(msg, null, {
9897
+ non_blocking: true, max_width: '360px'});
9898
+ }
9899
+
9900
+ function hideInstructions() {
9901
+ if (!alert) return;
9902
+ alert.close('fade');
9903
+ alert = null;
9795
9904
  }
9796
9905
 
9797
9906
  function turnOff() {
9798
- finishPath();
9907
+ var removed = 0;
9908
+ finishCurrentPath();
9799
9909
  if (polygonMode()) {
9800
- finishPolygons();
9910
+ removed = removeOpenPolygons();
9801
9911
  }
9802
9912
  clearDrawingInfo();
9803
- alert.close();
9804
- alert = null;
9913
+ hideInstructions();
9805
9914
  initialArcCount = -1;
9915
+ initialShapeCount = -1;
9806
9916
  if (gui.interaction.getMode() == 'drawing') {
9807
9917
  // mode change was not initiated by interactive menu -- turn off interactivity
9808
9918
  gui.interaction.turnOff();
9809
9919
  }
9810
- }
9811
-
9812
- function finish() {
9813
- if (polygonMode()) {
9814
- finishPolygons();
9920
+ if (removed > 0) {
9921
+ fullRedraw();
9815
9922
  }
9816
9923
  }
9817
9924
 
9818
- function finishPolygons() {
9819
- // step1: make a polyline layer containing just newly drawn paths
9925
+ // returns number of removed shapes
9926
+ function removeOpenPolygons() {
9820
9927
  var target = hit.getHitTarget();
9821
- var newShapeCount = target.arcs.size() - initialArcCount;
9822
- if (newShapeCount <= 0) return; // no paths added
9823
- var polygonLyr = target.source.layer;
9824
- var polygonRecords = polygonLyr.data ? polygonLyr.data.getRecords() : null;
9825
- var initialShapeCount = polygonLyr.shapes.length - newShapeCount;
9826
- var templateRecord;
9827
- if (polygonRecords) {
9828
- // use one of the newly created records as a template (they should all be the same)
9829
- templateRecord = polygonRecords.pop();
9830
- polygonRecords.splice(initialShapeCount); // remove new records
9831
- }
9832
- var polylineLyr = {
9833
- geometry_type: 'polyline',
9834
- shapes: polygonLyr.shapes.splice(initialShapeCount) // move new shapes to polyline layer
9835
- };
9836
-
9837
- // step2: convert polylines to polygons
9838
- //
9839
- // create a temp dataset containing both layers (so original arcs are preserved)
9840
- var tmp = Object.assign({}, target.source.dataset);
9841
- tmp.layers = tmp.layers.concat(polylineLyr);
9842
- var outputLayers = mapshaper.cmd.polygons([polylineLyr], tmp, {});
9843
-
9844
- // step3: add new polygons to the original polygons
9845
- outputLayers[0].shapes.forEach(function(shp) {
9846
- var rec = polygonRecords ? internal.copyRecord(templateRecord) : null;
9847
- polygonLyr.shapes.push(shp);
9848
- if (polygonRecords) {
9849
- polygonRecords.push(rec);
9928
+ var arcs = target.gui.source.dataset.arcs;
9929
+ var n = target.shapes.length;
9930
+ // delete open paths
9931
+ for (var i=initialShapeCount; i<n; i++) {
9932
+ var shp = target.shapes[i];
9933
+ if (!geom.pathIsClosed(shp[0], arcs)) { // assume open paths have one arc
9934
+ target.shapes[i] = null;
9850
9935
  }
9851
- });
9936
+ }
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;
9940
+ }
9852
9941
 
9853
- // step4: update map
9942
+ // updates display arcs and redraws all layers
9943
+ function fullRedraw() {
9854
9944
  gui.model.updated({arc_count: true});
9855
9945
  }
9856
9946
 
@@ -9861,10 +9951,25 @@
9861
9951
  prevClickEvent = prevHoverEvent = null;
9862
9952
  }
9863
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
+
9864
9967
  hit.on('dragstart', function(e) {
9865
9968
  if (!active() || drawing() || !hoverVertexInfo) return;
9969
+ hideInstructions();
9866
9970
  e.originalEvent.stopPropagation();
9867
9971
  _dragging = true;
9972
+ updateCursor();
9868
9973
  if (hoverVertexInfo.type == 'interpolated') {
9869
9974
  insertVertex$1(hit.getHitTarget(), hoverVertexInfo.i, hoverVertexInfo.point);
9870
9975
  hoverVertexInfo.ids = [hoverVertexInfo.i];
@@ -9878,11 +9983,10 @@
9878
9983
  var target = hit.getHitTarget();
9879
9984
  var p = ext.translatePixelCoords(e.x, e.y);
9880
9985
  if (gui.keyboard.shiftIsPressed()) {
9881
- internal.snapPointToArcEndpoint(p, hoverVertexInfo.ids, target.arcs);
9986
+ internal.snapPointToArcEndpoint(p, hoverVertexInfo.ids, target.gui.displayArcs);
9882
9987
  }
9883
- internal.snapVerticesToPoint(hoverVertexInfo.ids, p, target.arcs);
9988
+ internal.snapVerticesToPoint(hoverVertexInfo.ids, p, target.gui.displayArcs);
9884
9989
  hit.setHoverVertex(p, '');
9885
-
9886
9990
  // redrawing the whole map updates the data layer as well as the overlay layer
9887
9991
  // gui.dispatchEvent('map-needs-refresh');
9888
9992
  });
@@ -9890,9 +9994,9 @@
9890
9994
  hit.on('dragend', function(e) {
9891
9995
  if (!dragging()) return;
9892
9996
  _dragging = false;
9893
- // kludge to get dataset to recalculate internal bounding boxes
9894
9997
  var target = hit.getHitTarget();
9895
- target.arcs.transformPoints(function() {});
9998
+ // kludge to get dataset to recalculate internal bounding boxes
9999
+ target.gui.displayArcs.transformPoints(function() {});
9896
10000
  updateVertexCoords(target, hoverVertexInfo.ids);
9897
10001
  gui.dispatchEvent('vertex_dragend', hoverVertexInfo);
9898
10002
  gui.dispatchEvent('map-needs-refresh'); // redraw basemap
@@ -9903,12 +10007,10 @@
9903
10007
  // double-click finishes a path (when drawing)
9904
10008
  hit.on('dblclick', function(e) {
9905
10009
  if (!active()) return;
10010
+ // double click finishes a path
10011
+ // note: if the preceding 'click' finished the path, this does not fire
9906
10012
  if (drawing()) {
9907
- // double click finishes a path
9908
- // before: dblclick is preceded by two clicks, need another vertex delete
9909
- // now: second click is suppressed
9910
- // deleteLastVertex(hit.getHitTarget());
9911
- finishPath();
10013
+ finishCurrentPath();
9912
10014
  e.originalEvent.stopPropagation(); // prevent dblclick zoom
9913
10015
  return;
9914
10016
  }
@@ -9918,9 +10020,10 @@
9918
10020
  // ... or the closest point along the segment (for adding a new vertex)
9919
10021
  hit.on('hover', function(e) {
9920
10022
  if (!active() || dragging()) return;
10023
+
9921
10024
  if (drawing()) {
9922
10025
  if (!e.overMap) {
9923
- finishPath();
10026
+ finishCurrentPath();
9924
10027
  return;
9925
10028
  }
9926
10029
  if (gui.keyboard.shiftIsPressed()) {
@@ -9940,6 +10043,7 @@
9940
10043
  } else {
9941
10044
  clearHoverVertex();
9942
10045
  }
10046
+ updateCursor();
9943
10047
  prevHoverEvent = e;
9944
10048
  }, null, 100);
9945
10049
 
@@ -9948,27 +10052,26 @@
9948
10052
  if (!active()) return;
9949
10053
  if (detectDoubleClick(e)) return; // ignore second click of a dblclick
9950
10054
  var p = pixToDataCoords(e.x, e.y);
9951
- if (drawing() && hoverVertexInfo) {
9952
- // finish the path if a vertex is highlighted
9953
- p = hoverVertexInfo.point;
9954
- extendPath(p);
9955
- finishPath();
9956
- } else if (drawing()) {
9957
- extendPath(p);
10055
+ if (drawing()) {
10056
+ extendCurrentPath(hoverVertexInfo?.point || p);
9958
10057
  } else if (gui.keyboard.shiftIsPressed()) {
9959
10058
  deleteActiveVertex(e);
9960
10059
  } else {
9961
- startPath(p);
10060
+ startNewPath(p);
10061
+ hideInstructions();
10062
+ updateCursor();
9962
10063
  }
9963
10064
  prevClickEvent = e;
9964
10065
  });
9965
10066
 
9966
- // esc key finishes a path
10067
+ // esc or enter key finishes a path
9967
10068
  gui.keyboard.on('keydown', function(e) {
9968
- if (active() && e.keyName == 'esc') {
9969
- finishPath();
10069
+ if (active() && (e.keyName == 'esc' || e.keyName == 'enter')) {
10070
+ e.stopPropagation();
10071
+ finishCurrentPath();
10072
+ e.originalEvent.preventDefault(); // block console "enter"
9970
10073
  }
9971
- });
10074
+ }, null, 10);
9972
10075
 
9973
10076
  // detect second 'click' event of a double-click action
9974
10077
  function detectDoubleClick(evt) {
@@ -9980,16 +10083,24 @@
9980
10083
  return dbl;
9981
10084
  }
9982
10085
 
9983
- function deleteActiveVertex(e) {
9984
- var info = findDraggableVertices(e);
9985
- 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;
9986
10101
  var vId = info.ids[0];
9987
10102
  var target = hit.getHitTarget();
9988
- if (internal.vertexIsArcStart(vId, target.arcs) ||
9989
- internal.vertexIsArcEnd(vId, target.arcs)) {
9990
- // TODO: support removing arc endpoints
9991
- return;
9992
- }
10103
+ if (vertexIsEndpoint(info, target)) return;
9993
10104
  gui.dispatchEvent('vertex_delete', {
9994
10105
  target: target,
9995
10106
  vertex_id: vId
@@ -10015,28 +10126,30 @@
10015
10126
  var dist2 = dist / Math.sqrt(2);
10016
10127
  var minDist = Infinity;
10017
10128
  var cands = [
10018
- {x: x0, y: y0 + dist},
10019
- {x: x0, y: y0 - dist},
10020
- {x: x0 + dist, y: y0},
10021
- {x: x0 - dist, y: y0},
10022
- {x: x0 + dist2, y: y0 + dist2},
10023
- {x: x0 + dist2, y: y0 - dist2},
10024
- {x: x0 - dist2, y: y0 + dist2},
10025
- {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}
10026
10137
  ];
10027
10138
  var snapped = cands.reduce(function(memo, cand) {
10028
- 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);
10029
10140
  if (dist < minDist) {
10030
10141
  minDist = dist;
10031
10142
  return cand;
10032
10143
  }
10033
10144
  return memo;
10034
10145
  }, null);
10035
- thisEvt.x = snapped.x;
10036
- thisEvt.y = snapped.y;
10146
+ thisEvt.x = x0 + snapped.dx;
10147
+ thisEvt.y = y0 + snapped.dy;
10148
+
10149
+ return null;
10037
10150
  }
10038
10151
 
10039
- function finishPath() {
10152
+ function finishCurrentPath() {
10040
10153
  if (!drawing()) return;
10041
10154
  var target = hit.getHitTarget();
10042
10155
  if (getLastArcLength(target) <= 2) { // includes hover point
@@ -10045,33 +10158,64 @@
10045
10158
  deleteLastVertex(target);
10046
10159
  }
10047
10160
  clearDrawingInfo();
10048
- gui.model.updated({arc_count: true});
10161
+ fullRedraw();
10049
10162
  }
10050
10163
 
10051
10164
  // p: [x, y] source data coordinates
10052
- function startPath(p2) {
10165
+ function startNewPath(p2) {
10053
10166
  var target = hit.getHitTarget();
10054
- var p1 = hoverVertexInfo ? hoverVertexInfo.point : p2;
10167
+ var p1 = hoverVertexInfo?.point || p2;
10055
10168
  appendNewPath(target, p1, p2);
10056
10169
  gui.dispatchEvent('path_add', {target, p1, p2});
10057
- drawingId = target.layer.shapes.length - 1;
10170
+ drawingId = target.shapes.length - 1;
10058
10171
  hit.setDrawingId(drawingId);
10059
10172
  }
10060
10173
 
10061
- // p: [x, y] source data coordinates
10062
- function extendPath(p) {
10174
+ // p: [x, y] source data coordinates of new point on path
10175
+ function extendCurrentPath(p) {
10063
10176
  var target = hit.getHitTarget();
10064
- if (getLastArcLength(target) >= 2) {
10177
+ var shapes1, shapes2;
10178
+ // finish the path if a vertex is selected (but not an interpolated point)
10179
+ var finish = hoverVertexInfo?.type == 'vertex';
10180
+ if (getLastArcLength(target) < 2) {
10181
+ error$1('Defective path');
10182
+ }
10183
+ if (finish && polygonMode()) {
10184
+ shapes1 = target.shapes.slice(initialShapeCount);
10185
+ shapes2 = convertClosedPaths(shapes1);
10186
+ }
10187
+ if (shapes2) {
10188
+ replaceDrawnShapes(shapes2);
10189
+ gui.dispatchEvent('path_extend', {target, p, shapes1, shapes2});
10190
+ clearDrawingInfo();
10191
+ fullRedraw();
10192
+
10193
+ } else {
10194
+ appendVertex$1(target, p);
10065
10195
  gui.dispatchEvent('path_extend', {target, p});
10196
+ hit.triggerChangeEvent(); // trigger overlay redraw
10197
+ }
10198
+ }
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);
10066
10212
  }
10067
- appendVertex$1(target, p);
10068
- hit.triggerChangeEvent();
10069
10213
  }
10070
10214
 
10071
10215
  // p: [x, y] source data coordinates
10072
10216
  function updatePathEndpoint(p) {
10073
10217
  var target = hit.getHitTarget();
10074
- var i = target.arcs.getPointCount() - 1;
10218
+ var i = target.gui.displayArcs.getPointCount() - 1;
10075
10219
  if (hoverVertexInfo) {
10076
10220
  p = hoverVertexInfo.point; // snap to selected point
10077
10221
  }
@@ -10081,8 +10225,8 @@
10081
10225
 
10082
10226
  function findPathStartInfo(e) {
10083
10227
  var target = hit.getHitTarget();
10084
- var arcId = target.arcs.size() - 1;
10085
- var data = target.arcs.getVertexData();
10228
+ var arcId = target.gui.displayArcs.size() - 1;
10229
+ var data = target.gui.displayArcs.getVertexData();
10086
10230
  var i = data.ii[arcId];
10087
10231
  var x = data.xx[i];
10088
10232
  var y = data.yy[i];
@@ -10090,7 +10234,7 @@
10090
10234
  var dist = geom.distance2D(p[0], p[1], x, y);
10091
10235
  var pathLen = data.nn[arcId];
10092
10236
  var pixelDist = dist / ext.getPixelSize();
10093
- if (pixelDist > HOVER_THRESHOLD$1 || pathLen < 4) {
10237
+ if (pixelDist > HOVER_THRESHOLD || pathLen < 4) {
10094
10238
  return null;
10095
10239
  }
10096
10240
  var point = translateDisplayPoint(target, [x, y]);
@@ -10104,41 +10248,38 @@
10104
10248
  //
10105
10249
  function findDraggableVertices(e) {
10106
10250
  var target = hit.getHitTarget();
10107
- var shp = target.layer.shapes[e.id];
10251
+ var shp = target.shapes[e.id];
10108
10252
  var p = ext.translatePixelCoords(e.x, e.y);
10109
- var ids = internal.findNearestVertices(p, shp, target.arcs);
10110
- 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]);
10111
10255
  var dist = geom.distance2D(p[0], p[1], p2[0], p2[1]);
10112
10256
  var pixelDist = dist / ext.getPixelSize();
10113
- if (pixelDist > HOVER_THRESHOLD$1) {
10257
+ if (pixelDist > HOVER_THRESHOLD) {
10114
10258
  return null;
10115
10259
  }
10116
10260
  var point = getVertexCoords(target, ids[0]); // data coordinates
10117
10261
  // find out if the vertex is the endpoint of a single path
10118
10262
  // (which could be extended by a newly drawn path)
10119
10263
  var extendable = ids.length == 1 &&
10120
- internal.vertexIsArcEndpoint(ids[0], target.arcs);
10121
- var displayPoint = target.arcs.getVertex2(ids[0]);
10264
+ internal.vertexIsArcEndpoint(ids[0], target.gui.displayArcs);
10265
+ var displayPoint = target.gui.displayArcs.getVertex2(ids[0]);
10122
10266
  return {target, ids, extendable, point, displayPoint, type: 'vertex'};
10123
10267
  }
10124
10268
 
10269
+
10125
10270
  function findInterpolatedPoint(e) {
10126
10271
  var target = hit.getHitTarget();
10127
10272
  //// vertex insertion not supported with simplification
10128
10273
  // if (!target.arcs.isFlat()) return null;
10129
10274
  var p = ext.translatePixelCoords(e.x, e.y);
10130
10275
  var minDist = Infinity;
10131
- var shp = target.layer.shapes[e.id];
10276
+ var shp = target.shapes[e.id];
10132
10277
  var closest;
10133
- internal.forEachSegmentInShape(shp, target.arcs, function(i, j, xx, yy) {
10278
+ internal.forEachSegmentInShape(shp, target.gui.displayArcs, function(i, j, xx, yy) {
10134
10279
  var x1 = xx[i],
10135
10280
  y1 = yy[i],
10136
10281
  x2 = xx[j],
10137
10282
  y2 = yy[j],
10138
- // switching from midpoint to nearest point to the mouse
10139
- // cx = (x1 + x2) / 2,
10140
- // cy = (y1 + y2) / 2,
10141
- // p2 = [cx, cy],
10142
10283
  p2 = internal.findClosestPointOnSeg(p[0], p[1], x1, y1, x2, y2, 0),
10143
10284
  dist = geom.distance2D(p2[0], p2[1], p[0], p[1]);
10144
10285
  if (dist < minDist) {
@@ -10151,7 +10292,7 @@
10151
10292
  }
10152
10293
  });
10153
10294
 
10154
- if (closest.distance / ext.getPixelSize() > HOVER_THRESHOLD$1) {
10295
+ if (closest.distance / ext.getPixelSize() > HOVER_THRESHOLD) {
10155
10296
  return null;
10156
10297
  }
10157
10298
  closest.point = translateDisplayPoint(target, closest.displayPoint);
@@ -10160,6 +10301,45 @@
10160
10301
  return closest;
10161
10302
  }
10162
10303
 
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) {
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
10311
+ var tmpLyr = {
10312
+ geometry_type: 'polyline',
10313
+ shapes: shapes.concat()
10314
+ };
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);
10324
+ }
10325
+
10326
+ // Returns a function for testing if a shape is an unclosed path, and doesn't
10327
+ // overlap with an array of polygon shapes
10328
+ function getOpenPathTest(polygonShapes) {
10329
+ var polygonArcs = [];
10330
+ internal.forEachArcId(polygonShapes, function(arcId) {
10331
+ polygonArcs.push(internal.absArcId(arcId));
10332
+ });
10333
+
10334
+ return function(shp) {
10335
+ // assume that any compound shape is a polygon
10336
+ return shapeHasOneFwdArc(shp) && !polygonArcs.includes(shp[0][0]);
10337
+ };
10338
+ }
10339
+
10340
+ function shapeHasOneFwdArc(shp) {
10341
+ return shp.length == 1 && shp[0].length == 1 && shp[0][0] >= 0;
10342
+ }
10163
10343
  }
10164
10344
 
10165
10345
  function initPointDrawing(gui, ext, hit) {
@@ -10182,8 +10362,7 @@
10182
10362
  // x, y: pixel coordinates
10183
10363
  function addPoint(x, y) {
10184
10364
  var p = ext.translatePixelCoords(x, y);
10185
- var target = hit.getHitTarget();
10186
- var lyr = target.layer;
10365
+ var lyr = hit.getHitTarget();
10187
10366
  var fid = lyr.shapes.length;
10188
10367
  var d = appendNewDataRecord(lyr);
10189
10368
  if (d) {
@@ -10192,194 +10371,17 @@
10192
10371
  lyr.data.getRecords()[fid] = d;
10193
10372
  }
10194
10373
  lyr.shapes[fid] = [p];
10195
- updatePointCoords(target, fid);
10374
+ updatePointCoords(lyr, fid);
10196
10375
  }
10197
10376
 
10198
10377
 
10199
10378
  }
10200
10379
 
10201
- // pointer thresholds for hovering near a vertex or segment midpoint
10202
- var HOVER_THRESHOLD = 8;
10203
- var MIDPOINT_THRESHOLD = 11;
10204
-
10205
- function initVertexDragging(gui, ext, hit) {
10206
- var insertionPoint;
10207
- var dragInfo;
10208
-
10209
- function active() {
10210
- return gui.interaction.getMode() == 'vertices';
10211
- }
10212
-
10213
- function dragging() {
10214
- return active() && !!dragInfo;
10215
- }
10216
-
10217
- function setHoverVertex(id) {
10218
- var target = hit.getHitTarget();
10219
- hit.setHoverVertex(target.arcs.getVertex2(id));
10220
- }
10221
-
10222
- function clearHoverVertex() {
10223
- hit.clearHoverVertex();
10224
- }
10225
-
10226
- // return data on the nearest vertex (or identical vertices) to the pointer
10227
- // (if within a distance threshold)
10228
- //
10229
- function findDraggableVertices(e) {
10230
- var target = hit.getHitTarget();
10231
- var shp = target.layer.shapes[e.id];
10232
- var p = ext.translatePixelCoords(e.x, e.y);
10233
- var nearestIds = internal.findNearestVertices(p, shp, target.arcs);
10234
- var p2 = target.arcs.getVertex2(nearestIds[0]);
10235
- var dist = geom.distance2D(p[0], p[1], p2[0], p2[1]);
10236
- var pixelDist = dist / ext.getPixelSize();
10237
- if (pixelDist > HOVER_THRESHOLD) {
10238
- return null;
10239
- }
10240
- var points = nearestIds.map(function(i) {
10241
- return getVertexCoords(target, i); // data coordinates
10242
- });
10243
- return {
10244
- target: target,
10245
- ids: nearestIds,
10246
- points: points
10247
- };
10248
- }
10249
-
10250
- function findVertexInsertionPoint(e) {
10251
- var target = hit.getHitTarget();
10252
- //// vertex insertion not supported with simplification
10253
- // if (!target.arcs.isFlat()) return null;
10254
- var p = ext.translatePixelCoords(e.x, e.y);
10255
- var midpoint = findNearestMidpoint(p, e.id, target);
10256
- if (!midpoint ||
10257
- midpoint.distance / ext.getPixelSize() > MIDPOINT_THRESHOLD) return null;
10258
- return midpoint;
10259
- }
10260
-
10261
- hit.on('dragstart', function(e) {
10262
- if (!active()) return;
10263
- if (insertionPoint) {
10264
- var target = hit.getHitTarget();
10265
- insertVertex$1(target, insertionPoint.i, insertionPoint.point);
10266
- dragInfo = {
10267
- target: target,
10268
- insertion: true,
10269
- ids: [insertionPoint.i],
10270
- points: [insertionPoint.point]
10271
- };
10272
- insertionPoint = null;
10273
- } else {
10274
- dragInfo = findDraggableVertices(e);
10275
- }
10276
- if (dragInfo) {
10277
- setHoverVertex(dragInfo.ids[0]);
10278
- }
10279
- });
10280
-
10281
- hit.on('drag', function(e) {
10282
- if (!dragging()) return;
10283
- var target = hit.getHitTarget();
10284
- var p = ext.translatePixelCoords(e.x, e.y);
10285
- if (gui.keyboard.shiftIsPressed()) {
10286
- internal.snapPointToArcEndpoint(p, dragInfo.ids, target.arcs);
10287
- }
10288
- internal.snapVerticesToPoint(dragInfo.ids, p, target.arcs);
10289
- setHoverVertex(dragInfo.ids[0]);
10290
- // redrawing the whole map updates the data layer as well as the overlay layer
10291
- // gui.dispatchEvent('map-needs-refresh');
10292
- });
10293
-
10294
- hit.on('dragend', function(e) {
10295
- if (!dragging()) return;
10296
- // kludge to get dataset to recalculate internal bounding boxes
10297
- hit.getHitTarget().arcs.transformPoints(function() {});
10298
- clearHoverVertex();
10299
- updateVertexCoords(dragInfo.target, dragInfo.ids);
10300
- gui.dispatchEvent('vertex_dragend', dragInfo);
10301
- gui.dispatchEvent('map-needs-refresh');
10302
- dragInfo = null;
10303
- });
10304
-
10305
- hit.on('dblclick', function(e) {
10306
- if (!active()) return;
10307
- var info = findDraggableVertices(e); // same selection criteria as for dragging
10308
- if (!info) return;
10309
- var target = hit.getHitTarget();
10310
- var vId = info.ids[0];
10311
- if (internal.vertexIsArcStart(vId, target.arcs) ||
10312
- internal.vertexIsArcEnd(vId, target.arcs)) {
10313
- // TODO: support removing arc endpoints
10314
- return;
10315
- }
10316
- gui.dispatchEvent('vertex_delete', {
10317
- target: target,
10318
- vertex_id: vId
10319
- });
10320
- deleteVertex$1(target, vId);
10321
- clearHoverVertex();
10322
- gui.dispatchEvent('map-needs-refresh');
10323
- });
10324
-
10325
- // highlight hit vertex in path edit mode
10326
- hit.on('hover', function(e) {
10327
- insertionPoint = null;
10328
- if (!active() || dragging()) return; // no hover effect while dragging
10329
- var info = findDraggableVertices(e);
10330
- if (info) {
10331
- // hovering near a vertex: highlight the vertex
10332
- setHoverVertex(info.ids[0]);
10333
- return;
10334
- }
10335
- // if hovering near a segment midpoint: show the midpoint and save midpoint info
10336
- insertionPoint = findVertexInsertionPoint(e);
10337
- if (insertionPoint) {
10338
- hit.setHoverVertex(insertionPoint.displayPoint);
10339
- } else {
10340
- // pointer is not over a vertex: clear any hover effect
10341
- clearHoverVertex();
10342
- }
10343
- }, null, 100);
10344
- }
10345
-
10346
-
10347
- // Given a location @p (e.g. corresponding to the mouse pointer location),
10348
- // find the midpoint of two vertices on @shp suitable for inserting a new vertex
10349
- function findNearestMidpoint(p, fid, target) {
10350
- var arcs = target.arcs;
10351
- var shp = target.layer.shapes[fid];
10352
- var minDist = Infinity, v;
10353
- internal.forEachSegmentInShape(shp, arcs, function(i, j, xx, yy) {
10354
- var x1 = xx[i],
10355
- y1 = yy[i],
10356
- x2 = xx[j],
10357
- y2 = yy[j],
10358
- cx = (x1 + x2) / 2,
10359
- cy = (y1 + y2) / 2,
10360
- midpoint = [cx, cy],
10361
- dist = geom.distance2D(cx, cy, p[0], p[1]);
10362
- if (dist < minDist) {
10363
- minDist = dist;
10364
- v = {
10365
- i: (i < j ? i : j) + 1, // insertion point
10366
- segment: [i, j],
10367
- segmentLen: geom.distance2D(x1, y1, x2, y2),
10368
- displayPoint: midpoint,
10369
- point: translateDisplayPoint(target, midpoint),
10370
- distance: dist
10371
- };
10372
- }
10373
- });
10374
- return v || null;
10375
- }
10376
-
10377
10380
  function initInteractiveEditing(gui, ext, hit) {
10378
10381
  initLabelDragging(gui, ext, hit);
10379
10382
  initPointDragging(gui, ext, hit);
10380
10383
  initPointDrawing(gui, ext, hit);
10381
10384
  initLineEditing(gui, ext, hit);
10382
- initVertexDragging(gui, ext, hit);
10383
10385
  }
10384
10386
 
10385
10387
  function MapExtent(_position) {
@@ -10753,47 +10755,46 @@
10753
10755
  }
10754
10756
 
10755
10757
  // TODO: consider moving this upstream
10756
- function getArcsForRendering(obj, ext) {
10757
- var dataset = obj.source.dataset;
10758
+ function getArcsForRendering(lyr, ext) {
10759
+ var dataset = lyr.gui.source.dataset;
10758
10760
  var sourceArcs = dataset.arcs;
10759
- if (obj.geographic && dataset.displayArcs) {
10760
- return dataset.displayArcs.getScaledArcs(ext);
10761
+ if (lyr.gui.geographic && dataset.gui?.displayArcs) {
10762
+ return dataset.gui.displayArcs.getScaledArcs(ext);
10761
10763
  }
10762
- return obj.arcs;
10764
+ return lyr.gui.displayArcs;
10763
10765
  }
10764
10766
 
10765
- function drawOutlineLayerToCanvas(obj, canv, ext) {
10767
+ function drawOutlineLayerToCanvas(lyr, canv, ext) {
10766
10768
  var arcs;
10767
- var style = obj.style;
10769
+ var style = lyr.gui.style;
10770
+ var arcCounts = lyr.gui.arcCounts;
10768
10771
  var darkStyle = {strokeWidth: style.strokeWidth, strokeColor: style.strokeColors[1]},
10769
10772
  lightStyle = {strokeWidth: style.strokeWidth, strokeColor: style.strokeColors[0]};
10770
10773
  var filter;
10771
- if (internal.layerHasPaths(obj.layer)) {
10772
- if (!obj.arcCounts) {
10773
- obj.arcCounts = new Uint8Array(obj.arcs.size());
10774
- 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);
10775
10778
  }
10776
- if (obj.arcCounts) {
10777
- arcs = getArcsForRendering(obj, ext);
10778
- if (lightStyle.strokeColor) {
10779
- filter = getArcFilter(arcs, ext, false, obj.arcCounts);
10780
- canv.drawArcs(arcs, lightStyle, filter);
10781
- }
10782
- if (darkStyle.strokeColor && obj.layer.geometry_type != 'point') {
10783
- filter = getArcFilter(arcs, ext, true, obj.arcCounts);
10784
- canv.drawArcs(arcs, darkStyle, filter);
10785
- }
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);
10786
10787
  }
10787
10788
  }
10788
- if (obj.layer.geometry_type == 'point') {
10789
- canv.drawSquareDots(obj.layer.shapes, style);
10789
+ if (lyr.gui.displayLayer.geometry_type == 'point') {
10790
+ canv.drawSquareDots(lyr.gui.displayLayer.shapes, style);
10790
10791
  }
10791
10792
  }
10792
10793
 
10793
- function drawStyledLayerToCanvas(obj, canv, ext) {
10794
+ function drawStyledLayerToCanvas(lyr, canv, ext) {
10794
10795
  // TODO: add filter for out-of-view shapes
10795
- var style = obj.style;
10796
- var layer = obj.layer;
10796
+ var style = lyr.gui.style;
10797
+ var layer = lyr.gui.displayLayer;
10797
10798
  var arcs, filter;
10798
10799
  if (layer.geometry_type == 'point') {
10799
10800
  if (style.type == 'styled') {
@@ -10802,7 +10803,7 @@
10802
10803
  canv.drawSquareDots(layer.shapes, style);
10803
10804
  }
10804
10805
  } else {
10805
- arcs = getArcsForRendering(obj, ext);
10806
+ arcs = getArcsForRendering(lyr, ext);
10806
10807
  filter = getShapeFilter(arcs, ext);
10807
10808
  canv.drawStyledPaths(layer.shapes, arcs, style, filter);
10808
10809
  if (style.vertices) {
@@ -11396,12 +11397,12 @@
11396
11397
  }
11397
11398
  };
11398
11399
 
11399
- el.reposition = function(target, type) {
11400
+ el.reposition = function(lyr, type) {
11400
11401
  resize(ext);
11401
- reposition(target, type, ext);
11402
+ reposition(lyr, type, ext);
11402
11403
  };
11403
11404
 
11404
- el.drawLayer = function(target, type) {
11405
+ el.drawLayer = function(lyr, type) {
11405
11406
  var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
11406
11407
  var html = '';
11407
11408
  // generate a unique id so layer can be identified when symbols are repositioned
@@ -11409,24 +11410,25 @@
11409
11410
  var id = utils$1.getUniqueName();
11410
11411
  var classNames = [id, 'mapshaper-svg-layer', 'mapshaper-' + type + '-layer'];
11411
11412
  g.setAttribute('class', classNames.join(' '));
11412
- target.svg_id = id;
11413
+ lyr.gui.svg_id = id;
11414
+ lyr.gui.svg_container = g;
11413
11415
  resize(ext);
11414
11416
  if (type == 'label' || type == 'symbol') {
11415
- html = renderSymbols(target.layer, ext);
11417
+ html = renderSymbols(lyr.gui.displayLayer, ext);
11416
11418
  } else if (type == 'furniture') {
11417
- html = renderFurniture(target.layer, ext);
11419
+ html = renderFurniture(lyr.gui.displayLayer, ext);
11418
11420
  }
11419
11421
  g.innerHTML = html;
11420
11422
  svg.append(g);
11421
11423
 
11422
11424
  // prevent svg hit detection on inactive layers
11423
- if (!target.active) {
11425
+ if (!lyr.active) {
11424
11426
  g.style.pointerEvents = 'none';
11425
11427
  }
11426
11428
  };
11427
11429
 
11428
- function reposition(target, type, ext) {
11429
- var container = el.findChild('.' + target.svg_id);
11430
+ function reposition(lyr, type, ext) {
11431
+ var container = el.findChild('.' + lyr.gui.svg_id);
11430
11432
  if (!container || !container.node()) {
11431
11433
  console.error('[reposition] missing SVG container');
11432
11434
  return;
@@ -11434,9 +11436,9 @@
11434
11436
  var elements;
11435
11437
  if (type == 'symbol') {
11436
11438
  elements = El.findAll('.mapshaper-svg-symbol', container.node());
11437
- repositionSymbols(elements, target.layer, ext);
11439
+ repositionSymbols(elements, lyr.gui.displayLayer, ext);
11438
11440
  } else if (type == 'furniture') {
11439
- repositionFurniture(container.node(), target.layer, ext);
11441
+ repositionFurniture(container.node(), lyr.gui.displayLayer, ext);
11440
11442
  } else {
11441
11443
  // container.getElementsByTagName('text')
11442
11444
  error('Unsupported symbol type:', type);
@@ -11470,7 +11472,7 @@
11470
11472
  _svg.clear();
11471
11473
  }
11472
11474
  layers.forEach(function(lyr) {
11473
- var isSvgLayer = internal.layerHasSvgSymbols(lyr.layer) || internal.layerHasLabels(lyr.layer);
11475
+ var isSvgLayer = internal.layerHasSvgSymbols(lyr) || internal.layerHasLabels(lyr);
11474
11476
  if (isSvgLayer && !needSvgRedraw) {
11475
11477
  _svg.reposition(lyr, 'symbol');
11476
11478
  } else if (isSvgLayer) {
@@ -11519,7 +11521,7 @@
11519
11521
 
11520
11522
  function drawCanvasLayer(lyr, canv) {
11521
11523
  if (!lyr) return;
11522
- if (lyr.style.type == 'outline') {
11524
+ if (lyr.gui.style.type == 'outline') {
11523
11525
  drawOutlineLayerToCanvas(lyr, canv, ext);
11524
11526
  } else {
11525
11527
  drawStyledLayerToCanvas(lyr, canv, ext);
@@ -11587,8 +11589,8 @@
11587
11589
  gui.on('interaction_mode_change', function(e) {
11588
11590
  if (e.mode === 'box') {
11589
11591
  gui.enterMode('box_tool');
11590
- } else if (gui.getMode() == 'box_tool') {
11591
- gui.clearMode();
11592
+ } else if (_on) {
11593
+ turnOff();
11592
11594
  }
11593
11595
  });
11594
11596
 
@@ -11691,8 +11693,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11691
11693
  gui.on('interaction_mode_change', function(e) {
11692
11694
  if (e.mode === 'rectangles') {
11693
11695
  gui.enterMode('rectangle_tool');
11694
- } else if (gui.getMode() == 'rectangle_tool') {
11695
- gui.clearMode();
11696
+ } else if (_on) {
11697
+ turnOff();
11696
11698
  }
11697
11699
  });
11698
11700
 
@@ -11702,8 +11704,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11702
11704
  var id = e.id;
11703
11705
  if (e.id > -1 && e.pinned) {
11704
11706
  var target = hit.getHitTarget();
11705
- var path = target.layer.shapes[e.id][0];
11706
- var bbox = target.arcs.getSimpleShapeBounds(path).toArray();
11707
+ var path = target.shapes[e.id][0];
11708
+ var bbox = target.gui.displayArcs.getSimpleShapeBounds(path).toArray();
11707
11709
  box.setDataCoords(bbox);
11708
11710
  dragInfo = {
11709
11711
  id: e.id,
@@ -11711,7 +11713,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11711
11713
  ids: [],
11712
11714
  points: []
11713
11715
  };
11714
- var iter = target.arcs.getShapeIter(path);
11716
+ var iter = target.gui.displayArcs.getShapeIter(path);
11715
11717
  while (iter.hasNext()) {
11716
11718
  dragInfo.points.push([iter.x, iter.y]);
11717
11719
  dragInfo.ids.push(iter._arc.i);
@@ -11810,8 +11812,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11810
11812
  };
11811
11813
  }
11812
11814
 
11813
- function getDisplayLayerForTable(table) {
11814
- var n = table.size(),
11815
+ function getDisplayLayerForTable(tableArg) {
11816
+ var table = tableArg || new internal.DataTable(0),
11817
+ n = table.size(),
11815
11818
  cellWidth = 12,
11816
11819
  cellHeight = 5,
11817
11820
  gutter = 6,
@@ -11869,54 +11872,45 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11869
11872
  };
11870
11873
  }
11871
11874
 
11875
+ // lyr: a map layer with gui property
11872
11876
  // displayCRS: CRS to use for display, or null (which clears any current display CRS)
11873
- function projectDisplayLayer(lyr, displayCRS) {
11874
- var crsInfo = getDatasetCrsInfo(lyr.source.dataset);
11875
- var sourceCRS = crsInfo.crs;
11876
- var lyr2;
11877
- //if (!lyr.geographic || !sourceCRS) {
11878
- // let getDisplayLayer() handle case of unprojectable source
11879
- if (!lyr.geographic) {
11880
- return lyr;
11881
- }
11882
- if (lyr.dynamic_crs && internal.crsAreEqual(sourceCRS, lyr.dynamic_crs)) {
11883
- return lyr;
11884
- }
11885
- lyr2 = getDisplayLayer(lyr.source.layer, lyr.source.dataset, {crs: displayCRS});
11886
- // kludge: copy projection-related properties to original layer
11887
- lyr.dynamic_crs = lyr2.dynamic_crs;
11888
- lyr.layer = lyr2.layer;
11889
-
11890
- if (lyr.style && lyr.style.ids) {
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) {
11881
+ return;
11882
+ }
11883
+ if (lyr.gui.dynamic_crs && internal.crsAreEqual(sourceCRS, lyr.gui.dynamic_crs)) {
11884
+ return;
11885
+ }
11886
+ enhanceLayerForDisplay(lyr, lyr.gui.source.dataset, {crs: displayCRS});
11887
+ if (lyr.gui.style?.ids) {
11891
11888
  // re-apply layer filter
11892
- lyr.layer = filterLayerByIds(lyr.layer, lyr.style.ids);
11889
+ lyr.gui.displayLayer = filterLayerByIds(lyr.gui.displayLayer, lyr.gui.style.ids);
11893
11890
  }
11894
- lyr.invertPoint = lyr2.invertPoint;
11895
- lyr.projectPoint = lyr2.projectPoint;
11896
- lyr.bounds = lyr2.bounds;
11897
- lyr.arcs = lyr2.arcs;
11898
11891
  }
11899
11892
 
11900
11893
 
11901
- // Wrap a layer in an object along with information needed for rendering
11902
- function getDisplayLayer(layer, dataset, opts) {
11903
- var obj = {
11904
- layer: null,
11905
- arcs: null,
11906
- // 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,
11907
11903
  style: null,
11904
+ dynamic_crs: null,
11908
11905
  invertPoint: null,
11909
- projectPoint: null,
11910
- source: {
11911
- layer: layer,
11912
- dataset: dataset
11913
- },
11914
- empty: internal.getFeatureCount(layer) === 0
11906
+ projectPoint: null
11915
11907
  };
11916
11908
 
11917
11909
  var displayCRS = opts.crs || null;
11918
- // display arcs may have been generated when another layer in the dataset was converted for display... re-use if available
11919
- var displayArcs = dataset.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;
11920
11914
  var sourceCRS;
11921
11915
  var emptyArcs;
11922
11916
 
@@ -11924,8 +11918,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11924
11918
  var crsInfo = getDatasetCrsInfo(dataset);
11925
11919
  if (crsInfo.error) {
11926
11920
  // unprojectable dataset -- return empty layer
11927
- obj.empty = true;
11928
- obj.geographic = true;
11921
+ gui.unprojectable = true;
11929
11922
  } else {
11930
11923
  sourceCRS = crsInfo.crs;
11931
11924
  }
@@ -11933,7 +11926,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11933
11926
 
11934
11927
  // Assume that dataset.displayArcs is in the display CRS
11935
11928
  // (it must be deleted upstream if reprojection is needed)
11936
- if (!obj.empty && dataset.arcs && !displayArcs) {
11929
+ // if (!obj.empty && dataset.arcs && !displayArcs) {
11930
+ if (dataset.arcs && !displayArcs && !gui.unprojectable) {
11937
11931
  // project arcs, if needed
11938
11932
  if (needReprojectionForDisplay(sourceCRS, displayCRS)) {
11939
11933
  displayArcs = projectArcsForDisplay(dataset.arcs, sourceCRS, displayCRS);
@@ -11943,50 +11937,46 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
11943
11937
  }
11944
11938
 
11945
11939
  enhanceArcCollectionForDisplay(displayArcs);
11946
- dataset.displayArcs = displayArcs; // stash these in the dataset for other layers to use
11940
+ dataset.gui = {displayArcs}; // stash these in the dataset for other layers to use
11947
11941
  }
11948
11942
 
11949
11943
  if (internal.layerHasFurniture(layer)) {
11950
- obj.layer = layer;
11951
- obj.tabular = true;
11952
11944
  // TODO: consider how to render furniture in GUI
11953
- // obj.furniture = true;
11954
- // obj.furniture_type = internal.getFurnitureLayerType(layer);
11955
11945
  // treating furniture layers (other than frame) as tabular for now,
11956
11946
  // so there is something to show if they are selected
11957
- // obj.tabular = obj.furniture_type != 'frame';
11958
- } else if (obj.empty) {
11959
- obj.layer = {shapes: []}; // ideally we should avoid empty layers
11960
- } else if (!layer.geometry_type) {
11961
- obj.tabular = true;
11962
- } else {
11963
- obj.geographic = true;
11964
- obj.layer = layer;
11965
- obj.arcs = displayArcs;
11966
11947
  }
11967
11948
 
11968
- if (obj.tabular) {
11969
- 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;
11970
11960
  }
11971
11961
 
11972
11962
  // dynamic reprojection (arcs were already reprojected above)
11973
- if (obj.geographic && needReprojectionForDisplay(sourceCRS, displayCRS)) {
11974
- obj.dynamic_crs = displayCRS;
11975
- obj.invertPoint = internal.getProjTransform2(displayCRS, sourceCRS);
11976
- 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);
11977
11967
  if (internal.layerHasPoints(layer)) {
11978
- obj.layer = projectPointsForDisplay(layer, sourceCRS, displayCRS);
11968
+ gui.displayLayer = projectPointsForDisplay(layer, sourceCRS, displayCRS);
11979
11969
  } else if (internal.layerHasPaths(layer)) {
11980
11970
  emptyArcs = findEmptyArcs(displayArcs);
11981
11971
  if (emptyArcs.length > 0) {
11982
11972
  // Don't try to draw paths containing coordinates that failed to project
11983
- obj.layer = internal.filterPathLayerByArcIds(obj.layer, emptyArcs);
11973
+ gui.displayLayer = internal.filterPathLayerByArcIds(gui.displayLayer, emptyArcs);
11984
11974
  }
11985
11975
  }
11986
11976
  }
11987
11977
 
11988
- obj.bounds = getDisplayBounds(obj.layer, obj.arcs);
11989
- return obj;
11978
+ gui.bounds = getDisplayBounds(gui.displayLayer, gui.displayArcs);
11979
+ layer.gui = gui;
11990
11980
  }
11991
11981
 
11992
11982
 
@@ -12300,13 +12290,19 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12300
12290
 
12301
12291
  model.on('update', onUpdate);
12302
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
+ });
12303
12298
 
12304
12299
  // Update display of segment intersections
12305
12300
  this.setIntersectionLayer = function(lyr, dataset) {
12306
12301
  if (lyr == _intersectionLyr) return; // no change
12307
12302
  if (lyr) {
12308
- _intersectionLyr = getDisplayLayer(lyr, dataset, getDisplayOptions());
12309
- _intersectionLyr.style = getIntersectionStyle(_intersectionLyr.layer, getGlobalStyleOptions());
12303
+ enhanceLayerForDisplay(lyr, dataset, getDisplayOptions());
12304
+ _intersectionLyr = lyr;
12305
+ _intersectionLyr.gui.style = getIntersectionStyle(_intersectionLyr.gui.displayLayer, getGlobalStyleOptions());
12310
12306
  } else {
12311
12307
  _intersectionLyr = null;
12312
12308
  }
@@ -12335,9 +12331,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12335
12331
  };
12336
12332
 
12337
12333
  this.getDisplayCRS = function() {
12338
- if (!_activeLyr || !_activeLyr.geographic) return null;
12339
- if (_activeLyr.dynamic_crs) return _activeLyr.dynamic_crs;
12340
- 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);
12341
12337
  return info.crs || null;
12342
12338
  };
12343
12339
 
@@ -12376,7 +12372,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12376
12372
 
12377
12373
  // Reproject all visible map layers
12378
12374
  getContentLayers().forEach(function(lyr) {
12379
- projectDisplayLayer(lyr, newCRS);
12375
+ projectLayerForDisplay(lyr, newCRS);
12380
12376
  });
12381
12377
 
12382
12378
  // kludge to make sure all layers have styles
@@ -12387,13 +12383,13 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12387
12383
  updateFullBounds();
12388
12384
  };
12389
12385
 
12390
- function getGlobalStyleOptions() {
12386
+ function getGlobalStyleOptions(opts) {
12391
12387
  var mode = gui.state.interaction_mode;
12392
- return {
12388
+ return Object.assign({
12393
12389
  darkMode: !!gui.state.dark_basemap,
12394
12390
  outlineMode: mode == 'vertices',
12395
12391
  interactionMode: mode
12396
- };
12392
+ }, opts);
12397
12393
  }
12398
12394
 
12399
12395
  // Refresh map display in response to data changes, layer selection, etc.
@@ -12401,14 +12397,13 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12401
12397
  var prevLyr = _activeLyr || null;
12402
12398
  var fullBounds;
12403
12399
  var needReset;
12404
-
12405
12400
  if (!prevLyr) {
12406
12401
  initMap(); // first call
12407
12402
  }
12408
12403
 
12409
12404
  if (arcsMayHaveChanged(e.flags)) {
12410
12405
  // regenerate filtered arcs the next time they are needed for rendering
12411
- // delete e.dataset.displayArcs;
12406
+ // delete e.dataset.gui.displayArcs
12412
12407
  clearAllDisplayArcs();
12413
12408
 
12414
12409
  // reset simplification after projection (thresholds have changed)
@@ -12427,8 +12422,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12427
12422
  return;
12428
12423
  }
12429
12424
 
12430
- _activeLyr = getDisplayLayer(e.layer, e.dataset, getDisplayOptions());
12431
- _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());
12432
12428
  _activeLyr.active = true;
12433
12429
 
12434
12430
  if (popupCanStayOpen(e.flags)) {
@@ -12442,7 +12438,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12442
12438
  updateVisibleMapLayers();
12443
12439
  fullBounds = calcFullBounds();
12444
12440
 
12445
- if (!prevLyr || prevLyr.tabular || _activeLyr.tabular) {
12441
+ if (!prevLyr || prevLyr.gui.tabular || _activeLyr.gui.tabular) {
12446
12442
  needReset = true;
12447
12443
  } else {
12448
12444
  needReset = mapNeedsReset(fullBounds, _ext.getFullBounds(), _ext.getBounds(), e.flags);
@@ -12485,12 +12481,11 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12485
12481
  }
12486
12482
 
12487
12483
  function updateOverlayLayer(e) {
12488
- var style = getOverlayStyle(_activeLyr.layer, e, getGlobalStyleOptions());
12484
+ var style = getOverlayStyle(_activeLyr.gui.displayLayer, e, getGlobalStyleOptions());
12489
12485
  if (style) {
12490
- _overlayLyr = utils$1.defaults({
12491
- layer: filterLayerByIds(_activeLyr.layer, style.ids),
12492
- style: style
12493
- }, _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);
12494
12489
  } else {
12495
12490
  _overlayLyr = null;
12496
12491
  }
@@ -12521,7 +12516,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12521
12516
  var b = new Bounds();
12522
12517
  var layers = getContentLayers();
12523
12518
  layers.forEach(function(lyr) {
12524
- b.mergeBounds(lyr.bounds);
12519
+ b.mergeBounds(lyr.gui.bounds);
12525
12520
  });
12526
12521
 
12527
12522
  if (!b.hasBounds()) {
@@ -12544,7 +12539,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12544
12539
  var widthPx = _ext.width();
12545
12540
  var marginPct = widthPx < 700 && 3.5 || widthPx < 800 && 3 || 2.5;
12546
12541
  if (isTableView()) {
12547
- var n = internal.getFeatureCount(_activeLyr.layer);
12542
+ var n = internal.getFeatureCount(_activeLyr);
12548
12543
  marginPct = n < 5 && 20 || n < 100 && 10 || 4;
12549
12544
  }
12550
12545
  b.scale(1 + marginPct / 100 * 2);
@@ -12556,7 +12551,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12556
12551
  }
12557
12552
 
12558
12553
  function isActiveLayer(lyr) {
12559
- return _activeLyr && lyr == _activeLyr.source.layer || false;
12554
+ return _activeLyr && lyr == _activeLyr || false;
12560
12555
  }
12561
12556
 
12562
12557
  function isVisibleLayer(lyr) {
@@ -12564,12 +12559,12 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12564
12559
  }
12565
12560
 
12566
12561
  function isTableView() {
12567
- return !!_activeLyr.tabular;
12562
+ return !!_activeLyr.gui.tabular;
12568
12563
  }
12569
12564
 
12570
12565
  function findFrameLayer() {
12571
12566
  return getVisibleMapLayers().find(function(lyr) {
12572
- return internal.isFrameLayer(lyr.layer, lyr.arcs);
12567
+ return internal.isFrameLayer(lyr.gui.displayLayer, lyr.gui.displayArcs);
12573
12568
  });
12574
12569
  }
12575
12570
 
@@ -12580,12 +12575,15 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12580
12575
 
12581
12576
  function getFrameData() {
12582
12577
  var lyr = findFrameLayer();
12583
- return lyr && internal.getFrameLayerData(lyr.layer, lyr.arcs) || null;
12578
+ return lyr && internal.getFrameLayerData(lyr, lyr.gui.displayArcs) || null;
12584
12579
  }
12585
12580
 
12586
12581
  function clearAllDisplayArcs() {
12582
+ model.forEachLayer(function(lyr) {
12583
+ if (lyr.gui) delete lyr.gui.arcCounts;
12584
+ });
12587
12585
  model.getDatasets().forEach(function(o) {
12588
- delete o.displayArcs;
12586
+ delete o.gui;
12589
12587
  });
12590
12588
  }
12591
12589
 
@@ -12596,7 +12594,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12596
12594
  if (isActiveLayer(o.layer)) {
12597
12595
  layers.push(_activeLyr);
12598
12596
  } else if (!isTableView()) {
12599
- layers.push(getDisplayLayer(o.layer, o.dataset, getDisplayOptions()));
12597
+ enhanceLayerForDisplay(o.layer, o.dataset, getDisplayOptions());
12598
+ layers.push(o.layer);
12600
12599
  }
12601
12600
  });
12602
12601
  _visibleLayers = layers;
@@ -12618,13 +12617,13 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12618
12617
  return findActiveLayer(layers);
12619
12618
  }
12620
12619
  return layers.filter(function(o) {
12621
- return !!o.geographic;
12620
+ return !!o.gui.geographic;
12622
12621
  });
12623
12622
  }
12624
12623
 
12625
12624
  function getDrawableContentLayers() {
12626
12625
  return getContentLayers().filter(function(lyr) {
12627
- if (isActiveLayer(lyr.layer) && lyr.layer.hidden) return false;
12626
+ if (isActiveLayer(lyr) && lyr.hidden) return false;
12628
12627
  return true;
12629
12628
  });
12630
12629
  }
@@ -12638,31 +12637,33 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12638
12637
 
12639
12638
  function updateLayerStyles(layers) {
12640
12639
  layers.forEach(function(mapLayer, i) {
12640
+ var style;
12641
12641
  if (mapLayer.active) {
12642
12642
  // regenerating active style everytime, to support style change when
12643
12643
  // switching between outline and preview modes.
12644
- mapLayer.style = getActiveLayerStyle(mapLayer.layer, getGlobalStyleOptions());
12645
- 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) {
12646
12646
  // kludge to hide ghosted layers when reference layers are present
12647
12647
  // TODO: consider never showing ghosted layers (which appear after
12648
12648
  // commands like dissolve and filter).
12649
- mapLayer.style = utils$1.defaults({
12650
- strokeColors: [null, mapLayer.style.strokeColors[1]]
12651
- }, mapLayer.style);
12649
+ style = utils$1.defaults({
12650
+ strokeColors: [null, style.strokeColors[1]]
12651
+ }, style);
12652
12652
  }
12653
12653
  } else {
12654
- if (mapLayer.layer == _activeLyr.layer) {
12654
+ if (mapLayer == _activeLyr) {
12655
12655
  console.error("Error: shared map layer");
12656
12656
  }
12657
- mapLayer.style = getReferenceLayerStyle(mapLayer.layer, getGlobalStyleOptions());
12657
+ style = getReferenceLayerStyle(mapLayer.gui.displayLayer, getGlobalStyleOptions());
12658
12658
  }
12659
+ mapLayer.gui.style = style;
12659
12660
  });
12660
12661
  }
12661
12662
 
12662
12663
  function sortMapLayers(layers) {
12663
12664
  layers.sort(function(a, b) {
12664
12665
  // assume that each layer has a menu_order (assigned by updateLayerStackOrder())
12665
- return a.source.layer.menu_order - b.source.layer.menu_order;
12666
+ return a.menu_order - b.menu_order;
12666
12667
  });
12667
12668
  }
12668
12669
 
@@ -12742,6 +12743,99 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12742
12743
  });
12743
12744
  }
12744
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
+
12745
12839
  function GuiInstance(container, opts) {
12746
12840
  var gui = new ModeSwitcher();
12747
12841
  opts = utils$1.extend({
@@ -12760,15 +12854,15 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12760
12854
  gui.model = new Model(gui);
12761
12855
  gui.keyboard = new KeyboardEvents(gui);
12762
12856
  gui.buttons = new SidebarButtons(gui);
12763
- gui.map = new MshpMap(gui);
12764
- gui.interaction = new InteractionMode(gui);
12765
12857
  gui.session = new SessionHistory(gui);
12858
+ gui.contextMenu = new ContextMenu();
12766
12859
  gui.undo = new Undo(gui);
12767
- gui.state = {};
12768
-
12860
+ gui.map = new MshpMap(gui);
12769
12861
  if (opts.saveControl) {
12770
12862
  new SessionSnapshots(gui);
12771
12863
  }
12864
+ gui.interaction = new InteractionMode(gui);
12865
+ gui.state = {};
12772
12866
 
12773
12867
  var msgCount = 0;
12774
12868
  var clearMsg;