mapshaper 0.6.101 → 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.
@@ -6843,6 +6881,7 @@
6843
6881
  });
6844
6882
  }
6845
6883
 
6884
+
6846
6885
  function defaults(dest) {
6847
6886
  for (var i=1, n=arguments.length; i<n; i++) {
6848
6887
  var src = arguments[i] || {};
@@ -7267,6 +7306,10 @@
7267
7306
  }
7268
7307
  }
7269
7308
 
7309
+ function pickOne(arr) {
7310
+ return arr[Math.floor(Math.random() * arr.length)];
7311
+ }
7312
+
7270
7313
  // Sort an array of objects based on one or more properties.
7271
7314
  // Usage: sortOn(array, key1, asc?[, key2, asc? ...])
7272
7315
  //
@@ -8004,7 +8047,7 @@
8004
8047
  function getShapeHitTest(layer, ext, interactionMode, featureFilter) {
8005
8048
  var geoType = layer.gui.displayLayer.geometry_type;
8006
8049
  var test;
8007
- if (geoType == 'point' && layer.gui.style.type == 'styled') {
8050
+ if (geoType == 'point' && layer.gui.style.type == 'styled' && !layer.gui.style.dotSize) {
8008
8051
  test = getGraduatedCircleTest(getRadiusFunction(layer.gui.style));
8009
8052
  } else if (geoType == 'point') {
8010
8053
  test = pointTest;
@@ -8337,7 +8380,7 @@
8337
8380
 
8338
8381
  // e: pointer event
8339
8382
  return function(e) {
8340
- var p = ext.translatePixelCoords(e.x, e.y);
8383
+ var p = ext.pixCoordsToMapCoords(e.x, e.y);
8341
8384
  // update SVG hit test on each test, in case SVG layer has been redrawn
8342
8385
  // and the symbol container has changed
8343
8386
  var svgTest = getSvgHitTest(mapLayer);
@@ -9273,6 +9316,7 @@
9273
9316
  persistent: false,
9274
9317
  draggable: false // does dragging the map draw a box
9275
9318
  }, optsArg),
9319
+ clickToStart = opts.name == 'box-tool', // other versions use shift-drag
9276
9320
  box = new EventDispatcher(),
9277
9321
  stroke = 2,
9278
9322
  activeHandle = null,
@@ -9280,38 +9324,75 @@
9280
9324
  boxCoords = null,
9281
9325
  _on = false,
9282
9326
  _visible = false,
9327
+ clickStartPoint,
9283
9328
  handles;
9284
-
9285
9329
  if (opts.classname) {
9286
9330
  el.addClass(opts.classname);
9287
9331
  }
9288
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
+
9289
9345
  el.hide();
9290
9346
 
9291
9347
  gui.on('map_rendered', function() {
9292
- if (!_on || !_visible) return;
9348
+ if (!visible()) return;
9293
9349
  redraw();
9294
9350
  });
9295
9351
 
9296
- gui.on('shift_drag', function(e) {
9297
- if (!_on) return;
9298
- if (!opts.draggable) return;
9299
- boxCoords = getBoxCoords(e.data);
9300
- redraw();
9301
- box.dispatchEvent('drag');
9302
- });
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
+ }
9303
9395
 
9304
- gui.on('shift_drag_end', function(e) {
9305
- if (!_on || !_visible || !opts.draggable) return;
9306
- boxCoords = getBoxCoords(e.data);
9307
- var pix = coordsToPix(boxCoords, gui.map.getExtent());
9308
- box.dispatchEvent('dragend', {map_bbox: pix});
9309
- if (!opts.persistent) {
9310
- box.hide();
9311
- } else {
9312
- redraw();
9313
- }
9314
- });
9315
9396
 
9316
9397
  if (opts.handles) {
9317
9398
  handles = initHandles(el);
@@ -9324,7 +9405,7 @@
9324
9405
  });
9325
9406
 
9326
9407
  gui.map.getMouse().on('mousemove', function(e) {
9327
- if (!_on || !activeHandle || !prevXY || !boxCoords || !_visible) return;
9408
+ if (!resizing() || !prevXY) return;
9328
9409
  var xy = {x: e.pageX, y: e.pageY};
9329
9410
  var scaling = gui.keyboard.shiftIsPressed() && activeHandle.type == 'corner';
9330
9411
  if (scaling) {
@@ -9350,6 +9431,27 @@
9350
9431
  });
9351
9432
  }
9352
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
+
9353
9455
  function resizeBox(dx, dy, activeHandle) {
9354
9456
  var shifting = activeHandle.type == 'center';
9355
9457
  var centered = gui.keyboard.shiftIsPressed() && activeHandle.type == 'edge';
@@ -9376,7 +9478,7 @@
9376
9478
  }
9377
9479
 
9378
9480
  function rescaleBox(x, y) {
9379
- var p = gui.map.getExtent().translatePixelCoords(x, y);
9481
+ var p = gui.map.getExtent().pixCoordsToMapCoords(x, y);
9380
9482
  var cx = (boxCoords[0] + boxCoords[2])/2;
9381
9483
  var cy = (boxCoords[1] + boxCoords[3])/2;
9382
9484
  var dist2 = geom.distance2D(cx, cy, p[0], p[1]);
@@ -9406,12 +9508,16 @@
9406
9508
 
9407
9509
  box.turnOff = function() {
9408
9510
  _on = false;
9511
+ box.hide();
9409
9512
  };
9410
9513
 
9514
+ // remove the current box (if any)
9411
9515
  box.hide = function() {
9412
9516
  el.hide();
9413
9517
  boxCoords = null;
9414
9518
  _visible = false;
9519
+ clickStartPoint = null;
9520
+ activeHandle = null;
9415
9521
  };
9416
9522
 
9417
9523
  box.show = function(x1, y1, x2, y2) {
@@ -9451,12 +9557,35 @@
9451
9557
  }
9452
9558
 
9453
9559
  // get bbox coords in the display CRS
9454
- function getBoxCoords(e) {
9455
- 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());
9456
9565
  fixBounds(bbox);
9457
9566
  return bbox;
9458
9567
  }
9459
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
+
9460
9589
  function redraw() {
9461
9590
  if (!boxCoords) return;
9462
9591
  var ext = gui.map.getExtent();
@@ -9477,12 +9606,12 @@
9477
9606
  }
9478
9607
 
9479
9608
  function pixToCoords(bbox, ext) {
9480
- var a = ext.translatePixelCoords(bbox[0], bbox[1]);
9481
- 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]);
9482
9611
  return [a[0], b[1], b[0], a[1]];
9483
9612
  }
9484
9613
 
9485
-
9614
+ // enforce [minx, miny, maxx, maxy] order
9486
9615
  function fixBounds(bbox) {
9487
9616
  var tmp;
9488
9617
  if (bbox[0] > bbox[2]) {
@@ -9614,7 +9743,7 @@
9614
9743
  mouse.on('drag', function(e) {
9615
9744
  if (disabled()) return;
9616
9745
  if (shiftDrag) {
9617
- gui.dispatchEvent('shift_drag', getBoxData(e));
9746
+ gui.dispatchEvent('shift_drag', getBoxData(e, dragStartEvt));
9618
9747
  return;
9619
9748
  }
9620
9749
  if (++panCount == 1) {
@@ -9634,7 +9763,7 @@
9634
9763
  if (disabled()) return;
9635
9764
  if (shiftDrag) {
9636
9765
  shiftDrag = false;
9637
- gui.dispatchEvent('shift_drag_end', getBoxData(e));
9766
+ gui.dispatchEvent('shift_drag_end', getBoxData(e, dragStartEvt));
9638
9767
  zoomBox.turnOff();
9639
9768
  } else {
9640
9769
  El('body').removeClass('panning').removeClass('pan');
@@ -9658,10 +9787,10 @@
9658
9787
  return !'selection_tool,box_tool,rectangle_tool,drawing_tool'.includes(mode);
9659
9788
  }
9660
9789
 
9661
- function getBoxData(e) {
9790
+ function getBoxData(e1, e2) {
9662
9791
  return {
9663
- a: [e.x, e.y],
9664
- b: [dragStartEvt.x, dragStartEvt.y]
9792
+ a: [e1.x, e1.y],
9793
+ b: [e2.x, e2.y]
9665
9794
  };
9666
9795
  }
9667
9796
 
@@ -10545,8 +10674,8 @@
10545
10674
  var coords = target.shapes[id];
10546
10675
  deleteFeature(target, id);
10547
10676
  gui.dispatchEvent('feature_delete', {coords, d, target, fid: id});
10548
- gui.dispatchEvent('map-needs-refresh');
10549
10677
  hit.setHitId(-1);
10678
+ gui.dispatchEvent('map-needs-refresh');
10550
10679
  }
10551
10680
 
10552
10681
  hit.on('click', function(e) {
@@ -10601,12 +10730,12 @@
10601
10730
 
10602
10731
  function pixToDataCoords(x, y) {
10603
10732
  var target = hit.getHitTarget();
10604
- return translateDisplayPoint(target, ext.translatePixelCoords(x, y));
10733
+ return translateDisplayPoint(target, ext.pixCoordsToMapCoords(x, y));
10605
10734
  }
10606
10735
 
10607
10736
  function translateDeltaDisplayCoords(dx, dy, ext) {
10608
- var a = ext.translatePixelCoords(0, 0);
10609
- var b = ext.translatePixelCoords(dx, dy);
10737
+ var a = ext.pixCoordsToMapCoords(0, 0);
10738
+ var b = ext.pixCoordsToMapCoords(dx, dy);
10610
10739
  return [b[0] - a[0], b[1] - a[1]];
10611
10740
  }
10612
10741
  }
@@ -10886,7 +11015,7 @@
10886
11015
  e.originalEvent.stopPropagation();
10887
11016
  // dragging a vertex
10888
11017
  var target = hit.getHitTarget();
10889
- var p = ext.translatePixelCoords(e.x, e.y);
11018
+ var p = ext.pixCoordsToMapCoords(e.x, e.y);
10890
11019
  if (gui.keyboard.shiftIsPressed()) {
10891
11020
  internal.snapPointToArcEndpoint(p, hoverVertexInfo.ids, target.gui.displayArcs);
10892
11021
  }
@@ -10904,8 +11033,8 @@
10904
11033
  target.gui.displayArcs.transformPoints(function() {});
10905
11034
  updateVertexCoords(target, hoverVertexInfo.ids);
10906
11035
  gui.dispatchEvent('vertex_dragend', hoverVertexInfo);
10907
- gui.dispatchEvent('map-needs-refresh'); // redraw basemap
10908
11036
  clearHoverVertex();
11037
+ gui.dispatchEvent('map-needs-refresh'); // redraw basemap
10909
11038
  });
10910
11039
 
10911
11040
  // shift + double-click deletes a vertex (when not drawing)
@@ -11019,7 +11148,7 @@
11019
11148
 
11020
11149
  function pixToDataCoords(x, y) {
11021
11150
  var target = hit.getHitTarget();
11022
- return translateDisplayPoint(target, ext.translatePixelCoords(x, y));
11151
+ return translateDisplayPoint(target, ext.pixCoordsToMapCoords(x, y));
11023
11152
  }
11024
11153
 
11025
11154
  // Change the x, y pixel location of thisEvt so that the segment extending
@@ -11141,7 +11270,7 @@
11141
11270
  if (!pathDrawing()) return false;
11142
11271
  var target = hit.getHitTarget();
11143
11272
  var arcId = target.gui.displayArcs.size() - 1;
11144
- var p1 = ext.translatePixelCoords(e.x, e.y); // mouse coords
11273
+ var p1 = ext.pixCoordsToMapCoords(e.x, e.y); // mouse coords
11145
11274
  var p2 = internal.getArcStartCoords(arcId, target.gui.displayArcs); // vertex coords
11146
11275
  var p3 = internal.getArcStartCoords(arcId, target.gui.source.dataset.arcs);
11147
11276
  var dist = geom.distance2D(p1[0], p1[1], p2[0], p2[1]);
@@ -11163,7 +11292,7 @@
11163
11292
  function findDraggableVertices(e) {
11164
11293
  var target = hit.getHitTarget();
11165
11294
  var shp = target.shapes[e.id];
11166
- var p = ext.translatePixelCoords(e.x, e.y);
11295
+ var p = ext.pixCoordsToMapCoords(e.x, e.y);
11167
11296
  var ids = internal.findNearestVertices(p, shp, target.gui.displayArcs);
11168
11297
  var p2 = target.gui.displayArcs.getVertex2(ids[0]);
11169
11298
  var dist = geom.distance2D(p[0], p[1], p2[0], p2[1]);
@@ -11185,7 +11314,7 @@
11185
11314
  var target = hit.getHitTarget();
11186
11315
  //// vertex insertion not supported with simplification
11187
11316
  // if (!target.arcs.isFlat()) return null;
11188
- var p = ext.translatePixelCoords(e.x, e.y);
11317
+ var p = ext.pixCoordsToMapCoords(e.x, e.y);
11189
11318
  var minDist = Infinity;
11190
11319
  var shp = target.shapes[e.id];
11191
11320
  var closest;
@@ -11410,7 +11539,7 @@
11410
11539
  };
11411
11540
 
11412
11541
  // convert pixel coords (0,0 is top left corner of map) to display CRS coords
11413
- this.translatePixelCoords = function(x, y) {
11542
+ this.pixCoordsToMapCoords = function(x, y) {
11414
11543
  return this.getTransform().invert().transform(x, y);
11415
11544
  };
11416
11545
 
@@ -11682,7 +11811,7 @@
11682
11811
  var layer = lyr.gui.displayLayer;
11683
11812
  var arcs, filter;
11684
11813
  if (layer.geometry_type == 'point') {
11685
- if (style.type == 'styled') {
11814
+ if (style.type == 'styled' && !style.dotSize) {
11686
11815
  canv.drawPoints(layer.shapes, style);
11687
11816
  } else {
11688
11817
  canv.drawSquareDots(layer.shapes, style);
@@ -11738,11 +11867,15 @@
11738
11867
  var canv = El('canvas').node();
11739
11868
  canv.width = canv.height = 1;
11740
11869
  var ctx = canv.getContext('2d', {willReadFrequently: true});
11870
+ var memos = {};
11741
11871
  return function(col) {
11742
11872
  var pixels;
11873
+ if (!col) col = 'black';
11874
+ if (col in memos) return memos[col];
11743
11875
  ctx.fillStyle = col;
11744
11876
  ctx.fillRect(0, 0, 1, 1);
11745
11877
  pixels = new Uint32Array(ctx.getImageData(0, 0, 1, 1).data.buffer);
11878
+ memos[col] = pixels[0];
11746
11879
  return pixels[0];
11747
11880
  };
11748
11881
  }
@@ -11876,43 +12009,48 @@
11876
12009
  styler = style.styler || null,
11877
12010
  xmax = _canvas.width + size,
11878
12011
  ymax = _canvas.height + size,
11879
- color = style.dotColor || "black",
12012
+ xmin = -size,
12013
+ ymin = -size,
12014
+ color = style.dotColor || 'black',
11880
12015
  shp, x, y, i, j, n, m,
11881
12016
  mx = t.mx,
11882
12017
  my = t.my,
11883
12018
  bx = t.bx,
11884
12019
  by = t.by;
11885
12020
  if (size === 0) return;
11886
- if (size <= 6 && !styler) {
12021
+ if (size <= 6) {
11887
12022
  // optimized drawing of many small same-colored dots
11888
- _self.drawSquareDotsFaster(shapes, color, size, t);
12023
+ drawSquareDotsFaster(shapes, style, size, t);
11889
12024
  return;
11890
12025
  }
12026
+
11891
12027
  _ctx.fillStyle = color;
11892
12028
  for (i=0, n=shapes.length; i<n; i++) {
11893
- if (styler !== null) { // e.g. selected points
11894
- styler(style, i);
11895
- size = getDotSize(style);
11896
- if (style.dotColor != color) {
11897
- color = style.dotColor;
11898
- _ctx.fillStyle = color;
11899
- }
11900
- }
11901
12029
  shp = shapes[i];
11902
12030
  for (j=0, m=shp ? shp.length : 0; j<m; j++) {
11903
12031
  x = shp[j][0] * mx + bx;
11904
12032
  y = shp[j][1] * my + by;
11905
- 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
+ }
11906
12043
  drawSquare(x, y, size, _ctx);
11907
12044
  }
11908
12045
  }
11909
12046
  }
11910
12047
  };
11911
12048
 
11912
- _self.drawSquareDotsFaster = function(shapes, color, size, t) {
12049
+ function drawSquareDotsFaster(shapes, style, size, t) {
11913
12050
  var w = _canvas.width,
11914
12051
  h = _canvas.height,
11915
- rgba = _pixelColor(color),
12052
+ rgba = _pixelColor(style.dotColor || style.fillColor),
12053
+ styler = style.styler,
11916
12054
  imageData = _ctx.getImageData(0, 0, w, h),
11917
12055
  pixels = new Uint32Array(imageData.data.buffer),
11918
12056
  shp, x, y, i, j, n, m,
@@ -11920,18 +12058,33 @@
11920
12058
  my = t.my,
11921
12059
  bx = t.bx,
11922
12060
  by = t.by;
11923
- for (i=0, n=shapes.length; i<n; i++) {
11924
- shp = shapes[i];
11925
- for (j=0, m=shp ? shp.length : 0; j<m; j++) {
11926
- x = shp[j][0] * mx + bx;
11927
- y = shp[j][1] * my + by;
11928
- if (x >= 0 && y >= 0 && x <= w && y <= h) {
11929
- 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
+ }
11930
12083
  }
11931
12084
  }
11932
12085
  }
11933
12086
  _ctx.putImageData(imageData, 0, 0);
11934
- };
12087
+ }
11935
12088
 
11936
12089
  // color: 32-bit integer value containing rgba channel values
11937
12090
  // size: pixels on a side (assume integer)
@@ -12467,6 +12620,14 @@
12467
12620
  openAddFramePopup(gui, box.getDataCoords());
12468
12621
  });
12469
12622
 
12623
+ gui.keyboard.on('keydown', function(e) {
12624
+ if (e.keyName == 'esc') {
12625
+ reset();
12626
+ e.stopPropagation();
12627
+ }
12628
+
12629
+ }, 10);
12630
+
12470
12631
  gui.addMode('box_tool', turnOn, turnOff);
12471
12632
 
12472
12633
  gui.on('interaction_mode_change', function(e) {
@@ -12477,6 +12638,7 @@
12477
12638
  showInstructions();
12478
12639
  }
12479
12640
  } else if (_on) {
12641
+ gui.clearMode();
12480
12642
  turnOff();
12481
12643
  }
12482
12644
  });
@@ -12501,7 +12663,7 @@
12501
12663
  function showInstructions() {
12502
12664
  var isMac = navigator.userAgent.includes('Mac');
12503
12665
  var symbol = isMac ? '⌘' : '^';
12504
- 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.`;
12505
12667
  alert = showPopupAlert(msg, null, { non_blocking: true, max_width: '360px'});
12506
12668
  }
12507
12669
 
@@ -12673,9 +12835,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12673
12835
  map = this,
12674
12836
  _mouse = new MouseArea(el, position),
12675
12837
  _ext = new MapExtent(position),
12676
- _nav = new MapNav(gui, _ext, _mouse),
12677
12838
  _visibleLayers = [], // cached visible map layers
12678
- _hit,
12839
+ _hit, _nav,
12679
12840
  _intersectionLyr, _activeLyr, _overlayLyr,
12680
12841
  _renderer, _dynamicCRS;
12681
12842
 
@@ -12723,8 +12884,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12723
12884
  this.pixelCoordsToLngLatCoords = function(x, y) {
12724
12885
  var crsFrom = this.getDisplayCRS();
12725
12886
  if (!crsFrom) return null; // e.g. table view
12726
- var p1 = internal.toLngLat(_ext.translatePixelCoords(x, y), crsFrom);
12727
- 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);
12728
12889
  return p1 && p2 && p1[1] <= 90 && p1[1] >= -90 ?
12729
12890
  formatCoordsForDisplay(p1, p2) : null;
12730
12891
  };
@@ -12735,8 +12896,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12735
12896
  if (info && internal.isLatLngCRS(info.crs)) {
12736
12897
  return null; // latlon dataset
12737
12898
  }
12738
- var p1 = translateDisplayPoint(_activeLyr, _ext.translatePixelCoords(x, y));
12739
- 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));
12740
12901
  return p1 && p2 ? formatCoordsForDisplay(p1, p2) : null;
12741
12902
  };
12742
12903
 
@@ -12823,6 +12984,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12823
12984
  _ext.setFullBounds(calcFullBounds());
12824
12985
  _ext.resize();
12825
12986
  _renderer = new LayerRenderer(gui, el);
12987
+ _nav = new MapNav(gui, _ext, _mouse);
12826
12988
 
12827
12989
  if (opts.inspectorControl) {
12828
12990
  _hit = new HitControl(gui, _ext, _mouse),
@@ -12855,6 +13017,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12855
13017
 
12856
13018
  // Refresh map display in response to data changes, layer selection, etc.
12857
13019
  function onUpdate(e) {
13020
+ var updated = model.getActiveLayer();
12858
13021
  var prevLyr = _activeLyr || null;
12859
13022
  var fullBounds;
12860
13023
  var needReset;
@@ -12869,8 +13032,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12869
13032
 
12870
13033
  // reset simplification after projection (thresholds have changed)
12871
13034
  // TODO: preserve simplification pct (need to record pct before change)
12872
- if (e.flags.proj && e.dataset.arcs) {
12873
- e.dataset.arcs.setRetainedPct(1);
13035
+ if (e.flags.proj && updated.dataset.arcs) {
13036
+ updated.dataset.arcs.setRetainedPct(1);
12874
13037
  }
12875
13038
  }
12876
13039
 
@@ -12883,9 +13046,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12883
13046
  return;
12884
13047
  }
12885
13048
 
12886
- if (e.layer) {
12887
- _activeLyr = e.layer;
12888
- initActiveLayer(e.layer, e.dataset);
13049
+ if (updated.layer) {
13050
+ _activeLyr = updated.layer;
13051
+ initActiveLayer();
12889
13052
  } else {
12890
13053
  _activeLyr = null;
12891
13054
  }
@@ -12974,7 +13137,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
12974
13137
  function calcFullBounds() {
12975
13138
  var b;
12976
13139
  if (isPreviewView()) {
12977
- b = new Bounds(getFrameData().bbox);
13140
+ b = new Bounds(getFrameLayerData().bbox);
12978
13141
  } else {
12979
13142
  b = getContentLayerBounds();
12980
13143
  }
@@ -13014,10 +13177,10 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13014
13177
 
13015
13178
  // Preview view: symbols are scaled based on display size of frame layer
13016
13179
  function isPreviewView() {
13017
- return !isTableView() && !!getFrameData();
13180
+ return !isTableView() && !!getFrameLayerData();
13018
13181
  }
13019
13182
 
13020
- function getFrameData() {
13183
+ function getFrameLayerData() {
13021
13184
  var lyr = findFrameLayer();
13022
13185
  return lyr && internal.getFrameLayerData(lyr, lyr.gui.displayArcs) || null;
13023
13186
  }
@@ -13072,9 +13235,10 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13072
13235
  });
13073
13236
  }
13074
13237
 
13075
- function initActiveLayer(lyr, dataset) {
13076
- enhanceLayerForDisplay(lyr, dataset, getDisplayOptions());
13077
- 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());
13078
13242
  }
13079
13243
 
13080
13244
  function getDrawableFurnitureLayers(layers) {
@@ -13116,7 +13280,14 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13116
13280
  });
13117
13281
  }
13118
13282
 
13119
- 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]++;
13120
13291
  // This seems to smooth out navigation and keep overlay and basemap in sync.
13121
13292
  requestAnimationFrame(function() {drawLayers2(action);});
13122
13293
  }
@@ -13124,8 +13295,12 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13124
13295
  // action:
13125
13296
  // 'nav' map was panned/zoomed -- only map extent has changed
13126
13297
  // 'hover' highlight has changed -- only refresh overlay
13127
- // (default) anything could have changed
13298
+ // 'redraw' anything could have changed
13128
13299
  function drawLayers2(action) {
13300
+ if (--skipCounts[action] > 0) {
13301
+ // skip redraw if more draws are queued up
13302
+ return;
13303
+ }
13129
13304
  // sometimes styles need to be regenerated with 'hover' action (when?)
13130
13305
  var layersMayHaveChanged = action != 'nav'; // !action;
13131
13306
  var fullBounds;
@@ -13138,7 +13313,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13138
13313
  }
13139
13314
  if (layersMayHaveChanged) {
13140
13315
  // kludge to handle layer visibility toggling
13141
- _ext.setFrameData(isPreviewView() ? getFrameData() : null);
13316
+ _ext.setFrameData(isPreviewView() ? getFrameLayerData() : null);
13142
13317
  updateFullBounds();
13143
13318
  updateLayerStyles(contentLayers);
13144
13319
  updateLayerStackOrder(model.getLayers());// update menu_order property of all layers
@@ -13457,8 +13632,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13457
13632
  }
13458
13633
 
13459
13634
  function refresh() {
13460
- var crs = gui.map.getDisplayCRS();
13461
- 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
13462
13637
  fadeBtn.active(!off);
13463
13638
  clearBtn.active(!off);
13464
13639
  if (off) {
@@ -13504,10 +13679,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13504
13679
  gui.contextMenu = new ContextMenu();
13505
13680
  gui.undo = new Undo(gui);
13506
13681
  gui.map = new MshpMap(gui);
13507
- if (opts.saveControl) {
13508
- new SessionSnapshots(gui);
13509
- }
13510
- gui.interaction = new InteractionMode(gui);
13682
+
13511
13683
  gui.state = {};
13512
13684
 
13513
13685
  var msgCount = 0;
@@ -13516,6 +13688,11 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
13516
13688
  initModeRules(gui);
13517
13689
  gui.map.init();
13518
13690
 
13691
+ if (opts.saveControl) {
13692
+ new SessionSnapshots(gui);
13693
+ }
13694
+ gui.interaction = new InteractionMode(gui);
13695
+
13519
13696
  gui.showProgressMessage = function(msg) {
13520
13697
  if (!gui.progressMessage) {
13521
13698
  gui.progressMessage = El('div').addClass('progress-message')