mapshaper 0.6.67 → 0.6.68

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.
package/mapshaper.js CHANGED
@@ -14726,6 +14726,9 @@
14726
14726
  bounds: function() {
14727
14727
  return shapeBounds().toArray();
14728
14728
  },
14729
+ bbox: function() {
14730
+ return shapeBounds().toArray();
14731
+ },
14729
14732
  height: function() {
14730
14733
  return shapeBounds().height();
14731
14734
  },
@@ -17323,9 +17326,10 @@
17323
17326
  }
17324
17327
 
17325
17328
  function getGapRemovalMessage(removed, retained, areaLabel) {
17329
+ var tot = removed + retained;
17326
17330
  if (removed > 0 === false) return '';
17327
- return utils.format('Removed %,d / %,d sliver%s using %s',
17328
- removed, removed + retained, utils.pluralSuffix(removed), areaLabel);
17331
+ return utils.format('Removed %,d of %,d sliver%s using %s',
17332
+ removed, tot, utils.pluralSuffix(tot), areaLabel);
17329
17333
  }
17330
17334
 
17331
17335
  function dissolvePolygonGroups2(groups, lyr, dataset, opts) {
@@ -19274,12 +19278,12 @@
19274
19278
  // Kludge for applying fill and other styles to a <text> element
19275
19279
  // (for rendering labels in the GUI with the dot in Canvas, not SVG)
19276
19280
  function renderStyledLabel(rec) {
19277
- var o = renderLabel(rec);
19281
+ var o = renderLabel$1(rec);
19278
19282
  applyStyleAttributes(o, 'label', rec);
19279
19283
  return o;
19280
19284
  }
19281
19285
 
19282
- function renderLabel(rec) {
19286
+ function renderLabel$1(rec) {
19283
19287
  var line = toLabelString(rec['label-text']);
19284
19288
  var morelines, obj;
19285
19289
  // Accepting \n (two chars) as an alternative to the newline character
@@ -19324,7 +19328,7 @@
19324
19328
  var SvgLabels = /*#__PURE__*/Object.freeze({
19325
19329
  __proto__: null,
19326
19330
  renderStyledLabel: renderStyledLabel,
19327
- renderLabel: renderLabel
19331
+ renderLabel: renderLabel$1
19328
19332
  });
19329
19333
 
19330
19334
  // convert data records (properties like svg-symbol, label-text, fill, r) to svg symbols
@@ -19536,13 +19540,64 @@
19536
19540
  };
19537
19541
  }
19538
19542
 
19539
- // TODO: generalize to other kinds of furniture as they are developed
19540
- function getScalebarPosition(opts) {
19541
- var pos = opts.position || 'top-left';
19542
- return {
19543
- valign: pos.includes('top') ? 'top' : 'bottom',
19544
- halign: pos.includes('left') ? 'left' : 'right'
19543
+ function renderScalebar(d, frame) {
19544
+ if (!frame.crs) {
19545
+ message('Unable to render scalebar: unknown CRS.');
19546
+ return [];
19547
+ }
19548
+ if (frame.width > 0 === false) {
19549
+ return [];
19550
+ }
19551
+
19552
+ var opts = getScalebarOpts(d);
19553
+ var metersPerPx = getMapFrameMetersPerPixel(frame);
19554
+ var frameWidthPx = frame.width;
19555
+ var labels = d.label ? d.label.split(',') : null;
19556
+ var label1 = labels && labels[0] || null;
19557
+ var props1 = parseScalebarLabel(label1 || getAutoScalebarLabel(frameWidthPx, metersPerPx, 'mile'));
19558
+ var label2 = opts.style == 'b' && labels && labels[1] || null;
19559
+ var props2, length2;
19560
+
19561
+ if (props1.km > 0 === false) {
19562
+ message('Unusable scalebar label:', label1);
19563
+ return [];
19564
+ }
19565
+
19566
+ var length1 = Math.round(props1.km / metersPerPx * 1000);
19567
+ if (length1 > 0 === false) {
19568
+ stop("Null scalebar length");
19569
+ }
19570
+
19571
+ if (label2) {
19572
+ props2 = parseScalebarLabel(label2);
19573
+ length2 = Math.round(props2.km / metersPerPx * 1000);
19574
+ if (length2 > length1) {
19575
+ stop("First part of a dual-unit scalebar must be longer than the second part.");
19576
+ }
19577
+ }
19578
+
19579
+ var barPos = getScalebarPosition(opts);
19580
+ var labelPos = getLabelPosition(opts);
19581
+ var dx = barPos.xpos == 'right' ? frameWidthPx - length1 - opts.margin : opts.margin;
19582
+ var dy = barPos.ypos == 'bottom' ? frame.height - opts.margin : opts.margin;
19583
+
19584
+ // vshift to adjust for height above or below the baseline
19585
+ var labelHeight = Math.round(opts.label_offset + opts.tic_length + opts.font_size * 0.8 + opts.bar_width / 2);
19586
+ var bareHeight = Math.round(opts.bar_width / 2);
19587
+ var topHeight = labelPos.ypos == 'top' || label2 ? labelHeight : bareHeight;
19588
+ var bottomHeight = labelPos.ypos == 'bottom' || label2 ? labelHeight : bareHeight;
19589
+ if (barPos.ypos == 'top') {
19590
+ dy += topHeight;
19591
+ } else {
19592
+ dy -= bottomHeight;
19593
+ }
19594
+
19595
+ var g = renderAsSvg(length1, label1, length2, label2, opts);
19596
+ g.properties = {
19597
+ transform: 'translate(' + dx + ' ' + dy + ')'
19545
19598
  };
19599
+
19600
+ return [g];
19546
19601
  }
19547
19602
 
19548
19603
  var styleOpts = {
@@ -19569,105 +19624,112 @@
19569
19624
  return Object.assign({}, defaultOpts, styleOpts[style], d, {style: style});
19570
19625
  }
19571
19626
 
19572
- // approximate pixel height of the scalebar
19573
- function getScalebarHeight(opts) {
19574
- return Math.round(opts.bar_width + opts.label_offset +
19575
- opts.tic_length + opts.font_size * 0.8);
19627
+ function renderAsSvg(length, text, length2, text2, opts) {
19628
+ var labelPart = renderLabel(text, length, opts);
19629
+ var zeroLabelPart = renderLabel('0', length, Object.assign({flipx: true}, opts));
19630
+ var barPart = renderBar(length, length2, opts);
19631
+ var parts = opts.style == 'b' ? [zeroLabelPart, labelPart, barPart] : [labelPart, barPart];
19632
+ if (text2) {
19633
+ parts.push(renderLabel(text2, length2, Object.assign({flipy: true}, opts)));
19634
+ parts.push(renderLabel('0', length2, Object.assign({flipx: true, flipy: true}, opts)));
19635
+ }
19636
+ return {
19637
+ tag: 'g',
19638
+ children: parts
19639
+ };
19576
19640
  }
19577
19641
 
19578
- function renderAsSvg(length, text, opts) {
19579
- // label part
19580
- var xOff = opts.style == 'b' ? Math.round(opts.font_size / 4) : 0;
19581
- var alignLeft = opts.style == 'a' && opts.position.includes('left');
19582
- var anchorX = alignLeft ? -xOff : length + xOff;
19583
- var anchorY = opts.bar_width + + opts.tic_length + opts.label_offset;
19584
- if (opts.label_position == 'top') {
19585
- anchorY = -opts.label_offset - opts.tic_length;
19586
- }
19642
+ // TODO: generalize to other kinds of furniture as they are developed
19643
+ function getScalebarPosition(opts) {
19644
+ var pos = opts.position || 'top-left';
19645
+ return {
19646
+ ypos: pos.includes('top') ? 'top' : 'bottom',
19647
+ xpos: pos.includes('left') ? 'left' : 'right'
19648
+ };
19649
+ }
19650
+
19651
+ function renderLabel(text, length, opts) {
19652
+ var labelPos = getLabelPosition(opts);
19653
+ var anchorX = length * labelPos.kx + labelPos.dx;
19654
+ var bottomLabelY = opts.bar_width + opts.tic_length + opts.label_offset;
19655
+ var topLabelY = -opts.label_offset - opts.tic_length;
19656
+ var anchorY = labelPos.ypos == 'top' ? topLabelY : bottomLabelY;
19587
19657
  var labelOpts = {
19588
19658
  'label-text': text,
19589
19659
  'font-size': opts.font_size,
19590
- 'text-anchor': alignLeft ? 'start': 'end',
19591
- 'dominant-baseline': opts.label_position == 'top' ? 'auto' : 'hanging'
19660
+ 'text-anchor': labelPos.anchor,
19661
+ 'dominant-baseline': labelPos.ypos == 'top' ? 'auto' : 'hanging'
19592
19662
  //// 'dominant-baseline': labelPos == 'top' ? 'text-after-edge' : 'text-before-edge'
19593
19663
  // 'text-after-edge' is buggy in Safari and unsupported by Illustrator,
19594
19664
  // so I'm using 'hanging' and 'auto', which seem to be well supported.
19595
19665
  // downside: requires a kludgy multiplier to calculate scalebar height (see above)
19596
19666
  };
19597
- var labelPart = symbolRenderers.label(labelOpts, anchorX, anchorY);
19598
- var zeroOpts = Object.assign({}, labelOpts, {'label-text': '0', 'text-anchor': 'start'});
19599
- var zeroLabel = symbolRenderers.label(zeroOpts, -xOff, anchorY);
19667
+ return symbolRenderers.label(labelOpts, anchorX, anchorY);
19668
+ }
19600
19669
 
19601
- // bar part
19602
- var y = 0;
19603
- var y2 = opts.tic_length + opts.bar_width / 2;
19604
- var coords;
19605
- if (opts.label_position == "top") {
19606
- y2 = -y2;
19607
- }
19608
- if (opts.tic_length > 0) {
19609
- coords = [[0, y2], [0, y], [length, y], [length, y2]];
19670
+ function getLabelPosition(opts) {
19671
+ var pos = opts.label_position;
19672
+ var ypos = pos.includes('bottom') && 'bottom' || 'top';
19673
+ var dx = 0;
19674
+ var xpos;
19675
+ if (opts.style == 'a') {
19676
+ xpos = pos.includes('center') && 'center' || pos.includes('right') && 'right' || 'left';
19610
19677
  } else {
19611
- coords = [[0, y], [length, y]];
19678
+ xpos = 'right'; // style b
19679
+ }
19680
+ if (opts.flipx) {
19681
+ xpos = xpos == 'left' && 'right' || xpos == 'right' && 'left' || xpos;
19682
+ }
19683
+ if (opts.flipy) {
19684
+ ypos = ypos == 'top' && 'bottom' || ypos == 'bottom' && 'top' || ypos;
19685
+ }
19686
+ if (opts.style == 'b') {
19687
+ dx = xpos == 'left' && -opts.font_size / 4 || xpos == 'right' && opts.font_size / 4 || 0;
19612
19688
  }
19613
- var barPart = importLineString(coords);
19614
- Object.assign(barPart.properties, {
19615
- stroke: 'black',
19616
- fill: 'none',
19617
- 'stroke-width': opts.bar_width,
19618
- 'stroke-linecap': 'butt',
19619
- 'stroke-linejoin': 'miter'
19620
- });
19621
- var parts = opts.style == 'b' ? [zeroLabel, labelPart, barPart] : [labelPart, barPart];
19622
19689
  return {
19623
- tag: 'g',
19624
- children: parts
19690
+ xpos,
19691
+ ypos,
19692
+ dx,
19693
+ kx: xpos == 'right' && 1 || xpos == 'center' && 0.5 || 0,
19694
+ anchor: xpos == 'center' && 'middle' || xpos == 'left' && 'start' || 'end'
19625
19695
  };
19626
19696
  }
19627
19697
 
19628
- function renderScalebar(d, frame) {
19629
- if (!frame.crs) {
19630
- message('Unable to render scalebar: unknown CRS.');
19631
- return [];
19632
- }
19633
- if (frame.width > 0 === false) {
19634
- return [];
19635
- }
19636
-
19637
- var opts = getScalebarOpts(d);
19638
- var metersPerPx = getMapFrameMetersPerPixel(frame);
19639
- var frameWidthPx = frame.width;
19640
- var unit = d.label ? parseScalebarUnits(d.label) : 'mile';
19641
- var number = d.label ? parseScalebarNumber(d.label) : null;
19642
- var label = number && unit ? d.label : getAutoScalebarLabel(frameWidthPx, metersPerPx, unit);
19643
-
19644
- var scalebarKm = parseScalebarLabelToKm(label);
19645
- if (scalebarKm > 0 === false) {
19646
- message('Unusable scalebar label:', label);
19647
- return [];
19698
+ // length1: length of main bar
19699
+ // length2: length of optional second distance (assumes that length2 <= length1)
19700
+ function getStyleBCoords(length1, length2, opts) {
19701
+ var coords = [];
19702
+ var labelPos = getLabelPosition(opts);
19703
+ var y = opts.tic_length + opts.bar_width / 2;
19704
+ if (labelPos.ypos == "top") {
19705
+ y = -y;
19648
19706
  }
19649
-
19650
- var width = Math.round(scalebarKm / metersPerPx * 1000);
19651
- if (width > 0 === false) {
19652
- stop("Null scalebar length");
19707
+ coords.push([[0, y], [0, 0], [length1, 0], [length1, y]]);
19708
+ if (length2 > 0) {
19709
+ coords.push([[0, 0], [0, -y]]);
19710
+ coords.push([[length2, 0], [length2, -y]]);
19653
19711
  }
19712
+ return coords;
19713
+ }
19654
19714
 
19655
- var pos = getScalebarPosition(opts);
19656
- var height = getScalebarHeight(opts);
19657
- var dx = pos.halign == 'right' ? frameWidthPx - width - opts.margin : opts.margin;
19658
- var dy = pos.valign == 'bottom' ? frame.height - height - opts.margin : opts.margin;
19659
- if (opts.label_position == 'top') {
19660
- dy += Math.round(opts.label_offset + opts.tic_length + opts.font_size * 0.8 + opts.bar_width / 2);
19715
+ // length: length of scale bar in px
19716
+ // length2: length of optional dual-units portion of the scalebar
19717
+ function renderBar(length, length2, opts) {
19718
+ var coords;
19719
+ if (opts.style == 'b') {
19720
+ coords = getStyleBCoords(length, length2, opts);
19661
19721
  } else {
19662
- dy += Math.round(opts.bar_width / 2);
19722
+ coords = [[[0, 0], [length, 0]]];
19663
19723
  }
19664
-
19665
- var g = renderAsSvg(width, label, opts);
19666
- g.properties = {
19667
- transform: 'translate(' + dx + ' ' + dy + ')'
19668
- };
19669
-
19670
- return [g];
19724
+ var bar = importMultiLineString(coords);
19725
+ Object.assign(bar.properties, {
19726
+ stroke: 'black',
19727
+ fill: 'none',
19728
+ 'stroke-width': opts.bar_width,
19729
+ 'stroke-linecap': 'butt',
19730
+ 'stroke-linejoin': 'miter'
19731
+ });
19732
+ return bar;
19671
19733
  }
19672
19734
 
19673
19735
  // unit: 'km' || 'mile'
@@ -19701,6 +19763,20 @@
19701
19763
  return units == 'mile' ? value * 1.60934 : value;
19702
19764
  }
19703
19765
 
19766
+ function parseScalebarLabel(label) {
19767
+ var num = label ? parseScalebarNumber(label) : null;
19768
+ var units = label ? parseScalebarUnits(label) : 'mile';
19769
+ var km = NaN;
19770
+ if (units && num) {
19771
+ km = units == 'mile' ? num * 1.60934 : num;
19772
+ }
19773
+ return {
19774
+ number: num,
19775
+ units: units,
19776
+ km: km
19777
+ };
19778
+ }
19779
+
19704
19780
  function parseScalebarUnits(str) {
19705
19781
  var isMiles = /miles?$/.test(str.toLowerCase());
19706
19782
  var isKm = /(k\.m\.|km|kilometers?|kilometres?)$/.test(str.toLowerCase());
@@ -25821,7 +25897,11 @@ ${svg}
25821
25897
  describe: 'e.g. bottom-right (default is top-left)'
25822
25898
  })
25823
25899
  .option('label-position', {
25824
- describe: 'top or bottom'
25900
+ describe: 'top, bottom, top-center (style a), etc'
25901
+ })
25902
+ .option('dual-units', {
25903
+ // describe: 'display both metric and imperial units',
25904
+ type: 'flag'
25825
25905
  })
25826
25906
  .option('margin', {
25827
25907
  describe: 'offset in pixels from edge of map',
@@ -45224,7 +45304,7 @@ ${svg}
45224
45304
  });
45225
45305
  }
45226
45306
 
45227
- var version = "0.6.67";
45307
+ var version = "0.6.68";
45228
45308
 
45229
45309
  // Parse command line args into commands and run them
45230
45310
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.6.67",
3
+ "version": "0.6.68",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
@@ -7820,6 +7820,7 @@
7820
7820
  function onMouseUp(e) {
7821
7821
  var evt = procMouseEvent(e),
7822
7822
  elapsed, dx, dy;
7823
+ _self.dispatchEvent('mouseup', evt);
7823
7824
  if (_dragging) {
7824
7825
  stopDragging(evt);
7825
7826
  }
@@ -7847,6 +7848,7 @@
7847
7848
 
7848
7849
  function onMouseMove(e) {
7849
7850
  var evt = procMouseEvent(e);
7851
+ _self.dispatchEvent('mousemove', evt);
7850
7852
  if (!_dragging && _downEvt && _downEvt.hover) {
7851
7853
  _dragging = true;
7852
7854
  _self.dispatchEvent('dragstart', evt);
@@ -7969,37 +7971,21 @@
7969
7971
  });
7970
7972
  });
7971
7973
 
7972
- document.addEventListener('mousemove', function(e) {
7974
+ gui.map.getMouse().on('mousemove', function(e) {
7973
7975
  if (!_on || !activeHandle || !prevXY || !boxCoords || !_visible) return;
7974
7976
  var xy = {x: e.pageX, y: e.pageY};
7975
- var scale = gui.map.getExtent().getPixelSize();
7976
- var dx = (xy.x - prevXY.x) * scale;
7977
- var dy = -(xy.y - prevXY.y) * scale;
7978
- var shifting = activeHandle.col == 'center' && activeHandle.row == 'center';
7979
- var centered = gui.keyboard.shiftIsPressed() && !shifting;
7980
- if (activeHandle.col == 'left' || shifting) {
7981
- boxCoords[0] += dx;
7982
- if (centered) boxCoords[2] -= dx;
7983
- }
7984
- if (activeHandle.col == 'right' || shifting) {
7985
- boxCoords[2] += dx;
7986
- if (centered) boxCoords[0] -= dx;
7987
- }
7988
- if (activeHandle.row == 'top' || shifting) {
7989
- boxCoords[3] += dy;
7990
- if (centered) boxCoords[1] -= dy;
7991
- }
7992
- if (activeHandle.row == 'bottom' || shifting) {
7993
- boxCoords[1] += dy;
7994
- if (centered) boxCoords[3] -= dy;
7977
+ var scaling = gui.keyboard.shiftIsPressed() && activeHandle.type == 'corner';
7978
+ if (scaling) {
7979
+ rescaleBox(e.x, e.y);
7980
+ } else {
7981
+ resizeBox(xy.x - prevXY.x, xy.y - prevXY.y, activeHandle);
7995
7982
  }
7996
-
7997
7983
  prevXY = xy;
7998
7984
  redraw();
7999
7985
  box.dispatchEvent('handle_drag');
8000
7986
  });
8001
7987
 
8002
- document.addEventListener('mouseup', function() {
7988
+ gui.map.getMouse().on('mouseup', function(e) {
8003
7989
  if (activeHandle && _on) {
8004
7990
  activeHandle.el.css('background', null);
8005
7991
  activeHandle = null;
@@ -8012,6 +7998,43 @@
8012
7998
  });
8013
7999
  }
8014
8000
 
8001
+ function resizeBox(dx, dy, activeHandle) {
8002
+ var shifting = activeHandle.type == 'center';
8003
+ var centered = gui.keyboard.shiftIsPressed() && activeHandle.type == 'edge';
8004
+ var scale = gui.map.getExtent().getPixelSize();
8005
+ dx *= scale;
8006
+ dy *= -scale;
8007
+
8008
+ if (activeHandle.col == 'left' || shifting) {
8009
+ boxCoords[0] += dx;
8010
+ if (centered) boxCoords[2] -= dx;
8011
+ }
8012
+ if (activeHandle.col == 'right' || shifting) {
8013
+ boxCoords[2] += dx;
8014
+ if (centered) boxCoords[0] -= dx;
8015
+ }
8016
+ if (activeHandle.row == 'top' || shifting) {
8017
+ boxCoords[3] += dy;
8018
+ if (centered) boxCoords[1] -= dy;
8019
+ }
8020
+ if (activeHandle.row == 'bottom' || shifting) {
8021
+ boxCoords[1] += dy;
8022
+ if (centered) boxCoords[3] -= dy;
8023
+ }
8024
+ }
8025
+
8026
+ function rescaleBox(x, y) {
8027
+ var p = gui.map.getExtent().translatePixelCoords(x, y);
8028
+ var cx = (boxCoords[0] + boxCoords[2])/2;
8029
+ var cy = (boxCoords[1] + boxCoords[3])/2;
8030
+ var dist2 = geom.distance2D(cx, cy, p[0], p[1]);
8031
+ var dist = geom.distance2D(cx, cy, boxCoords[0], boxCoords[1]);
8032
+ var k = dist2 / dist;
8033
+ var dx = (boxCoords[2] - cx) * k;
8034
+ var dy = (boxCoords[3] - cy) * k;
8035
+ boxCoords = [cx - dx, cy - dy, cx + dx, cy + dy];
8036
+ }
8037
+
8015
8038
  box.setDataCoords = function(bbox) {
8016
8039
  boxCoords = bbox;
8017
8040
  redraw();
@@ -8107,8 +8130,10 @@
8107
8130
  // if (i == 4) continue; // skip middle handle
8108
8131
  var c = Math.floor(i / 3);
8109
8132
  var r = i % 3;
8133
+ var type = i == 4 && 'center' || c != 1 && r != 1 && 'corner' || 'edge';
8110
8134
  handles.push({
8111
8135
  el: El('div').addClass('handle').appendTo(el),
8136
+ type: type,
8112
8137
  col: c == 0 && 'left' || c == 1 && 'center' || 'right',
8113
8138
  row: r == 0 && 'top' || r == 1 && 'center' || 'bottom'
8114
8139
  });
@@ -9347,50 +9372,22 @@
9347
9372
  dotColor: violet,
9348
9373
  dotSize: 3
9349
9374
  }, polyline: {
9350
- strokeColor: black, // violet,
9375
+ strokeColor: violet, // black, // violet,
9351
9376
  strokeWidth: 3
9352
9377
  }
9353
9378
  };
9354
9379
 
9355
- function getIntersectionStyle(lyr) {
9380
+ function getIntersectionStyle(lyr, opts) {
9356
9381
  return getDefaultStyle(lyr, intersectionStyle);
9357
9382
  }
9358
9383
 
9359
- function getDefaultStyle(lyr, baseStyle) {
9360
- var style = utils$1.extend({}, baseStyle);
9361
- // reduce the dot size of large point layers
9362
- if (lyr.geometry_type == 'point' && style.dotSize > 0) {
9363
- style.dotSize *= getDotScale$1(lyr);
9364
- }
9365
- return style;
9366
- }
9367
-
9368
- function getDotScale$1(lyr) {
9369
- var topTier = 50000;
9370
- var n = countPoints(lyr.shapes, topTier + 2); // short-circuit point counting above top threshold
9371
- var k = n < 200 && 4 || n < 2500 && 3 || n < 10000 && 2 || 1;
9372
- // var k = n >= topTier && 0.25 || n > 10000 && 0.45 || n > 2500 && 0.65 || n > 200 && 0.85 || 1;
9373
- return k;
9374
- }
9375
-
9376
- function countPoints(shapes, max) {
9377
- var count = 0;
9378
- var i, n, shp;
9379
- max = max || Infinity;
9380
- for (i=0, n=shapes.length; i<n && count<=max; i++) {
9381
- shp = shapes[i];
9382
- count += shp ? shp.length : 0;
9383
- }
9384
- return count;
9385
- }
9386
-
9387
9384
  // Style for unselected layers with visibility turned on
9388
9385
  // (styled layers have)
9389
- function getReferenceStyle(lyr) {
9386
+ function getReferenceLayerStyle(lyr, opts) {
9390
9387
  var style;
9391
- if (layerHasCanvasDisplayStyle(lyr)) {
9388
+ if (layerHasCanvasDisplayStyle(lyr) && !opts.outlineMode) {
9392
9389
  style = getCanvasDisplayStyle(lyr);
9393
- } else if (internal.layerHasLabels(lyr)) {
9390
+ } else if (internal.layerHasLabels(lyr) && !opts.outlineMode) {
9394
9391
  style = {dotSize: 0}; // no reference dots if labels are visible
9395
9392
  } else {
9396
9393
  style = getDefaultStyle(lyr, referenceStyle);
@@ -9398,13 +9395,13 @@
9398
9395
  return style;
9399
9396
  }
9400
9397
 
9401
- function getActiveStyle(lyr, darkMode) {
9398
+ function getActiveLayerStyle(lyr, opts) {
9402
9399
  var style;
9403
- if (layerHasCanvasDisplayStyle(lyr)) {
9400
+ if (layerHasCanvasDisplayStyle(lyr) && !opts.outlineMode) {
9404
9401
  style = getCanvasDisplayStyle(lyr);
9405
- } else if (internal.layerHasLabels(lyr)) {
9402
+ } else if (internal.layerHasLabels(lyr) && !opts.outlineMode) {
9406
9403
  style = getDefaultStyle(lyr, activeStyleForLabels);
9407
- } else if (darkMode) {
9404
+ } else if (opts.darkMode) {
9408
9405
  style = getDefaultStyle(lyr, activeStyleDarkMode);
9409
9406
  } else {
9410
9407
  style = getDefaultStyle(lyr, activeStyle);
@@ -9412,21 +9409,6 @@
9412
9409
  return style;
9413
9410
  }
9414
9411
 
9415
- // style for vertex edit mode
9416
- function getVertexStyle(lyr, o) {
9417
- return {
9418
- ids: o.ids,
9419
- overlay: true,
9420
- strokeColor: black,
9421
- strokeWidth: 1.5,
9422
- vertices: true,
9423
- vertex_overlay_color: violet,
9424
- vertex_overlay: o.hit_coordinates || null,
9425
- selected_points: o.selected_points || null,
9426
- fillColor: null
9427
- };
9428
- }
9429
-
9430
9412
  // Returns a display style for the overlay layer.
9431
9413
  // The overlay layer renders several kinds of feature, each of which is displayed
9432
9414
  // with a different style.
@@ -9435,47 +9417,103 @@
9435
9417
  // * selected shapes
9436
9418
  // * pinned shapes
9437
9419
  //
9438
- function getOverlayStyle(lyr, o) {
9439
- if (o.mode == 'vertices') {
9440
- return getVertexStyle(lyr, o);
9420
+ function getOverlayStyle(baseLyr, o, opts) {
9421
+ if (opts.interactionMode == 'vertices') {
9422
+ return getVertexStyle(baseLyr, o);
9441
9423
  }
9442
- var geomType = lyr.geometry_type;
9424
+ var geomType = baseLyr.geometry_type;
9443
9425
  var topId = o.id; // pinned id (if pinned) or hover id
9444
9426
  var topIdx = -1;
9445
9427
  var styler = function(style, i) {
9446
- utils$1.extend(style, i === topIdx ? topStyle: baseStyle);
9428
+ var defaultStyle = i === topIdx ? topStyle : outlineStyle;
9429
+ if (baseStyle.styler) {
9430
+ Object.assign(style, baseStyle);
9431
+ baseStyle.styler(style, i);
9432
+ style.strokeColor = defaultStyle.strokeColor;
9433
+ style.fillColor = defaultStyle.fillColor;
9434
+ } else {
9435
+ Object.assign(style, defaultStyle);
9436
+ }
9447
9437
  };
9448
- var baseStyle = getDefaultStyle(lyr, selectionStyles[geomType]);
9438
+ // var baseStyle = getDefaultStyle(baseLyr, selectionStyles[geomType]);
9439
+ var baseStyle = getActiveLayerStyle(baseLyr, opts);
9440
+ var outlineStyle = getDefaultStyle(baseLyr, selectionStyles[geomType]);
9449
9441
  var topStyle;
9450
9442
  var ids = o.ids.filter(function(i) {
9451
9443
  return i != o.id; // move selected id to the end
9452
9444
  });
9453
9445
  if (o.id > -1) { // pinned or hover style
9454
- topStyle = getSelectedFeatureStyle(lyr, o);
9446
+ topStyle = getSelectedFeatureStyle(baseLyr, o, opts);
9455
9447
  topIdx = ids.length;
9456
9448
  ids.push(o.id); // put the pinned/hover feature last in the render order
9457
9449
  }
9458
9450
  var style = {
9459
- styler: styler,
9460
- ids: ids,
9451
+ baseStyle: baseStyle,
9452
+ styler,
9453
+ ids,
9461
9454
  overlay: true
9462
9455
  };
9463
9456
 
9464
- if (layerHasCanvasDisplayStyle(lyr)) {
9457
+ if (layerHasCanvasDisplayStyle(baseLyr) && !opts.outlineMode) {
9465
9458
  if (geomType == 'point') {
9466
- style.styler = getOverlayPointStyler(getCanvasDisplayStyle(lyr).styler, styler);
9459
+ style.styler = getOverlayPointStyler(getCanvasDisplayStyle(baseLyr).styler, styler);
9467
9460
  }
9468
9461
  style.type = 'styled';
9469
9462
  }
9470
9463
  return ids.length > 0 ? style : null;
9471
9464
  }
9472
9465
 
9473
- function getSelectedFeatureStyle(lyr, o) {
9466
+
9467
+ function getDefaultStyle(lyr, baseStyle) {
9468
+ var style = Object.assign({}, baseStyle);
9469
+ // reduce the dot size of large point layers
9470
+ if (lyr.geometry_type == 'point' && style.dotSize > 0) {
9471
+ style.dotSize *= getDotScale$1(lyr);
9472
+ }
9473
+ return style;
9474
+ }
9475
+
9476
+ function getDotScale$1(lyr) {
9477
+ var topTier = 10000;
9478
+ var n = countPoints(lyr.shapes, topTier); // short-circuit point counting above top threshold
9479
+ var k = n < 200 && 4 || n < 2500 && 3 || n < topTier && 2 || 1;
9480
+ return k;
9481
+ }
9482
+
9483
+ function countPoints(shapes, max) {
9484
+ var count = 0;
9485
+ var i, n, shp;
9486
+ max = max || Infinity;
9487
+ for (i=0, n=shapes.length; i<n && count<max; i++) {
9488
+ shp = shapes[i];
9489
+ count += shp ? shp.length : 0;
9490
+ }
9491
+ return count;
9492
+ }
9493
+
9494
+
9495
+ // style for vertex edit mode
9496
+ function getVertexStyle(lyr, o) {
9497
+ return {
9498
+ ids: o.ids,
9499
+ overlay: true,
9500
+ strokeColor: black,
9501
+ strokeWidth: 1.5,
9502
+ vertices: true,
9503
+ vertex_overlay_color: violet,
9504
+ vertex_overlay: o.hit_coordinates || null,
9505
+ selected_points: o.selected_points || null,
9506
+ fillColor: null
9507
+ };
9508
+ }
9509
+
9510
+
9511
+ function getSelectedFeatureStyle(lyr, o, opts) {
9474
9512
  var isPinned = o.pinned;
9475
9513
  var inSelection = o.ids.indexOf(o.id) > -1;
9476
9514
  var geomType = lyr.geometry_type;
9477
9515
  var style;
9478
- if (isPinned && o.mode == 'rectangles') {
9516
+ if (isPinned && opts.interactionMode == 'rectangles') {
9479
9517
  // kludge for rectangle editing mode
9480
9518
  style = selectionStyles[geomType];
9481
9519
  } else if (isPinned) {
@@ -9903,19 +9941,13 @@
9903
9941
  }
9904
9942
 
9905
9943
  function calcBounds(cx, cy, scale) {
9906
- var full, bounds, w, h;
9907
- if (_frame) {
9908
- full = fillOutFrameBounds(_frame);
9909
- } else {
9910
- full = fillOut(_fullBounds);
9911
- }
9944
+ var full = fillOut(_fullBounds);
9912
9945
  if (_strictBounds) {
9913
9946
  full = fitIn(full, _strictBounds);
9914
9947
  }
9915
- w = full.width() / scale;
9916
- h = full.height() / scale;
9917
- bounds = new Bounds(cx - w/2, cy - h/2, cx + w/2, cy + h/2);
9918
- return bounds;
9948
+ var w = full.width() / scale;
9949
+ var h = full.height() / scale;
9950
+ return new Bounds(cx - w/2, cy - h/2, cx + w/2, cy + h/2);
9919
9951
  }
9920
9952
 
9921
9953
  // Calculate viewport bounds from frame data
@@ -10235,7 +10267,7 @@
10235
10267
  var iter = new internal.ShapeIter(arcs);
10236
10268
  var t = getScaledTransform(_ext);
10237
10269
  var bounds = _ext.getBounds();
10238
- var radius = (style.strokeWidth > 2 ? style.strokeWidth * 0.9 : 2) * GUI.getPixelRatio() * getScaledLineScale(_ext);
10270
+ var radius = (style.strokeWidth > 2 ? style.strokeWidth * 0.9 : 2) * GUI.getPixelRatio() * getScaledLineScale(_ext, style);
10239
10271
  var color = style.strokeColor || 'black';
10240
10272
 
10241
10273
  var i, j, p;
@@ -10269,7 +10301,7 @@
10269
10301
  _self.drawStyledPaths = function(shapes, arcs, style, filter) {
10270
10302
  var styleIndex = {};
10271
10303
  var batchSize = 1500;
10272
- var startPath = getPathStart(_ext, getScaledLineScale(_ext));
10304
+ var startPath = getPathStart(_ext, getScaledLineScale(_ext, style));
10273
10305
  var draw = getShapePencil(arcs, _ext);
10274
10306
  var key, item, shp;
10275
10307
  var styler = style.styler || null;
@@ -10445,7 +10477,7 @@
10445
10477
  return (style.strokeWidth > 0 ? style.strokeColor + '~' + style.strokeWidth +
10446
10478
  '~' + (style.lineDash ? style.lineDash + '~' : '') : '') +
10447
10479
  (style.fillColor || '') +
10448
- // styles with <1 opacity are no longer batch-rendered
10480
+ // styles with <1 opacity are no longer batch-rendered, not relevent to key
10449
10481
  // (style.strokeOpacity >= 0 ? style.strokeOpacity + '~' : '') : '') +
10450
10482
  // (style.fillOpacity ? '~' + style.fillOpacity : '') +
10451
10483
  // (style.opacity < 1 ? '~' + style.opacity : '') +
@@ -10454,9 +10486,17 @@
10454
10486
  return _self;
10455
10487
  }
10456
10488
 
10457
- function getScaledLineScale(ext) {
10489
+ function getScaledLineScale(ext, style) {
10458
10490
  var previewScale = ext.getSymbolScale();
10459
- return previewScale == 1 ? getLineScale(ext) : previewScale;
10491
+ var k = 1;
10492
+ if (previewScale == 1 || style.type != 'styled' || style.baseStyle && style.baseStyle.type != 'styled') {
10493
+ return getLineScale(ext);
10494
+ }
10495
+ if (style.baseStyle?.type == 'styled') {
10496
+ // bump up overlay line width in preview mode
10497
+ k = previewScale < 2 && 2 || previewScale < 5 && 1.5 || previewScale < 10 && 1.25 || 1.1;
10498
+ }
10499
+ return previewScale * k;
10460
10500
  }
10461
10501
 
10462
10502
  // Vary line width according to zoom ratio.
@@ -10626,9 +10666,6 @@
10626
10666
  return function(ctx, style) {
10627
10667
  var strokeWidth;
10628
10668
  ctx.beginPath();
10629
- // if (style.opacity >= 0) {
10630
- // ctx.globalAlpha = style.opacity;
10631
- // }
10632
10669
  if (style.strokeWidth > 0) {
10633
10670
  strokeWidth = style.strokeWidth;
10634
10671
  if (pixRatio > 1) {
@@ -11587,7 +11624,7 @@
11587
11624
  if (lyr == _intersectionLyr) return; // no change
11588
11625
  if (lyr) {
11589
11626
  _intersectionLyr = getDisplayLayer(lyr, dataset, getDisplayOptions());
11590
- _intersectionLyr.style = getIntersectionStyle(_intersectionLyr.layer);
11627
+ _intersectionLyr.style = getIntersectionStyle(_intersectionLyr.layer, getGlobalStyleOptions());
11591
11628
  } else {
11592
11629
  _intersectionLyr = null;
11593
11630
  }
@@ -11623,6 +11660,7 @@
11623
11660
  };
11624
11661
 
11625
11662
  this.getExtent = function() {return _ext;};
11663
+ this.getMouse = function() {return _mouse;};
11626
11664
  this.isActiveLayer = isActiveLayer;
11627
11665
  this.isVisibleLayer = isVisibleLayer;
11628
11666
  this.getActiveLayer = function() { return _activeLyr; };
@@ -11659,6 +11697,15 @@
11659
11697
  updateFullBounds();
11660
11698
  };
11661
11699
 
11700
+ function getGlobalStyleOptions() {
11701
+ var mode = gui.state.interaction_mode;
11702
+ return {
11703
+ darkMode: !!gui.state.dark_basemap,
11704
+ outlineMode: mode == 'vertices',
11705
+ interactionMode: mode
11706
+ };
11707
+ }
11708
+
11662
11709
  // Refresh map display in response to data changes, layer selection, etc.
11663
11710
  function onUpdate(e) {
11664
11711
  var prevLyr = _activeLyr || null;
@@ -11691,7 +11738,6 @@
11691
11738
  }
11692
11739
 
11693
11740
  _activeLyr = getDisplayLayer(e.layer, e.dataset, getDisplayOptions());
11694
- _activeLyr.style = getActiveStyle(_activeLyr.layer, gui.state.dark_basemap);
11695
11741
  _activeLyr.active = true;
11696
11742
 
11697
11743
  if (popupCanStayOpen(e.flags)) {
@@ -11749,7 +11795,7 @@
11749
11795
  }
11750
11796
 
11751
11797
  function updateOverlayLayer(e) {
11752
- var style = getOverlayStyle(_activeLyr.layer, e);
11798
+ var style = getOverlayStyle(_activeLyr.layer, e, getGlobalStyleOptions());
11753
11799
  if (style) {
11754
11800
  _overlayLyr = utils$1.defaults({
11755
11801
  layer: filterLayerByIds(_activeLyr.layer, style.ids),
@@ -11896,9 +11942,10 @@
11896
11942
  function updateLayerStyles(layers) {
11897
11943
  layers.forEach(function(mapLayer, i) {
11898
11944
  if (mapLayer.active) {
11899
- // assume: style is already assigned
11945
+ // regenerating active style everytime, to support style change when
11946
+ // switching between outline and preview modes.
11947
+ mapLayer.style = getActiveLayerStyle(mapLayer.layer, getGlobalStyleOptions());
11900
11948
  if (mapLayer.style.type != 'styled' && layers.length > 1 && mapLayer.style.strokeColors) {
11901
- // if (false) { // always show ghosted arcs
11902
11949
  // kludge to hide ghosted layers when reference layers are present
11903
11950
  // TODO: consider never showing ghosted layers (which appear after
11904
11951
  // commands like dissolve and filter).
@@ -11910,7 +11957,7 @@
11910
11957
  if (mapLayer.layer == _activeLyr.layer) {
11911
11958
  console.error("Error: shared map layer");
11912
11959
  }
11913
- mapLayer.style = getReferenceStyle(mapLayer.layer);
11960
+ mapLayer.style = getReferenceLayerStyle(mapLayer.layer, getGlobalStyleOptions());
11914
11961
  }
11915
11962
  });
11916
11963
  }
package/www/mapshaper.js CHANGED
@@ -14726,6 +14726,9 @@
14726
14726
  bounds: function() {
14727
14727
  return shapeBounds().toArray();
14728
14728
  },
14729
+ bbox: function() {
14730
+ return shapeBounds().toArray();
14731
+ },
14729
14732
  height: function() {
14730
14733
  return shapeBounds().height();
14731
14734
  },
@@ -17323,9 +17326,10 @@
17323
17326
  }
17324
17327
 
17325
17328
  function getGapRemovalMessage(removed, retained, areaLabel) {
17329
+ var tot = removed + retained;
17326
17330
  if (removed > 0 === false) return '';
17327
- return utils.format('Removed %,d / %,d sliver%s using %s',
17328
- removed, removed + retained, utils.pluralSuffix(removed), areaLabel);
17331
+ return utils.format('Removed %,d of %,d sliver%s using %s',
17332
+ removed, tot, utils.pluralSuffix(tot), areaLabel);
17329
17333
  }
17330
17334
 
17331
17335
  function dissolvePolygonGroups2(groups, lyr, dataset, opts) {
@@ -19274,12 +19278,12 @@
19274
19278
  // Kludge for applying fill and other styles to a <text> element
19275
19279
  // (for rendering labels in the GUI with the dot in Canvas, not SVG)
19276
19280
  function renderStyledLabel(rec) {
19277
- var o = renderLabel(rec);
19281
+ var o = renderLabel$1(rec);
19278
19282
  applyStyleAttributes(o, 'label', rec);
19279
19283
  return o;
19280
19284
  }
19281
19285
 
19282
- function renderLabel(rec) {
19286
+ function renderLabel$1(rec) {
19283
19287
  var line = toLabelString(rec['label-text']);
19284
19288
  var morelines, obj;
19285
19289
  // Accepting \n (two chars) as an alternative to the newline character
@@ -19324,7 +19328,7 @@
19324
19328
  var SvgLabels = /*#__PURE__*/Object.freeze({
19325
19329
  __proto__: null,
19326
19330
  renderStyledLabel: renderStyledLabel,
19327
- renderLabel: renderLabel
19331
+ renderLabel: renderLabel$1
19328
19332
  });
19329
19333
 
19330
19334
  // convert data records (properties like svg-symbol, label-text, fill, r) to svg symbols
@@ -19536,13 +19540,64 @@
19536
19540
  };
19537
19541
  }
19538
19542
 
19539
- // TODO: generalize to other kinds of furniture as they are developed
19540
- function getScalebarPosition(opts) {
19541
- var pos = opts.position || 'top-left';
19542
- return {
19543
- valign: pos.includes('top') ? 'top' : 'bottom',
19544
- halign: pos.includes('left') ? 'left' : 'right'
19543
+ function renderScalebar(d, frame) {
19544
+ if (!frame.crs) {
19545
+ message('Unable to render scalebar: unknown CRS.');
19546
+ return [];
19547
+ }
19548
+ if (frame.width > 0 === false) {
19549
+ return [];
19550
+ }
19551
+
19552
+ var opts = getScalebarOpts(d);
19553
+ var metersPerPx = getMapFrameMetersPerPixel(frame);
19554
+ var frameWidthPx = frame.width;
19555
+ var labels = d.label ? d.label.split(',') : null;
19556
+ var label1 = labels && labels[0] || null;
19557
+ var props1 = parseScalebarLabel(label1 || getAutoScalebarLabel(frameWidthPx, metersPerPx, 'mile'));
19558
+ var label2 = opts.style == 'b' && labels && labels[1] || null;
19559
+ var props2, length2;
19560
+
19561
+ if (props1.km > 0 === false) {
19562
+ message('Unusable scalebar label:', label1);
19563
+ return [];
19564
+ }
19565
+
19566
+ var length1 = Math.round(props1.km / metersPerPx * 1000);
19567
+ if (length1 > 0 === false) {
19568
+ stop("Null scalebar length");
19569
+ }
19570
+
19571
+ if (label2) {
19572
+ props2 = parseScalebarLabel(label2);
19573
+ length2 = Math.round(props2.km / metersPerPx * 1000);
19574
+ if (length2 > length1) {
19575
+ stop("First part of a dual-unit scalebar must be longer than the second part.");
19576
+ }
19577
+ }
19578
+
19579
+ var barPos = getScalebarPosition(opts);
19580
+ var labelPos = getLabelPosition(opts);
19581
+ var dx = barPos.xpos == 'right' ? frameWidthPx - length1 - opts.margin : opts.margin;
19582
+ var dy = barPos.ypos == 'bottom' ? frame.height - opts.margin : opts.margin;
19583
+
19584
+ // vshift to adjust for height above or below the baseline
19585
+ var labelHeight = Math.round(opts.label_offset + opts.tic_length + opts.font_size * 0.8 + opts.bar_width / 2);
19586
+ var bareHeight = Math.round(opts.bar_width / 2);
19587
+ var topHeight = labelPos.ypos == 'top' || label2 ? labelHeight : bareHeight;
19588
+ var bottomHeight = labelPos.ypos == 'bottom' || label2 ? labelHeight : bareHeight;
19589
+ if (barPos.ypos == 'top') {
19590
+ dy += topHeight;
19591
+ } else {
19592
+ dy -= bottomHeight;
19593
+ }
19594
+
19595
+ var g = renderAsSvg(length1, label1, length2, label2, opts);
19596
+ g.properties = {
19597
+ transform: 'translate(' + dx + ' ' + dy + ')'
19545
19598
  };
19599
+
19600
+ return [g];
19546
19601
  }
19547
19602
 
19548
19603
  var styleOpts = {
@@ -19569,105 +19624,112 @@
19569
19624
  return Object.assign({}, defaultOpts, styleOpts[style], d, {style: style});
19570
19625
  }
19571
19626
 
19572
- // approximate pixel height of the scalebar
19573
- function getScalebarHeight(opts) {
19574
- return Math.round(opts.bar_width + opts.label_offset +
19575
- opts.tic_length + opts.font_size * 0.8);
19627
+ function renderAsSvg(length, text, length2, text2, opts) {
19628
+ var labelPart = renderLabel(text, length, opts);
19629
+ var zeroLabelPart = renderLabel('0', length, Object.assign({flipx: true}, opts));
19630
+ var barPart = renderBar(length, length2, opts);
19631
+ var parts = opts.style == 'b' ? [zeroLabelPart, labelPart, barPart] : [labelPart, barPart];
19632
+ if (text2) {
19633
+ parts.push(renderLabel(text2, length2, Object.assign({flipy: true}, opts)));
19634
+ parts.push(renderLabel('0', length2, Object.assign({flipx: true, flipy: true}, opts)));
19635
+ }
19636
+ return {
19637
+ tag: 'g',
19638
+ children: parts
19639
+ };
19576
19640
  }
19577
19641
 
19578
- function renderAsSvg(length, text, opts) {
19579
- // label part
19580
- var xOff = opts.style == 'b' ? Math.round(opts.font_size / 4) : 0;
19581
- var alignLeft = opts.style == 'a' && opts.position.includes('left');
19582
- var anchorX = alignLeft ? -xOff : length + xOff;
19583
- var anchorY = opts.bar_width + + opts.tic_length + opts.label_offset;
19584
- if (opts.label_position == 'top') {
19585
- anchorY = -opts.label_offset - opts.tic_length;
19586
- }
19642
+ // TODO: generalize to other kinds of furniture as they are developed
19643
+ function getScalebarPosition(opts) {
19644
+ var pos = opts.position || 'top-left';
19645
+ return {
19646
+ ypos: pos.includes('top') ? 'top' : 'bottom',
19647
+ xpos: pos.includes('left') ? 'left' : 'right'
19648
+ };
19649
+ }
19650
+
19651
+ function renderLabel(text, length, opts) {
19652
+ var labelPos = getLabelPosition(opts);
19653
+ var anchorX = length * labelPos.kx + labelPos.dx;
19654
+ var bottomLabelY = opts.bar_width + opts.tic_length + opts.label_offset;
19655
+ var topLabelY = -opts.label_offset - opts.tic_length;
19656
+ var anchorY = labelPos.ypos == 'top' ? topLabelY : bottomLabelY;
19587
19657
  var labelOpts = {
19588
19658
  'label-text': text,
19589
19659
  'font-size': opts.font_size,
19590
- 'text-anchor': alignLeft ? 'start': 'end',
19591
- 'dominant-baseline': opts.label_position == 'top' ? 'auto' : 'hanging'
19660
+ 'text-anchor': labelPos.anchor,
19661
+ 'dominant-baseline': labelPos.ypos == 'top' ? 'auto' : 'hanging'
19592
19662
  //// 'dominant-baseline': labelPos == 'top' ? 'text-after-edge' : 'text-before-edge'
19593
19663
  // 'text-after-edge' is buggy in Safari and unsupported by Illustrator,
19594
19664
  // so I'm using 'hanging' and 'auto', which seem to be well supported.
19595
19665
  // downside: requires a kludgy multiplier to calculate scalebar height (see above)
19596
19666
  };
19597
- var labelPart = symbolRenderers.label(labelOpts, anchorX, anchorY);
19598
- var zeroOpts = Object.assign({}, labelOpts, {'label-text': '0', 'text-anchor': 'start'});
19599
- var zeroLabel = symbolRenderers.label(zeroOpts, -xOff, anchorY);
19667
+ return symbolRenderers.label(labelOpts, anchorX, anchorY);
19668
+ }
19600
19669
 
19601
- // bar part
19602
- var y = 0;
19603
- var y2 = opts.tic_length + opts.bar_width / 2;
19604
- var coords;
19605
- if (opts.label_position == "top") {
19606
- y2 = -y2;
19607
- }
19608
- if (opts.tic_length > 0) {
19609
- coords = [[0, y2], [0, y], [length, y], [length, y2]];
19670
+ function getLabelPosition(opts) {
19671
+ var pos = opts.label_position;
19672
+ var ypos = pos.includes('bottom') && 'bottom' || 'top';
19673
+ var dx = 0;
19674
+ var xpos;
19675
+ if (opts.style == 'a') {
19676
+ xpos = pos.includes('center') && 'center' || pos.includes('right') && 'right' || 'left';
19610
19677
  } else {
19611
- coords = [[0, y], [length, y]];
19678
+ xpos = 'right'; // style b
19679
+ }
19680
+ if (opts.flipx) {
19681
+ xpos = xpos == 'left' && 'right' || xpos == 'right' && 'left' || xpos;
19682
+ }
19683
+ if (opts.flipy) {
19684
+ ypos = ypos == 'top' && 'bottom' || ypos == 'bottom' && 'top' || ypos;
19685
+ }
19686
+ if (opts.style == 'b') {
19687
+ dx = xpos == 'left' && -opts.font_size / 4 || xpos == 'right' && opts.font_size / 4 || 0;
19612
19688
  }
19613
- var barPart = importLineString(coords);
19614
- Object.assign(barPart.properties, {
19615
- stroke: 'black',
19616
- fill: 'none',
19617
- 'stroke-width': opts.bar_width,
19618
- 'stroke-linecap': 'butt',
19619
- 'stroke-linejoin': 'miter'
19620
- });
19621
- var parts = opts.style == 'b' ? [zeroLabel, labelPart, barPart] : [labelPart, barPart];
19622
19689
  return {
19623
- tag: 'g',
19624
- children: parts
19690
+ xpos,
19691
+ ypos,
19692
+ dx,
19693
+ kx: xpos == 'right' && 1 || xpos == 'center' && 0.5 || 0,
19694
+ anchor: xpos == 'center' && 'middle' || xpos == 'left' && 'start' || 'end'
19625
19695
  };
19626
19696
  }
19627
19697
 
19628
- function renderScalebar(d, frame) {
19629
- if (!frame.crs) {
19630
- message('Unable to render scalebar: unknown CRS.');
19631
- return [];
19632
- }
19633
- if (frame.width > 0 === false) {
19634
- return [];
19635
- }
19636
-
19637
- var opts = getScalebarOpts(d);
19638
- var metersPerPx = getMapFrameMetersPerPixel(frame);
19639
- var frameWidthPx = frame.width;
19640
- var unit = d.label ? parseScalebarUnits(d.label) : 'mile';
19641
- var number = d.label ? parseScalebarNumber(d.label) : null;
19642
- var label = number && unit ? d.label : getAutoScalebarLabel(frameWidthPx, metersPerPx, unit);
19643
-
19644
- var scalebarKm = parseScalebarLabelToKm(label);
19645
- if (scalebarKm > 0 === false) {
19646
- message('Unusable scalebar label:', label);
19647
- return [];
19698
+ // length1: length of main bar
19699
+ // length2: length of optional second distance (assumes that length2 <= length1)
19700
+ function getStyleBCoords(length1, length2, opts) {
19701
+ var coords = [];
19702
+ var labelPos = getLabelPosition(opts);
19703
+ var y = opts.tic_length + opts.bar_width / 2;
19704
+ if (labelPos.ypos == "top") {
19705
+ y = -y;
19648
19706
  }
19649
-
19650
- var width = Math.round(scalebarKm / metersPerPx * 1000);
19651
- if (width > 0 === false) {
19652
- stop("Null scalebar length");
19707
+ coords.push([[0, y], [0, 0], [length1, 0], [length1, y]]);
19708
+ if (length2 > 0) {
19709
+ coords.push([[0, 0], [0, -y]]);
19710
+ coords.push([[length2, 0], [length2, -y]]);
19653
19711
  }
19712
+ return coords;
19713
+ }
19654
19714
 
19655
- var pos = getScalebarPosition(opts);
19656
- var height = getScalebarHeight(opts);
19657
- var dx = pos.halign == 'right' ? frameWidthPx - width - opts.margin : opts.margin;
19658
- var dy = pos.valign == 'bottom' ? frame.height - height - opts.margin : opts.margin;
19659
- if (opts.label_position == 'top') {
19660
- dy += Math.round(opts.label_offset + opts.tic_length + opts.font_size * 0.8 + opts.bar_width / 2);
19715
+ // length: length of scale bar in px
19716
+ // length2: length of optional dual-units portion of the scalebar
19717
+ function renderBar(length, length2, opts) {
19718
+ var coords;
19719
+ if (opts.style == 'b') {
19720
+ coords = getStyleBCoords(length, length2, opts);
19661
19721
  } else {
19662
- dy += Math.round(opts.bar_width / 2);
19722
+ coords = [[[0, 0], [length, 0]]];
19663
19723
  }
19664
-
19665
- var g = renderAsSvg(width, label, opts);
19666
- g.properties = {
19667
- transform: 'translate(' + dx + ' ' + dy + ')'
19668
- };
19669
-
19670
- return [g];
19724
+ var bar = importMultiLineString(coords);
19725
+ Object.assign(bar.properties, {
19726
+ stroke: 'black',
19727
+ fill: 'none',
19728
+ 'stroke-width': opts.bar_width,
19729
+ 'stroke-linecap': 'butt',
19730
+ 'stroke-linejoin': 'miter'
19731
+ });
19732
+ return bar;
19671
19733
  }
19672
19734
 
19673
19735
  // unit: 'km' || 'mile'
@@ -19701,6 +19763,20 @@
19701
19763
  return units == 'mile' ? value * 1.60934 : value;
19702
19764
  }
19703
19765
 
19766
+ function parseScalebarLabel(label) {
19767
+ var num = label ? parseScalebarNumber(label) : null;
19768
+ var units = label ? parseScalebarUnits(label) : 'mile';
19769
+ var km = NaN;
19770
+ if (units && num) {
19771
+ km = units == 'mile' ? num * 1.60934 : num;
19772
+ }
19773
+ return {
19774
+ number: num,
19775
+ units: units,
19776
+ km: km
19777
+ };
19778
+ }
19779
+
19704
19780
  function parseScalebarUnits(str) {
19705
19781
  var isMiles = /miles?$/.test(str.toLowerCase());
19706
19782
  var isKm = /(k\.m\.|km|kilometers?|kilometres?)$/.test(str.toLowerCase());
@@ -25821,7 +25897,11 @@ ${svg}
25821
25897
  describe: 'e.g. bottom-right (default is top-left)'
25822
25898
  })
25823
25899
  .option('label-position', {
25824
- describe: 'top or bottom'
25900
+ describe: 'top, bottom, top-center (style a), etc'
25901
+ })
25902
+ .option('dual-units', {
25903
+ // describe: 'display both metric and imperial units',
25904
+ type: 'flag'
25825
25905
  })
25826
25906
  .option('margin', {
25827
25907
  describe: 'offset in pixels from edge of map',
@@ -45224,7 +45304,7 @@ ${svg}
45224
45304
  });
45225
45305
  }
45226
45306
 
45227
- var version = "0.6.67";
45307
+ var version = "0.6.68";
45228
45308
 
45229
45309
  // Parse command line args into commands and run them
45230
45310
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.