mapshaper 0.7.70 → 0.7.71

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
@@ -7549,19 +7549,19 @@
7549
7549
  pointN = [CK.lengthMG, CK.lengthMG * Math.tan(30 * D2R$5)];
7550
7550
  CK.pointA = [CK.lengthMA, 0];
7551
7551
  CK.pointB = lineIntersection(CK.pointM, 30, CK.pointA, 45);
7552
- CK.lengthAG = distance$1(CK.pointA, CK.pointG);
7553
- CK.lengthAB = distance$1(CK.pointA, CK.pointB);
7554
- lengthMB = distance$1(CK.pointM, CK.pointB);
7555
- lengthMN = distance$1(CK.pointM, pointN);
7556
- lengthNG = distance$1(pointN, CK.pointG);
7552
+ CK.lengthAG = distance$2(CK.pointA, CK.pointG);
7553
+ CK.lengthAB = distance$2(CK.pointA, CK.pointB);
7554
+ lengthMB = distance$2(CK.pointM, CK.pointB);
7555
+ lengthMN = distance$2(CK.pointM, pointN);
7556
+ lengthNG = distance$2(pointN, CK.pointG);
7557
7557
  CK.pointD = interpolate(lengthMB, lengthMN, pointN, CK.pointM);
7558
7558
  CK.pointF = [CK.lengthMG, lengthNG - lengthMB];
7559
7559
  CK.pointE = [
7560
7560
  pointN[0] - CK.lengthMA * Math.sin(30 * D2R$5),
7561
7561
  pointN[1] - CK.lengthMA * Math.cos(30 * D2R$5)
7562
7562
  ];
7563
- CK.lengthGF = distance$1(CK.pointG, CK.pointF);
7564
- CK.lengthBD = distance$1(CK.pointB, CK.pointD);
7563
+ CK.lengthGF = distance$2(CK.pointG, CK.pointF);
7564
+ CK.lengthBD = distance$2(CK.pointB, CK.pointD);
7565
7565
  CK.lengthGFE = CK.lengthGF + CK.lengthAB;
7566
7566
  CK.deltaMEq = CK.lengthGFE / 45;
7567
7567
  CK.lengthAP75 = 15 * CK.lengthParallel73to90At0;
@@ -7588,7 +7588,7 @@
7588
7588
  k * CK.pointD[0] - CK.pointD[1])
7589
7589
  );
7590
7590
  CK.pointC[0] = k * CK.pointC[1];
7591
- CK.radius = distance$1(CK.pointC, CK.pointD);
7591
+ CK.radius = distance$2(CK.pointC, CK.pointD);
7592
7592
  }
7593
7593
 
7594
7594
  function ll2mp(lon, lat) {
@@ -7619,12 +7619,12 @@
7619
7619
  );
7620
7620
  var l15;
7621
7621
  if (hit[0]) {
7622
- l15 = lT + distance$1(jointT(m), hit[1]);
7622
+ l15 = lT + distance$2(jointT(m), hit[1]);
7623
7623
  } else {
7624
7624
  hit = circleLineIntersection(
7625
7625
  CK.pointC, CK.radius, jointE(m), jointT(m)
7626
7626
  );
7627
- l15 = lT - distance$1(jointT(m), hit[1]);
7627
+ l15 = lT - distance$2(jointT(m), hit[1]);
7628
7628
  }
7629
7629
  return p <= 15 ? zoneK(m, p, l15) : zoneL(m, p, l15);
7630
7630
  }
@@ -7660,8 +7660,8 @@
7660
7660
  function zoneH(m, p) {
7661
7661
  var p75 = parallel75(45);
7662
7662
  var p73 = parallel73(m).parallel73;
7663
- var lF = distance$1(CK.pointT, CK.pointB);
7664
- var lF75 = distance$1(CK.pointB, p75);
7663
+ var lF = distance$2(CK.pointT, CK.pointB);
7664
+ var lF75 = distance$2(CK.pointB, p75);
7665
7665
  var l = (75 - p) * (lF75 + lF) / 2;
7666
7666
  return l <= lF75 ?
7667
7667
  interpolate(l, lF75, p75, CK.pointB) :
@@ -7688,7 +7688,7 @@
7688
7688
  function zoneJ(m, p) {
7689
7689
  var p75 = parallel75(m);
7690
7690
  var p73a = parallel73(m);
7691
- var lF75 = distance$1(jointF(m), p75);
7691
+ var lF75 = distance$2(jointF(m), p75);
7692
7692
  var l = (75 - p) * (lF75 - p73a.lengthParallel73) / 2;
7693
7693
  return l <= lF75 ?
7694
7694
  interpolate(l, lF75, p75, jointF(m)) :
@@ -7745,11 +7745,11 @@
7745
7745
  }
7746
7746
 
7747
7747
  function lengthTorridSegment(m) {
7748
- return distance$1(jointE(m), jointT(m));
7748
+ return distance$2(jointE(m), jointT(m));
7749
7749
  }
7750
7750
 
7751
7751
  function lengthMiddleSegment(m) {
7752
- return distance$1(jointT(m), jointF(m));
7752
+ return distance$2(jointT(m), jointF(m));
7753
7753
  }
7754
7754
 
7755
7755
  function parallel73(m) {
@@ -7757,15 +7757,15 @@
7757
7757
  var jF = jointF(m);
7758
7758
  if (m <= 30) {
7759
7759
  p73 = radialPoint(CK.pointA, CK.lengthAP73, m);
7760
- lF = distance$1(jF, p73);
7760
+ lF = distance$2(jF, p73);
7761
7761
  } else {
7762
7762
  p73 = lineIntersection(CK.pointT, -60, jF, m);
7763
- lF = distance$1(jF, p73);
7763
+ lF = distance$2(jF, p73);
7764
7764
  if (m > 44) {
7765
7765
  xy = lineIntersection(CK.pointT, -60, jF, 2 * m / 3);
7766
7766
  if (xy[0] > p73[0]) {
7767
7767
  p73 = xy;
7768
- lF = -distance$1(jF, p73);
7768
+ lF = -distance$2(jF, p73);
7769
7769
  }
7770
7770
  }
7771
7771
  }
@@ -7838,7 +7838,7 @@
7838
7838
  return createCahillKeyesTransform(mg, true);
7839
7839
  }
7840
7840
 
7841
- function distance$1(a, b) {
7841
+ function distance$2(a, b) {
7842
7842
  return Math.hypot(a[0] - b[0], a[1] - b[1]);
7843
7843
  }
7844
7844
 
@@ -26365,10 +26365,116 @@
26365
26365
  return String(str).trim().split(splitRxp);
26366
26366
  }
26367
26367
 
26368
+ // The id of the <pattern> element a fill-pattern code is exported as. It is
26369
+ // also how the code is recovered when an exported SVG is imported again (see
26370
+ // decodePatternId()), so a change here has to keep decoding the ids of files
26371
+ // that are already out there.
26368
26372
  function getHashId(str) {
26369
26373
  return ('hash_' + str).replace(/[()# ,_]+/g, '_'); // replace some chars that occur in colors
26370
26374
  }
26371
26375
 
26376
+ // The fill-pattern code a <pattern> id was made from, or null if the id is not
26377
+ // one of ours. The id keeps the tokens of the code but drops the # of a hex
26378
+ // colour and the parentheses and commas of rgb() and hsl(), so those are put
26379
+ // back by where the colours fall in each pattern type's syntax. A hex colour
26380
+ // has to have its # back even where the parser would not need it: without it,
26381
+ // a colour of digits reads as a number.
26382
+ function decodePatternId(id) {
26383
+ var tokens, code;
26384
+ if (!/^hash_./.test(id || '')) return null;
26385
+ tokens = mergeColorFunctions(id.substr(5).split('_').filter(Boolean));
26386
+ tokens = restoreHexColors(tokens);
26387
+ if (!tokens) return null;
26388
+ code = tokens.join(' ');
26389
+ return parsePattern(code) ? code : null;
26390
+ }
26391
+
26392
+ function mergeColorFunctions(tokens) {
26393
+ var out = [];
26394
+ var tok, argc, args;
26395
+ for (var i=0; i<tokens.length; i++) {
26396
+ tok = tokens[i];
26397
+ if (/^(rgb|hsl)a?$/i.test(tok)) {
26398
+ argc = /a$/i.test(tok) ? 4 : 3;
26399
+ args = tokens.slice(i + 1, i + 1 + argc);
26400
+ i += args.length;
26401
+ if (tokens[i + 1] == '/' && i + 2 < tokens.length) {
26402
+ // space-separated form with an alpha: rgb(0 0 0 / 50%)
26403
+ out.push(tok + '(' + args.join(' ') + ' / ' + tokens[i + 2] + ')');
26404
+ i += 2;
26405
+ } else {
26406
+ out.push(tok + '(' + args.join(',') + ')');
26407
+ }
26408
+ } else {
26409
+ out.push(tok);
26410
+ }
26411
+ }
26412
+ return out;
26413
+ }
26414
+
26415
+ // Marks each token as a colour or not, by pattern type, and gives the colours
26416
+ // that look like hex values their # back. Returns null for an unknown type.
26417
+ function restoreHexColors(tokens) {
26418
+ var type = tokens[0] || '';
26419
+ var colorAt;
26420
+ if (type.startsWith('dot') || type.startsWith('square')) {
26421
+ colorAt = getDotColorPositions(tokens);
26422
+ } else if (type.startsWith('dash')) {
26423
+ colorAt = getDashColorPositions(tokens);
26424
+ } else if (type.startsWith('hatch') || isNumberToken(type)) {
26425
+ colorAt = getHatchColorPositions(tokens);
26426
+ } else {
26427
+ return null;
26428
+ }
26429
+ return tokens.map(function(tok, i) {
26430
+ return colorAt[i] && /^[0-9a-f]+$/i.test(tok) && [3, 4, 6, 8].includes(tok.length) ?
26431
+ '#' + tok : tok;
26432
+ });
26433
+ }
26434
+
26435
+ // [hatches] [rotation] width1 color1 [width2 color2 ...]
26436
+ function getHatchColorPositions(tokens) {
26437
+ var start = isNumberToken(tokens[0]) ? 0 : 1;
26438
+ var rest = tokens.length - start;
26439
+ var first = start + (rest % 2 == 1 ? 1 : 0); // odd count: a rotation first
26440
+ return tokens.map(function(tok, i) {
26441
+ return i >= first && (i - first) % 2 == 1;
26442
+ });
26443
+ }
26444
+
26445
+ // dots|squares [rotation] size color1 [color2 ...] spacing background
26446
+ function getDotColorPositions(tokens) {
26447
+ var last = tokens.length - 1;
26448
+ // A rotation is there if the second number is also a size -- but a colour
26449
+ // of digits looks like a number too, so a bare one counts as a colour
26450
+ // unless a unit or a decimal point says otherwise.
26451
+ var rotated = /deg$/.test(tokens[1] || '') ||
26452
+ last >= 5 && isNumberToken(tokens[2]) && !mightBeHexDigits(tokens[2]);
26453
+ var firstColor = rotated ? 3 : 2;
26454
+ return tokens.map(function(tok, i) {
26455
+ return i == last || i >= firstColor && i < last - 1;
26456
+ });
26457
+ }
26458
+
26459
+ // dashes [rotation] dash-length space-length width color1 [color2 ...] spacing background
26460
+ function getDashColorPositions(tokens) {
26461
+ var last = tokens.length - 1;
26462
+ var rotated = /deg$/.test(tokens[1] || '') ||
26463
+ last >= 7 && isNumberToken(tokens[4]) && !mightBeHexDigits(tokens[4]);
26464
+ var firstColor = rotated ? 5 : 4;
26465
+ return tokens.map(function(tok, i) {
26466
+ return i == last || i >= firstColor && i < last - 1;
26467
+ });
26468
+ }
26469
+
26470
+ function isNumberToken(tok) {
26471
+ return /^-?(\d+\.?\d*|\.\d+)(px|deg)?$/.test(tok || '');
26472
+ }
26473
+
26474
+ function mightBeHexDigits(tok) {
26475
+ return /^\d+$/.test(tok) && [3, 4, 6, 8].includes(tok.length);
26476
+ }
26477
+
26372
26478
  // properties: properties object of a path data object (prior to conversion to SVG)
26373
26479
  // defs: array of definition objects
26374
26480
  //
@@ -26382,7 +26488,10 @@
26382
26488
  if (!hash) return;
26383
26489
  defs.push(hash);
26384
26490
  }
26385
- properties.fill = hash.href;
26491
+ // The feature's own fill is kept as the paint's fallback colour, which SVG
26492
+ // draws only if the pattern cannot be. It is also how the fill survives
26493
+ // being exported and imported again.
26494
+ properties.fill = properties.fill ? hash.href + ' ' + properties.fill : hash.href;
26386
26495
  }
26387
26496
 
26388
26497
  function makeSVGPatternFill(str, id) {
@@ -26462,6 +26571,8 @@
26462
26571
  var SvgHatch = /*#__PURE__*/Object.freeze({
26463
26572
  __proto__: null,
26464
26573
  convertFillPattern: convertFillPattern,
26574
+ decodePatternId: decodePatternId,
26575
+ getHashId: getHashId,
26465
26576
  parseDashes: parseDashes,
26466
26577
  parseDots: parseDots,
26467
26578
  parseHatches: parseHatches,
@@ -26759,6 +26870,11 @@
26759
26870
  'callout-color': 'color',
26760
26871
  'callout-width': 'number',
26761
26872
  'callout-opacity': 'number',
26873
+ // arrowheads at the ends of a line -- see svg-line-arrows.mjs. Drawn as
26874
+ // shapes, not written as attributes, so not in propertiesBySymbolType.
26875
+ 'line-start': 'lineend',
26876
+ 'line-end': 'lineend',
26877
+ 'line-end-size': 'number',
26762
26878
  'letter-spacing': 'measure',
26763
26879
  'line-height': 'measure',
26764
26880
  opacity: 'number',
@@ -27070,6 +27186,8 @@
27070
27186
  val = parseCalloutType(strVal);
27071
27187
  } else if (type == 'calloutend') {
27072
27188
  val = parseCalloutEnd(strVal);
27189
+ } else if (type == 'lineend') {
27190
+ val = parseLineEnd(strVal);
27073
27191
  } else if (type == 'pointpair') {
27074
27192
  val = parsePointPair(strVal);
27075
27193
  val = val ? formatPointPair(val) : null;
@@ -27121,6 +27239,12 @@
27121
27239
  return /^(arrow|open-arrow|none)$/.test(end) ? end : null;
27122
27240
  }
27123
27241
 
27242
+ // A line's ends take a callout's markers, and a dot
27243
+ function parseLineEnd(str) {
27244
+ var end = String(str).trim().toLowerCase();
27245
+ return end == 'dot' ? end : parseCalloutEnd(end);
27246
+ }
27247
+
27124
27248
  // "x,y" -> [x, y], or null. Stored as a string so that a pair is set and
27125
27249
  // cleared as one value and reads plainly in a table.
27126
27250
  function parsePointPair(str) {
@@ -27240,6 +27364,7 @@
27240
27364
  parseCalloutEnd: parseCalloutEnd,
27241
27365
  parseCalloutType: parseCalloutType,
27242
27366
  parseLabelPosition: parseLabelPosition,
27367
+ parseLineEnd: parseLineEnd,
27243
27368
  parsePointPair: parsePointPair,
27244
27369
  parseStyleLiteral: parseStyleLiteral,
27245
27370
  parseSvgMeasure: parseSvgMeasure,
@@ -27742,6 +27867,85 @@
27742
27867
  toPixels: toPixels
27743
27868
  });
27744
27869
 
27870
+ // Arrowhead geometry shared by label callouts (svg-label-callout.mjs) and the
27871
+ // ends of line features (svg-line-arrows.mjs), so that the two draw the same
27872
+ // heads. Everything is in px, y down.
27873
+
27874
+ // The angle at an arrowhead's point, degrees. The open one is wider, since a
27875
+ // narrow chevron drawn with a line reads as a thickened line more than as an
27876
+ // arrow.
27877
+ var ARROW_ANGLE = 35;
27878
+ var OPEN_ARROW_ANGLE = 70;
27879
+ // How far into a solid arrowhead the line reaches, as a fraction of the
27880
+ // head's length: far enough that its cap is hidden, short of the tip.
27881
+ var ARROW_LINE_OVERLAP = 0.7;
27882
+
27883
+ var DEFAULT_LINE_WIDTH$2 = 1;
27884
+
27885
+ // An arrowhead's size when nothing says, in px: the length of its sides. 10
27886
+ // for a 1px line, growing with the line, so that a heavier line does not end
27887
+ // in a head too small to read.
27888
+ function getDefaultArrowSize(lineWidth) {
27889
+ var w = lineWidth > 0 ? lineWidth : DEFAULT_LINE_WIDTH$2;
27890
+ return 7 + 3 * w;
27891
+ }
27892
+
27893
+ // [tip, wing, wing], for a head with sides @side long meeting at @angle degrees,
27894
+ // pointing at @tip from direction @dir (a unit vector from the tip back along
27895
+ // the line)
27896
+ function getArrowHead(tip, dir, side, angle) {
27897
+ var len = getArrowHeadLength(side, angle);
27898
+ var half = side * Math.sin(angle / 2 * Math.PI / 180);
27899
+ var bx = tip[0] + dir[0] * len;
27900
+ var by = tip[1] + dir[1] * len;
27901
+ return [tip, [bx - dir[1] * half, by + dir[0] * half], [bx + dir[1] * half, by - dir[0] * half]];
27902
+ }
27903
+
27904
+ // How far along the line a head with sides @side long reaches
27905
+ function getArrowHeadLength(side, angle) {
27906
+ return side * Math.cos(angle / 2 * Math.PI / 180);
27907
+ }
27908
+
27909
+ // The part of polyline @coords after it first leaves the circle of radius @r
27910
+ // around its first point, starting on the circle; or null if it never does.
27911
+ function trimPolyline$1(coords, r) {
27912
+ var p0 = coords[0];
27913
+ for (var i = 1; i < coords.length; i++) {
27914
+ if (distance$1(p0, coords[i]) > r) {
27915
+ return [getCircleExit(p0, r, coords[i - 1], coords[i])].concat(coords.slice(i));
27916
+ }
27917
+ }
27918
+ return null;
27919
+ }
27920
+
27921
+ // The point where segment @p-@q, which starts inside the circle and ends
27922
+ // outside it, crosses it.
27923
+ function getCircleExit(c, r, p, q) {
27924
+ var dx = q[0] - p[0];
27925
+ var dy = q[1] - p[1];
27926
+ var ex = p[0] - c[0];
27927
+ var ey = p[1] - c[1];
27928
+ var a = dx * dx + dy * dy;
27929
+ var b = 2 * (dx * ex + dy * ey);
27930
+ var k = ex * ex + ey * ey - r * r;
27931
+ var s = (-b + Math.sqrt(Math.max(0, b * b - 4 * a * k))) / (2 * a);
27932
+ s = Math.max(0, Math.min(1, s));
27933
+ return [p[0] + s * dx, p[1] + s * dy];
27934
+ }
27935
+
27936
+ function getUnitVector(p, q) {
27937
+ var dx = q[0] - p[0];
27938
+ var dy = q[1] - p[1];
27939
+ var len = Math.sqrt(dx * dx + dy * dy);
27940
+ return len > 0 ? [dx / len, dy / len] : null;
27941
+ }
27942
+
27943
+ function distance$1(p, q) {
27944
+ var dx = q[0] - p[0];
27945
+ var dy = q[1] - p[1];
27946
+ return Math.sqrt(dx * dx + dy * dy);
27947
+ }
27948
+
27745
27949
  // A callout: a line from a label's anchor to its text, straight, elbowed or
27746
27950
  // curved, optionally ending in an arrowhead at the anchor. A dot at the anchor
27747
27951
  // is the label's icon, not a callout marker.
@@ -27754,7 +27958,7 @@
27754
27958
  // See docs/development/text-annotation-design.md.
27755
27959
 
27756
27960
  var DEFAULT_PADDING$1 = 3;
27757
- var DEFAULT_LINE_WIDTH = 1;
27961
+ var DEFAULT_LINE_WIDTH$1 = 1;
27758
27962
  // Space left between the anchor's symbol and the end of the line
27759
27963
  var SYMBOL_CLEARANCE = 2;
27760
27964
  // How far an automatic curve bows out, as a fraction of its chord
@@ -27762,14 +27966,6 @@
27762
27966
  // The shortest first leg an automatic elbow into the top or bottom of the text
27763
27967
  // is drawn with, px
27764
27968
  var MIN_LEG = 8;
27765
- // The angle at an arrowhead's point, degrees. The open one is wider, since a
27766
- // narrow chevron drawn with a line reads as a thickened line more than as an
27767
- // arrow.
27768
- var ARROW_ANGLE = 44;
27769
- var OPEN_ARROW_ANGLE = 70;
27770
- // How far into a solid arrowhead the line reaches, as a fraction of the
27771
- // head's length: far enough that its cap is hidden, short of the tip.
27772
- var ARROW_LINE_OVERLAP = 0.7;
27773
27969
  // Where glyphs sit relative to their baseline, in ems, for estimating the
27774
27970
  // height of a block of text that is measured only for its width
27775
27971
  var ASCENT = 0.8;
@@ -27814,8 +28010,7 @@
27814
28010
  // default line, and growing with the line, so that a heavier line does not end
27815
28011
  // in a head too small to read.
27816
28012
  function getDefaultCalloutEndSize(end, lineWidth) {
27817
- var w = lineWidth > 0 ? lineWidth : DEFAULT_LINE_WIDTH;
27818
- return 7 + 3 * w;
28013
+ return getDefaultArrowSize(lineWidth);
27819
28014
  }
27820
28015
 
27821
28016
  // 'arrow', 'open-arrow' or 'none'
@@ -27923,7 +28118,7 @@
27923
28118
  v = o.via || getAutoElbow(a, t, box);
27924
28119
  // A corner on either end is no corner -- straight under the text, say --
27925
28120
  // and a leg of no length has no direction for an arrowhead to take.
27926
- path = {kind: 'polyline', coords: distance(a, v) > 0 && distance(v, t) > 0 ?
28121
+ path = {kind: 'polyline', coords: distance$1(a, v) > 0 && distance$1(v, t) > 0 ?
27927
28122
  [a, v, t] : [a, t]};
27928
28123
  } else if (o.type == 'curve') {
27929
28124
  v = o.via || getAutoBend(a, t);
@@ -27951,7 +28146,7 @@
27951
28146
  // the chevron's point: the point is set back by that much, so that the
27952
28147
  // arrow ends where a filled one would. The join and the round cap at the
27953
28148
  // far end each add half a line width to an arm, which is taken off it.
27954
- lineWidth = o.width > 0 ? o.width : DEFAULT_LINE_WIDTH;
28149
+ lineWidth = o.width > 0 ? o.width : DEFAULT_LINE_WIDTH$1;
27955
28150
  path = trimPath(path, lineWidth / 2) || path;
27956
28151
  dir = getStartDirection(path);
27957
28152
  if (!dir) return null;
@@ -28128,44 +28323,21 @@
28128
28323
  function hasLength(path) {
28129
28324
  var c = path.coords;
28130
28325
  for (var i = 1; i < c.length; i++) {
28131
- if (distance(c[0], c[i]) > 0) return true;
28326
+ if (distance$1(c[0], c[i]) > 0) return true;
28132
28327
  }
28133
28328
  return false;
28134
28329
  }
28135
28330
 
28136
28331
  function trimPolyline(coords, r) {
28137
- var p0 = coords[0];
28138
- for (var i = 1; i < coords.length; i++) {
28139
- if (distance(p0, coords[i]) > r) {
28140
- return {
28141
- kind: 'polyline',
28142
- coords: [getCircleExit(p0, r, coords[i - 1], coords[i])].concat(coords.slice(i))
28143
- };
28144
- }
28145
- }
28146
- return null;
28147
- }
28148
-
28149
- // The point where segment @p-@q, which starts inside the circle and ends
28150
- // outside it, crosses it.
28151
- function getCircleExit(c, r, p, q) {
28152
- var dx = q[0] - p[0];
28153
- var dy = q[1] - p[1];
28154
- var ex = p[0] - c[0];
28155
- var ey = p[1] - c[1];
28156
- var a = dx * dx + dy * dy;
28157
- var b = 2 * (dx * ex + dy * ey);
28158
- var k = ex * ex + ey * ey - r * r;
28159
- var s = (-b + Math.sqrt(Math.max(0, b * b - 4 * a * k))) / (2 * a);
28160
- s = Math.max(0, Math.min(1, s));
28161
- return [p[0] + s * dx, p[1] + s * dy];
28332
+ var trimmed = trimPolyline$1(coords, r);
28333
+ return trimmed ? {kind: 'polyline', coords: trimmed} : null;
28162
28334
  }
28163
28335
 
28164
28336
  function trimBezier(c, r) {
28165
28337
  var steps = 64;
28166
28338
  var lo = 0, hi = -1, mid, i;
28167
28339
  for (i = 1; i <= steps; i++) {
28168
- if (distance(c[0], getBezierPoint(c, i / steps)) > r) {
28340
+ if (distance$1(c[0], getBezierPoint(c, i / steps)) > r) {
28169
28341
  hi = i / steps;
28170
28342
  lo = (i - 1) / steps;
28171
28343
  break;
@@ -28174,7 +28346,7 @@
28174
28346
  if (hi < 0) return null;
28175
28347
  for (i = 0; i < 24; i++) {
28176
28348
  mid = (lo + hi) / 2;
28177
- if (distance(c[0], getBezierPoint(c, mid)) > r) hi = mid;
28349
+ if (distance$1(c[0], getBezierPoint(c, mid)) > r) hi = mid;
28178
28350
  else lo = mid;
28179
28351
  }
28180
28352
  return {kind: 'bezier', coords: splitBezier(c, hi)};
@@ -28200,7 +28372,7 @@
28200
28372
  function getStartDirection(path) {
28201
28373
  var c = path.coords;
28202
28374
  var next = c[1];
28203
- if (path.kind == 'bezier' && distance(c[0], c[1]) === 0) next = c[2];
28375
+ if (path.kind == 'bezier' && distance$1(c[0], c[1]) === 0) next = c[2];
28204
28376
  return getUnitVector(c[0], next);
28205
28377
  }
28206
28378
 
@@ -28232,8 +28404,8 @@
28232
28404
  ctrl = intersectRays(start, dir, end, getUnitVector(end, rest.coords[1]));
28233
28405
  // A curve that doubles back, or runs straight on: a control point on the
28234
28406
  // head's axis still takes the line out of the head straight.
28235
- if (!ctrl) ctrl = [start[0] + dir[0] * distance(start, end) / 2,
28236
- start[1] + dir[1] * distance(start, end) / 2];
28407
+ if (!ctrl) ctrl = [start[0] + dir[0] * distance$1(start, end) / 2,
28408
+ start[1] + dir[1] * distance$1(start, end) / 2];
28237
28409
  return {dir: dir, path: {kind: 'bezier', coords: [start, ctrl, end]}};
28238
28410
  }
28239
28411
 
@@ -28251,20 +28423,6 @@
28251
28423
  return s > 0 && u > 0 ? [p[0] + d[0] * s, p[1] + d[1] * s] : null;
28252
28424
  }
28253
28425
 
28254
- // [tip, wing, wing], for a head with sides @side long meeting at @angle degrees
28255
- function getArrowHead(tip, dir, side, angle) {
28256
- var len = getArrowHeadLength(side, angle);
28257
- var half = side * Math.sin(angle / 2 * Math.PI / 180);
28258
- var bx = tip[0] + dir[0] * len;
28259
- var by = tip[1] + dir[1] * len;
28260
- return [tip, [bx - dir[1] * half, by + dir[0] * half], [bx + dir[1] * half, by - dir[0] * half]];
28261
- }
28262
-
28263
- // How far along the line a head with sides @side long reaches
28264
- function getArrowHeadLength(side, angle) {
28265
- return side * Math.cos(angle / 2 * Math.PI / 180);
28266
- }
28267
-
28268
28426
  // Utilities
28269
28427
 
28270
28428
  function padBox$1(box, pad) {
@@ -28281,19 +28439,6 @@
28281
28439
  return p[0] > box.xmin && p[0] < box.xmax && p[1] > box.ymin && p[1] < box.ymax;
28282
28440
  }
28283
28441
 
28284
- function getUnitVector(p, q) {
28285
- var dx = q[0] - p[0];
28286
- var dy = q[1] - p[1];
28287
- var len = Math.sqrt(dx * dx + dy * dy);
28288
- return len > 0 ? [dx / len, dy / len] : null;
28289
- }
28290
-
28291
- function distance(p, q) {
28292
- var dx = q[0] - p[0];
28293
- var dy = q[1] - p[1];
28294
- return Math.sqrt(dx * dx + dy * dy);
28295
- }
28296
-
28297
28442
  function lerp(p, q, t) {
28298
28443
  return [p[0] + (q[0] - p[0]) * t, p[1] + (q[1] - p[1]) * t];
28299
28444
  }
@@ -28303,8 +28448,8 @@
28303
28448
  }
28304
28449
 
28305
28450
  function getLineWidth(rec) {
28306
- var w = getNumber(rec['callout-width'], DEFAULT_LINE_WIDTH);
28307
- return w > 0 ? w : DEFAULT_LINE_WIDTH;
28451
+ var w = getNumber(rec['callout-width'], DEFAULT_LINE_WIDTH$1);
28452
+ return w > 0 ? w : DEFAULT_LINE_WIDTH$1;
28308
28453
  }
28309
28454
 
28310
28455
  function getFontSizeInPx$1(rec) {
@@ -28357,6 +28502,9 @@
28357
28502
  return str;
28358
28503
  }
28359
28504
 
28505
+ // A ring icon's stroke, unless the record sets one; 1px read as too faint.
28506
+ var RING_STROKE_WIDTH = 1.5;
28507
+
28360
28508
  var symbolRenderers = {
28361
28509
  line: line,
28362
28510
  polygon: polygon,
@@ -28429,6 +28577,7 @@
28429
28577
  if (type == 'square') return square(getIconStyleData(d, r), 0, 0);
28430
28578
  if (type == 'ring') return ring(getIconStyleData(d, r), 0, 0);
28431
28579
  if (type == 'star') return star(getIconStyleData(d, r));
28580
+ if (type == 'nyt-star') return nytStar(getIconStyleData(d, r));
28432
28581
  return empty();
28433
28582
  }
28434
28583
 
@@ -28438,8 +28587,11 @@
28438
28587
  return d.r > 0 ? d.r : 5;
28439
28588
  }
28440
28589
  size = d['icon-size'];
28441
- if (type == 'circle' || type == 'ring') {
28590
+ if (type == 'circle') {
28442
28591
  size -= 1;
28592
+ } else if (type == 'ring') {
28593
+ // the stroke's outer half is inside icon-size
28594
+ size -= RING_STROKE_WIDTH;
28443
28595
  } else if (type == 'star') {
28444
28596
  size += 1;
28445
28597
  }
@@ -28546,7 +28698,7 @@
28546
28698
  o.properties.fill = 'none';
28547
28699
  o.properties.stroke = d.fill;
28548
28700
  if (!o.properties['stroke-width']) {
28549
- o.properties['stroke-width'] = 1;
28701
+ o.properties['stroke-width'] = RING_STROKE_WIDTH;
28550
28702
  }
28551
28703
  return o;
28552
28704
  }
@@ -28558,6 +28710,43 @@
28558
28710
  return o;
28559
28711
  }
28560
28712
 
28713
+ // A white nine-pointed star with a heavy outline in the icon's colour, drawn
28714
+ // from the source artwork's 40-unit box. The box includes the outline and its
28715
+ // mitred points, so icon-size is the width of the whole symbol.
28716
+ var NYT_STAR_BOX = 40;
28717
+ var NYT_STAR_STROKE = 5;
28718
+ var NYT_STAR_POINTS = [19.9843, 5.7196, 23.1494, 11.7629, 29.4528, 9.1512,
28719
+ 27.9924, 15.8152, 34.5, 17.8645, 29.0961, 22.0321, 32.7631, 27.7841,
28720
+ 25.9457, 27.504, 25.0572, 34.2675, 20.0149, 29.6714, 14.9847, 34.2804,
28721
+ 14.0769, 27.5192, 7.2619, 27.8167, 10.9166, 22.0553, 5.5, 17.9016,
28722
+ 12.0024, 15.8357, 10.527, 9.1754, 16.835, 11.771];
28723
+
28724
+ function nytStar(d) {
28725
+ var k = d.r * 2 / NYT_STAR_BOX;
28726
+ var o = importPolygon([getNytStarCoords(k)]);
28727
+ applyStyleAttributes(o, 'point', d, nonCirclePointFilter);
28728
+ o.properties.stroke = o.properties.fill;
28729
+ o.properties.fill = '#fff';
28730
+ o.properties['stroke-width'] = roundToHundredths(NYT_STAR_STROKE * k);
28731
+ o.properties['stroke-miterlimit'] = 10;
28732
+ return o;
28733
+ }
28734
+
28735
+ function getNytStarCoords(k) {
28736
+ var c = NYT_STAR_BOX / 2;
28737
+ var coords = [];
28738
+ for (var i=0; i<NYT_STAR_POINTS.length; i+=2) {
28739
+ coords.push([roundToHundredths((NYT_STAR_POINTS[i] - c) * k),
28740
+ roundToHundredths((NYT_STAR_POINTS[i + 1] - c) * k)]);
28741
+ }
28742
+ coords.push(coords[0].concat());
28743
+ return coords;
28744
+ }
28745
+
28746
+ function roundToHundredths(n) {
28747
+ return Math.round(n * 100) / 100;
28748
+ }
28749
+
28561
28750
  function nonCirclePointFilter(k) {
28562
28751
  return k != 'r';
28563
28752
  }
@@ -28637,6 +28826,7 @@
28637
28826
 
28638
28827
  var SvgSymbols = /*#__PURE__*/Object.freeze({
28639
28828
  __proto__: null,
28829
+ RING_STROKE_WIDTH: RING_STROKE_WIDTH,
28640
28830
  featureHasIcon: featureHasIcon,
28641
28831
  getAnchorSymbolRadius: getAnchorSymbolRadius,
28642
28832
  getIconRadius: getIconRadius,
@@ -30079,6 +30269,198 @@
30079
30269
  }
30080
30270
  */
30081
30271
 
30272
+ // Markers at the ends of line features: line-start= and line-end= take
30273
+ // arrow (a filled triangle), open-arrow (a stroked chevron), dot (a filled
30274
+ // circle centred on the end) or none, and line-end-size= is the length of a
30275
+ // head's sides in px, for both ends, or a dot's diameter. A dot with no size
30276
+ // of its own is DOT_SIZE_RATIO of an arrowhead's default size.
30277
+ //
30278
+ // The heads are drawn as shapes rather than as SVG <marker>s, so that the
30279
+ // canvas in the GUI and the exported SVG draw the same thing, and so that a
30280
+ // solid head can have the line stop inside it, where its cap is hidden. Every
30281
+ // part of a multi-part line gets its own heads: part order is not something
30282
+ // the commands that make multi-part lines preserve.
30283
+ //
30284
+ // Coordinates are px, y down: the SVG export's space, or the canvas's.
30285
+
30286
+ var DEFAULT_LINE_WIDTH = 1;
30287
+ // 6px across at the default size for a 1px line
30288
+ var DOT_SIZE_RATIO = 0.6;
30289
+
30290
+ // 'arrow', 'open-arrow', 'dot' or 'none'
30291
+ function getLineEndType(rec, field) {
30292
+ return rec && rec[field] && parseLineEnd(rec[field]) || 'none';
30293
+ }
30294
+
30295
+ function lineHasArrows(rec) {
30296
+ return getLineEndType(rec, 'line-start') != 'none' ||
30297
+ getLineEndType(rec, 'line-end') != 'none';
30298
+ }
30299
+
30300
+ // What getLineArrowShape() needs from a record, at scale @scale (the factor
30301
+ // the canvas multiplies line widths by; 1 for SVG).
30302
+ function getLineArrowOpts(rec, scale) {
30303
+ return makeLineArrowOpts(rec['line-start'], rec['line-end'], rec['line-end-size'],
30304
+ rec['stroke-width'], scale);
30305
+ }
30306
+
30307
+ // The same, from the values themselves, for the canvas, which has them in a
30308
+ // style object rather than a record.
30309
+ function makeLineArrowOpts(start, end, size, strokeWidth, scale) {
30310
+ var k = scale > 0 ? scale : 1;
30311
+ var w = isSvgNumber(strokeWidth) && Number(strokeWidth) >= 0 ?
30312
+ Number(strokeWidth) : DEFAULT_LINE_WIDTH;
30313
+ var hasSize = isSvgNumber(size) && Number(size) > 0;
30314
+ var side = hasSize ? Number(size) : getDefaultArrowSize(w);
30315
+ return {
30316
+ start: start && parseLineEnd(start) || 'none',
30317
+ end: end && parseLineEnd(end) || 'none',
30318
+ size: side * k,
30319
+ dotSize: (hasSize ? side : side * DOT_SIZE_RATIO) * k,
30320
+ width: w * k
30321
+ };
30322
+ }
30323
+
30324
+ // Field names, for a layer that may have them
30325
+ var lineArrowFields = ['line-start', 'line-end', 'line-end-size'];
30326
+
30327
+ // One part of a line, with its heads: {coords, heads}, where coords is the
30328
+ // line to stroke -- cut back into a solid head, so that its cap does not show
30329
+ // past the tip -- and heads is a list of {type, points}: a solid head's
30330
+ // triangle, tip first, or an open head's chevron, wing to tip to wing. A dot
30331
+ // is {type, center, radius}.
30332
+ //
30333
+ // A head points along the chord from its tip to where the line first reaches
30334
+ // the head's length from it, rather than along the last segment, which on
30335
+ // detailed or noisy data can be a jog of a fraction of a pixel pointing
30336
+ // anywhere. The line is then straightened to meet the head along its axis. An
30337
+ // end whose part is too short to hold its head gets none.
30338
+ function getLineArrowShape(coords, opts) {
30339
+ var out = {coords: coords, heads: []};
30340
+ var end;
30341
+ if (opts.start != 'none') {
30342
+ addHead(out, opts.start, opts);
30343
+ }
30344
+ if (opts.end != 'none') {
30345
+ end = {coords: out.coords.slice().reverse(), heads: out.heads};
30346
+ addHead(end, opts.end, opts);
30347
+ out.coords = end.coords.reverse();
30348
+ }
30349
+ return out;
30350
+ }
30351
+
30352
+ // Puts a head of @type at the first point of @shape.coords
30353
+ function addHead(shape, type, opts) {
30354
+ var coords = shape.coords;
30355
+ var width = opts.width > 0 ? opts.width : DEFAULT_LINE_WIDTH;
30356
+ var tip, side, angle, len, rest, dir, head;
30357
+ if (!coords || coords.length < 2) return;
30358
+ if (type == 'dot') {
30359
+ addDot(shape, (opts.dotSize || opts.size * DOT_SIZE_RATIO) / 2);
30360
+ return;
30361
+ }
30362
+ if (type == 'open-arrow') {
30363
+ // Stroked with a round join, which reaches half a line width past the
30364
+ // chevron's point: the point is set back by that much, so that the arrow
30365
+ // ends where a solid one would, and the arms lose half a line width each
30366
+ // to the join and the cap.
30367
+ coords = trimPolyline$1(coords, width / 2);
30368
+ if (!coords) return;
30369
+ side = Math.max(opts.size - width, opts.size / 2);
30370
+ angle = OPEN_ARROW_ANGLE;
30371
+ } else {
30372
+ side = opts.size;
30373
+ angle = ARROW_ANGLE;
30374
+ }
30375
+ tip = coords[0];
30376
+ len = getArrowHeadLength(side, angle);
30377
+ rest = trimPolyline$1(coords, len);
30378
+ dir = rest ? getUnitVector(tip, rest[0]) : null;
30379
+ if (!dir) return;
30380
+ head = getArrowHead(tip, dir, side, angle);
30381
+ if (type == 'open-arrow') {
30382
+ shape.heads.push({type: type, points: [head[1], head[0], head[2]]});
30383
+ shape.coords = [tip].concat(rest);
30384
+ } else {
30385
+ shape.heads.push({type: type, points: head});
30386
+ shape.coords = [[tip[0] + dir[0] * len * ARROW_LINE_OVERLAP,
30387
+ tip[1] + dir[1] * len * ARROW_LINE_OVERLAP]].concat(rest);
30388
+ }
30389
+ }
30390
+
30391
+ // The line stops halfway to the dot's edge, which hides its cap in the dot
30392
+ // (for lines narrower than the dot's radius) while overlapping it no more than
30393
+ // a solid head does. A part too short to be cut back keeps its dot.
30394
+ function addDot(shape, radius) {
30395
+ var center = shape.coords[0];
30396
+ var rest = trimPolyline$1(shape.coords, radius / 2);
30397
+ shape.heads.push({type: 'dot', center: center, radius: radius});
30398
+ if (rest) shape.coords = rest;
30399
+ }
30400
+
30401
+ // A line feature with arrowheads, as SVG: a group holding the line and its
30402
+ // heads, for the feature's style attributes to go on. @parts is a list of
30403
+ // polylines (a LineString's coordinates are one).
30404
+ function renderArrowLine(parts, rec) {
30405
+ var opts = getLineArrowOpts(rec, 1);
30406
+ var color = rec.stroke || 'black';
30407
+ var lines = [];
30408
+ var children = [];
30409
+ parts.forEach(function(part) {
30410
+ var shape = getLineArrowShape(part, opts);
30411
+ lines.push(shape.coords.map(roundPoint));
30412
+ shape.heads.forEach(function(head) {
30413
+ children.push(renderHead(head, color, rec));
30414
+ });
30415
+ });
30416
+ children.unshift(importMultiLineString(lines));
30417
+ return {tag: 'g', properties: {}, children: children};
30418
+ }
30419
+
30420
+ function renderHead(head, color, rec) {
30421
+ var points, center;
30422
+ if (head.type == 'dot') {
30423
+ center = roundPoint(head.center);
30424
+ return {tag: 'circle', properties: addFillOpacity({cx: center[0], cy: center[1],
30425
+ r: Math.round(head.radius * 100) / 100, fill: color, stroke: 'none'}, rec)};
30426
+ }
30427
+ points = head.points.map(roundPoint);
30428
+ var d = 'M ' + points.map(function(p) { return p[0] + ' ' + p[1]; }).join(' L ');
30429
+ var props;
30430
+ if (head.type == 'arrow') {
30431
+ props = addFillOpacity({d: d + ' Z', fill: color, stroke: 'none'}, rec);
30432
+ } else {
30433
+ // The line's own dashes would break the chevron up, and its cap may be
30434
+ // square or butt; the chevron is always drawn whole and rounded.
30435
+ props = {d: d, fill: 'none', 'stroke-dasharray': 'none',
30436
+ 'stroke-linecap': 'round', 'stroke-linejoin': 'round'};
30437
+ }
30438
+ return {tag: 'path', properties: props};
30439
+ }
30440
+
30441
+ // A filled marker is as transparent as the line it is drawn in
30442
+ function addFillOpacity(props, rec) {
30443
+ if (isSvgNumber(rec['stroke-opacity'])) {
30444
+ props['fill-opacity'] = Number(rec['stroke-opacity']);
30445
+ }
30446
+ return props;
30447
+ }
30448
+
30449
+ function roundPoint(p) {
30450
+ return [Math.round(p[0] * 100) / 100, Math.round(p[1] * 100) / 100];
30451
+ }
30452
+
30453
+ var SvgLineArrows = /*#__PURE__*/Object.freeze({
30454
+ __proto__: null,
30455
+ getLineArrowOpts: getLineArrowOpts,
30456
+ getLineArrowShape: getLineArrowShape,
30457
+ getLineEndType: getLineEndType,
30458
+ lineArrowFields: lineArrowFields,
30459
+ lineHasArrows: lineHasArrows,
30460
+ makeLineArrowOpts: makeLineArrowOpts,
30461
+ renderArrowLine: renderArrowLine
30462
+ });
30463
+
30082
30464
  var geojsonImporters = {
30083
30465
  Point: importPoint,
30084
30466
  Polygon: importPolygon,
@@ -30101,6 +30483,9 @@
30101
30483
  // rather than several labels at several points
30102
30484
  svgObj = renderPathLabel(d, geom.coordinates,
30103
30485
  {report: opts.path_label_report, id: featureId});
30486
+ } else if (msType == 'polyline' && geom.coordinates && lineHasArrows(d)) {
30487
+ svgObj = renderArrowLine(geomType == 'LineString' ?
30488
+ [geom.coordinates] : geom.coordinates, d);
30104
30489
  } else if (geomType && geom.coordinates) {
30105
30490
  svgObj = geojsonImporters[geomType](geom.coordinates, d);
30106
30491
  }
@@ -43955,6 +44340,15 @@ ${svg}
43955
44340
  .option('stroke-opacity', {
43956
44341
  describe: 'stroke opacity'
43957
44342
  })
44343
+ .option('line-start', {
44344
+ describe: 'marker at the start of each line: arrow, open-arrow, dot or none'
44345
+ })
44346
+ .option('line-end', {
44347
+ describe: 'marker at the end of each line: arrow, open-arrow, dot or none'
44348
+ })
44349
+ .option('line-end-size', {
44350
+ describe: 'length of an arrowhead\'s sides in px (default grows with stroke-width)'
44351
+ })
43958
44352
  .option('opacity', {
43959
44353
  describe: 'opacity; example: 0.5'
43960
44354
  })
@@ -49567,9 +49961,28 @@ ${svg}
49567
49961
  props[key] = val;
49568
49962
  }
49569
49963
  }
49964
+ importPatternFill(props);
49570
49965
  return props;
49571
49966
  }
49572
49967
 
49968
+ // A fill of url(#id), with or without a fallback colour after it, is a
49969
+ // reference to a paint server in <defs>. The ones mapshaper exports are
49970
+ // pattern fills whose id encodes the fill-pattern code, and the fallback is
49971
+ // the feature's own fill. A reference that is not one of those is dropped:
49972
+ // nothing can draw a url() stored as a colour. Its fallback, if any, is kept.
49973
+ function importPatternFill(props) {
49974
+ var match = /^url\(\s*['"]?#([^'")]+)['"]?\s*\)\s*(.*)$/.exec(String(props.fill || '').trim());
49975
+ var code;
49976
+ if (!match) return;
49977
+ code = decodePatternId(match[1]);
49978
+ if (code) props['fill-pattern'] = code;
49979
+ if (match[2]) {
49980
+ props.fill = match[2];
49981
+ } else {
49982
+ delete props.fill;
49983
+ }
49984
+ }
49985
+
49573
49986
  function getNodeStyles(node) {
49574
49987
  var styles = {};
49575
49988
  var attrs = node.attributes || [];
@@ -51877,8 +52290,13 @@ ${svg}
51877
52290
 
51878
52291
  var iconNames = ['circle', 'square', 'ring', 'star'];
51879
52292
 
52293
+ // Icons that render everywhere but are not advertised: the GUI offers them only
52294
+ // to some users (see gui-nyt.mjs), and a map made with one has to draw the same
52295
+ // for anyone who opens it.
52296
+ var unlistedIconNames = ['nyt-star'];
52297
+
51880
52298
  function isSupportedIconName(name) {
51881
- return iconNames.indexOf(name) > -1;
52299
+ return iconNames.indexOf(name) > -1 || unlistedIconNames.indexOf(name) > -1;
51882
52300
  }
51883
52301
 
51884
52302
  cmd.svgStyle = function(lyr, dataset, opts) {
@@ -53038,13 +53456,17 @@ ${svg}
53038
53456
  var sw;
53039
53457
  if (!(r > 0)) return null;
53040
53458
  if (type == 'ring') {
53041
- // drawn as a stroke, one px wide unless stroke-width says otherwise
53459
+ // drawn as a stroke, RING_STROKE_WIDTH wide unless stroke-width says otherwise
53042
53460
  sw = +d['stroke-width'];
53043
- return getSquareBox(r + (sw > 0 ? sw : 1) / 2, 0, 0);
53461
+ return getSquareBox(r + (sw > 0 ? sw : RING_STROKE_WIDTH) / 2, 0, 0);
53044
53462
  }
53045
53463
  if (type == 'circle' || type == 'square' || type == 'star') {
53046
53464
  return getSquareBox(r + getStrokeOutset(d), 0, 0);
53047
53465
  }
53466
+ if (type == 'nyt-star') {
53467
+ // the outline is part of the symbol's size (see nytStar())
53468
+ return getSquareBox(r, 0, 0);
53469
+ }
53048
53470
  return null; // unsupported icons are not drawn
53049
53471
  }
53050
53472
 
@@ -54004,10 +54426,10 @@ ${svg}
54004
54426
  }
54005
54427
 
54006
54428
  function isClosedPath$1(arr) {
54007
- return isArrayOfPoints(arr) && arr.length > 3 && samePoint$2(arr[0], arr[arr.length - 1]);
54429
+ return isArrayOfPoints(arr) && arr.length > 3 && samePoint$3(arr[0], arr[arr.length - 1]);
54008
54430
  }
54009
54431
 
54010
- function samePoint$2(a, b) {
54432
+ function samePoint$3(a, b) {
54011
54433
  return a[0] == b[0] && a[1] == b[1];
54012
54434
  }
54013
54435
 
@@ -56194,12 +56616,12 @@ ${svg}
56194
56616
  return arr[arr.length - 1];
56195
56617
  }
56196
56618
 
56197
- function samePoint$1(a, b) {
56619
+ function samePoint$2(a, b) {
56198
56620
  return a && b && a[0] === b[0] && a[1] === b[1];
56199
56621
  }
56200
56622
 
56201
56623
  function isClosedPath(arr) {
56202
- return samePoint$1(arr[0], lastEl(arr));
56624
+ return samePoint$2(arr[0], lastEl(arr));
56203
56625
  }
56204
56626
 
56205
56627
  // remove likely rounding errors
@@ -56360,7 +56782,7 @@ ${svg}
56360
56782
  if (!nextPart) {
56361
56783
  return null;
56362
56784
  }
56363
- if (samePoint$1(ring[0], nextPart[0])) {
56785
+ if (samePoint$2(ring[0], nextPart[0])) {
56364
56786
  // done!
56365
56787
  ring.push(ring[0]); // close the ring
56366
56788
  return ring;
@@ -56409,7 +56831,7 @@ ${svg}
56409
56831
 
56410
56832
  function findPartStartingAt(parts, firstPoint) {
56411
56833
  for (var i=0; i<parts.length; i++) {
56412
- if (samePoint$1(parts[i][0], firstPoint)) {
56834
+ if (samePoint$2(parts[i][0], firstPoint)) {
56413
56835
  return parts[i];
56414
56836
  }
56415
56837
  }
@@ -56433,7 +56855,7 @@ ${svg}
56433
56855
  var part = [];
56434
56856
  var firstPoint = path[0];
56435
56857
  var lastPoint = lastEl(path);
56436
- var closed = samePoint$1(firstPoint, lastPoint);
56858
+ var closed = samePoint$2(firstPoint, lastPoint);
56437
56859
  var p, pp, y;
56438
56860
  for (var i=0, n=path.length; i<n; i++) {
56439
56861
  p = path[i];
@@ -61099,10 +61521,10 @@ ${svg}
61099
61521
  var p = recenteredVertex(ctx, points[i]);
61100
61522
  // Successive off-center vertices can re-center onto the same neck point;
61101
61523
  // keep one copy so the chain has no zero-length segments.
61102
- if (!samePoint(p, out[out.length - 1])) out.push(p);
61524
+ if (!samePoint$1(p, out[out.length - 1])) out.push(p);
61103
61525
  }
61104
61526
  var last = points[points.length - 1];
61105
- if (!samePoint(last, out[out.length - 1])) out.push(last);
61527
+ if (!samePoint$1(last, out[out.length - 1])) out.push(last);
61106
61528
  return out.length >= 2 ? out : points;
61107
61529
  }
61108
61530
 
@@ -61205,7 +61627,7 @@ ${svg}
61205
61627
  out[1] = ay + t * dy;
61206
61628
  }
61207
61629
 
61208
- function samePoint(a, b) {
61630
+ function samePoint$1(a, b) {
61209
61631
  return a[0] === b[0] && a[1] === b[1];
61210
61632
  }
61211
61633
 
@@ -65565,7 +65987,7 @@ ${svg}
65565
65987
  debug('Short ring', coords);
65566
65988
  return;
65567
65989
  }
65568
- if (!samePoint$1(coords[0], lastEl(coords))) {
65990
+ if (!samePoint$2(coords[0], lastEl(coords))) {
65569
65991
  error('Open polygon ring');
65570
65992
  }
65571
65993
  rings.push(coords); // accumulate rings
@@ -84575,7 +84997,7 @@ ${svg}
84575
84997
  return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
84576
84998
  }
84577
84999
 
84578
- var version = "0.7.70";
85000
+ var version = "0.7.71";
84579
85001
 
84580
85002
  // Parse command line args into commands and run them
84581
85003
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -87183,6 +87605,257 @@ ${svg}
87183
87605
  restoreCapturedUnits: restoreCapturedUnits
87184
87606
  });
87185
87607
 
87608
+ // Smooths a freehand ("pencil") stroke with a Gaussian kernel while it is
87609
+ // being drawn, placing vertices one at a time, a little behind the pointer.
87610
+ // Vertices never move once they are placed.
87611
+ //
87612
+ // The trace of the pointer samples is densified to even spacing and smoothed
87613
+ // with the Gaussian kernel that -smooth uses, as a plain weighted average in
87614
+ // arc length. (-smooth adds a quadratic correction that keeps bends from being
87615
+ // flattened, but it lets through far more of the jitter of a slow stroke.) A
87616
+ // smoothed point depends only on the trace within a window around it, so it
87617
+ // is final as soon as the trace extends a window radius beyond it. At the
87618
+ // start of the stroke the trace is extended by an odd reflection about its
87619
+ // first point, which keeps the smoothed curve starting exactly there, and the
87620
+ // end is treated the same way when the stroke is finished.
87621
+ //
87622
+ // Vertices are kept from the smoothed points where the curve has turned by a
87623
+ // given angle since the last vertex, or where the chord from the last vertex
87624
+ // would stray too far from a gentle curve -- the rule -smooth uses to space
87625
+ // its output. So vertices are close together at bends and far apart on
87626
+ // straight stretches, and they appear one at a time.
87627
+ //
87628
+ // Distances are in pixels. For samples in other units, the pixelSize option
87629
+ // gives the size of a pixel in those units.
87630
+
87631
+ // Standard deviation of the smoothing kernel, in arc length. Below about 5px,
87632
+ // the jitter of a slow stroke comes through; larger values round off bends a
87633
+ // few pixels across.
87634
+ var SIGMA = 8;
87635
+
87636
+ // Half-width of the smoothing window, in multiples of SIGMA. This is also how
87637
+ // far the pointer has to be ahead of a smoothed point for it to be final.
87638
+ var WINDOW_RADIUS = 3;
87639
+
87640
+ // Spacing of the densified trace, and of the smoothed points that are tested
87641
+ // as vertices. Small enough that a bend of radius SIGMA turns by only a few
87642
+ // degrees per step.
87643
+ var SOURCE_STEP = 0.5;
87644
+ var OUTPUT_STEP = 0.5;
87645
+
87646
+ // Greatest turn between consecutive segments of the output, and greatest
87647
+ // distance of the output from the smoothed curve along gentle bends.
87648
+ var BEND_ANGLE = 12;
87649
+ var MAX_DEVIATION = 0.75;
87650
+
87651
+ // Spacing of the points returned by getPreview(), and the standard deviation
87652
+ // of the kernel that smooths the preview at the pointer. The preview is
87653
+ // smoothed more heavily further from the pointer, up to SIGMA where it meets
87654
+ // the placed vertices, so that it runs into them without a step. A small
87655
+ // kernel at the pointer removes pixel jitter without lagging behind.
87656
+ var PREVIEW_SPACING = 2;
87657
+ var PREVIEW_SIGMA = 2;
87658
+
87659
+ // start: [x, y] first sample
87660
+ // opts: (optional) {sigma, previewSigma, bendAngle (degrees), maxDeviation, pixelSize}
87661
+ function GaussianStrokeFitter(start, opts) {
87662
+ var px = opts && opts.pixelSize > 0 ? opts.pixelSize : 1;
87663
+ var sigma = getOpt(opts, 'sigma', SIGMA) * px;
87664
+ var radius = sigma * WINDOW_RADIUS;
87665
+ var previewSigma = Math.min(getOpt(opts, 'previewSigma', PREVIEW_SIGMA) * px, sigma);
87666
+ var step = SOURCE_STEP * px;
87667
+ var outputStep = OUTPUT_STEP * px;
87668
+ var decimator = new BendDecimator(start, getOpt(opts, 'bendAngle', BEND_ANGLE) * Math.PI / 180,
87669
+ getOpt(opts, 'maxDeviation', MAX_DEVIATION) * px);
87670
+ var samples = [[start[0], start[1]]];
87671
+ // the densified trace: arc length and coordinates
87672
+ var tt = [0], xx = [start[0]], yy = [start[1]];
87673
+ var traceLen = 0; // length of the densified part of the trace
87674
+ var nextT = step; // arc length of the next densified point
87675
+ var phi = outputStep; // arc length of the next smoothed point
87676
+
87677
+ // Returns the vertices placed by the new sample, which may be none.
87678
+ this.addSample = function(p) {
87679
+ var n = samples.length;
87680
+ if (!isSample(p) || samePoint(p, samples[n - 1])) return [];
87681
+ samples.push([p[0], p[1]]);
87682
+ densifySegment(n - 1);
87683
+ return placeSmoothedPoints(traceLen - radius, false);
87684
+ };
87685
+
87686
+ // Returns the vertices that complete the stroke, ending at the last sample.
87687
+ this.finish = function() {
87688
+ var n = samples.length, end, placed, p;
87689
+ if (n < 2) return [];
87690
+ end = samples[n - 1];
87691
+ if (tt[tt.length - 1] < traceLen) {
87692
+ tt.push(traceLen);
87693
+ xx.push(end[0]);
87694
+ yy.push(end[1]);
87695
+ }
87696
+ placed = placeSmoothedPoints(traceLen - outputStep / 2, true);
87697
+ p = decimator.push(end);
87698
+ if (p) placed.push(p);
87699
+ placed.push([end[0], end[1]]);
87700
+ return placed;
87701
+ };
87702
+
87703
+ // Returns points to show between the last placed vertex and the pointer
87704
+ // (not including either): the latest smoothed point, followed by the rest of
87705
+ // the trace, smoothed less and less towards the pointer (see PREVIEW_SIGMA).
87706
+ // These points are not final.
87707
+ this.getPreview = function() {
87708
+ var cand = decimator.getCandidate();
87709
+ var t0 = cand ? phi - outputStep : 0; // arc length of the candidate
87710
+ var spacing = PREVIEW_SPACING * px;
87711
+ var out = cand ? [cand] : [];
87712
+ var t, s;
87713
+ for (t = t0 + spacing; t < traceLen - spacing / 2; t += spacing) {
87714
+ s = previewSigma + (sigma - previewSigma) * (traceLen - t) / (traceLen - t0);
87715
+ out.push(smoothAt(t, true, s));
87716
+ }
87717
+ return out;
87718
+ };
87719
+
87720
+ // Smooths the trace up to arc length @maxT and returns the vertices placed
87721
+ // from the smoothed points.
87722
+ function placeSmoothedPoints(maxT, atEnd) {
87723
+ var placed = [], p;
87724
+ for (; phi <= maxT; phi += outputStep) {
87725
+ p = decimator.push(smoothAt(phi, atEnd));
87726
+ if (p) placed.push(p);
87727
+ }
87728
+ return placed;
87729
+ }
87730
+
87731
+ // atEnd: treat the last sample as the end of the stroke
87732
+ // s: (optional) standard deviation of the kernel, if not sigma
87733
+ function smoothAt(t, atEnd, s) {
87734
+ var r = s ? s * WINDOW_RADIUS : radius,
87735
+ n = tt.length,
87736
+ lo = firstIndexAbove(t - r),
87737
+ hi = firstIndexAbove(t + r),
87738
+ end = samples[samples.length - 1],
87739
+ wt = [], wx = [], wy = [], i;
87740
+ // odd reflection of the start of the trace
87741
+ for (i = Math.min(firstIndexAbove(r - t), n) - 1; i > 0; i--) {
87742
+ wt.push(-tt[i]);
87743
+ wx.push(2 * xx[0] - xx[i]);
87744
+ wy.push(2 * yy[0] - yy[i]);
87745
+ }
87746
+ for (i = lo; i < hi; i++) {
87747
+ wt.push(tt[i]);
87748
+ wx.push(xx[i]);
87749
+ wy.push(yy[i]);
87750
+ }
87751
+ if (atEnd) {
87752
+ // the trace stops short of the last sample until finish() adds it
87753
+ if (tt[n - 1] < traceLen) {
87754
+ wt.push(traceLen);
87755
+ wx.push(end[0]);
87756
+ wy.push(end[1]);
87757
+ }
87758
+ // odd reflection of the end of the trace
87759
+ for (i = n - 1; i >= 0 && 2 * traceLen - tt[i] < t + r; i--) {
87760
+ if (tt[i] >= traceLen) continue;
87761
+ wt.push(2 * traceLen - tt[i]);
87762
+ wx.push(2 * end[0] - xx[i]);
87763
+ wy.push(2 * end[1] - yy[i]);
87764
+ }
87765
+ }
87766
+ return smoothPoint(wt, [wx, wy], 0, wt.length, t, 'gaussian', s || sigma, r, 0);
87767
+ }
87768
+
87769
+ // first index of the trace with arc length > @t
87770
+ function firstIndexAbove(t) {
87771
+ var lo = 0, hi = tt.length, mid;
87772
+ while (lo < hi) {
87773
+ mid = (lo + hi) >> 1;
87774
+ if (tt[mid] <= t) lo = mid + 1;
87775
+ else hi = mid;
87776
+ }
87777
+ return lo;
87778
+ }
87779
+
87780
+ // Adds points to the trace at even spacing from sample @i to the next one.
87781
+ function densifySegment(i) {
87782
+ var a = samples[i],
87783
+ b = samples[i + 1],
87784
+ len = distance(a, b),
87785
+ f;
87786
+ for (; nextT <= traceLen + len; nextT += step) {
87787
+ f = (nextT - traceLen) / len;
87788
+ tt.push(nextT);
87789
+ xx.push(a[0] + (b[0] - a[0]) * f);
87790
+ yy.push(a[1] + (b[1] - a[1]) * f);
87791
+ }
87792
+ traceLen += len;
87793
+ }
87794
+ }
87795
+
87796
+ // Picks vertices from a stream of closely spaced points along a smooth curve,
87797
+ // keeping a point once the curve has turned by @bendAngle (radians) since the
87798
+ // last kept point, or once the chord from the last kept point would bow more
87799
+ // than @maxDeviation from the curve (estimated as for a circular arc). This is
87800
+ // the rule -smooth uses (decimateByBend() in mapshaper-smooth-algos.mjs),
87801
+ // applied one point at a time.
87802
+ function BendDecimator(start, bendAngle, maxDeviation) {
87803
+ var kept = start, prev = start, candidate = null, turn = 0;
87804
+
87805
+ // Returns the point before @p if it is kept, or null.
87806
+ this.push = function(p) {
87807
+ var out = null;
87808
+ if (candidate) {
87809
+ turn += getTurn(prev, candidate, p);
87810
+ if (turn >= bendAngle || distance(kept, p) * turn / 8 >= maxDeviation) {
87811
+ out = kept = candidate;
87812
+ turn = 0;
87813
+ }
87814
+ prev = candidate;
87815
+ }
87816
+ candidate = p;
87817
+ return out;
87818
+ };
87819
+
87820
+ // The latest point, which has not been kept yet
87821
+ this.getCandidate = function() {
87822
+ return candidate;
87823
+ };
87824
+ }
87825
+
87826
+ function getTurn(a, b, c) {
87827
+ var ux = b[0] - a[0], uy = b[1] - a[1],
87828
+ vx = c[0] - b[0], vy = c[1] - b[1],
87829
+ len = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)),
87830
+ cos;
87831
+ if (!(len > 0)) return 0;
87832
+ cos = (ux * vx + uy * vy) / len;
87833
+ return Math.acos(Math.min(1, Math.max(-1, cos)));
87834
+ }
87835
+
87836
+ function getOpt(opts, name, defaultVal) {
87837
+ return opts && opts[name] > 0 ? opts[name] : defaultVal;
87838
+ }
87839
+
87840
+ function isSample(p) {
87841
+ return !!p && isFinite(p[0]) && isFinite(p[1]);
87842
+ }
87843
+
87844
+ function samePoint(a, b) {
87845
+ return a[0] == b[0] && a[1] == b[1];
87846
+ }
87847
+
87848
+ function distance(a, b) {
87849
+ var dx = b[0] - a[0], dy = b[1] - a[1];
87850
+ return Math.sqrt(dx * dx + dy * dy);
87851
+ }
87852
+
87853
+ var PencilGaussian = /*#__PURE__*/Object.freeze({
87854
+ __proto__: null,
87855
+ BendDecimator: BendDecimator,
87856
+ GaussianStrokeFitter: GaussianStrokeFitter
87857
+ });
87858
+
87186
87859
  // Attach functions exported by modules to the "internal" object,
87187
87860
  // so they can be run by tests and by the GUI.
87188
87861
  // TODO: rewrite tests to import functions directly from modules,
@@ -87192,7 +87865,7 @@ ${svg}
87192
87865
  internal.svg = Object.assign({}, SvgStringify, SvgPathUtils, GeojsonToSvg,
87193
87866
  SvgFeatureUtils,
87194
87867
  SvgLabels, SvgSymbols, SvgLabelPaths, SvgLabelFit, SvgLabelAlign,
87195
- SvgLabelMetrics, SvgLabelHalo, SvgLabelCallout, SvgLabelMarkup);
87868
+ SvgLabelMetrics, SvgLabelHalo, SvgLabelCallout, SvgLabelMarkup, SvgLineArrows);
87196
87869
 
87197
87870
  // Reached through the bundle rather than imported from source, unlike most of
87198
87871
  // what tests use, because these modules load fs and fontkit through the
@@ -87317,6 +87990,7 @@ ${svg}
87317
87990
  PathExport,
87318
87991
  Pathfinder,
87319
87992
  PathfinderUtils,
87993
+ PencilGaussian,
87320
87994
  PathImport,
87321
87995
  PathRepair,
87322
87996
  PathUtils,