mapshaper 0.6.102 → 0.6.103

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.
@@ -67,6 +67,7 @@
67
67
  get formatIntlNumber () { return formatIntlNumber; },
68
68
  get formatNumberForDisplay () { return formatNumberForDisplay; },
69
69
  get shuffle () { return shuffle; },
70
+ get pickOne () { return pickOne; },
70
71
  get sortOn () { return sortOn; },
71
72
  get genericSort () { return genericSort; },
72
73
  get getSortedIds () { return getSortedIds; },
@@ -1156,21 +1157,18 @@
1156
1157
 
1157
1158
 
1158
1159
  function adjustPointSymbolSizes(layers, overlayLyr, ext) {
1159
- var bbox = ext.getBounds().scale(1.5).toArray();
1160
- var testInBounds = function(p) {
1161
- return p[0] > bbox[0] && p[0] < bbox[2] && p[1] > bbox[1] && p[1] < bbox[3];
1162
- };
1163
- var topTier = 50000;
1160
+ var bbox = ext.getBounds().scale(1.3).toArray(); // add buffer
1161
+ // var topTier = 50000; // can be a bottleneck
1162
+ var topTier = 10000; // short-circuit counting here
1164
1163
  var count = 0;
1165
1164
  layers = layers.filter(function(lyr) {
1166
1165
  return lyr.geometry_type == 'point' && lyr.gui.style.dotSize > 0;
1167
1166
  });
1168
1167
  layers.forEach(function(lyr) {
1169
- // short-circuit point counting above top threshold
1170
- count += countPoints(lyr.gui.displayLayer.shapes, topTier, testInBounds);
1168
+ count += countPoints(lyr.gui.displayLayer.shapes, topTier, bbox);
1171
1169
  });
1172
1170
  count = Math.min(topTier, count) || 1;
1173
- var k = Math.pow(6 - utils$1.clamp(Math.log10(count), 1, 5), 1.3);
1171
+ var k = Math.pow(5 - utils$1.clamp(Math.log10(count), 1, 4), 1.25);
1174
1172
 
1175
1173
  // zoom adjustments
1176
1174
  var mapScale = ext.scale();
@@ -1178,10 +1176,11 @@
1178
1176
  k *= Math.pow(mapScale + 0.5, 0.35);
1179
1177
  } else if (mapScale > 1) {
1180
1178
  // scale faster at first
1181
- k *= Math.pow(Math.min(mapScale, 4), 0.15);
1182
- k *= Math.pow(mapScale, 0.05);
1179
+ k *= Math.pow(Math.min(mapScale, 4), 0.25);
1180
+ k *= Math.pow(mapScale, 0.02);
1183
1181
  }
1184
1182
 
1183
+
1185
1184
  // scale down when map is small
1186
1185
  var smallSide = Math.min(ext.width(), ext.height());
1187
1186
  k *= utils$1.clamp(smallSide / 500, 0.5, 1);
@@ -1194,13 +1193,17 @@
1194
1193
  }
1195
1194
  }
1196
1195
 
1197
- function countPoints(shapes, max, filter) {
1196
+ function countPoints(shapes, max, bbox) {
1198
1197
  var count = 0;
1199
- var i, j, n, m, shp;
1200
- for (i=0, n=shapes.length; i<n && count<max; i++) {
1198
+ var shp, p;
1199
+ // short-circuit point counting above top threshold
1200
+ for (var i=0, n=shapes.length; i<n && count<max; i++) {
1201
1201
  shp = shapes[i];
1202
- for (j=0, m=(shp ? shp.length : 0); j<m; j++) {
1203
- count += filter(shp[j]) ? 1 : 0;
1202
+ for (var j=0, m=(shp ? shp.length : 0); j<m; j++) {
1203
+ p = shp[j];
1204
+ if (p[0] > bbox[0] && p[0] < bbox[2] && p[1] > bbox[1] && p[1] < bbox[3]) {
1205
+ count ++;
1206
+ }
1204
1207
  }
1205
1208
  }
1206
1209
  return count;
@@ -1367,6 +1370,15 @@
1367
1370
  });
1368
1371
  }
1369
1372
 
1373
+ // save file to selected folder if supported, else to downloads
1374
+ function saveBlobToLocalFile2(filename, blob) {
1375
+ if (window.showSaveFilePicker) {
1376
+ saveBlobToSelectedFile(filename, blob);
1377
+ } else {
1378
+ saveBlobToDownloadsFolder(filename, blob);
1379
+ }
1380
+ }
1381
+
1370
1382
  async function saveBlobToLocalFile(filename, blob, done) {
1371
1383
  var chooseDir = GUI.getSavedValue('choose-save-dir');
1372
1384
  done = done || function() {};
@@ -1454,6 +1466,7 @@
1454
1466
 
1455
1467
  function saveBlobToDownloadsFolder(filename, blob, done) {
1456
1468
  var anchor, blobUrl;
1469
+ done = done || function() {};
1457
1470
  try {
1458
1471
  blobUrl = URL.createObjectURL(blob);
1459
1472
  } catch(e) {
@@ -1475,7 +1488,23 @@
1475
1488
  }, 400);
1476
1489
  }
1477
1490
 
1478
- var idb = require('idb-keyval');
1491
+ // Several dependencies are loaded via require()
1492
+ var f;
1493
+ if (typeof require == 'function') {
1494
+ // Node.js context: native require() function
1495
+ f = require;
1496
+ } else if (typeof window == 'object' && window.modules) {
1497
+ // running in web GUI
1498
+ f = function(name) {
1499
+ return window.modules[name];
1500
+ };
1501
+ } else {
1502
+ // stub to avoid runtime error in a handful of tests
1503
+ f = function() {};
1504
+ }
1505
+ var require$1 = f;
1506
+
1507
+ var idb = require$1('idb-keyval');
1479
1508
  // https://github.com/jakearchibald/idb
1480
1509
  // https://github.com/jakearchibald/idb-keyval
1481
1510
  var sessionId = getUniqId('session');
@@ -1563,10 +1592,9 @@
1563
1592
  var obj = await idb.get(item.id);
1564
1593
  await internal.compressSnapshotForExport(obj);
1565
1594
  var buf = internal.pack(obj);
1566
- // choose output filename and directory every time
1567
- // saveBlobToLocalFile('mapshaper_snapshot.msx', new Blob([buf]));
1568
1595
  var fileName = `snapshot-${String(item.number).padStart(2, '0')}.msx`;
1569
- saveBlobToSelectedFile(fileName, new Blob([buf]), function() {});
1596
+ // choose output filename and directory every time, if supported
1597
+ saveBlobToLocalFile2(fileName, new Blob([buf]));
1570
1598
  }).text('export');
1571
1599
  El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
1572
1600
  await removeSnapshotById(item.id);
@@ -1606,6 +1634,7 @@
1606
1634
 
1607
1635
  async function saveSnapshot(gui) {
1608
1636
  var obj = await captureSnapshot(gui);
1637
+
1609
1638
  if (!obj) return;
1610
1639
  // storing an unpacked object is usually a bit faster (~20%)
1611
1640
  // note: we don't know the size of unpacked snapshot objects
@@ -1693,9 +1722,7 @@
1693
1722
  // in file size in a typical polygon or polyline file, but longer processing time
1694
1723
  var opts = {compact: false, active_layer: lyr};
1695
1724
  var datasets = gui.model.getDatasets();
1696
- // console.time('msx');
1697
1725
  var obj = await internal.exportDatasetsToPack(datasets, opts);
1698
- // console.timeEnd('msx')
1699
1726
  obj.gui = getGuiState(gui);
1700
1727
  return obj;
1701
1728
  }
@@ -2672,7 +2699,7 @@
2672
2699
  // load Proj.4 CRS definition files dynamically
2673
2700
  //
2674
2701
  async function loadProjLibs(opts) {
2675
- var mproj = require('mproj');
2702
+ var mproj = require$1('mproj');
2676
2703
  var libs = internal.findProjLibs([opts.init || '', opts.match || '', opts.crs || ''].join(' '));
2677
2704
  libs = libs.filter(function(name) {return !mproj.internal.mproj_search_libcache(name);}); // skip loaded libs
2678
2705
  for (var libName of libs) {
@@ -2836,8 +2863,8 @@
2836
2863
  return getDefaultStyle(lyr, intersectionStyle);
2837
2864
  }
2838
2865
 
2839
- // Style for unselected layers with visibility turned on
2840
- // (styled layers have)
2866
+ // Display style for unselected layers with visibility turned on
2867
+ // (may be fully styled or outlined)
2841
2868
  function getReferenceLayerStyle(lyr, opts) {
2842
2869
  var style;
2843
2870
  if (layerHasCanvasDisplayStyle(lyr) && !opts.outlineMode) {
@@ -2919,6 +2946,7 @@
2919
2946
  style.styler = getOverlayPointStyler(getCanvasDisplayStyle(baseLyr).styler, styler);
2920
2947
  }
2921
2948
  style.type = 'styled';
2949
+
2922
2950
  }
2923
2951
  return ids.length > 0 ? style : null;
2924
2952
  }
@@ -3022,6 +3050,7 @@
3022
3050
  // array of field names of relevant svg display properties
3023
3051
  fields = getCanvasStyleFields(lyr).filter(function(f) {return f in styleIndex;}),
3024
3052
  records = lyr.data.getRecords();
3053
+
3025
3054
  var styler = function(style, i) {
3026
3055
  var rec = records[i];
3027
3056
  var fname, val;
@@ -3045,14 +3074,20 @@
3045
3074
  style.fillColor = 'black';
3046
3075
  }
3047
3076
  };
3048
- return {styler: styler, type: 'styled'};
3077
+ var style = {styler: styler, type: 'styled'};
3078
+ // use squares if radius is missing... (TODO: check behavior with labels, etc)
3079
+ if (lyr.geometry_type == 'point' && fields.includes('r') === false) {
3080
+ style.dotSize = 1;
3081
+ }
3082
+ return style;
3049
3083
  }
3050
3084
 
3051
- // check if layer should be displayed with styles
3085
+ // check if layer should be displayed with a full style
3052
3086
  function layerHasCanvasDisplayStyle(lyr) {
3053
3087
  var fields = getCanvasStyleFields(lyr);
3054
3088
  if (lyr.geometry_type == 'point') {
3055
- return fields.indexOf('r') > -1; // require 'r' field for point symbols
3089
+ // return fields.indexOf('r') > -1; // require 'r' field for point symbols
3090
+ return fields.includes('fill') || fields.includes('r'); // support colored squares
3056
3091
  }
3057
3092
  return utils$1.difference(fields, ['opacity', 'class']).length > 0;
3058
3093
  }
@@ -6090,6 +6125,9 @@
6090
6125
 
6091
6126
  utils$1.inherit(ModeSwitcher, EventDispatcher);
6092
6127
 
6128
+ // export var ESC = 27;
6129
+ var SPACE = 32;
6130
+
6093
6131
  function KeyboardEvents(gui) {
6094
6132
  var self = this;
6095
6133
  var shiftDown = false;
@@ -6103,7 +6141,7 @@
6103
6141
  ctrlDown = e.ctrlKey;
6104
6142
  metaDown = e.metaKey;
6105
6143
  altDown = e.altKey;
6106
- if (e.keyCode == 32) {
6144
+ if (e.keyCode == SPACE) {
6107
6145
  spaceDown = evtName == 'keydown';
6108
6146
  }
6109
6147
  }
@@ -6113,13 +6151,13 @@
6113
6151
  }
6114
6152
 
6115
6153
  document.addEventListener('keyup', function(e) {
6116
- if (!GUI.isActiveInstance(gui) || e.repeat && e.keyCode == 32) return;
6154
+ if (!GUI.isActiveInstance(gui) || e.repeat && e.keyCode == SPACE) return;
6117
6155
  updateControlKeys(e, 'keyup');
6118
6156
  self.dispatchEvent('keyup', getEventData(e));
6119
6157
  });
6120
6158
 
6121
6159
  document.addEventListener('keydown', function(e) {
6122
- if (!GUI.isActiveInstance(gui) || e.repeat && e.keyCode == 32) return;
6160
+ if (!GUI.isActiveInstance(gui) || e.repeat && e.keyCode == SPACE) return;
6123
6161
  updateControlKeys(e, 'keydown');
6124
6162
  self.dispatchEvent('keydown', getEventData(e));
6125
6163
  });
@@ -6444,7 +6482,7 @@
6444
6482
  if (flags.select) {
6445
6483
  self.dispatchEvent('select', active);
6446
6484
  }
6447
- self.dispatchEvent('update', utils$1.extend({flags: flags}, active));
6485
+ self.dispatchEvent('update', {flags: flags});
6448
6486
  };
6449
6487
 
6450
6488
  self.selectLayer = function(lyr, dataset) {
@@ -6470,7 +6508,7 @@
6470
6508
  }
6471
6509
 
6472
6510
  // Fall back to browserify's Buffer polyfill
6473
- var B = typeof Buffer != 'undefined' ? Buffer : require('buffer').Buffer;
6511
+ var B = typeof Buffer != 'undefined' ? Buffer : require$1('buffer').Buffer;
6474
6512
 
6475
6513
  // This module provides a way for multiple jobs to run together asynchronously
6476
6514
  // while keeping job-level context variables (like "defs") separate.
@@ -7268,6 +7306,10 @@
7268
7306
  }
7269
7307
  }
7270
7308
 
7309
+ function pickOne(arr) {
7310
+ return arr[Math.floor(Math.random() * arr.length)];
7311
+ }
7312
+
7271
7313
  // Sort an array of objects based on one or more properties.
7272
7314
  // Usage: sortOn(array, key1, asc?[, key2, asc? ...])
7273
7315
  //
@@ -8005,7 +8047,7 @@
8005
8047
  function getShapeHitTest(layer, ext, interactionMode, featureFilter) {
8006
8048
  var geoType = layer.gui.displayLayer.geometry_type;
8007
8049
  var test;
8008
- if (geoType == 'point' && layer.gui.style.type == 'styled') {
8050
+ if (geoType == 'point' && layer.gui.style.type == 'styled' && !layer.gui.style.dotSize) {
8009
8051
  test = getGraduatedCircleTest(getRadiusFunction(layer.gui.style));
8010
8052
  } else if (geoType == 'point') {
8011
8053
  test = pointTest;
@@ -8338,7 +8380,7 @@
8338
8380
 
8339
8381
  // e: pointer event
8340
8382
  return function(e) {
8341
- var p = ext.translatePixelCoords(e.x, e.y);
8383
+ var p = ext.pixCoordsToMapCoords(e.x, e.y);
8342
8384
  // update SVG hit test on each test, in case SVG layer has been redrawn
8343
8385
  // and the symbol container has changed
8344
8386
  var svgTest = getSvgHitTest(mapLayer);
@@ -9274,6 +9316,7 @@
9274
9316
  persistent: false,
9275
9317
  draggable: false // does dragging the map draw a box
9276
9318
  }, optsArg),
9319
+ clickToStart = opts.name == 'box-tool', // other versions use shift-drag
9277
9320
  box = new EventDispatcher(),
9278
9321
  stroke = 2,
9279
9322
  activeHandle = null,
@@ -9281,38 +9324,75 @@
9281
9324
  boxCoords = null,
9282
9325
  _on = false,
9283
9326
  _visible = false,
9327
+ clickStartPoint,
9284
9328
  handles;
9285
-
9286
9329
  if (opts.classname) {
9287
9330
  el.addClass(opts.classname);
9288
9331
  }
9289
9332
 
9333
+ function drawing() {
9334
+ return visible() && !activeHandle;
9335
+ }
9336
+
9337
+ function resizing() {
9338
+ return !!activeHandle && visible();
9339
+ }
9340
+
9341
+ function visible() {
9342
+ return _on && !!boxCoords && _visible;
9343
+ }
9344
+
9290
9345
  el.hide();
9291
9346
 
9292
9347
  gui.on('map_rendered', function() {
9293
- if (!_on || !_visible) return;
9348
+ if (!visible()) return;
9294
9349
  redraw();
9295
9350
  });
9296
9351
 
9297
- gui.on('shift_drag', function(e) {
9298
- if (!_on) return;
9299
- if (!opts.draggable) return;
9300
- boxCoords = getBoxCoords(e.data);
9301
- redraw();
9302
- box.dispatchEvent('drag');
9303
- });
9352
+ if (clickToStart) {
9353
+ gui.on('map_click', function(e) {
9354
+ if (!_on) {
9355
+ // clickStartPoint = null;
9356
+ return;
9357
+ }
9358
+
9359
+ var p = [e.x, e.y];
9360
+ if (drawing()) {
9361
+ finishDrawingBox(e);
9362
+ } else if (!resizing()) {
9363
+ clickStartPoint = p;
9364
+ }
9365
+ });
9366
+
9367
+ gui.map.getMouse().on('hover', function(e) {
9368
+ if (!_on) return;
9369
+ var p = [e.x, e.y];
9370
+ if (clickStartPoint) {
9371
+ updateDrawnBox(clickStartPoint, p);
9372
+ }
9373
+ // box.dispatchEvent('drag');
9374
+ });
9375
+ }
9376
+
9377
+ if (!clickToStart) {
9378
+ gui.on('shift_drag', function(e) {
9379
+ if (!_on) return;
9380
+ if (!opts.draggable) return;
9381
+ if (clickToStart) return;
9382
+ updateDrawnBox(e.data.b, e.data.a);
9383
+ box.dispatchEvent('drag');
9384
+ });
9385
+
9386
+ gui.on('shift_drag_end', function(e) {
9387
+ if (!_on || !_visible || !opts.draggable) return;
9388
+ if (clickToStart) return;
9389
+ updateDrawnBox(e.data.b, e.data.a, true);
9390
+ if (!opts.persistent) {
9391
+ box.hide();
9392
+ }
9393
+ });
9394
+ }
9304
9395
 
9305
- gui.on('shift_drag_end', function(e) {
9306
- if (!_on || !_visible || !opts.draggable) return;
9307
- boxCoords = getBoxCoords(e.data);
9308
- var pix = coordsToPix(boxCoords, gui.map.getExtent());
9309
- box.dispatchEvent('dragend', {map_bbox: pix});
9310
- if (!opts.persistent) {
9311
- box.hide();
9312
- } else {
9313
- redraw();
9314
- }
9315
- });
9316
9396
 
9317
9397
  if (opts.handles) {
9318
9398
  handles = initHandles(el);
@@ -9325,7 +9405,7 @@
9325
9405
  });
9326
9406
 
9327
9407
  gui.map.getMouse().on('mousemove', function(e) {
9328
- if (!_on || !activeHandle || !prevXY || !boxCoords || !_visible) return;
9408
+ if (!resizing() || !prevXY) return;
9329
9409
  var xy = {x: e.pageX, y: e.pageY};
9330
9410
  var scaling = gui.keyboard.shiftIsPressed() && activeHandle.type == 'corner';
9331
9411
  if (scaling) {
@@ -9351,6 +9431,27 @@
9351
9431
  });
9352
9432
  }
9353
9433
 
9434
+ // p1: origin
9435
+ // p2: pointer location
9436
+ function updateDrawnBox(p1, p2, final) {
9437
+ if (!boxCoords) {
9438
+ el.classed('hittable', false);
9439
+ }
9440
+ boxCoords = getBoxCoords(p1, p2);
9441
+ redraw();
9442
+ if (final) {
9443
+ el.classed('hittable', true);
9444
+ var pix = coordsToPix(boxCoords, gui.map.getExtent());
9445
+ box.dispatchEvent('dragend', {map_bbox: pix});
9446
+ }
9447
+ }
9448
+
9449
+ function finishDrawingBox(e) {
9450
+ if (!clickStartPoint) return;
9451
+ updateDrawnBox(clickStartPoint, [e.x, e.y], true);
9452
+ clickStartPoint = null;
9453
+ }
9454
+
9354
9455
  function resizeBox(dx, dy, activeHandle) {
9355
9456
  var shifting = activeHandle.type == 'center';
9356
9457
  var centered = gui.keyboard.shiftIsPressed() && activeHandle.type == 'edge';
@@ -9377,7 +9478,7 @@
9377
9478
  }
9378
9479
 
9379
9480
  function rescaleBox(x, y) {
9380
- var p = gui.map.getExtent().translatePixelCoords(x, y);
9481
+ var p = gui.map.getExtent().pixCoordsToMapCoords(x, y);
9381
9482
  var cx = (boxCoords[0] + boxCoords[2])/2;
9382
9483
  var cy = (boxCoords[1] + boxCoords[3])/2;
9383
9484
  var dist2 = geom.distance2D(cx, cy, p[0], p[1]);
@@ -9407,12 +9508,16 @@
9407
9508
 
9408
9509
  box.turnOff = function() {
9409
9510
  _on = false;
9511
+ box.hide();
9410
9512
  };
9411
9513
 
9514
+ // remove the current box (if any)
9412
9515
  box.hide = function() {
9413
9516
  el.hide();
9414
9517
  boxCoords = null;
9415
9518
  _visible = false;
9519
+ clickStartPoint = null;
9520
+ activeHandle = null;
9416
9521
  };
9417
9522
 
9418
9523
  box.show = function(x1, y1, x2, y2) {
@@ -9452,12 +9557,35 @@
9452
9557
  }
9453
9558
 
9454
9559
  // get bbox coords in the display CRS
9455
- function getBoxCoords(e) {
9456
- var bbox = pixToCoords(e.a.concat(e.b), gui.map.getExtent());
9560
+ function getBoxCoords(p1, p2) {
9561
+ if (gui.keyboard.shiftIsPressed()) {
9562
+ p2 = getSquareCorner(p1[0], p1[1], p2[0], p2[1]);
9563
+ }
9564
+ var bbox = pixToCoords(p1.concat(p2), gui.map.getExtent());
9457
9565
  fixBounds(bbox);
9458
9566
  return bbox;
9459
9567
  }
9460
9568
 
9569
+ function getSquareCorner(x1, y1, x2, y2) {
9570
+ var dx = x2 - x1;
9571
+ var dy = y2 - y1;
9572
+ if (dy === 0 && dx === 0) {
9573
+ return [x2, y2];
9574
+ }
9575
+ if (dy === 0) {
9576
+ dy = 1;
9577
+ }
9578
+ if (dx === 0) {
9579
+ dx = 1;
9580
+ }
9581
+ if (Math.abs(dx) > Math.abs(dy)) {
9582
+ dy = dy * Math.abs(dx / dy);
9583
+ } else {
9584
+ dx = dx * Math.abs(dy / dx);
9585
+ }
9586
+ return [x1 + dx, y1 + dy];
9587
+ }
9588
+
9461
9589
  function redraw() {
9462
9590
  if (!boxCoords) return;
9463
9591
  var ext = gui.map.getExtent();
@@ -9478,12 +9606,12 @@
9478
9606
  }
9479
9607
 
9480
9608
  function pixToCoords(bbox, ext) {
9481
- var a = ext.translatePixelCoords(bbox[0], bbox[1]);
9482
- var b = ext.translatePixelCoords(bbox[2], bbox[3]);
9609
+ var a = ext.pixCoordsToMapCoords(bbox[0], bbox[1]);
9610
+ var b = ext.pixCoordsToMapCoords(bbox[2], bbox[3]);
9483
9611
  return [a[0], b[1], b[0], a[1]];
9484
9612
  }
9485
9613
 
9486
-
9614
+ // enforce [minx, miny, maxx, maxy] order
9487
9615
  function fixBounds(bbox) {
9488
9616
  var tmp;
9489
9617
  if (bbox[0] > bbox[2]) {
@@ -9615,7 +9743,7 @@
9615
9743
  mouse.on('drag', function(e) {
9616
9744
  if (disabled()) return;
9617
9745
  if (shiftDrag) {
9618
- gui.dispatchEvent('shift_drag', getBoxData(e));
9746
+ gui.dispatchEvent('shift_drag', getBoxData(e, dragStartEvt));
9619
9747
  return;
9620
9748
  }
9621
9749
  if (++panCount == 1) {
@@ -9635,7 +9763,7 @@
9635
9763
  if (disabled()) return;
9636
9764
  if (shiftDrag) {
9637
9765
  shiftDrag = false;
9638
- gui.dispatchEvent('shift_drag_end', getBoxData(e));
9766
+ gui.dispatchEvent('shift_drag_end', getBoxData(e, dragStartEvt));
9639
9767
  zoomBox.turnOff();
9640
9768
  } else {
9641
9769
  El('body').removeClass('panning').removeClass('pan');
@@ -9659,10 +9787,10 @@
9659
9787
  return !'selection_tool,box_tool,rectangle_tool,drawing_tool'.includes(mode);
9660
9788
  }
9661
9789
 
9662
- function getBoxData(e) {
9790
+ function getBoxData(e1, e2) {
9663
9791
  return {
9664
- a: [e.x, e.y],
9665
- b: [dragStartEvt.x, dragStartEvt.y]
9792
+ a: [e1.x, e1.y],
9793
+ b: [e2.x, e2.y]
9666
9794
  };
9667
9795
  }
9668
9796
 
@@ -10546,8 +10674,8 @@
10546
10674
  var coords = target.shapes[id];
10547
10675
  deleteFeature(target, id);
10548
10676
  gui.dispatchEvent('feature_delete', {coords, d, target, fid: id});
10549
- gui.dispatchEvent('map-needs-refresh');
10550
10677
  hit.setHitId(-1);
10678
+ gui.dispatchEvent('map-needs-refresh');
10551
10679
  }
10552
10680
 
10553
10681
  hit.on('click', function(e) {
@@ -10602,12 +10730,12 @@
10602
10730
 
10603
10731
  function pixToDataCoords(x, y) {
10604
10732
  var target = hit.getHitTarget();
10605
- return translateDisplayPoint(target, ext.translatePixelCoords(x, y));
10733
+ return translateDisplayPoint(target, ext.pixCoordsToMapCoords(x, y));
10606
10734
  }
10607
10735
 
10608
10736
  function translateDeltaDisplayCoords(dx, dy, ext) {
10609
- var a = ext.translatePixelCoords(0, 0);
10610
- var b = ext.translatePixelCoords(dx, dy);
10737
+ var a = ext.pixCoordsToMapCoords(0, 0);
10738
+ var b = ext.pixCoordsToMapCoords(dx, dy);
10611
10739
  return [b[0] - a[0], b[1] - a[1]];
10612
10740
  }
10613
10741
  }
@@ -10887,7 +11015,7 @@
10887
11015
  e.originalEvent.stopPropagation();
10888
11016
  // dragging a vertex
10889
11017
  var target = hit.getHitTarget();
10890
- var p = ext.translatePixelCoords(e.x, e.y);
11018
+ var p = ext.pixCoordsToMapCoords(e.x, e.y);
10891
11019
  if (gui.keyboard.shiftIsPressed()) {
10892
11020
  internal.snapPointToArcEndpoint(p, hoverVertexInfo.ids, target.gui.displayArcs);
10893
11021
  }
@@ -10905,8 +11033,8 @@
10905
11033
  target.gui.displayArcs.transformPoints(function() {});
10906
11034
  updateVertexCoords(target, hoverVertexInfo.ids);
10907
11035
  gui.dispatchEvent('vertex_dragend', hoverVertexInfo);
10908
- gui.dispatchEvent('map-needs-refresh'); // redraw basemap
10909
11036
  clearHoverVertex();
11037
+ gui.dispatchEvent('map-needs-refresh'); // redraw basemap
10910
11038
  });
10911
11039
 
10912
11040
  // shift + double-click deletes a vertex (when not drawing)
@@ -11020,7 +11148,7 @@
11020
11148
 
11021
11149
  function pixToDataCoords(x, y) {
11022
11150
  var target = hit.getHitTarget();
11023
- return translateDisplayPoint(target, ext.translatePixelCoords(x, y));
11151
+ return translateDisplayPoint(target, ext.pixCoordsToMapCoords(x, y));
11024
11152
  }
11025
11153
 
11026
11154
  // Change the x, y pixel location of thisEvt so that the segment extending
@@ -11142,7 +11270,7 @@
11142
11270
  if (!pathDrawing()) return false;
11143
11271
  var target = hit.getHitTarget();
11144
11272
  var arcId = target.gui.displayArcs.size() - 1;
11145
- var p1 = ext.translatePixelCoords(e.x, e.y); // mouse coords
11273
+ var p1 = ext.pixCoordsToMapCoords(e.x, e.y); // mouse coords
11146
11274
  var p2 = internal.getArcStartCoords(arcId, target.gui.displayArcs); // vertex coords
11147
11275
  var p3 = internal.getArcStartCoords(arcId, target.gui.source.dataset.arcs);
11148
11276
  var dist = geom.distance2D(p1[0], p1[1], p2[0], p2[1]);
@@ -11164,7 +11292,7 @@
11164
11292
  function findDraggableVertices(e) {
11165
11293
  var target = hit.getHitTarget();
11166
11294
  var shp = target.shapes[e.id];
11167
- var p = ext.translatePixelCoords(e.x, e.y);
11295
+ var p = ext.pixCoordsToMapCoords(e.x, e.y);
11168
11296
  var ids = internal.findNearestVertices(p, shp, target.gui.displayArcs);
11169
11297
  var p2 = target.gui.displayArcs.getVertex2(ids[0]);
11170
11298
  var dist = geom.distance2D(p[0], p[1], p2[0], p2[1]);
@@ -11186,7 +11314,7 @@
11186
11314
  var target = hit.getHitTarget();
11187
11315
  //// vertex insertion not supported with simplification
11188
11316
  // if (!target.arcs.isFlat()) return null;
11189
- var p = ext.translatePixelCoords(e.x, e.y);
11317
+ var p = ext.pixCoordsToMapCoords(e.x, e.y);
11190
11318
  var minDist = Infinity;
11191
11319
  var shp = target.shapes[e.id];
11192
11320
  var closest;
@@ -11411,7 +11539,7 @@
11411
11539
  };
11412
11540
 
11413
11541
  // convert pixel coords (0,0 is top left corner of map) to display CRS coords
11414
- this.translatePixelCoords = function(x, y) {
11542
+ this.pixCoordsToMapCoords = function(x, y) {
11415
11543
  return this.getTransform().invert().transform(x, y);
11416
11544
  };
11417
11545
 
@@ -11683,7 +11811,7 @@
11683
11811
  var layer = lyr.gui.displayLayer;
11684
11812
  var arcs, filter;
11685
11813
  if (layer.geometry_type == 'point') {
11686
- if (style.type == 'styled') {
11814
+ if (style.type == 'styled' && !style.dotSize) {
11687
11815
  canv.drawPoints(layer.shapes, style);
11688
11816
  } else {
11689
11817
  canv.drawSquareDots(layer.shapes, style);
@@ -11739,11 +11867,15 @@
11739
11867
  var canv = El('canvas').node();
11740
11868
  canv.width = canv.height = 1;
11741
11869
  var ctx = canv.getContext('2d', {willReadFrequently: true});
11870
+ var memos = {};
11742
11871
  return function(col) {
11743
11872
  var pixels;
11873
+ if (!col) col = 'black';
11874
+ if (col in memos) return memos[col];
11744
11875
  ctx.fillStyle = col;
11745
11876
  ctx.fillRect(0, 0, 1, 1);
11746
11877
  pixels = new Uint32Array(ctx.getImageData(0, 0, 1, 1).data.buffer);
11878
+ memos[col] = pixels[0];
11747
11879
  return pixels[0];
11748
11880
  };
11749
11881
  }
@@ -11877,43 +12009,48 @@
11877
12009
  styler = style.styler || null,
11878
12010
  xmax = _canvas.width + size,
11879
12011
  ymax = _canvas.height + size,
11880
- color = style.dotColor || "black",
12012
+ xmin = -size,
12013
+ ymin = -size,
12014
+ color = style.dotColor || 'black',
11881
12015
  shp, x, y, i, j, n, m,
11882
12016
  mx = t.mx,
11883
12017
  my = t.my,
11884
12018
  bx = t.bx,
11885
12019
  by = t.by;
11886
12020
  if (size === 0) return;
11887
- if (size <= 6 && !styler) {
12021
+ if (size <= 6) {
11888
12022
  // optimized drawing of many small same-colored dots
11889
- _self.drawSquareDotsFaster(shapes, color, size, t);
12023
+ drawSquareDotsFaster(shapes, style, size, t);
11890
12024
  return;
11891
12025
  }
12026
+
11892
12027
  _ctx.fillStyle = color;
11893
12028
  for (i=0, n=shapes.length; i<n; i++) {
11894
- if (styler !== null) { // e.g. selected points
11895
- styler(style, i);
11896
- size = getDotSize(style);
11897
- if (style.dotColor != color) {
11898
- color = style.dotColor;
11899
- _ctx.fillStyle = color;
11900
- }
11901
- }
11902
12029
  shp = shapes[i];
11903
12030
  for (j=0, m=shp ? shp.length : 0; j<m; j++) {
11904
12031
  x = shp[j][0] * mx + bx;
11905
12032
  y = shp[j][1] * my + by;
11906
- if (x > -size && y > -size && x < xmax && y < ymax) {
12033
+ if (x > xmin && y > ymin && x < xmax && y < ymax) {
12034
+ if (styler !== null) { // e.g. selected points
12035
+ styler(style, i);
12036
+ // avoid updating dot size for every shape (assuming dotSize is not dynamic)
12037
+ // size = getDotSize(style);
12038
+ if (style.dotColor != color) {
12039
+ color = style.dotColor || style.fillColor;
12040
+ _ctx.fillStyle = color;
12041
+ }
12042
+ }
11907
12043
  drawSquare(x, y, size, _ctx);
11908
12044
  }
11909
12045
  }
11910
12046
  }
11911
12047
  };
11912
12048
 
11913
- _self.drawSquareDotsFaster = function(shapes, color, size, t) {
12049
+ function drawSquareDotsFaster(shapes, style, size, t) {
11914
12050
  var w = _canvas.width,
11915
12051
  h = _canvas.height,
11916
- rgba = _pixelColor(color),
12052
+ rgba = _pixelColor(style.dotColor || style.fillColor),
12053
+ styler = style.styler,
11917
12054
  imageData = _ctx.getImageData(0, 0, w, h),
11918
12055
  pixels = new Uint32Array(imageData.data.buffer),
11919
12056
  shp, x, y, i, j, n, m,
@@ -11921,18 +12058,33 @@
11921
12058
  my = t.my,
11922
12059
  bx = t.bx,
11923
12060
  by = t.by;
11924
- for (i=0, n=shapes.length; i<n; i++) {
11925
- shp = shapes[i];
11926
- for (j=0, m=shp ? shp.length : 0; j<m; j++) {
11927
- x = shp[j][0] * mx + bx;
11928
- y = shp[j][1] * my + by;
11929
- if (x >= 0 && y >= 0 && x <= w && y <= h) {
11930
- drawSquareFaster(x, y, rgba, size, pixels, w, h);
12061
+ if (styler) {
12062
+ for (i=0, n=shapes.length; i<n; i++) {
12063
+ shp = shapes[i];
12064
+ for (j=0, m=shp ? shp.length : 0; j<m; j++) {
12065
+ x = shp[j][0] * mx + bx;
12066
+ y = shp[j][1] * my + by;
12067
+ if (x >= 0 && y >= 0 && x <= w && y <= h) {
12068
+ styler(style, i);
12069
+ rgba = _pixelColor(style.dotColor || style.fillColor);
12070
+ drawSquareFaster(x, y, rgba, size, pixels, w, h);
12071
+ }
12072
+ }
12073
+ }
12074
+ } else {
12075
+ for (i=0, n=shapes.length; i<n; i++) {
12076
+ shp = shapes[i];
12077
+ for (j=0, m=shp ? shp.length : 0; j<m; j++) {
12078
+ x = shp[j][0] * mx + bx;
12079
+ y = shp[j][1] * my + by;
12080
+ if (x >= 0 && y >= 0 && x <= w && y <= h) {
12081
+ drawSquareFaster(x, y, rgba, size, pixels, w, h);
12082
+ }
11931
12083
  }
11932
12084
  }
11933
12085
  }
11934
12086
  _ctx.putImageData(imageData, 0, 0);
11935
- };
12087
+ }
11936
12088
 
11937
12089
  // color: 32-bit integer value containing rgba channel values
11938
12090
  // size: pixels on a side (assume integer)
@@ -12468,6 +12620,14 @@
12468
12620
  openAddFramePopup(gui, box.getDataCoords());
12469
12621
  });
12470
12622
 
12623
+ gui.keyboard.on('keydown', function(e) {
12624
+ if (e.keyName == 'esc') {
12625
+ reset();
12626
+ e.stopPropagation();
12627
+ }
12628
+
12629
+ }, 10);
12630
+
12471
12631
  gui.addMode('box_tool', turnOn, turnOff);
12472
12632
 
12473
12633
  gui.on('interaction_mode_change', function(e) {
@@ -12478,6 +12638,7 @@
12478
12638
  showInstructions();
12479
12639
  }
12480
12640
  } else if (_on) {
12641
+ gui.clearMode();
12481
12642
  turnOff();
12482
12643
  }
12483
12644
  });
@@ -12502,7 +12663,7 @@
12502
12663
  function showInstructions() {
12503
12664
  var isMac = navigator.userAgent.includes('Mac');
12504
12665
  var symbol = isMac ? '⌘' : '^';
12505
- var msg = `Instructions: Shift-drag to draw a rectangle. Drag handles to resize. Shift-drag handles to resize symmetrically.`;
12666
+ var msg = `Instructions: Click to start a rectangle. Drag handles to resize. Press shift key to resize symmetrically.`;
12506
12667
  alert = showPopupAlert(msg, null, { non_blocking: true, max_width: '360px'});
12507
12668
  }
12508
12669
 
@@ -12674,9 +12835,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12674
12835
  map = this,
12675
12836
  _mouse = new MouseArea(el, position),
12676
12837
  _ext = new MapExtent(position),
12677
- _nav = new MapNav(gui, _ext, _mouse),
12678
12838
  _visibleLayers = [], // cached visible map layers
12679
- _hit,
12839
+ _hit, _nav,
12680
12840
  _intersectionLyr, _activeLyr, _overlayLyr,
12681
12841
  _renderer, _dynamicCRS;
12682
12842
 
@@ -12724,8 +12884,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12724
12884
  this.pixelCoordsToLngLatCoords = function(x, y) {
12725
12885
  var crsFrom = this.getDisplayCRS();
12726
12886
  if (!crsFrom) return null; // e.g. table view
12727
- var p1 = internal.toLngLat(_ext.translatePixelCoords(x, y), crsFrom);
12728
- var p2 = internal.toLngLat(_ext.translatePixelCoords(x+1, y+1), crsFrom);
12887
+ var p1 = internal.toLngLat(_ext.pixCoordsToMapCoords(x, y), crsFrom);
12888
+ var p2 = internal.toLngLat(_ext.pixCoordsToMapCoords(x+1, y+1), crsFrom);
12729
12889
  return p1 && p2 && p1[1] <= 90 && p1[1] >= -90 ?
12730
12890
  formatCoordsForDisplay(p1, p2) : null;
12731
12891
  };
@@ -12736,8 +12896,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12736
12896
  if (info && internal.isLatLngCRS(info.crs)) {
12737
12897
  return null; // latlon dataset
12738
12898
  }
12739
- var p1 = translateDisplayPoint(_activeLyr, _ext.translatePixelCoords(x, y));
12740
- var p2 = translateDisplayPoint(_activeLyr, _ext.translatePixelCoords(x+1, y+1));
12899
+ var p1 = translateDisplayPoint(_activeLyr, _ext.pixCoordsToMapCoords(x, y));
12900
+ var p2 = translateDisplayPoint(_activeLyr, _ext.pixCoordsToMapCoords(x+1, y+1));
12741
12901
  return p1 && p2 ? formatCoordsForDisplay(p1, p2) : null;
12742
12902
  };
12743
12903
 
@@ -12824,6 +12984,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12824
12984
  _ext.setFullBounds(calcFullBounds());
12825
12985
  _ext.resize();
12826
12986
  _renderer = new LayerRenderer(gui, el);
12987
+ _nav = new MapNav(gui, _ext, _mouse);
12827
12988
 
12828
12989
  if (opts.inspectorControl) {
12829
12990
  _hit = new HitControl(gui, _ext, _mouse),
@@ -12856,6 +13017,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12856
13017
 
12857
13018
  // Refresh map display in response to data changes, layer selection, etc.
12858
13019
  function onUpdate(e) {
13020
+ var updated = model.getActiveLayer();
12859
13021
  var prevLyr = _activeLyr || null;
12860
13022
  var fullBounds;
12861
13023
  var needReset;
@@ -12870,8 +13032,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12870
13032
 
12871
13033
  // reset simplification after projection (thresholds have changed)
12872
13034
  // TODO: preserve simplification pct (need to record pct before change)
12873
- if (e.flags.proj && e.dataset.arcs) {
12874
- e.dataset.arcs.setRetainedPct(1);
13035
+ if (e.flags.proj && updated.dataset.arcs) {
13036
+ updated.dataset.arcs.setRetainedPct(1);
12875
13037
  }
12876
13038
  }
12877
13039
 
@@ -12884,9 +13046,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12884
13046
  return;
12885
13047
  }
12886
13048
 
12887
- if (e.layer) {
12888
- _activeLyr = e.layer;
12889
- initActiveLayer(e.layer, e.dataset);
13049
+ if (updated.layer) {
13050
+ _activeLyr = updated.layer;
13051
+ initActiveLayer();
12890
13052
  } else {
12891
13053
  _activeLyr = null;
12892
13054
  }
@@ -13073,9 +13235,10 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13073
13235
  });
13074
13236
  }
13075
13237
 
13076
- function initActiveLayer(lyr, dataset) {
13077
- enhanceLayerForDisplay(lyr, dataset, getDisplayOptions());
13078
- lyr.gui.style = getActiveLayerStyle(lyr.gui.displayLayer, getGlobalStyleOptions());
13238
+ function initActiveLayer() {
13239
+ var active = model.getActiveLayer();
13240
+ enhanceLayerForDisplay(active.layer, active.dataset, getDisplayOptions());
13241
+ active.layer.gui.style = getActiveLayerStyle(active.layer.gui.displayLayer, getGlobalStyleOptions());
13079
13242
  }
13080
13243
 
13081
13244
  function getDrawableFurnitureLayers(layers) {
@@ -13117,7 +13280,14 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13117
13280
  });
13118
13281
  }
13119
13282
 
13120
- function drawLayers(action) {
13283
+ var skipCounts = {
13284
+ nav: 0,
13285
+ hover: 0,
13286
+ redraw: 0
13287
+ };
13288
+ function drawLayers(actionArg) {
13289
+ var action = actionArg || 'redraw';
13290
+ skipCounts[action]++;
13121
13291
  // This seems to smooth out navigation and keep overlay and basemap in sync.
13122
13292
  requestAnimationFrame(function() {drawLayers2(action);});
13123
13293
  }
@@ -13125,8 +13295,12 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13125
13295
  // action:
13126
13296
  // 'nav' map was panned/zoomed -- only map extent has changed
13127
13297
  // 'hover' highlight has changed -- only refresh overlay
13128
- // (default) anything could have changed
13298
+ // 'redraw' anything could have changed
13129
13299
  function drawLayers2(action) {
13300
+ if (--skipCounts[action] > 0) {
13301
+ // skip redraw if more draws are queued up
13302
+ return;
13303
+ }
13130
13304
  // sometimes styles need to be regenerated with 'hover' action (when?)
13131
13305
  var layersMayHaveChanged = action != 'nav'; // !action;
13132
13306
  var fullBounds;
@@ -13458,8 +13632,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13458
13632
  }
13459
13633
 
13460
13634
  function refresh() {
13461
- var crs = gui.map.getDisplayCRS();
13462
- var off = !crs || !enabled() || !map || loading || !activeStyle;
13635
+ var off = !enabled() || !map || loading || !activeStyle ||
13636
+ !gui.map.getDisplayCRS(); // may be slow if getting bounds of many shapes
13463
13637
  fadeBtn.active(!off);
13464
13638
  clearBtn.active(!off);
13465
13639
  if (off) {
@@ -13505,10 +13679,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13505
13679
  gui.contextMenu = new ContextMenu();
13506
13680
  gui.undo = new Undo(gui);
13507
13681
  gui.map = new MshpMap(gui);
13508
- if (opts.saveControl) {
13509
- new SessionSnapshots(gui);
13510
- }
13511
- gui.interaction = new InteractionMode(gui);
13682
+
13512
13683
  gui.state = {};
13513
13684
 
13514
13685
  var msgCount = 0;
@@ -13517,6 +13688,11 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13517
13688
  initModeRules(gui);
13518
13689
  gui.map.init();
13519
13690
 
13691
+ if (opts.saveControl) {
13692
+ new SessionSnapshots(gui);
13693
+ }
13694
+ gui.interaction = new InteractionMode(gui);
13695
+
13520
13696
  gui.showProgressMessage = function(msg) {
13521
13697
  if (!gui.progressMessage) {
13522
13698
  gui.progressMessage = El('div').addClass('progress-message')