mapshaper 0.6.63 → 0.6.66

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.
@@ -1714,6 +1714,47 @@
1714
1714
  }
1715
1715
  }
1716
1716
 
1717
+ function openAddLayerPopup(gui) {
1718
+ var popup = showPopupAlert('', 'Add empty layer');
1719
+ var el = popup.container();
1720
+ el.addClass('option-menu');
1721
+ var html = `<div><input type="text" class="layer-name text-input" placeholder="layer name"></div>
1722
+ <div style="margin: 2px 0 4px;">
1723
+ Type: &nbsp;
1724
+ <label><input type="radio" name="geomtype" checked value="point" class="radio">point</label> &nbsp;
1725
+ <label><input type="radio" name="geomtype" value="polygon" class="radio">polygon</label> &nbsp;
1726
+ <label><input type="radio" name="geomtype" value="polyline" class="radio">line</label>
1727
+ </div>
1728
+ <div tabindex="0" class="btn dialog-btn">Create</div></span>`;
1729
+ el.html(html);
1730
+ var name = el.findChild('.layer-name');
1731
+ name.node().focus();
1732
+ var btn = el.findChild('.btn').on('click', function() {
1733
+ var nameStr = name.node().value.trim();
1734
+ var type = el.findChild('input:checked').node().value;
1735
+ addLayer(gui, nameStr, type);
1736
+ popup.close();
1737
+ });
1738
+ }
1739
+
1740
+ function addLayer(gui, name, type) {
1741
+ var targ = gui.model.getActiveLayer();
1742
+ var crsInfo = targ && internal.getDatasetCrsInfo(targ.dataset);
1743
+ var dataset = {
1744
+ layers: [{
1745
+ name: name || undefined,
1746
+ geometry_type: type,
1747
+ shapes: []
1748
+ }],
1749
+ info: {}
1750
+ };
1751
+ if (crsInfo) {
1752
+ internal.setDatasetCrsInfo(dataset, crsInfo);
1753
+ }
1754
+ gui.model.addDataset(dataset);
1755
+ gui.model.updated({select: true});
1756
+ }
1757
+
1717
1758
  // @cb function(<FileList>)
1718
1759
  function DropControl(gui, el, cb) {
1719
1760
  var area = El(el);
@@ -1832,7 +1873,10 @@
1832
1873
  new FileChooser('#file-selection-btn', receiveFiles);
1833
1874
  new FileChooser('#import-buttons .add-btn', receiveFiles);
1834
1875
  new FileChooser('#add-file-btn', receiveFiles);
1835
- // new SimpleButton('#add-empty-btn').on('click', addEmptyLayer);
1876
+ new SimpleButton('#add-empty-btn').on('click', function() {
1877
+ gui.clearMode(); // close import dialog
1878
+ openAddLayerPopup(gui);
1879
+ });
1836
1880
  initDropArea('#import-quick-drop', true);
1837
1881
  initDropArea('#import-drop');
1838
1882
  gui.keyboard.onMenuSubmit(El('#import-options'), importQueuedFiles);
@@ -2040,18 +2084,7 @@
2040
2084
  return true;
2041
2085
  }
2042
2086
 
2043
- function addEmptyLayer() {
2044
- var dataset = {
2045
- layers: [{
2046
- name: 'New layer',
2047
- geometry_type: 'point',
2048
- shapes: []
2049
- }],
2050
- info: {}
2051
- };
2052
- model.addDataset(dataset);
2053
- gui.clearMode();
2054
- }
2087
+
2055
2088
 
2056
2089
  function filesMayContainPaths(files) {
2057
2090
  return utils$1.some(files, function(f) {
@@ -2565,6 +2598,7 @@
2565
2598
  }
2566
2599
  }
2567
2600
 
2601
+ // coords: [x, y] point in data CRS (not display CRS)
2568
2602
  function setPointCoords(lyr, fid, coords) {
2569
2603
  lyr.source.layer.shapes[fid] = coords;
2570
2604
  if (isProjectedLayer(lyr)) {
@@ -2578,12 +2612,31 @@
2578
2612
  internal.snapVerticesToPoint(ids, lyr.invertPoint(p[0], p[1]), lyr.source.dataset.arcs, true);
2579
2613
  }
2580
2614
 
2615
+ function setRectangleCoords(lyr, ids, coords) {
2616
+ ids.forEach(function(id, i) {
2617
+ var p = coords[i];
2618
+ internal.snapVerticesToPoint([id], p, lyr.source.dataset.arcs, true);
2619
+ if (isProjectedLayer(lyr)) {
2620
+ internal.snapVerticesToPoint([id], lyr.projectPoint(p[0], p[1]), lyr.arcs, true);
2621
+ }
2622
+ });
2623
+ }
2624
+
2625
+ // lyr: display layer
2626
+ // export function updateRectangleCoords(lyr, ids, coords) {
2627
+ // if (!isProjectedLayer(lyr)) return;
2628
+ // ids.forEach(function(id, i) {
2629
+ // var p = coords[i];
2630
+ // internal.snapVerticesToPoint([id], lyr.invertPoint(p[0], p[1]), lyr.source.dataset.arcs, true);
2631
+ // });
2632
+ // }
2633
+
2581
2634
  function isProjectedLayer(lyr) {
2582
2635
  // TODO: could do some validation on the layer's contents
2583
2636
  return !!(lyr.source && lyr.invertPoint);
2584
2637
  }
2585
2638
 
2586
- // Update data coordinates by projecting display coordinates
2639
+ // Update source data coordinates by projecting display coordinates
2587
2640
  function updatePointCoords(lyr, fid) {
2588
2641
  if (!isProjectedLayer(lyr)) return;
2589
2642
  var displayShp = lyr.layer.shapes[fid];
@@ -3856,7 +3909,7 @@
3856
3909
  async function exportMenuSelection(targets) {
3857
3910
  var opts = getExportOpts();
3858
3911
  // note: command line "target" option gets ignored
3859
- var files = await internal.exportTargetLayers(targets, opts);
3912
+ var files = await internal.exportTargetLayers(model, targets, opts);
3860
3913
  gui.session.layersExported(getTargetLayerIds(), getExportOptsAsString());
3861
3914
  if (files.length == 1 && checkboxOn(clipboardCheckbox)) {
3862
3915
  await saveFileContentToClipboard(files[0].content);
@@ -4637,6 +4690,19 @@
4637
4690
  addHistoryState(stashedUndo, redo);
4638
4691
  });
4639
4692
 
4693
+ gui.on('rectangle_dragend', function(e) {
4694
+ var target = e.data.target;
4695
+ var points1 = e.points;
4696
+ var points2 = e.ids.map(id => getVertexCoords(target, id));
4697
+ var undo = function() {
4698
+ setRectangleCoords(target, e.ids, points1);
4699
+ };
4700
+ var redo = function() {
4701
+ setRectangleCoords(target, e.ids, points2);
4702
+ };
4703
+ addHistoryState(undo, redo);
4704
+ });
4705
+
4640
4706
  gui.on('vertex_dragend', function(e) {
4641
4707
  var target = e.data.target;
4642
4708
  var startPoint = e.points[0]; // in data coords
@@ -4682,6 +4748,9 @@
4682
4748
  }
4683
4749
 
4684
4750
  this.undo = function() {
4751
+ // firing even if history is empty
4752
+ // (because this event may trigger a new history state)
4753
+ gui.dispatchEvent('undo_redo_pre');
4685
4754
  var item = getHistoryItem();
4686
4755
  if (item) {
4687
4756
  offset++;
@@ -4691,6 +4760,7 @@
4691
4760
  };
4692
4761
 
4693
4762
  this.redo = function() {
4763
+ gui.dispatchEvent('undo_redo_pre');
4694
4764
  if (offset <= 0) return;
4695
4765
  offset--;
4696
4766
  var item = getHistoryItem();
@@ -4764,6 +4834,30 @@
4764
4834
  });
4765
4835
  }
4766
4836
 
4837
+ function ToggleButton(el) {
4838
+ var btn = El(el),
4839
+ self = new EventDispatcher(),
4840
+ on = false;
4841
+
4842
+ btn.on('click', function(e) {
4843
+ on = !on;
4844
+ btn.classed('active', on);
4845
+ self.dispatchEvent('click', {on: on, active: on});
4846
+ });
4847
+
4848
+ self.turnOff = function() {
4849
+ on = false;
4850
+ btn.removeClass('active');
4851
+ };
4852
+
4853
+ self.turnOn = function() {
4854
+ on = true;
4855
+ btn.addClass('active');
4856
+ };
4857
+
4858
+ return self;
4859
+ }
4860
+
4767
4861
  function ModeSwitcher() {
4768
4862
  var self = this;
4769
4863
  var mode = null;
@@ -4852,11 +4946,11 @@
4852
4946
  var menus = {
4853
4947
  standard: ['info', 'selection', 'data', 'box'],
4854
4948
  polygons: ['info', 'selection', 'data', 'box', 'vertices'],
4855
- lines: ['info', 'selection', 'data', 'box', 'vertices'],
4949
+ rectangles: ['info', 'selection', 'data', 'box', 'rectangles', 'vertices'],
4950
+ lines: ['info', 'selection', 'data', 'box', 'vertices' /*, 'draw-lines'*/],
4856
4951
  table: ['info', 'selection', 'data'],
4857
- labels: ['info', 'selection', 'data', 'box', 'labels', 'location'],
4858
- // points: ['info', 'selection', 'data', 'box', 'location', 'add-points']
4859
- points: ['info', 'selection', 'data', 'box', 'location']
4952
+ labels: ['info', 'selection', 'data', 'box', 'labels', 'location', 'add-points'],
4953
+ points: ['info', 'selection', 'data', 'box', 'location', 'add-points']
4860
4954
  };
4861
4955
 
4862
4956
  var prompts = {
@@ -4875,6 +4969,8 @@
4875
4969
  vertices: 'edit vertices',
4876
4970
  selection: 'select features',
4877
4971
  'add-points': 'add points',
4972
+ 'draw-lines': 'draw lines',
4973
+ rectangles: 'drag-to-resize',
4878
4974
  off: 'turn off'
4879
4975
  };
4880
4976
  var btn, menu;
@@ -4929,11 +5025,11 @@
4929
5025
  };
4930
5026
 
4931
5027
  this.modeUsesSelection = function(mode) {
4932
- return ['info', 'selection', 'data', 'labels', 'location', 'vertices'].includes(mode);
5028
+ return ['info', 'selection', 'data', 'labels', 'location', 'vertices', 'rectangles'].includes(mode);
4933
5029
  };
4934
5030
 
4935
5031
  this.modeUsesPopup = function(mode) {
4936
- return ['info', 'selection', 'data', 'box', 'labels', 'location'].includes(mode);
5032
+ return ['info', 'selection', 'data', 'box', 'labels', 'location', 'rectangles'].includes(mode);
4937
5033
  };
4938
5034
 
4939
5035
  this.getMode = getInteractionMode;
@@ -4975,7 +5071,8 @@
4975
5071
  return menus.lines;
4976
5072
  }
4977
5073
  if (internal.layerHasPaths(o.layer) && o.layer.geometry_type == 'polygon') {
4978
- return menus.polygons;
5074
+ return internal.layerOnlyHasRectangles(o.layer, o.dataset.arcs) ?
5075
+ menus.rectangles : menus.polygons;
4979
5076
  }
4980
5077
  return menus.standard;
4981
5078
  }
@@ -5116,7 +5213,9 @@
5116
5213
  };
5117
5214
 
5118
5215
  self.selectLayer = function(lyr, dataset) {
5119
- if (self.getActiveLayer().layer == lyr) return;
5216
+ if (self.getActiveLayer().layer == lyr) {
5217
+ return;
5218
+ }
5120
5219
  self.setDefaultTarget([lyr], dataset);
5121
5220
  self.updated({select: true});
5122
5221
  gui.session.setTargetLayer(lyr);
@@ -6822,7 +6921,6 @@
6822
6921
  return internal.svg.getTransform(p, scale);
6823
6922
  }
6824
6923
 
6825
-
6826
6924
  function repositionSymbols(elements, layer, ext) {
6827
6925
  var el, idx, shp, p, displayOn, inView, displayBounds;
6828
6926
  for (var i=0, n=elements.length; i<n; i++) {
@@ -6846,7 +6944,7 @@
6846
6944
  }
6847
6945
  }
6848
6946
 
6849
- function renderSymbols(lyr, ext, type) {
6947
+ function renderSymbols(lyr, ext) {
6850
6948
  var records = lyr.data.getRecords();
6851
6949
  var symbols = lyr.shapes.map(function(shp, i) {
6852
6950
  var d = records[i];
@@ -6954,6 +7052,8 @@
6954
7052
  var interactionMode;
6955
7053
  var targetLayer;
6956
7054
  var hitTest;
7055
+ var pinnedOn; // used in multi-edit mode (selection) for toggling pinning behavior
7056
+
6957
7057
  // event priority is higher than navigation, so stopping propagation disables
6958
7058
  // pan navigation
6959
7059
  var priority = 2;
@@ -7012,6 +7112,7 @@
7012
7112
  updateSelectionState(null); // no hit data, no event
7013
7113
  active = false;
7014
7114
  hitTest = null;
7115
+ pinnedOn = false;
7015
7116
  }
7016
7117
  }
7017
7118
 
@@ -7029,7 +7130,8 @@
7029
7130
 
7030
7131
  function clickable() {
7031
7132
  // click used to pin popup and select features
7032
- return interactionMode == 'data' || interactionMode == 'info' || interactionMode == 'selection';
7133
+ return interactionMode == 'data' || interactionMode == 'info' ||
7134
+ interactionMode == 'selection' || interactionMode == 'rectangles';
7033
7135
  }
7034
7136
 
7035
7137
  self.getHitId = function() {return storedData.id;};
@@ -7047,6 +7149,13 @@
7047
7149
  updateSelectionState({ids: ids});
7048
7150
  };
7049
7151
 
7152
+ self.setPinning = function(val) {
7153
+ if (pinnedOn != val) {
7154
+ pinnedOn = val;
7155
+ triggerHitEvent('change');
7156
+ }
7157
+ };
7158
+
7050
7159
  self.setTransientIds = function(ids) {
7051
7160
  // turnOn('selection');
7052
7161
  transientIds = ids || [];
@@ -7116,7 +7225,7 @@
7116
7225
  // make sure popup is unpinned and turned off when switching editing modes
7117
7226
  // (some modes do not support pinning)
7118
7227
  gui.on('interaction_mode_change', function(e) {
7119
- updateSelectionState(null);
7228
+ self.clearSelection();
7120
7229
  // if (e.mode == 'off' || e.mode == 'box') {
7121
7230
  if (gui.interaction.modeUsesSelection(e.mode)) {
7122
7231
  turnOn(e.mode);
@@ -7125,6 +7234,10 @@
7125
7234
  }
7126
7235
  });
7127
7236
 
7237
+ gui.on('undo_redo_pre', function() {
7238
+ self.clearSelection();
7239
+ });
7240
+
7128
7241
  gui.on('box_drag_start', function() {
7129
7242
  self.clearHover();
7130
7243
  });
@@ -7277,6 +7390,9 @@
7277
7390
  if (transientIds.length) {
7278
7391
  eventData.ids = utils$1.uniq(transientIds.concat(eventData.ids || []));
7279
7392
  }
7393
+ if (pinnedOn) {
7394
+ eventData.pinned = true;
7395
+ }
7280
7396
  self.dispatchEvent(type, eventData);
7281
7397
  }
7282
7398
 
@@ -7787,6 +7903,10 @@
7787
7903
  _on = false,
7788
7904
  handles;
7789
7905
 
7906
+ if (opts.classname) {
7907
+ el.addClass(opts.classname);
7908
+ }
7909
+
7790
7910
  el.hide();
7791
7911
 
7792
7912
  gui.on('map_rendered', function() {
@@ -7829,14 +7949,17 @@
7829
7949
  var scale = gui.map.getExtent().getPixelSize();
7830
7950
  var dx = (xy.x - prevXY.x) * scale;
7831
7951
  var dy = -(xy.y - prevXY.y) * scale;
7832
- if (activeHandle.col == 'left') {
7952
+ var center = activeHandle.col == 'center' && activeHandle.row == 'center';
7953
+ if (activeHandle.col == 'left' || center) {
7833
7954
  boxCoords[0] += dx;
7834
- } else if (activeHandle.col == 'right') {
7955
+ }
7956
+ if (activeHandle.col == 'right' || center) {
7835
7957
  boxCoords[2] += dx;
7836
7958
  }
7837
- if (activeHandle.row == 'top') {
7959
+ if (activeHandle.row == 'top' || center) {
7838
7960
  boxCoords[3] += dy;
7839
- } else if (activeHandle.row == 'bottom') {
7961
+ }
7962
+ if (activeHandle.row == 'bottom' || center) {
7840
7963
  boxCoords[1] += dy;
7841
7964
  }
7842
7965
  prevXY = xy;
@@ -7857,6 +7980,11 @@
7857
7980
  });
7858
7981
  }
7859
7982
 
7983
+ box.setDataCoords = function(bbox) {
7984
+ boxCoords = bbox;
7985
+ redraw();
7986
+ };
7987
+
7860
7988
  box.getDataCoords = function() {
7861
7989
  if (!boxCoords) return null;
7862
7990
  var dataBox = getBBoxCoords(gui.map.getActiveLayer(), boxCoords);
@@ -7883,8 +8011,8 @@
7883
8011
  props = {
7884
8012
  top: Math.min(y1, y2),
7885
8013
  left: Math.min(x1, x2),
7886
- width: Math.max(w - stroke * 2, 1),
7887
- height: Math.max(h - stroke * 2, 1)
8014
+ width: Math.max(w - stroke / 2, 1),
8015
+ height: Math.max(h - stroke / 2, 1)
7888
8016
  };
7889
8017
  el.css(props);
7890
8018
  el.show();
@@ -7942,7 +8070,7 @@
7942
8070
  function initHandles(el) {
7943
8071
  var handles = [];
7944
8072
  for (var i=0; i<9; i++) {
7945
- if (i == 4) continue;
8073
+ // if (i == 4) continue; // skip middle handle
7946
8074
  var c = Math.floor(i / 3);
7947
8075
  var r = i % 3;
7948
8076
  handles.push({
@@ -8179,6 +8307,7 @@
8179
8307
  }
8180
8308
 
8181
8309
  function turnOff() {
8310
+ dataBtn.turnOff();
8182
8311
  box.turnOff();
8183
8312
  reset();
8184
8313
  _on = false;
@@ -8189,10 +8318,17 @@
8189
8318
  }
8190
8319
 
8191
8320
  function reset() {
8192
- popup.hide();
8321
+ hidePopup();
8322
+ setPinning(false);
8193
8323
  hit.clearSelection();
8194
8324
  }
8195
8325
 
8326
+ function setPinning(on) {
8327
+ hit.setPinning(on);
8328
+ if (on) dataBtn.turnOn();
8329
+ else dataBtn.turnOff();
8330
+ }
8331
+
8196
8332
  function getIdsOpt() {
8197
8333
  return hit.getSelectionIds().join(',');
8198
8334
  }
@@ -8207,11 +8343,15 @@
8207
8343
  popup.show();
8208
8344
  updateCoords();
8209
8345
  } else {
8210
- popup.hide();
8211
- hideCoords();
8346
+ hidePopup();
8212
8347
  }
8213
8348
  });
8214
8349
 
8350
+ function hidePopup() {
8351
+ popup.hide();
8352
+ hideCoords();
8353
+ }
8354
+
8215
8355
  new SimpleButton(popup.findChild('.delete-btn')).on('click', function() {
8216
8356
  var cmd = '-filter invert ids=' + getIdsOpt();
8217
8357
  runCommand(cmd);
@@ -8227,6 +8367,10 @@
8227
8367
  runCommand(cmd);
8228
8368
  });
8229
8369
 
8370
+ var dataBtn = new ToggleButton(popup.findChild('.data-btn')).on('click', function(e) {
8371
+ setPinning(e.on);
8372
+ });
8373
+
8230
8374
  new SimpleButton(popup.findChild('.duplicate-btn')).on('click', function() {
8231
8375
  var cmd = '-filter + name=selection ids=' + getIdsOpt();
8232
8376
  runCommand(cmd);
@@ -8264,19 +8408,19 @@
8264
8408
  hideCoords();
8265
8409
  return;
8266
8410
  }
8267
- El(coordsBtn.node()).addClass('selected-btn');
8411
+ El(coordsBtn.node()).addClass('active');
8268
8412
  coords.text(bbox.join(','));
8269
8413
  coords.show();
8270
8414
  GUI.selectElement(coords.node());
8271
8415
  }
8272
8416
 
8273
8417
  function hideCoords() {
8274
- El(coordsBtn.node()).removeClass('selected-btn');
8418
+ El(coordsBtn.node()).removeClass('active');
8275
8419
  coords.hide();
8276
8420
  }
8277
8421
 
8278
8422
  function runCommand(cmd, turnOff) {
8279
- popup.hide();
8423
+ hidePopup();
8280
8424
  gui.quiet(true);
8281
8425
  if (gui.console) gui.console.runMapshaperCommands(cmd, function(err) {
8282
8426
  gui.quiet(false);
@@ -8286,6 +8430,55 @@
8286
8430
  }
8287
8431
  }
8288
8432
 
8433
+ function openAddFieldPopup(gui, ids, lyr) {
8434
+ var popup = showPopupAlert('', 'Add field');
8435
+ var el = popup.container();
8436
+ el.addClass('option-menu');
8437
+ var html = `<div><input type="text" class="field-name text-input" placeholder="field name"></div>
8438
+ <div><input type="text" class="field-value text-input" placeholder="value"><div>
8439
+ <div tabindex="0" class="btn dialog-btn">Apply</div> <span class="inline-checkbox"><input type="checkbox" class="all" />assign value to all records</span>`;
8440
+ el.html(html);
8441
+
8442
+ var name = el.findChild('.field-name');
8443
+ name.node().focus();
8444
+ var val = el.findChild('.field-value');
8445
+ var box = el.findChild('.all');
8446
+ var btn = el.findChild('.btn').on('click', function() {
8447
+ var table = internal.getLayerDataTable(lyr); // creates new table if missing
8448
+ var all = box.node().checked;
8449
+ var nameStr = name.node().value.trim();
8450
+ if (!nameStr) return;
8451
+ if (table.fieldExists(nameStr)) {
8452
+ name.node().value = '';
8453
+ return;
8454
+ }
8455
+ var valStr = val.node().value.trim();
8456
+ var value = internal.parseUnknownType(valStr);
8457
+ // table.addField(nameStr, function(d) {
8458
+ // // parse each time to avoid multiple references to objects
8459
+ // return (all || d == rec) ? parseUnknownType(valStr) : null;
8460
+ // });
8461
+
8462
+ var cmdStr = `-each "d['${nameStr}'] = `;
8463
+ if (!all) {
8464
+ cmdStr += ids.length == 1 ?
8465
+ `this.id != ${ids[0]}` :
8466
+ `!${JSON.stringify(ids)}.includes(this.id)`;
8467
+ cmdStr += ' ? null : ';
8468
+ }
8469
+ valStr = JSON.stringify(JSON.stringify(value)); // add escapes to strings
8470
+ cmdStr = valStr.replace('"', cmdStr);
8471
+
8472
+ gui.console.runMapshaperCommands(cmdStr, function(err) {
8473
+ if (!err) {
8474
+ popup.close();
8475
+ } else {
8476
+ console.error(err);
8477
+ }
8478
+ });
8479
+ });
8480
+ }
8481
+
8289
8482
  // toNext, toPrev: trigger functions for switching between multiple records
8290
8483
  function Popup(gui, toNext, toPrev) {
8291
8484
  var self = new EventDispatcher();
@@ -8299,7 +8492,6 @@
8299
8492
  var navInfo = El('span').addClass('popup-nav-info').appendTo(nav);
8300
8493
  var nextLink = El('span').addClass('popup-nav-arrow colored-text').appendTo(nav).text('▶');
8301
8494
  var refresh = null;
8302
- var currIds = [];
8303
8495
 
8304
8496
  el.addClass('rollover'); // used as a sentinel for the hover function
8305
8497
 
@@ -8310,16 +8502,20 @@
8310
8502
  });
8311
8503
 
8312
8504
  self.show = function(id, ids, lyr, pinned) {
8313
- var editable = pinned && gui.interaction.getMode() == 'data';
8505
+ var singleEdit = pinned && gui.interaction.getMode() == 'data';
8506
+ var multiEdit = pinned && gui.interaction.getMode() == 'selection';
8314
8507
  var maxHeight = parent.node().clientHeight - 36;
8315
- currIds = [id];
8508
+ var recIds = multiEdit ? ids : [id];
8509
+
8316
8510
  // stash a function for refreshing the current popup when data changes
8317
8511
  // while the popup is being displayed (e.g. while dragging a label)
8318
8512
  refresh = function() {
8319
- render(content, id, lyr, editable);
8513
+ render(content, recIds, lyr, singleEdit || multiEdit);
8320
8514
  };
8321
8515
  refresh();
8322
- if (ids && ids.length > 1) {
8516
+ if (multiEdit) {
8517
+ showRecords(ids.length);
8518
+ } else if (ids && ids.length > 1 && !multiEdit) {
8323
8519
  showNav(id, ids, pinned);
8324
8520
  } else {
8325
8521
  tab.hide();
@@ -8333,7 +8529,6 @@
8333
8529
  self.hide = function() {
8334
8530
  if (!isOpen()) return;
8335
8531
  refresh = null;
8336
- currIds = [];
8337
8532
  // make sure any pending edits are made before re-rendering popup
8338
8533
  GUI.blurActiveElement(); // this should be more selective -- could cause a glitch if typing in console
8339
8534
  content.empty();
@@ -8347,6 +8542,13 @@
8347
8542
  return el.visible();
8348
8543
  }
8349
8544
 
8545
+ function showRecords(n) {
8546
+ navInfo.text(n);
8547
+ nextLink.css('display','none');
8548
+ prevLink.css('display','none');
8549
+ tab.show();
8550
+ }
8551
+
8350
8552
  function showNav(id, ids, pinned) {
8351
8553
  var num = ids.indexOf(id) + 1;
8352
8554
  navInfo.text(' ' + num + ' / ' + ids.length + ' ');
@@ -8355,28 +8557,12 @@
8355
8557
  tab.show();
8356
8558
  }
8357
8559
 
8358
- function render(el, recId, lyr, editable) {
8359
- var recIds = [recId]; // TODO: support multiple ids
8560
+ function render(el, recIds, lyr, editable) {
8360
8561
  var table = lyr.data; // table can be null (e.g. if layer has no attribute data)
8361
- var rec = table && (editable ? table.getRecordAt(recId) : table.getReadOnlyRecordAt(recId)) || {};
8362
- var tableEl = El('table').addClass('selectable'),
8363
- rows = 0;
8364
- // self.hide(); // clean up if panel is already open
8562
+ var tableEl = table ? renderTable(recIds, table, editable) : null;
8365
8563
  el.empty(); // clean up if panel is already open
8366
- utils$1.forEachProperty(rec, function(v, k) {
8367
- var type;
8368
- // missing GeoJSON fields are set to undefined on import; skip these
8369
- if (v !== undefined) {
8370
- type = getFieldType(v, k, table);
8371
- renderRow(tableEl, rec, k, type, editable);
8372
- rows++;
8373
- }
8374
- });
8375
-
8376
- tableEl.appendTo(el);
8377
- if (rows > 0) {
8378
- // tableEl.appendTo(el);
8379
-
8564
+ if (tableEl) {
8565
+ tableEl.appendTo(el);
8380
8566
  tableEl.on('copy', function(e) {
8381
8567
  // remove leading or trailing tabs that sometimes get copied when
8382
8568
  // selecting from a table
@@ -8387,7 +8573,6 @@
8387
8573
  e.preventDefault(); // don't copy original string with tabs
8388
8574
  }
8389
8575
  });
8390
-
8391
8576
  } else {
8392
8577
  // Some individual features can have undefined values for some or all of
8393
8578
  // their data properties (properties are set to undefined when an input JSON file
@@ -8401,72 +8586,65 @@
8401
8586
  var line = El('div').appendTo(el);
8402
8587
  El('span').addClass('add-field-btn').appendTo(line).on('click', async function(e) {
8403
8588
  // show "add field" dialog
8404
- renderAddFieldPopup(recIds, lyr);
8589
+ openAddFieldPopup(gui, recIds, lyr);
8405
8590
  }).text('+ add field');
8406
8591
  }
8407
8592
  }
8408
8593
 
8409
- function renderAddFieldPopup(ids, lyr) {
8410
- var popup = showPopupAlert('', 'Add field');
8411
- var el = popup.container();
8412
- el.addClass('option-menu');
8413
- var html = `<div><input type="text" class="field-name text-input" placeholder="field name"></div>
8414
- <div><input type="text" class="field-value text-input" placeholder="value"><div>
8415
- <div tabindex="0" class="btn dialog-btn">Apply</div> <span class="inline-checkbox"><input type="checkbox" class="all" />assign value to all records</span>`;
8416
- el.html(html);
8417
-
8418
- var name = el.findChild('.field-name');
8419
- name.node().focus();
8420
- var val = el.findChild('.field-value');
8421
- var box = el.findChild('.all');
8422
- var btn = el.findChild('.btn').on('click', function() {
8423
- var table = internal.getLayerDataTable(lyr); // creates new table if missing
8424
- var all = box.node().checked;
8425
- var nameStr = name.node().value.trim();
8426
- if (!nameStr) return;
8427
- if (table.fieldExists(nameStr)) {
8428
- name.node().value = '';
8429
- return;
8594
+ function renderTable(recIds, table, editable) {
8595
+ var tableEl = El('table').addClass('selectable');
8596
+ var rows = 0;
8597
+ var rec;
8598
+ if (recIds.length == 1) {
8599
+ rec = editable ?
8600
+ table.getReadOnlyRecordAt(recIds[0]) :
8601
+ table.getRecordAt(recIds[0]);
8602
+ } else {
8603
+ rec = getMultiRecord(recIds, table);
8604
+ }
8605
+ utils$1.forEachProperty(rec, function(v, k) {
8606
+ // missing GeoJSON fields are set to undefined on import; skip these
8607
+ if (v === undefined) return;
8608
+ var rowEl = renderRow(k, v, recIds, table, editable);
8609
+ if (rowEl) {
8610
+ rowEl.appendTo(tableEl);
8611
+ rows++;
8430
8612
  }
8431
- var valStr = val.node().value.trim();
8432
- var value = parseUnknownType(valStr);
8433
- // table.addField(nameStr, function(d) {
8434
- // // parse each time to avoid multiple references to objects
8435
- // return (all || d == rec) ? parseUnknownType(valStr) : null;
8436
- // });
8437
-
8438
- var cmdStr = `-each "d['${nameStr}'] = `;
8439
- if (!all) {
8440
- cmdStr += ids.length == 1 ?
8441
- `this.id != ${ids[0]}` :
8442
- `!${JSON.stringify(ids)}.includes(this.id)`;
8443
- cmdStr += ' ? null : ';
8444
- }
8445
- valStr = JSON.stringify(JSON.stringify(value)); // add escapes to strings
8446
- cmdStr = valStr.replace('"', cmdStr);
8447
-
8448
- gui.console.runMapshaperCommands(cmdStr, function(err) {
8449
- if (!err) {
8450
- popup.close();
8451
- } else {
8452
- console.error(err);
8613
+ });
8614
+ return rows > 0 ? tableEl : null;
8615
+ }
8616
+
8617
+ function getMultiRecord(recIds, table) {
8618
+ var fields = table.getFields();
8619
+ var rec = {};
8620
+ recIds.forEach(function(id) {
8621
+ var d = table.getRecordAt(id) || {};
8622
+ var k, v;
8623
+ for (var i=0; i<fields.length; i++) {
8624
+ k = fields[i];
8625
+ v = d[k];
8626
+ if (k in rec === false) {
8627
+ rec[k] = v;
8628
+ } else if (rec[k] !== v) {
8629
+ rec[k] = null;
8453
8630
  }
8454
- });
8631
+ }
8455
8632
  });
8633
+ return rec;
8456
8634
  }
8457
8635
 
8458
- function renderRow(table, rec, key, type, editable) {
8459
- var val = rec[key];
8636
+
8637
+ function renderRow(key, val, recIds, table, editable) {
8638
+ var type = getFieldType(val, key, table);
8460
8639
  var str = formatInspectorValue(val, type);
8461
8640
  var rowHtml = `<td class="field-name">${key}</td><td><span class="value">${utils$1.htmlEscape(str)}</span> </td>`;
8462
- var cell = El('tr')
8463
- .appendTo(table)
8464
- .html(rowHtml)
8465
- .findChild('.value');
8466
- setFieldClass(cell, val, type);
8641
+ var rowEl = El('tr').html(rowHtml);
8642
+ var cellEl = rowEl.findChild('.value');
8643
+ setFieldClass(cellEl, val, type);
8467
8644
  if (editable) {
8468
- editItem(cell, rec, key, type);
8645
+ editItem(cellEl, key, val, recIds, table, type);
8469
8646
  }
8647
+ return rowEl;
8470
8648
  }
8471
8649
 
8472
8650
  function setFieldClass(el, val, type) {
@@ -8479,10 +8657,10 @@
8479
8657
  el.classed('empty', isEmpty);
8480
8658
  }
8481
8659
 
8482
- function editItem(el, rec, key, type) {
8660
+ function editItem(el, key, val, recIds, table, type) {
8483
8661
  var input = new ClickText2(el),
8484
- strval = formatInspectorValue(rec[key], type),
8485
- parser = getInputParser(type);
8662
+ strval = formatInspectorValue(val, type),
8663
+ parser = internal.getInputParser(type);
8486
8664
  el.parent().addClass('editable-cell');
8487
8665
  el.addClass('colored-text dot-underline');
8488
8666
  input.on('change', function(e) {
@@ -8494,17 +8672,27 @@
8494
8672
  } else if (strval != strval2) {
8495
8673
  // field content has changed
8496
8674
  strval = strval2;
8497
- gui.dispatchEvent('data_preupdate', {ids: currIds}); // for undo/redo
8498
- rec[key] = val2;
8499
- gui.dispatchEvent('data_postupdate', {ids: currIds});
8675
+ gui.dispatchEvent('data_preupdate', {ids: recIds}); // for undo/redo
8676
+ // rec[key] = val2;
8677
+ updateRecords(recIds, key, val2, table);
8678
+ gui.dispatchEvent('data_postupdate', {ids: recIds});
8500
8679
  input.value(strval);
8501
8680
  setFieldClass(el, val2, type);
8502
- self.dispatchEvent('data_updated', {field: key, value: val2, ids: currIds});
8681
+ self.dispatchEvent('data_updated', {field: key, value: val2, ids: recIds});
8503
8682
  }
8504
8683
  });
8505
8684
  }
8506
8685
  }
8507
8686
 
8687
+ function updateRecords(ids, f, v, table) {
8688
+ var records = table.getRecords();
8689
+ ids.forEach(function(id) {
8690
+ var d = records[id] || {};
8691
+ d[f] = v;
8692
+ records[id] = d;
8693
+ });
8694
+ }
8695
+
8508
8696
  function formatInspectorValue(val, type) {
8509
8697
  var str;
8510
8698
  if (type == 'date') {
@@ -8517,60 +8705,10 @@
8517
8705
  return str;
8518
8706
  }
8519
8707
 
8520
- var inputParsers = {
8521
- date: function(raw) {
8522
- var d = new Date(raw);
8523
- return isNaN(+d) ? null : d;
8524
- },
8525
- string: function(raw) {
8526
- return raw;
8527
- },
8528
- number: function(raw) {
8529
- var val = Number(raw);
8530
- if (raw == 'NaN') {
8531
- val = NaN;
8532
- } else if (isNaN(val)) {
8533
- val = null;
8534
- }
8535
- return val;
8536
- },
8537
- object: function(raw) {
8538
- var val = null;
8539
- try {
8540
- val = JSON.parse(raw);
8541
- } catch(e) {}
8542
- return val;
8543
- },
8544
- boolean: function(raw) {
8545
- var val = null;
8546
- if (raw == 'true') {
8547
- val = true;
8548
- } else if (raw == 'false') {
8549
- val = false;
8550
- }
8551
- return val;
8552
- },
8553
- multiple: function(raw) {
8554
- var val = Number(raw);
8555
- return isNaN(val) ? raw : val;
8556
- }
8557
- };
8558
-
8559
- function parseUnknownType(str) {
8560
- var val = inputParsers.number(str);
8561
- if (val !== null) return val;
8562
- val = inputParsers.object(str);
8563
- if (val !== null) return val;
8564
- return str;
8565
- }
8566
-
8567
- function getInputParser(type) {
8568
- return inputParsers[type || 'multiple'];
8569
- }
8570
8708
 
8571
8709
  function getFieldType(val, key, table) {
8572
8710
  // if a field has a null value, look at entire column to identify type
8573
- return internal.getValueType(val) || internal.getColumnType(key, table.getRecords());
8711
+ return internal.getValueType(val) || internal.getColumnType(key, table.getRecords()) ;
8574
8712
  }
8575
8713
 
8576
8714
  function InspectionControl2(gui, hit) {
@@ -8595,16 +8733,20 @@
8595
8733
  });
8596
8734
 
8597
8735
  hit.on('change', function(e) {
8598
- var ids;
8599
8736
  if (!inspecting()) return;
8600
- ids = e.mode == 'selection' ? null : e.ids;
8601
- inspect(e.id, e.pinned, ids);
8737
+ var ids;
8738
+ if (e.mode == 'selection') {
8739
+ ids = e.pinned && e.ids || [];
8740
+ } else {
8741
+ ids = e.ids || [];
8742
+ }
8743
+ inspect(e.id, ids, e.pinned);
8602
8744
  });
8603
8745
 
8604
8746
  // id: Id of a feature in the active layer, or -1
8605
- function inspect(id, pin, ids) {
8747
+ function inspect(id, ids, pin) {
8606
8748
  var target = hit.getHitTarget();
8607
- if (id > -1 && inspecting() && target && target.layer) {
8749
+ if ((id > -1 || ids && ids.length > 0) && inspecting() && target && target.layer) {
8608
8750
  _popup.show(id, ids, target.layer, pin);
8609
8751
  } else {
8610
8752
  _popup.hide();
@@ -8903,6 +9045,9 @@
8903
9045
  hit.clearVertexOverlay();
8904
9046
  }
8905
9047
 
9048
+ // return data on the nearest vertex (or identical vertices) to the pointer
9049
+ // (if within a distance threshold)
9050
+ //
8906
9051
  function findDraggableVertices(e) {
8907
9052
  var target = hit.getHitTarget();
8908
9053
  var shp = target.layer.shapes[e.id];
@@ -9065,35 +9210,6 @@
9065
9210
  // }
9066
9211
  }
9067
9212
 
9068
- function initPointDrawing(gui, ext, hit) {
9069
- function active(e) {
9070
- return e.id > -1 && gui.interaction.getMode() == 'add-points';
9071
- }
9072
- }
9073
-
9074
- function Pencil(gui, mouse, hit) {
9075
- var self = this;
9076
- var _on = false;
9077
-
9078
- self.turnOn = function() {
9079
- _on = true;
9080
- };
9081
-
9082
- self.turnOff = function() {
9083
- _on = false;
9084
- };
9085
-
9086
-
9087
-
9088
-
9089
-
9090
-
9091
- }
9092
-
9093
- function initDrawing(gui, ext, mouse, hit) {
9094
- initPointDrawing(gui, new Pencil(gui, mouse, hit));
9095
- }
9096
-
9097
9213
  var darkStroke = "#334",
9098
9214
  lightStroke = "#b7d9ea",
9099
9215
  violet = "#cc6acc",
@@ -9322,7 +9438,10 @@
9322
9438
  var inSelection = o.ids.indexOf(o.id) > -1;
9323
9439
  var geomType = lyr.geometry_type;
9324
9440
  var style;
9325
- if (isPinned) {
9441
+ if (isPinned && o.mode == 'rectangles') {
9442
+ // kludge for rectangle editing mode
9443
+ style = selectionStyles[geomType];
9444
+ } else if (isPinned) {
9326
9445
  // a feature is pinned
9327
9446
  style = pinnedStyles[geomType];
9328
9447
  } else if (inSelection) {
@@ -9417,6 +9536,59 @@
9417
9536
  return internal.findPropertiesBySymbolGeom(fields, lyr.geometry_type);
9418
9537
  }
9419
9538
 
9539
+ function initPointDrawing(gui, ext, mouse, hit) {
9540
+
9541
+ gui.on('interaction_mode_change', function(e) {
9542
+ gui.container.findChild('.map-layers').classed('add-points', e.mode === 'add-points');
9543
+ });
9544
+
9545
+ function active() {
9546
+ return gui.interaction.getMode() == 'add-points';
9547
+ }
9548
+
9549
+ mouse.on('click', function(e) {
9550
+ if (!active()) return;
9551
+ addPoint(e.x, e.y);
9552
+ gui.dispatchEvent('map-needs-refresh');
9553
+ });
9554
+
9555
+ // x, y: pixel coordinates
9556
+ function addPoint(x, y) {
9557
+ var p = ext.translatePixelCoords(x, y);
9558
+ var target = hit.getHitTarget();
9559
+ var lyr = target.layer;
9560
+ var fid = lyr.shapes.length;
9561
+ var d = lyr.data ? getEmptyDataRecord(lyr.data) : null;
9562
+ if (d) {
9563
+ // this seems to work even for projected layers -- the data tables
9564
+ // of projected and original data seem to be shared.
9565
+ lyr.data.getRecords()[fid] = d;
9566
+ // TODO: handle SVG symbol layer
9567
+ if (internal.layerHasLabels(lyr)) {
9568
+ d['label-text'] = 'TBD'; // without text, new labels will be invisible
9569
+ } else if (layerHasCanvasDisplayStyle(lyr)) {
9570
+ d.r = 3; // show a black circle if layer is styled
9571
+ }
9572
+ }
9573
+ lyr.shapes[fid] = [p];
9574
+ updatePointCoords(target, fid);
9575
+ }
9576
+
9577
+ function getEmptyDataRecord(table) {
9578
+ return table.getFields().reduce(function(memo, name) {
9579
+ memo[name] = null;
9580
+ return memo;
9581
+ }, {});
9582
+ }
9583
+ }
9584
+
9585
+ // import { initLineDrawing } from './gui-draw-lines';
9586
+
9587
+ function initDrawing(gui, ext, mouse, hit) {
9588
+ initPointDrawing(gui, ext, mouse, hit);
9589
+ // initLineDrawing(gui, ext, mouse, hit);
9590
+ }
9591
+
9420
9592
  function MapExtent(_position) {
9421
9593
  var _scale = 1,
9422
9594
  _cx, _cy, // center in geographic units
@@ -10444,7 +10616,7 @@
10444
10616
  target.svg_id = id;
10445
10617
  resize(ext);
10446
10618
  if (type == 'label' || type == 'symbol') {
10447
- html = renderSymbols(target.layer, ext, type);
10619
+ html = renderSymbols(target.layer, ext);
10448
10620
  } else if (type == 'furniture') {
10449
10621
  html = renderFurniture(target.layer, ext);
10450
10622
  }
@@ -10458,13 +10630,17 @@
10458
10630
  };
10459
10631
 
10460
10632
  function reposition(target, type, ext) {
10461
- var container = el.findChild('.' + target.svg_id).node();
10633
+ var container = el.findChild('.' + target.svg_id);
10634
+ if (!container || !container.node()) {
10635
+ console.error('[reposition] missing SVG container');
10636
+ return;
10637
+ }
10462
10638
  var elements;
10463
10639
  if (type == 'symbol') {
10464
- elements = El.findAll('.mapshaper-svg-symbol', container);
10640
+ elements = El.findAll('.mapshaper-svg-symbol', container.node());
10465
10641
  repositionSymbols(elements, target.layer, ext);
10466
10642
  } else if (type == 'furniture') {
10467
- repositionFurniture(container, target.layer, ext);
10643
+ repositionFurniture(container.node(), target.layer, ext);
10468
10644
  } else {
10469
10645
  // container.getElementsByTagName('text')
10470
10646
  error('Unsupported symbol type:', type);
@@ -10479,7 +10655,7 @@
10479
10655
  return el;
10480
10656
  }
10481
10657
 
10482
- function LayerStack(gui, container, ext, mouse) {
10658
+ function LayerRenderer(gui, container, ext, mouse) {
10483
10659
  var el = El(container),
10484
10660
  _mainCanv = new DisplayCanvas().appendTo(el),
10485
10661
  _overlayCanv = new DisplayCanvas().appendTo(el),
@@ -10499,14 +10675,12 @@
10499
10675
  }
10500
10676
  layers.forEach(function(lyr) {
10501
10677
  var isSvgLayer = internal.layerHasSvgSymbols(lyr.layer) || internal.layerHasLabels(lyr.layer);
10502
- //if (!svgType || svgType == 'label') { // svg labels may have canvas dots
10503
- if (!isSvgLayer) { // svg labels may have canvas dots
10504
- drawCanvasLayer(lyr, _mainCanv);
10505
- }
10506
10678
  if (isSvgLayer && !needSvgRedraw) {
10507
10679
  _svg.reposition(lyr, 'symbol');
10508
10680
  } else if (isSvgLayer) {
10509
10681
  _svg.drawLayer(lyr, 'symbol');
10682
+ } else {
10683
+ drawCanvasLayer(lyr, _mainCanv);
10510
10684
  }
10511
10685
  });
10512
10686
  };
@@ -10604,7 +10778,8 @@
10604
10778
  });
10605
10779
 
10606
10780
  new SimpleButton(popup.findChild('.rect-btn')).on('click', function() {
10607
- runCommand('-rectangle + bbox=' + box.getDataCoords().join(','));
10781
+ var cmd = '-rectangle + bbox=' + box.getDataCoords().join(',');
10782
+ runCommand(cmd);
10608
10783
  });
10609
10784
 
10610
10785
  gui.addMode('box_tool', turnOn, turnOff);
@@ -10682,6 +10857,81 @@
10682
10857
  return self;
10683
10858
  }
10684
10859
 
10860
+ function RectangleControl(gui, hit) {
10861
+ var box = new HighlightBox(gui, {persistent: true, handles: true, classname: 'rectangles'});
10862
+ var _on = false;
10863
+ var dragInfo;
10864
+
10865
+ gui.addMode('rectangle_tool', turnOn, turnOff);
10866
+
10867
+ gui.on('interaction_mode_change', function(e) {
10868
+ if (e.mode === 'rectangles') {
10869
+ gui.enterMode('rectangle_tool');
10870
+ } else if (gui.getMode() == 'rectangle_tool') {
10871
+ gui.clearMode();
10872
+ }
10873
+ });
10874
+
10875
+ hit.on('change', function(e) {
10876
+ if (!_on) return;
10877
+ // TODO: handle multiple hits (see gui-inspection-control)
10878
+ var id = e.id;
10879
+ if (e.id > -1 && e.pinned) {
10880
+ var target = hit.getHitTarget();
10881
+ var path = target.layer.shapes[e.id][0];
10882
+ var bbox = target.arcs.getSimpleShapeBounds(path).toArray();
10883
+ box.setDataCoords(bbox);
10884
+ dragInfo = {
10885
+ id: e.id,
10886
+ target: target,
10887
+ ids: [],
10888
+ points: []
10889
+ };
10890
+ var iter = target.arcs.getShapeIter(path);
10891
+ while (iter.hasNext()) {
10892
+ dragInfo.points.push([iter.x, iter.y]);
10893
+ dragInfo.ids.push(iter._arc.i);
10894
+ }
10895
+ gui.container.findChild('.map-layers').classed('dragging', true);
10896
+
10897
+ } else if (dragInfo) {
10898
+ gui.dispatchEvent('rectangle_dragend', dragInfo); // save undo state
10899
+ gui.container.findChild('.map-layers').classed('dragging', false);
10900
+ reset();
10901
+ } else {
10902
+ box.hide();
10903
+ }
10904
+
10905
+ });
10906
+
10907
+ box.on('handle_drag', function(e) {
10908
+ if (!_on || !dragInfo) return;
10909
+ var coords = internal.bboxToCoords(box.getDataCoords());
10910
+ setRectangleCoords(dragInfo.target, dragInfo.ids, coords);
10911
+ gui.dispatchEvent('map-needs-refresh');
10912
+ });
10913
+
10914
+ function turnOn() {
10915
+ box.turnOn();
10916
+ _on = true;
10917
+ }
10918
+
10919
+ function turnOff() {
10920
+ box.turnOff();
10921
+ if (gui.interaction.getMode() == 'rectangles') {
10922
+ // mode change was not initiated by interactive menu -- turn off interactivity
10923
+ gui.interaction.turnOff();
10924
+ }
10925
+ _on = false;
10926
+ reset();
10927
+ }
10928
+
10929
+ function reset() {
10930
+ box.hide();
10931
+ dragInfo = null;
10932
+ }
10933
+ }
10934
+
10685
10935
  // Create low-detail versions of large arc collections for faster rendering
10686
10936
  // at zoomed-out scales.
10687
10937
  function enhanceArcCollectionForDisplay(unfilteredArcs) {
@@ -11185,7 +11435,7 @@
11185
11435
  _hit,
11186
11436
  _basemap,
11187
11437
  _intersectionLyr, _activeLyr, _overlayLyr,
11188
- _stack, _dynamicCRS;
11438
+ _renderer, _dynamicCRS;
11189
11439
 
11190
11440
  _basemap = new Basemap(gui, _ext);
11191
11441
 
@@ -11280,13 +11530,12 @@
11280
11530
  clearAllDisplayArcs();
11281
11531
 
11282
11532
  // Reproject all visible map layers
11283
- if (_activeLyr) projectDisplayLayer(_activeLyr, newCRS);
11284
- if (_intersectionLyr) projectDisplayLayer(_intersectionLyr, newCRS);
11285
- if (_overlayLyr) {
11286
- projectDisplayLayer(_overlayLyr, newCRS);
11287
- }
11288
- updateVisibleMapLayers(); // any other display layers will be projected as they are regenerated
11289
- updateLayerStyles(getDrawableContentLayers()); // kludge to make sure all layers have styles
11533
+ getDrawableContentLayers().forEach(function(lyr) {
11534
+ projectDisplayLayer(lyr, newCRS);
11535
+ });
11536
+
11537
+ // kludge to make sure all layers have styles
11538
+ updateLayerStyles(getDrawableContentLayers());
11290
11539
 
11291
11540
  // Update map extent (also triggers redraw)
11292
11541
  projectMapExtent(_ext, oldCRS, this.getDisplayCRS(), calcFullBounds());
@@ -11364,7 +11613,7 @@
11364
11613
  // Initialization just before displaying the map for the first time
11365
11614
  function initMap() {
11366
11615
  _ext.resize();
11367
- _stack = new LayerStack(gui, el, _ext, _mouse);
11616
+ _renderer = new LayerRenderer(gui, el, _ext, _mouse);
11368
11617
  gui.buttons.show();
11369
11618
 
11370
11619
  if (opts.inspectorControl) {
@@ -11372,8 +11621,9 @@
11372
11621
  new InspectionControl2(gui, _hit);
11373
11622
  new SelectionTool(gui, _ext, _hit),
11374
11623
  new BoxTool(gui, _ext, _nav),
11624
+ new RectangleControl(gui, _hit),
11375
11625
  initInteractiveEditing(gui, _ext, _hit);
11376
- // initDrawing(gui, _ext, _mouse, _hit);
11626
+ initDrawing(gui, _ext, _mouse, _hit);
11377
11627
  _hit.on('change', updateOverlayLayer);
11378
11628
  }
11379
11629
 
@@ -11404,7 +11654,6 @@
11404
11654
 
11405
11655
  // 'hover' avoids redrawing all svg symbols when only highlight needs to refresh
11406
11656
  drawLayers('hover');
11407
- // drawLayers();
11408
11657
  }
11409
11658
 
11410
11659
  function getDisplayOptions() {
@@ -11439,10 +11688,7 @@
11439
11688
  _ext.setFullBounds(calcFullBounds(), getStrictBounds());
11440
11689
  }
11441
11690
 
11442
- function calcFullBounds() {
11443
- if (isPreviewView()) {
11444
- return internal.getFrameLayerBounds(internal.findFrameLayer(model));
11445
- }
11691
+ function getVisibleContentBounds() {
11446
11692
  var b = new Bounds();
11447
11693
  var layers = getDrawableContentLayers();
11448
11694
  layers.forEach(function(lyr) {
@@ -11453,6 +11699,14 @@
11453
11699
  // assign bounds to empty layers, to prevent rendering errors downstream
11454
11700
  b.setBounds(0,0,0,0);
11455
11701
  }
11702
+ return b;
11703
+ }
11704
+
11705
+ function calcFullBounds() {
11706
+ if (isPreviewView()) {
11707
+ return internal.getFrameLayerBounds(internal.findFrameLayer(model));
11708
+ }
11709
+ var b = getVisibleContentBounds();
11456
11710
 
11457
11711
  // add margin
11458
11712
  // use larger margin for small sizes
@@ -11478,10 +11732,6 @@
11478
11732
  return isActiveLayer(lyr) || lyr.pinned;
11479
11733
  }
11480
11734
 
11481
- function isFrameLayer(lyr) {
11482
- return !!(lyr && lyr == internal.findFrameLayer(model));
11483
- }
11484
-
11485
11735
  function isTableView() {
11486
11736
  return !isPreviewView() && !!_activeLyr.tabular;
11487
11737
  }
@@ -11613,11 +11863,11 @@
11613
11863
  }
11614
11864
  // RENDERING
11615
11865
  // draw main content layers
11616
- _stack.drawMainLayers(contentLayers, action);
11866
+ _renderer.drawMainLayers(contentLayers, action);
11617
11867
  // draw hover & selection overlay
11618
- _stack.drawOverlayLayer(_overlayLyr, action);
11868
+ _renderer.drawOverlayLayer(_overlayLyr, action);
11619
11869
  // draw furniture
11620
- _stack.drawFurnitureLayers(furnitureLayers, action);
11870
+ // _renderer.drawFurnitureLayers(furnitureLayers, action);
11621
11871
  gui.dispatchEvent('map_rendered');
11622
11872
  }
11623
11873
  }