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 +785 -111
- package/package.json +1 -1
- package/www/mapshaper-gui.js +803 -137
- package/www/mapshaper.js +785 -111
- package/www/page.css +20 -0
package/www/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$
|
|
7553
|
-
CK.lengthAB = distance$
|
|
7554
|
-
lengthMB = distance$
|
|
7555
|
-
lengthMN = distance$
|
|
7556
|
-
lengthNG = distance$
|
|
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$
|
|
7564
|
-
CK.lengthBD = distance$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
7664
|
-
var lF75 = distance$
|
|
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$
|
|
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$
|
|
7748
|
+
return distance$2(jointE(m), jointT(m));
|
|
7749
7749
|
}
|
|
7750
7750
|
|
|
7751
7751
|
function lengthMiddleSegment(m) {
|
|
7752
|
-
return distance$
|
|
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$
|
|
7760
|
+
lF = distance$2(jF, p73);
|
|
7761
7761
|
} else {
|
|
7762
7762
|
p73 = lineIntersection(CK.pointT, -60, jF, m);
|
|
7763
|
-
lF = distance$
|
|
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$
|
|
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$
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
28138
|
-
|
|
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'
|
|
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'] =
|
|
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,
|
|
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 :
|
|
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$
|
|
54429
|
+
return isArrayOfPoints(arr) && arr.length > 3 && samePoint$3(arr[0], arr[arr.length - 1]);
|
|
54008
54430
|
}
|
|
54009
54431
|
|
|
54010
|
-
function samePoint$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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.
|
|
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,
|