mapshaper 0.6.66 → 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/www/mapshaper.js CHANGED
@@ -13397,10 +13397,11 @@
13397
13397
 
13398
13398
  // str: display size in px, pt or in
13399
13399
  // using: 72pt per inch, 1pt per pixel.
13400
- function parseSizeParam(str) {
13401
- var num = parseFloat(str),
13402
- units = /px$/.test(str) && 'px' || /pt$/.test(str) && 'pt' ||
13403
- /in$/.test(str) && 'in' || !isNaN(+str) && 'px' || null;
13400
+ function parseSizeParam(p) {
13401
+ var str = String(p),
13402
+ num = parseFloat(str),
13403
+ units = /px|pix/.test(str) && 'px' || /pt|point/.test(str) && 'pt' ||
13404
+ /in/.test(str) && 'in' || !isNaN(+str) && 'px' || null;
13404
13405
  if (isNaN(num) || !units) {
13405
13406
  stop('Invalid size:', str);
13406
13407
  }
@@ -14725,6 +14726,9 @@
14725
14726
  bounds: function() {
14726
14727
  return shapeBounds().toArray();
14727
14728
  },
14729
+ bbox: function() {
14730
+ return shapeBounds().toArray();
14731
+ },
14728
14732
  height: function() {
14729
14733
  return shapeBounds().height();
14730
14734
  },
@@ -17322,9 +17326,10 @@
17322
17326
  }
17323
17327
 
17324
17328
  function getGapRemovalMessage(removed, retained, areaLabel) {
17329
+ var tot = removed + retained;
17325
17330
  if (removed > 0 === false) return '';
17326
- return utils.format('Removed %,d / %,d sliver%s using %s',
17327
- 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);
17328
17333
  }
17329
17334
 
17330
17335
  function dissolvePolygonGroups2(groups, lyr, dataset, opts) {
@@ -18228,7 +18233,7 @@
18228
18233
  var frameLyr = findFrameLayerInDataset(dataset);
18229
18234
  var frameData;
18230
18235
  if (frameLyr) {
18231
- frameData = getFrameLayerData(frameLyr, dataset);
18236
+ frameData = getFrameLayerData(frameLyr, dataset.arcs);
18232
18237
  } else {
18233
18238
  frameData = calcFrameData(dataset, exportOpts);
18234
18239
  }
@@ -18237,8 +18242,8 @@
18237
18242
  }
18238
18243
 
18239
18244
 
18240
- function getFrameLayerData(lyr, dataset) {
18241
- var bounds = getLayerBounds(lyr, dataset.arcs);
18245
+ function getFrameLayerData(lyr, arcs) {
18246
+ var bounds = getLayerBounds(lyr, arcs);
18242
18247
  var d = lyr.data.getReadOnlyRecordAt(0);
18243
18248
  var w = d.width || 800;
18244
18249
  var h = w * bounds.height() / bounds.width();
@@ -18250,10 +18255,6 @@
18250
18255
  };
18251
18256
  }
18252
18257
 
18253
- // export function getFrameLayerData(lyr) {
18254
- // return lyr.data && lyr.data.getReadOnlyRecordAt(0);
18255
- // }
18256
-
18257
18258
 
18258
18259
  function calcFrameData(dataset, opts) {
18259
18260
  var bounds;
@@ -18287,31 +18288,34 @@
18287
18288
 
18288
18289
 
18289
18290
  // @lyr dataset layer
18290
- function isFrameLayer(lyr, dataset) {
18291
+ function isFrameLayer(lyr, arcs) {
18291
18292
  return getFurnitureLayerType(lyr) == 'frame' &&
18292
- layerIsRectangle(lyr, dataset.arcs);
18293
+ layerIsRectangle(lyr, arcs);
18293
18294
  }
18294
18295
 
18295
18296
  function findFrameLayerInDataset(dataset) {
18296
18297
  return utils.find(dataset.layers, function(lyr) {
18297
- return isFrameLayer(lyr, dataset);
18298
+ return isFrameLayer(lyr, dataset.arcs);
18298
18299
  });
18299
18300
  }
18300
18301
 
18302
+ // TODO: handle multiple frames in catalog
18301
18303
  function findFrameDataset(catalog) {
18302
- var target = utils.find(catalog.getLayers(), function(o) {
18303
- return isFrameLayer(o.layer, o.dataset);
18304
- });
18305
- return target ? target.dataset : null;
18304
+ var target = findFrame(catalog);
18305
+ return target && target.dataset || null;
18306
18306
  }
18307
18307
 
18308
18308
  function findFrameLayer(catalog) {
18309
- var target = utils.find(catalog.getLayers(), function(o) {
18310
- return isFrameLayer(o.layer, o.dataset);
18311
- });
18309
+ var target = findFrame(catalog);
18312
18310
  return target && target.layer || null;
18313
18311
  }
18314
18312
 
18313
+ function findFrame(catalog) {
18314
+ return utils.find(catalog.getLayers(), function(o) {
18315
+ return isFrameLayer(o.layer, o.dataset.arcs);
18316
+ });
18317
+ }
18318
+
18315
18319
  function getFrameLayerBounds(lyr) {
18316
18320
  return new Bounds(getFurnitureLayerData(lyr).bbox);
18317
18321
  }
@@ -18445,10 +18449,13 @@
18445
18449
  var FrameData = /*#__PURE__*/Object.freeze({
18446
18450
  __proto__: null,
18447
18451
  getFrameData: getFrameData,
18452
+ getFrameLayerData: getFrameLayerData,
18448
18453
  getFrameSize: getFrameSize,
18454
+ isFrameLayer: isFrameLayer,
18449
18455
  findFrameLayerInDataset: findFrameLayerInDataset,
18450
18456
  findFrameDataset: findFrameDataset,
18451
18457
  findFrameLayer: findFrameLayer,
18458
+ findFrame: findFrame,
18452
18459
  getFrameLayerBounds: getFrameLayerBounds,
18453
18460
  getMapFrameMetersPerPixel: getMapFrameMetersPerPixel,
18454
18461
  calcOutputSizeInPixels: calcOutputSizeInPixels,
@@ -18888,6 +18895,7 @@
18888
18895
  'font-family': null,
18889
18896
  'font-size': null,
18890
18897
  'font-style': null,
18898
+ 'font-stretch': null,
18891
18899
  'font-weight': null,
18892
18900
  'label-text': null, // leaving this null
18893
18901
  'letter-spacing': 'measure',
@@ -18937,7 +18945,7 @@
18937
18945
  polyline: utils.arrayToIndex(commonProperties.concat('stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit')),
18938
18946
  point: utils.arrayToIndex(commonProperties.concat('fill', 'r')),
18939
18947
  label: utils.arrayToIndex(commonProperties.concat(
18940
- 'fill,font-family,font-size,text-anchor,font-weight,font-style,letter-spacing,dominant-baseline'.split(',')))
18948
+ 'fill,font-family,font-size,text-anchor,font-weight,font-style,font-stretch,letter-spacing,dominant-baseline'.split(',')))
18941
18949
  };
18942
18950
 
18943
18951
  // symType: point, polygon, polyline, label
@@ -19270,12 +19278,12 @@
19270
19278
  // Kludge for applying fill and other styles to a <text> element
19271
19279
  // (for rendering labels in the GUI with the dot in Canvas, not SVG)
19272
19280
  function renderStyledLabel(rec) {
19273
- var o = renderLabel(rec);
19281
+ var o = renderLabel$1(rec);
19274
19282
  applyStyleAttributes(o, 'label', rec);
19275
19283
  return o;
19276
19284
  }
19277
19285
 
19278
- function renderLabel(rec) {
19286
+ function renderLabel$1(rec) {
19279
19287
  var line = toLabelString(rec['label-text']);
19280
19288
  var morelines, obj;
19281
19289
  // Accepting \n (two chars) as an alternative to the newline character
@@ -19320,7 +19328,7 @@
19320
19328
  var SvgLabels = /*#__PURE__*/Object.freeze({
19321
19329
  __proto__: null,
19322
19330
  renderStyledLabel: renderStyledLabel,
19323
- renderLabel: renderLabel
19331
+ renderLabel: renderLabel$1
19324
19332
  });
19325
19333
 
19326
19334
  // convert data records (properties like svg-symbol, label-text, fill, r) to svg symbols
@@ -19532,13 +19540,64 @@
19532
19540
  };
19533
19541
  }
19534
19542
 
19535
- // TODO: generalize to other kinds of furniture as they are developed
19536
- function getScalebarPosition(opts) {
19537
- var pos = opts.position || 'top-left';
19538
- return {
19539
- valign: pos.includes('top') ? 'top' : 'bottom',
19540
- 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 + ')'
19541
19598
  };
19599
+
19600
+ return [g];
19542
19601
  }
19543
19602
 
19544
19603
  var styleOpts = {
@@ -19565,105 +19624,112 @@
19565
19624
  return Object.assign({}, defaultOpts, styleOpts[style], d, {style: style});
19566
19625
  }
19567
19626
 
19568
- // approximate pixel height of the scalebar
19569
- function getScalebarHeight(opts) {
19570
- return Math.round(opts.bar_width + opts.label_offset +
19571
- 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
+ };
19572
19640
  }
19573
19641
 
19574
- function renderAsSvg(length, text, opts) {
19575
- // label part
19576
- var xOff = opts.style == 'b' ? Math.round(opts.font_size / 4) : 0;
19577
- var alignLeft = opts.style == 'a' && opts.position.includes('left');
19578
- var anchorX = alignLeft ? -xOff : length + xOff;
19579
- var anchorY = opts.bar_width + + opts.tic_length + opts.label_offset;
19580
- if (opts.label_position == 'top') {
19581
- anchorY = -opts.label_offset - opts.tic_length;
19582
- }
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;
19583
19657
  var labelOpts = {
19584
19658
  'label-text': text,
19585
19659
  'font-size': opts.font_size,
19586
- 'text-anchor': alignLeft ? 'start': 'end',
19587
- 'dominant-baseline': opts.label_position == 'top' ? 'auto' : 'hanging'
19660
+ 'text-anchor': labelPos.anchor,
19661
+ 'dominant-baseline': labelPos.ypos == 'top' ? 'auto' : 'hanging'
19588
19662
  //// 'dominant-baseline': labelPos == 'top' ? 'text-after-edge' : 'text-before-edge'
19589
19663
  // 'text-after-edge' is buggy in Safari and unsupported by Illustrator,
19590
19664
  // so I'm using 'hanging' and 'auto', which seem to be well supported.
19591
19665
  // downside: requires a kludgy multiplier to calculate scalebar height (see above)
19592
19666
  };
19593
- var labelPart = symbolRenderers.label(labelOpts, anchorX, anchorY);
19594
- var zeroOpts = Object.assign({}, labelOpts, {'label-text': '0', 'text-anchor': 'start'});
19595
- var zeroLabel = symbolRenderers.label(zeroOpts, -xOff, anchorY);
19667
+ return symbolRenderers.label(labelOpts, anchorX, anchorY);
19668
+ }
19596
19669
 
19597
- // bar part
19598
- var y = 0;
19599
- var y2 = opts.tic_length + opts.bar_width / 2;
19600
- var coords;
19601
- if (opts.label_position == "top") {
19602
- y2 = -y2;
19603
- }
19604
- if (opts.tic_length > 0) {
19605
- 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';
19606
19677
  } else {
19607
- 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;
19608
19688
  }
19609
- var barPart = importLineString(coords);
19610
- Object.assign(barPart.properties, {
19611
- stroke: 'black',
19612
- fill: 'none',
19613
- 'stroke-width': opts.bar_width,
19614
- 'stroke-linecap': 'butt',
19615
- 'stroke-linejoin': 'miter'
19616
- });
19617
- var parts = opts.style == 'b' ? [zeroLabel, labelPart, barPart] : [labelPart, barPart];
19618
19689
  return {
19619
- tag: 'g',
19620
- 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'
19621
19695
  };
19622
19696
  }
19623
19697
 
19624
- function renderScalebar(d, frame) {
19625
- if (!frame.crs) {
19626
- message('Unable to render scalebar: unknown CRS.');
19627
- return [];
19628
- }
19629
- if (frame.width > 0 === false) {
19630
- return [];
19631
- }
19632
-
19633
- var opts = getScalebarOpts(d);
19634
- var metersPerPx = getMapFrameMetersPerPixel(frame);
19635
- var frameWidthPx = frame.width;
19636
- var unit = d.label ? parseScalebarUnits(d.label) : 'mile';
19637
- var number = d.label ? parseScalebarNumber(d.label) : null;
19638
- var label = number && unit ? d.label : getAutoScalebarLabel(frameWidthPx, metersPerPx, unit);
19639
-
19640
- var scalebarKm = parseScalebarLabelToKm(label);
19641
- if (scalebarKm > 0 === false) {
19642
- message('Unusable scalebar label:', label);
19643
- 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;
19644
19706
  }
19645
-
19646
- var width = Math.round(scalebarKm / metersPerPx * 1000);
19647
- if (width > 0 === false) {
19648
- 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]]);
19649
19711
  }
19712
+ return coords;
19713
+ }
19650
19714
 
19651
- var pos = getScalebarPosition(opts);
19652
- var height = getScalebarHeight(opts);
19653
- var dx = pos.halign == 'right' ? frameWidthPx - width - opts.margin : opts.margin;
19654
- var dy = pos.valign == 'bottom' ? frame.height - height - opts.margin : opts.margin;
19655
- if (opts.label_position == 'top') {
19656
- 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);
19657
19721
  } else {
19658
- dy += Math.round(opts.bar_width / 2);
19722
+ coords = [[[0, 0], [length, 0]]];
19659
19723
  }
19660
-
19661
- var g = renderAsSvg(width, label, opts);
19662
- g.properties = {
19663
- transform: 'translate(' + dx + ' ' + dy + ')'
19664
- };
19665
-
19666
- 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;
19667
19733
  }
19668
19734
 
19669
19735
  // unit: 'km' || 'mile'
@@ -19697,6 +19763,20 @@
19697
19763
  return units == 'mile' ? value * 1.60934 : value;
19698
19764
  }
19699
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
+
19700
19780
  function parseScalebarUnits(str) {
19701
19781
  var isMiles = /miles?$/.test(str.toLowerCase());
19702
19782
  var isKm = /(k\.m\.|km|kilometers?|kilometres?)$/.test(str.toLowerCase());
@@ -19725,84 +19805,9 @@
19725
19805
  parseScalebarLabelToKm: parseScalebarLabelToKm
19726
19806
  });
19727
19807
 
19728
- cmd.frame = function(catalog, source, opts) {
19729
- var size, bounds, tmp, dataset;
19730
- if (+opts.width > 0 === false && +opts.pixels > 0 === false) {
19731
- stop("Missing a width or area");
19732
- }
19733
- if (opts.width && opts.height) {
19734
- opts = utils.extend({}, opts);
19735
- // Height is a string containing either a number or a
19736
- // comma-sep. pair of numbers (range); here we convert height to
19737
- // an aspect-ratio parameter for the rectangle() function
19738
- opts.aspect_ratio = getAspectRatioArg(opts.width, opts.height);
19739
- // TODO: currently returns max,min aspect ratio, should return in min,max order
19740
- // (rectangle() function should handle max,min argument correctly now anyway)
19741
- }
19742
- tmp = cmd.rectangle(source, opts);
19743
- bounds = getDatasetBounds(tmp);
19744
- if (probablyDecimalDegreeBounds(bounds)) {
19745
- stop('Frames require projected, not geographical coordinates');
19746
- } else if (!getDatasetCRS(tmp)) {
19747
- message('Warning: missing projection data. Assuming coordinates are meters and k (scale factor) is 1');
19748
- }
19749
- size = getFrameSize(bounds, opts);
19750
- if (size[0] > 0 === false) {
19751
- stop('Missing a valid frame width');
19752
- }
19753
- if (size[1] > 0 === false) {
19754
- stop('Missing a valid frame height');
19755
- }
19756
- dataset = {info: {}, layers:[{
19757
- name: opts.name || 'frame',
19758
- data: new DataTable([{
19759
- width: size[0],
19760
- height: size[1],
19761
- bbox: bounds.toArray(),
19762
- type: 'frame'
19763
- }])
19764
- }]};
19765
- catalog.addDataset(dataset);
19766
- };
19767
-
19768
-
19769
- // Convert width and height args to aspect ratio arg for the rectangle() function
19770
- function getAspectRatioArg(widthArg, heightArg) {
19771
- // heightArg is a string containing either a number or a
19772
- // comma-sep. pair of numbers (range);
19773
- return heightArg.split(',').map(function(opt) {
19774
- var height = Number(opt),
19775
- width = Number(widthArg);
19776
- if (!opt) return '';
19777
- return width / height;
19778
- }).reverse().join(',');
19779
- }
19780
-
19781
-
19782
- function renderFrame(d) {
19783
- var lineWidth = 1,
19784
- // inset stroke by half of line width
19785
- off = lineWidth / 2,
19786
- obj = importPolygon([[[off, off], [off, d.height - off],
19787
- [d.width - off, d.height - off],
19788
- [d.width - off, off], [off, off]]]);
19789
- utils.extend(obj.properties, {
19790
- fill: 'none',
19791
- stroke: d.stroke || 'black',
19792
- 'stroke-width': d['stroke-width'] || lineWidth
19793
- });
19794
- return [obj];
19795
- }
19796
-
19797
- var Frame = /*#__PURE__*/Object.freeze({
19798
- __proto__: null,
19799
- getAspectRatioArg: getAspectRatioArg,
19800
- renderFrame: renderFrame
19801
- });
19802
-
19803
19808
  var furnitureRenderers = {
19804
- scalebar: renderScalebar,
19805
- frame: renderFrame
19809
+ scalebar: renderScalebar
19810
+ // frame: renderFrame
19806
19811
  };
19807
19812
 
19808
19813
  // @lyr a layer in a dataset
@@ -25185,15 +25190,15 @@ ${svg}
25185
25190
  .validate(validateProjOpts);
25186
25191
 
25187
25192
  parser.command('rectangle')
25188
- .describe('create a rectangle from a bbox or target layer extent')
25193
+ .describe('create a rectangle from a bbox or target layer')
25189
25194
  .option('bbox', {
25190
25195
  describe: 'rectangle coordinates (xmin,ymin,xmax,ymax)',
25191
25196
  type: 'bbox'
25192
25197
  })
25193
25198
  .option('offset', offsetOpt)
25194
25199
  .option('width', {
25195
- // describe: 'set viewport width in pixels',
25196
- type: 'number'
25200
+ describe: 'set width of map in pixels, use rectangle as frame'
25201
+ // type: 'number' // use string, to allow units (e.g. in, px, pt)
25197
25202
  })
25198
25203
  .option('aspect-ratio', aspectRatioOpt)
25199
25204
  .option('source', {
@@ -25470,7 +25475,10 @@ ${svg}
25470
25475
  .option('font-style', {
25471
25476
  describe: 'CSS font style property of labels (e.g. italic)'
25472
25477
  })
25473
- .option('letter-spacing', {
25478
+ .option('font-stretch', {
25479
+ describe: 'CSS font stretch property of labels (e.g. condensed)'
25480
+ })
25481
+ .option('letter-spacing', {
25474
25482
  describe: 'CSS letter-spacing property of labels'
25475
25483
  })
25476
25484
  .option('line-height', {
@@ -25762,20 +25770,20 @@ ${svg}
25762
25770
  describe: 'frame coordinates (xmin,ymin,xmax,ymax)',
25763
25771
  type: 'bbox'
25764
25772
  })
25765
- .option('offset', offsetOpt)
25773
+ // .option('offset', offsetOpt)
25766
25774
  .option('width', {
25767
- describe: 'pixel width of output (default is 800)'
25768
- })
25769
- .option('height', {
25770
- describe: 'pixel height of output (may be a range)'
25771
- })
25772
- .option('pixels', {
25773
- describe: 'area of output in pixels (alternative to width and height)',
25774
- type: 'number'
25775
- })
25776
- .option('source', {
25777
- describe: 'name of layer to enclose'
25775
+ describe: 'width of output (default is 800px)'
25778
25776
  })
25777
+ // .option('height', {
25778
+ // describe: 'pixel height of output (may be a range)'
25779
+ // })
25780
+ // .option('pixels', {
25781
+ // describe: 'area of output in pixels (alternative to width and height)',
25782
+ // type: 'number'
25783
+ // })
25784
+ // .option('source', {
25785
+ // describe: 'name of layer to enclose'
25786
+ // })
25779
25787
  .option('name', nameOpt);
25780
25788
 
25781
25789
  parser.command('fuzzy-join')
@@ -25889,7 +25897,11 @@ ${svg}
25889
25897
  describe: 'e.g. bottom-right (default is top-left)'
25890
25898
  })
25891
25899
  .option('label-position', {
25892
- 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'
25893
25905
  })
25894
25906
  .option('margin', {
25895
25907
  describe: 'offset in pixels from edge of map',
@@ -37774,6 +37786,79 @@ ${svg}
37774
37786
  return parsed[0];
37775
37787
  }
37776
37788
 
37789
+ cmd.frame = function(catalog, source, opts) {
37790
+ var size, bounds, tmp, dataset;
37791
+ if (+opts.width > 0 === false && +opts.pixels > 0 === false) {
37792
+ stop("Missing a width or area");
37793
+ }
37794
+ if (opts.width && opts.height) {
37795
+ opts = utils.extend({}, opts);
37796
+ // Height is a string containing either a number or a
37797
+ // comma-sep. pair of numbers (range); here we convert height to
37798
+ // an aspect-ratio parameter for the rectangle() function
37799
+ opts.aspect_ratio = getAspectRatioArg(opts.width, opts.height);
37800
+ // TODO: currently returns max,min aspect ratio, should return in min,max order
37801
+ // (rectangle() function should handle max,min argument correctly now anyway)
37802
+ }
37803
+ tmp = cmd.rectangle(source, opts);
37804
+ bounds = getDatasetBounds(tmp);
37805
+ if (probablyDecimalDegreeBounds(bounds)) {
37806
+ stop('Frames require projected, not geographical coordinates');
37807
+ } else if (!getDatasetCRS(tmp)) {
37808
+ message('Warning: missing projection data. Assuming coordinates are meters and k (scale factor) is 1');
37809
+ }
37810
+ size = getFrameSize(bounds, opts);
37811
+ if (size[0] > 0 === false) {
37812
+ stop('Missing a valid frame width');
37813
+ }
37814
+ if (size[1] > 0 === false) {
37815
+ stop('Missing a valid frame height');
37816
+ }
37817
+ dataset = {info: {}, layers:[{
37818
+ name: opts.name || 'frame',
37819
+ data: new DataTable([{
37820
+ width: size[0],
37821
+ height: size[1],
37822
+ bbox: bounds.toArray(),
37823
+ type: 'frame'
37824
+ }])
37825
+ }]};
37826
+ catalog.addDataset(dataset);
37827
+ };
37828
+
37829
+
37830
+ // Convert width and height args to aspect ratio arg for the rectangle() function
37831
+ function getAspectRatioArg(widthArg, heightArg) {
37832
+ // heightArg is a string containing either a number or a
37833
+ // comma-sep. pair of numbers (range);
37834
+ return heightArg.split(',').map(function(opt) {
37835
+ var height = Number(opt),
37836
+ width = Number(widthArg);
37837
+ if (!opt) return '';
37838
+ return width / height;
37839
+ }).reverse().join(',');
37840
+ }
37841
+
37842
+ // export function renderFrame(d) {
37843
+ // var lineWidth = 1,
37844
+ // // inset stroke by half of line width
37845
+ // off = lineWidth / 2,
37846
+ // obj = importPolygon([[[off, off], [off, d.height - off],
37847
+ // [d.width - off, d.height - off],
37848
+ // [d.width - off, off], [off, off]]]);
37849
+ // utils.extend(obj.properties, {
37850
+ // fill: 'none',
37851
+ // stroke: d.stroke || 'black',
37852
+ // 'stroke-width': d['stroke-width'] || lineWidth
37853
+ // });
37854
+ // return [obj];
37855
+ // }
37856
+
37857
+ var Frame = /*#__PURE__*/Object.freeze({
37858
+ __proto__: null,
37859
+ getAspectRatioArg: getAspectRatioArg
37860
+ });
37861
+
37777
37862
  cmd.filterIslands = function(lyr, dataset, optsArg) {
37778
37863
  var opts = utils.extend({sliver_control: 0}, optsArg); // no sliver control
37779
37864
  var arcs = dataset.arcs;
@@ -38540,6 +38625,13 @@ ${svg}
38540
38625
  // Create rectangles around one or more target layers
38541
38626
  //
38542
38627
  cmd.rectangle2 = function(target, opts) {
38628
+ // if target layer is a rectangle and we're applying frame properties,
38629
+ // turn the target into a frame instead of creating a new rectangle
38630
+ if (target.layers.length == 1 && opts.width &&
38631
+ layerIsRectangle(target.layers[0], target.dataset.arcs)) {
38632
+ applyFrameProperties(target.layers[0], opts);
38633
+ return;
38634
+ }
38543
38635
  var datasets = target.layers.map(function(lyr) {
38544
38636
  var dataset = cmd.rectangle({layer: lyr, dataset: target.dataset}, opts);
38545
38637
  setOutputLayerName(dataset.layers[0], lyr, null, opts);
@@ -38569,16 +38661,21 @@ ${svg}
38569
38661
  properties: {},
38570
38662
  geometry: bboxToPolygon(bounds.toArray(), opts)
38571
38663
  };
38572
- if (opts.width > 0) {
38573
- feature.properties.width = opts.width;
38574
- feature.properties.type = 'frame';
38575
- }
38576
38664
  var dataset = importGeoJSON(feature, {});
38665
+ applyFrameProperties(dataset.layers[0], opts);
38577
38666
  dataset.layers[0].name = opts.name || 'rectangle';
38578
38667
  setDatasetCrsInfo(dataset, crsInfo);
38579
38668
  return dataset;
38580
38669
  };
38581
38670
 
38671
+ function applyFrameProperties(lyr, opts) {
38672
+ if (!opts.width) return;
38673
+ if (!lyr.data) initDataTable(lyr);
38674
+ var d = lyr.data.getRecords()[0] || {};
38675
+ d.width = parseSizeParam(opts.width);
38676
+ d.type = 'frame';
38677
+ }
38678
+
38582
38679
  function applyRectangleOptions(bounds, crs, opts) {
38583
38680
  var isGeoBox = probablyDecimalDegreeBounds(bounds);
38584
38681
  if (opts.offset) {
@@ -45207,7 +45304,7 @@ ${svg}
45207
45304
  });
45208
45305
  }
45209
45306
 
45210
- var version = "0.6.66";
45307
+ var version = "0.6.68";
45211
45308
 
45212
45309
  // Parse command line args into commands and run them
45213
45310
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.