mapshaper 0.7.69 → 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 +1707 -844
- package/package.json +1 -1
- package/www/mapshaper-gui.js +1949 -272
- package/www/mapshaper.js +1707 -844
- package/www/page.css +39 -3
package/mapshaper.js
CHANGED
|
@@ -5882,13 +5882,13 @@
|
|
|
5882
5882
|
return msg;
|
|
5883
5883
|
}
|
|
5884
5884
|
|
|
5885
|
-
function requirePointLayer$
|
|
5885
|
+
function requirePointLayer$2(lyr, msg) {
|
|
5886
5886
|
if (!lyr || lyr.geometry_type !== 'point')
|
|
5887
5887
|
stop$1(layerTypeMessage(lyr, "Expected a point layer", msg));
|
|
5888
5888
|
}
|
|
5889
5889
|
|
|
5890
5890
|
function requireSinglePointLayer(lyr, msg) {
|
|
5891
|
-
requirePointLayer$
|
|
5891
|
+
requirePointLayer$2(lyr);
|
|
5892
5892
|
if (countMultiPartFeatures(lyr.shapes) > 0) {
|
|
5893
5893
|
stop$1(msg || 'This command requires single points; layer contains multi-point features.');
|
|
5894
5894
|
}
|
|
@@ -6128,7 +6128,7 @@
|
|
|
6128
6128
|
requireDataFields: requireDataFields,
|
|
6129
6129
|
requireNotRasterLayer: requireNotRasterLayer,
|
|
6130
6130
|
requirePathLayer: requirePathLayer,
|
|
6131
|
-
requirePointLayer: requirePointLayer$
|
|
6131
|
+
requirePointLayer: requirePointLayer$2,
|
|
6132
6132
|
requirePolygonLayer: requirePolygonLayer,
|
|
6133
6133
|
requirePolylineLayer: requirePolylineLayer,
|
|
6134
6134
|
requireSinglePointLayer: requireSinglePointLayer,
|
|
@@ -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
|
|
|
@@ -26245,10 +26245,10 @@
|
|
|
26245
26245
|
parts.unshift('hatches');
|
|
26246
26246
|
obj = parseHatches(parts); // hatches is the default, name can be omitted
|
|
26247
26247
|
}
|
|
26248
|
-
|
|
26249
|
-
|
|
26250
|
-
|
|
26251
|
-
|
|
26248
|
+
// Silent: this is also how a value is tested for being a pattern at all
|
|
26249
|
+
// (-style tries every value as a literal before trying it as an
|
|
26250
|
+
// expression), so a failure here is not necessarily a mistake. The SVG
|
|
26251
|
+
// exporter reports the patterns it has to drop.
|
|
26252
26252
|
return obj;
|
|
26253
26253
|
}
|
|
26254
26254
|
|
|
@@ -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,13 +26488,19 @@
|
|
|
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) {
|
|
26389
26498
|
var o = parsePattern(str);
|
|
26390
26499
|
var svg;
|
|
26391
|
-
if (!o)
|
|
26500
|
+
if (!o) {
|
|
26501
|
+
message('Invalid pattern, ignoring:', str);
|
|
26502
|
+
return null;
|
|
26503
|
+
}
|
|
26392
26504
|
if (o.type == 'hatches') {
|
|
26393
26505
|
svg = makeHatchPatternSVG(o);
|
|
26394
26506
|
} else if (o.type == 'dots' || o.type == 'squares') {
|
|
@@ -26459,6 +26571,8 @@
|
|
|
26459
26571
|
var SvgHatch = /*#__PURE__*/Object.freeze({
|
|
26460
26572
|
__proto__: null,
|
|
26461
26573
|
convertFillPattern: convertFillPattern,
|
|
26574
|
+
decodePatternId: decodePatternId,
|
|
26575
|
+
getHashId: getHashId,
|
|
26462
26576
|
parseDashes: parseDashes,
|
|
26463
26577
|
parseDots: parseDots,
|
|
26464
26578
|
parseHatches: parseHatches,
|
|
@@ -26756,6 +26870,11 @@
|
|
|
26756
26870
|
'callout-color': 'color',
|
|
26757
26871
|
'callout-width': 'number',
|
|
26758
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',
|
|
26759
26878
|
'letter-spacing': 'measure',
|
|
26760
26879
|
'line-height': 'measure',
|
|
26761
26880
|
opacity: 'number',
|
|
@@ -27067,6 +27186,8 @@
|
|
|
27067
27186
|
val = parseCalloutType(strVal);
|
|
27068
27187
|
} else if (type == 'calloutend') {
|
|
27069
27188
|
val = parseCalloutEnd(strVal);
|
|
27189
|
+
} else if (type == 'lineend') {
|
|
27190
|
+
val = parseLineEnd(strVal);
|
|
27070
27191
|
} else if (type == 'pointpair') {
|
|
27071
27192
|
val = parsePointPair(strVal);
|
|
27072
27193
|
val = val ? formatPointPair(val) : null;
|
|
@@ -27118,6 +27239,12 @@
|
|
|
27118
27239
|
return /^(arrow|open-arrow|none)$/.test(end) ? end : null;
|
|
27119
27240
|
}
|
|
27120
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
|
+
|
|
27121
27248
|
// "x,y" -> [x, y], or null. Stored as a string so that a pair is set and
|
|
27122
27249
|
// cleared as one value and reads plainly in a table.
|
|
27123
27250
|
function parsePointPair(str) {
|
|
@@ -27237,6 +27364,7 @@
|
|
|
27237
27364
|
parseCalloutEnd: parseCalloutEnd,
|
|
27238
27365
|
parseCalloutType: parseCalloutType,
|
|
27239
27366
|
parseLabelPosition: parseLabelPosition,
|
|
27367
|
+
parseLineEnd: parseLineEnd,
|
|
27240
27368
|
parsePointPair: parsePointPair,
|
|
27241
27369
|
parseStyleLiteral: parseStyleLiteral,
|
|
27242
27370
|
parseSvgMeasure: parseSvgMeasure,
|
|
@@ -27383,6 +27511,126 @@
|
|
|
27383
27511
|
splitLabelHalos: splitLabelHalos
|
|
27384
27512
|
});
|
|
27385
27513
|
|
|
27514
|
+
// Bold words inside a label: label-text='Israel <b>restricted access</b> here'.
|
|
27515
|
+
//
|
|
27516
|
+
// The markup is part of the text rather than a column beside it, so that the
|
|
27517
|
+
// words and their weight cannot drift apart: an expression, a join or a -labels
|
|
27518
|
+
// text= that rewrites the text rewrites the bold with it. The GUI editor never
|
|
27519
|
+
// shows the tags -- it reads them into ranges and writes them back.
|
|
27520
|
+
//
|
|
27521
|
+
// Only a matched <b>...</b> pair with no other b tag inside it is markup.
|
|
27522
|
+
// Anything else -- an unclosed <b>, a stray </b>, "a < b" -- is text, and is
|
|
27523
|
+
// drawn as typed.
|
|
27524
|
+
//
|
|
27525
|
+
// See docs/development/label-tool-design.md.
|
|
27526
|
+
|
|
27527
|
+
var BOLD_PAIR_RXP = /<b>((?:(?!<\/?b>)[\s\S])*?)<\/b>/gi;
|
|
27528
|
+
var BOLD_TAG_RXP = /<\/?b>/i;
|
|
27529
|
+
|
|
27530
|
+
// The font-weight a bold run is drawn with: the keyword, which a browser and
|
|
27531
|
+
// the Node text measurer both resolve to the family's own bold face.
|
|
27532
|
+
var LABEL_BOLD_WEIGHT = 'bold';
|
|
27533
|
+
|
|
27534
|
+
// @str -> {text, bold}: the text with its bold pairs removed, and the bold
|
|
27535
|
+
// stretches as ascending, non-overlapping [start, end) offsets into it.
|
|
27536
|
+
function parseBoldMarkup(str) {
|
|
27537
|
+
var s = str === null || str === undefined ? '' : String(str);
|
|
27538
|
+
var text = '', bold = [], last = 0, m;
|
|
27539
|
+
if (!BOLD_TAG_RXP.test(s)) return {text: s, bold: bold};
|
|
27540
|
+
BOLD_PAIR_RXP.lastIndex = 0;
|
|
27541
|
+
while ((m = BOLD_PAIR_RXP.exec(s))) {
|
|
27542
|
+
text += s.substring(last, m.index);
|
|
27543
|
+
if (m[1]) {
|
|
27544
|
+
bold.push([text.length, text.length + m[1].length]);
|
|
27545
|
+
text += m[1];
|
|
27546
|
+
}
|
|
27547
|
+
last = m.index + m[0].length;
|
|
27548
|
+
}
|
|
27549
|
+
text += s.substring(last);
|
|
27550
|
+
return {text: text, bold: normalizeBoldRanges(bold)};
|
|
27551
|
+
}
|
|
27552
|
+
|
|
27553
|
+
function stripBoldMarkup(str) {
|
|
27554
|
+
return parseBoldMarkup(str).text;
|
|
27555
|
+
}
|
|
27556
|
+
|
|
27557
|
+
// Sorted, clipped to non-empty, with touching and overlapping ranges merged.
|
|
27558
|
+
// Returns a new array.
|
|
27559
|
+
function normalizeBoldRanges(ranges) {
|
|
27560
|
+
var sorted = (ranges || []).filter(function(r) {
|
|
27561
|
+
return r && r[1] > r[0];
|
|
27562
|
+
}).map(function(r) {
|
|
27563
|
+
return [r[0], r[1]];
|
|
27564
|
+
}).sort(function(a, b) {
|
|
27565
|
+
return a[0] - b[0];
|
|
27566
|
+
});
|
|
27567
|
+
var out = [];
|
|
27568
|
+
sorted.forEach(function(r) {
|
|
27569
|
+
var prev = out[out.length - 1];
|
|
27570
|
+
if (prev && r[0] <= prev[1]) {
|
|
27571
|
+
prev[1] = Math.max(prev[1], r[1]);
|
|
27572
|
+
} else {
|
|
27573
|
+
out.push(r);
|
|
27574
|
+
}
|
|
27575
|
+
});
|
|
27576
|
+
return out;
|
|
27577
|
+
}
|
|
27578
|
+
|
|
27579
|
+
// The characters [start, end) of @text as runs of one weight:
|
|
27580
|
+
// [{text, bold}, ...], with nothing for an empty stretch.
|
|
27581
|
+
function getBoldRuns(text, bold, start, end) {
|
|
27582
|
+
var runs = [], pos = start, i, r, a, b;
|
|
27583
|
+
for (i = 0; i < bold.length && pos < end; i++) {
|
|
27584
|
+
r = bold[i];
|
|
27585
|
+
a = Math.max(r[0], pos);
|
|
27586
|
+
b = Math.min(r[1], end);
|
|
27587
|
+
if (b <= a) continue;
|
|
27588
|
+
if (a > pos) runs.push({text: text.substring(pos, a), bold: false});
|
|
27589
|
+
runs.push({text: text.substring(a, b), bold: true});
|
|
27590
|
+
pos = b;
|
|
27591
|
+
}
|
|
27592
|
+
if (pos < end) runs.push({text: text.substring(pos, end), bold: false});
|
|
27593
|
+
return runs;
|
|
27594
|
+
}
|
|
27595
|
+
|
|
27596
|
+
// @runs -> @obj with the first plain run as its value and the rest as <tspan>s,
|
|
27597
|
+
// which is the shape the stringifier writes as mixed content. A label with no
|
|
27598
|
+
// bold in it comes out exactly as it did before bold existed: one value and no
|
|
27599
|
+
// children. Returns @obj.
|
|
27600
|
+
function applyRunsToSvgObject(obj, runs) {
|
|
27601
|
+
var i = 0;
|
|
27602
|
+
var children = [];
|
|
27603
|
+
if (runs.length > 0 && !runs[0].bold) {
|
|
27604
|
+
obj.value = runs[0].text;
|
|
27605
|
+
i = 1;
|
|
27606
|
+
} else {
|
|
27607
|
+
obj.value = '';
|
|
27608
|
+
}
|
|
27609
|
+
for (; i < runs.length; i++) {
|
|
27610
|
+
children.push(renderRun(runs[i]));
|
|
27611
|
+
}
|
|
27612
|
+
if (children.length > 0) {
|
|
27613
|
+
obj.children = children.concat(obj.children || []);
|
|
27614
|
+
}
|
|
27615
|
+
return obj;
|
|
27616
|
+
}
|
|
27617
|
+
|
|
27618
|
+
function renderRun(run) {
|
|
27619
|
+
var o = {tag: 'tspan', value: run.text};
|
|
27620
|
+
if (run.bold) o.properties = {'font-weight': LABEL_BOLD_WEIGHT};
|
|
27621
|
+
return o;
|
|
27622
|
+
}
|
|
27623
|
+
|
|
27624
|
+
var SvgLabelMarkup = /*#__PURE__*/Object.freeze({
|
|
27625
|
+
__proto__: null,
|
|
27626
|
+
LABEL_BOLD_WEIGHT: LABEL_BOLD_WEIGHT,
|
|
27627
|
+
applyRunsToSvgObject: applyRunsToSvgObject,
|
|
27628
|
+
getBoldRuns: getBoldRuns,
|
|
27629
|
+
normalizeBoldRanges: normalizeBoldRanges,
|
|
27630
|
+
parseBoldMarkup: parseBoldMarkup,
|
|
27631
|
+
stripBoldMarkup: stripBoldMarkup
|
|
27632
|
+
});
|
|
27633
|
+
|
|
27386
27634
|
// Accepting \n (two chars) as an alternative to the newline character
|
|
27387
27635
|
// (sometimes, '\n' is not converted to newline, e.g. in a Makefile)
|
|
27388
27636
|
// Also accepting <br>
|
|
@@ -27399,21 +27647,45 @@
|
|
|
27399
27647
|
var anyLabelBreakRxp = /(\n|\\n|<br>|<wbr>)/i;
|
|
27400
27648
|
var softBreakRxp = /^<wbr>$/i;
|
|
27401
27649
|
|
|
27402
|
-
// The lines a label's text is drawn as
|
|
27403
|
-
// dropped: a trailing space would move an end- or middle-justified line by a
|
|
27404
|
-
// space's width.
|
|
27650
|
+
// The lines a label's text is drawn as, without its bold markup.
|
|
27405
27651
|
function splitLabelLines(text) {
|
|
27406
|
-
|
|
27407
|
-
|
|
27652
|
+
return splitLabelLineRuns(text).map(function(runs) {
|
|
27653
|
+
return runs.map(function(run) { return run.text; }).join('');
|
|
27654
|
+
});
|
|
27655
|
+
}
|
|
27656
|
+
|
|
27657
|
+
// The lines a label's text is drawn as, each one a list of runs of one weight:
|
|
27658
|
+
// [[{text, bold}, ...], ...]. A bold stretch can cross a line break, and is
|
|
27659
|
+
// then a run on each line.
|
|
27660
|
+
//
|
|
27661
|
+
// Whitespace before a soft break is dropped: a trailing space would move an
|
|
27662
|
+
// end- or middle-justified line by a space's width.
|
|
27663
|
+
function splitLabelLineRuns(text) {
|
|
27664
|
+
var o = parseBoldMarkup(String(text));
|
|
27665
|
+
var parts = o.text.split(anyLabelBreakRxp);
|
|
27666
|
+
var pos = parts[0].length;
|
|
27667
|
+
var lines = [getBoldRuns(o.text, o.bold, 0, pos)];
|
|
27408
27668
|
for (var i = 1; i < parts.length; i += 2) {
|
|
27409
27669
|
if (softBreakRxp.test(parts[i])) {
|
|
27410
|
-
lines[lines.length - 1]
|
|
27670
|
+
trimTrailingSpace(lines[lines.length - 1]);
|
|
27411
27671
|
}
|
|
27412
|
-
|
|
27672
|
+
pos += parts[i].length;
|
|
27673
|
+
lines.push(getBoldRuns(o.text, o.bold, pos, pos + parts[i + 1].length));
|
|
27674
|
+
pos += parts[i + 1].length;
|
|
27413
27675
|
}
|
|
27414
27676
|
return lines;
|
|
27415
27677
|
}
|
|
27416
27678
|
|
|
27679
|
+
function trimTrailingSpace(runs) {
|
|
27680
|
+
var last;
|
|
27681
|
+
while (runs.length > 0) {
|
|
27682
|
+
last = runs[runs.length - 1];
|
|
27683
|
+
last.text = last.text.replace(/\s+$/, '');
|
|
27684
|
+
if (last.text) return;
|
|
27685
|
+
runs.pop();
|
|
27686
|
+
}
|
|
27687
|
+
}
|
|
27688
|
+
|
|
27417
27689
|
function removeSoftBreaks(text) {
|
|
27418
27690
|
return String(text).replace(/<wbr>/gi, '');
|
|
27419
27691
|
}
|
|
@@ -27464,7 +27736,7 @@
|
|
|
27464
27736
|
// already been through it, so calling it here as well costs nothing and means
|
|
27465
27737
|
// every way into the renderer draws a label in the position it is stored in.
|
|
27466
27738
|
var rec = resolveLabelPosition(recArg);
|
|
27467
|
-
var morelines =
|
|
27739
|
+
var morelines = splitLabelLineRuns(toLabelString(rec['label-text']));
|
|
27468
27740
|
var line = morelines.shift();
|
|
27469
27741
|
var obj;
|
|
27470
27742
|
var dx = applyAlignmentShift(rec);
|
|
@@ -27475,23 +27747,21 @@
|
|
|
27475
27747
|
y: dy,
|
|
27476
27748
|
x: dx
|
|
27477
27749
|
};
|
|
27478
|
-
obj = {
|
|
27750
|
+
obj = applyRunsToSvgObject({
|
|
27479
27751
|
tag: 'text',
|
|
27480
|
-
value: line,
|
|
27481
27752
|
properties: properties
|
|
27482
|
-
};
|
|
27753
|
+
}, line);
|
|
27483
27754
|
if (morelines.length > 0) {
|
|
27484
27755
|
// multiline label
|
|
27485
|
-
obj.children = [];
|
|
27756
|
+
obj.children = obj.children || [];
|
|
27486
27757
|
morelines.forEach(function(line) {
|
|
27487
|
-
var tspan = {
|
|
27758
|
+
var tspan = applyRunsToSvgObject({
|
|
27488
27759
|
tag: 'tspan',
|
|
27489
|
-
value: line,
|
|
27490
27760
|
properties: {
|
|
27491
27761
|
x: dx,
|
|
27492
27762
|
dy: getLineHeightDy(rec['line-height'])
|
|
27493
27763
|
}
|
|
27494
|
-
};
|
|
27764
|
+
}, line);
|
|
27495
27765
|
obj.children.push(tspan);
|
|
27496
27766
|
});
|
|
27497
27767
|
}
|
|
@@ -27591,11 +27861,91 @@
|
|
|
27591
27861
|
removeSoftBreaks: removeSoftBreaks,
|
|
27592
27862
|
renderLabel: renderLabel$1,
|
|
27593
27863
|
renderStyledLabel: renderStyledLabel,
|
|
27864
|
+
splitLabelLineRuns: splitLabelLineRuns,
|
|
27594
27865
|
splitLabelLines: splitLabelLines,
|
|
27595
27866
|
toLabelString: toLabelString,
|
|
27596
27867
|
toPixels: toPixels
|
|
27597
27868
|
});
|
|
27598
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
|
+
|
|
27599
27949
|
// A callout: a line from a label's anchor to its text, straight, elbowed or
|
|
27600
27950
|
// curved, optionally ending in an arrowhead at the anchor. A dot at the anchor
|
|
27601
27951
|
// is the label's icon, not a callout marker.
|
|
@@ -27608,7 +27958,7 @@
|
|
|
27608
27958
|
// See docs/development/text-annotation-design.md.
|
|
27609
27959
|
|
|
27610
27960
|
var DEFAULT_PADDING$1 = 3;
|
|
27611
|
-
var DEFAULT_LINE_WIDTH = 1;
|
|
27961
|
+
var DEFAULT_LINE_WIDTH$1 = 1;
|
|
27612
27962
|
// Space left between the anchor's symbol and the end of the line
|
|
27613
27963
|
var SYMBOL_CLEARANCE = 2;
|
|
27614
27964
|
// How far an automatic curve bows out, as a fraction of its chord
|
|
@@ -27616,14 +27966,6 @@
|
|
|
27616
27966
|
// The shortest first leg an automatic elbow into the top or bottom of the text
|
|
27617
27967
|
// is drawn with, px
|
|
27618
27968
|
var MIN_LEG = 8;
|
|
27619
|
-
// The angle at an arrowhead's point, degrees. The open one is wider, since a
|
|
27620
|
-
// narrow chevron drawn with a line reads as a thickened line more than as an
|
|
27621
|
-
// arrow.
|
|
27622
|
-
var ARROW_ANGLE = 44;
|
|
27623
|
-
var OPEN_ARROW_ANGLE = 70;
|
|
27624
|
-
// How far into a solid arrowhead the line reaches, as a fraction of the
|
|
27625
|
-
// head's length: far enough that its cap is hidden, short of the tip.
|
|
27626
|
-
var ARROW_LINE_OVERLAP = 0.7;
|
|
27627
27969
|
// Where glyphs sit relative to their baseline, in ems, for estimating the
|
|
27628
27970
|
// height of a block of text that is measured only for its width
|
|
27629
27971
|
var ASCENT = 0.8;
|
|
@@ -27668,8 +28010,7 @@
|
|
|
27668
28010
|
// default line, and growing with the line, so that a heavier line does not end
|
|
27669
28011
|
// in a head too small to read.
|
|
27670
28012
|
function getDefaultCalloutEndSize(end, lineWidth) {
|
|
27671
|
-
|
|
27672
|
-
return 7 + 3 * w;
|
|
28013
|
+
return getDefaultArrowSize(lineWidth);
|
|
27673
28014
|
}
|
|
27674
28015
|
|
|
27675
28016
|
// 'arrow', 'open-arrow' or 'none'
|
|
@@ -27777,7 +28118,7 @@
|
|
|
27777
28118
|
v = o.via || getAutoElbow(a, t, box);
|
|
27778
28119
|
// A corner on either end is no corner -- straight under the text, say --
|
|
27779
28120
|
// and a leg of no length has no direction for an arrowhead to take.
|
|
27780
|
-
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 ?
|
|
27781
28122
|
[a, v, t] : [a, t]};
|
|
27782
28123
|
} else if (o.type == 'curve') {
|
|
27783
28124
|
v = o.via || getAutoBend(a, t);
|
|
@@ -27805,7 +28146,7 @@
|
|
|
27805
28146
|
// the chevron's point: the point is set back by that much, so that the
|
|
27806
28147
|
// arrow ends where a filled one would. The join and the round cap at the
|
|
27807
28148
|
// far end each add half a line width to an arm, which is taken off it.
|
|
27808
|
-
lineWidth = o.width > 0 ? o.width : DEFAULT_LINE_WIDTH;
|
|
28149
|
+
lineWidth = o.width > 0 ? o.width : DEFAULT_LINE_WIDTH$1;
|
|
27809
28150
|
path = trimPath(path, lineWidth / 2) || path;
|
|
27810
28151
|
dir = getStartDirection(path);
|
|
27811
28152
|
if (!dir) return null;
|
|
@@ -27982,44 +28323,21 @@
|
|
|
27982
28323
|
function hasLength(path) {
|
|
27983
28324
|
var c = path.coords;
|
|
27984
28325
|
for (var i = 1; i < c.length; i++) {
|
|
27985
|
-
if (distance(c[0], c[i]) > 0) return true;
|
|
28326
|
+
if (distance$1(c[0], c[i]) > 0) return true;
|
|
27986
28327
|
}
|
|
27987
28328
|
return false;
|
|
27988
28329
|
}
|
|
27989
28330
|
|
|
27990
28331
|
function trimPolyline(coords, r) {
|
|
27991
|
-
var
|
|
27992
|
-
|
|
27993
|
-
if (distance(p0, coords[i]) > r) {
|
|
27994
|
-
return {
|
|
27995
|
-
kind: 'polyline',
|
|
27996
|
-
coords: [getCircleExit(p0, r, coords[i - 1], coords[i])].concat(coords.slice(i))
|
|
27997
|
-
};
|
|
27998
|
-
}
|
|
27999
|
-
}
|
|
28000
|
-
return null;
|
|
28001
|
-
}
|
|
28002
|
-
|
|
28003
|
-
// The point where segment @p-@q, which starts inside the circle and ends
|
|
28004
|
-
// outside it, crosses it.
|
|
28005
|
-
function getCircleExit(c, r, p, q) {
|
|
28006
|
-
var dx = q[0] - p[0];
|
|
28007
|
-
var dy = q[1] - p[1];
|
|
28008
|
-
var ex = p[0] - c[0];
|
|
28009
|
-
var ey = p[1] - c[1];
|
|
28010
|
-
var a = dx * dx + dy * dy;
|
|
28011
|
-
var b = 2 * (dx * ex + dy * ey);
|
|
28012
|
-
var k = ex * ex + ey * ey - r * r;
|
|
28013
|
-
var s = (-b + Math.sqrt(Math.max(0, b * b - 4 * a * k))) / (2 * a);
|
|
28014
|
-
s = Math.max(0, Math.min(1, s));
|
|
28015
|
-
return [p[0] + s * dx, p[1] + s * dy];
|
|
28332
|
+
var trimmed = trimPolyline$1(coords, r);
|
|
28333
|
+
return trimmed ? {kind: 'polyline', coords: trimmed} : null;
|
|
28016
28334
|
}
|
|
28017
28335
|
|
|
28018
28336
|
function trimBezier(c, r) {
|
|
28019
28337
|
var steps = 64;
|
|
28020
28338
|
var lo = 0, hi = -1, mid, i;
|
|
28021
28339
|
for (i = 1; i <= steps; i++) {
|
|
28022
|
-
if (distance(c[0], getBezierPoint(c, i / steps)) > r) {
|
|
28340
|
+
if (distance$1(c[0], getBezierPoint(c, i / steps)) > r) {
|
|
28023
28341
|
hi = i / steps;
|
|
28024
28342
|
lo = (i - 1) / steps;
|
|
28025
28343
|
break;
|
|
@@ -28028,7 +28346,7 @@
|
|
|
28028
28346
|
if (hi < 0) return null;
|
|
28029
28347
|
for (i = 0; i < 24; i++) {
|
|
28030
28348
|
mid = (lo + hi) / 2;
|
|
28031
|
-
if (distance(c[0], getBezierPoint(c, mid)) > r) hi = mid;
|
|
28349
|
+
if (distance$1(c[0], getBezierPoint(c, mid)) > r) hi = mid;
|
|
28032
28350
|
else lo = mid;
|
|
28033
28351
|
}
|
|
28034
28352
|
return {kind: 'bezier', coords: splitBezier(c, hi)};
|
|
@@ -28054,7 +28372,7 @@
|
|
|
28054
28372
|
function getStartDirection(path) {
|
|
28055
28373
|
var c = path.coords;
|
|
28056
28374
|
var next = c[1];
|
|
28057
|
-
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];
|
|
28058
28376
|
return getUnitVector(c[0], next);
|
|
28059
28377
|
}
|
|
28060
28378
|
|
|
@@ -28086,8 +28404,8 @@
|
|
|
28086
28404
|
ctrl = intersectRays(start, dir, end, getUnitVector(end, rest.coords[1]));
|
|
28087
28405
|
// A curve that doubles back, or runs straight on: a control point on the
|
|
28088
28406
|
// head's axis still takes the line out of the head straight.
|
|
28089
|
-
if (!ctrl) ctrl = [start[0] + dir[0] * distance(start, end) / 2,
|
|
28090
|
-
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];
|
|
28091
28409
|
return {dir: dir, path: {kind: 'bezier', coords: [start, ctrl, end]}};
|
|
28092
28410
|
}
|
|
28093
28411
|
|
|
@@ -28105,20 +28423,6 @@
|
|
|
28105
28423
|
return s > 0 && u > 0 ? [p[0] + d[0] * s, p[1] + d[1] * s] : null;
|
|
28106
28424
|
}
|
|
28107
28425
|
|
|
28108
|
-
// [tip, wing, wing], for a head with sides @side long meeting at @angle degrees
|
|
28109
|
-
function getArrowHead(tip, dir, side, angle) {
|
|
28110
|
-
var len = getArrowHeadLength(side, angle);
|
|
28111
|
-
var half = side * Math.sin(angle / 2 * Math.PI / 180);
|
|
28112
|
-
var bx = tip[0] + dir[0] * len;
|
|
28113
|
-
var by = tip[1] + dir[1] * len;
|
|
28114
|
-
return [tip, [bx - dir[1] * half, by + dir[0] * half], [bx + dir[1] * half, by - dir[0] * half]];
|
|
28115
|
-
}
|
|
28116
|
-
|
|
28117
|
-
// How far along the line a head with sides @side long reaches
|
|
28118
|
-
function getArrowHeadLength(side, angle) {
|
|
28119
|
-
return side * Math.cos(angle / 2 * Math.PI / 180);
|
|
28120
|
-
}
|
|
28121
|
-
|
|
28122
28426
|
// Utilities
|
|
28123
28427
|
|
|
28124
28428
|
function padBox$1(box, pad) {
|
|
@@ -28135,19 +28439,6 @@
|
|
|
28135
28439
|
return p[0] > box.xmin && p[0] < box.xmax && p[1] > box.ymin && p[1] < box.ymax;
|
|
28136
28440
|
}
|
|
28137
28441
|
|
|
28138
|
-
function getUnitVector(p, q) {
|
|
28139
|
-
var dx = q[0] - p[0];
|
|
28140
|
-
var dy = q[1] - p[1];
|
|
28141
|
-
var len = Math.sqrt(dx * dx + dy * dy);
|
|
28142
|
-
return len > 0 ? [dx / len, dy / len] : null;
|
|
28143
|
-
}
|
|
28144
|
-
|
|
28145
|
-
function distance(p, q) {
|
|
28146
|
-
var dx = q[0] - p[0];
|
|
28147
|
-
var dy = q[1] - p[1];
|
|
28148
|
-
return Math.sqrt(dx * dx + dy * dy);
|
|
28149
|
-
}
|
|
28150
|
-
|
|
28151
28442
|
function lerp(p, q, t) {
|
|
28152
28443
|
return [p[0] + (q[0] - p[0]) * t, p[1] + (q[1] - p[1]) * t];
|
|
28153
28444
|
}
|
|
@@ -28157,8 +28448,8 @@
|
|
|
28157
28448
|
}
|
|
28158
28449
|
|
|
28159
28450
|
function getLineWidth(rec) {
|
|
28160
|
-
var w = getNumber(rec['callout-width'], DEFAULT_LINE_WIDTH);
|
|
28161
|
-
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;
|
|
28162
28453
|
}
|
|
28163
28454
|
|
|
28164
28455
|
function getFontSizeInPx$1(rec) {
|
|
@@ -28211,6 +28502,9 @@
|
|
|
28211
28502
|
return str;
|
|
28212
28503
|
}
|
|
28213
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
|
+
|
|
28214
28508
|
var symbolRenderers = {
|
|
28215
28509
|
line: line,
|
|
28216
28510
|
polygon: polygon,
|
|
@@ -28283,6 +28577,7 @@
|
|
|
28283
28577
|
if (type == 'square') return square(getIconStyleData(d, r), 0, 0);
|
|
28284
28578
|
if (type == 'ring') return ring(getIconStyleData(d, r), 0, 0);
|
|
28285
28579
|
if (type == 'star') return star(getIconStyleData(d, r));
|
|
28580
|
+
if (type == 'nyt-star') return nytStar(getIconStyleData(d, r));
|
|
28286
28581
|
return empty();
|
|
28287
28582
|
}
|
|
28288
28583
|
|
|
@@ -28292,8 +28587,11 @@
|
|
|
28292
28587
|
return d.r > 0 ? d.r : 5;
|
|
28293
28588
|
}
|
|
28294
28589
|
size = d['icon-size'];
|
|
28295
|
-
if (type == 'circle'
|
|
28590
|
+
if (type == 'circle') {
|
|
28296
28591
|
size -= 1;
|
|
28592
|
+
} else if (type == 'ring') {
|
|
28593
|
+
// the stroke's outer half is inside icon-size
|
|
28594
|
+
size -= RING_STROKE_WIDTH;
|
|
28297
28595
|
} else if (type == 'star') {
|
|
28298
28596
|
size += 1;
|
|
28299
28597
|
}
|
|
@@ -28400,7 +28698,7 @@
|
|
|
28400
28698
|
o.properties.fill = 'none';
|
|
28401
28699
|
o.properties.stroke = d.fill;
|
|
28402
28700
|
if (!o.properties['stroke-width']) {
|
|
28403
|
-
o.properties['stroke-width'] =
|
|
28701
|
+
o.properties['stroke-width'] = RING_STROKE_WIDTH;
|
|
28404
28702
|
}
|
|
28405
28703
|
return o;
|
|
28406
28704
|
}
|
|
@@ -28412,6 +28710,43 @@
|
|
|
28412
28710
|
return o;
|
|
28413
28711
|
}
|
|
28414
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
|
+
|
|
28415
28750
|
function nonCirclePointFilter(k) {
|
|
28416
28751
|
return k != 'r';
|
|
28417
28752
|
}
|
|
@@ -28491,6 +28826,7 @@
|
|
|
28491
28826
|
|
|
28492
28827
|
var SvgSymbols = /*#__PURE__*/Object.freeze({
|
|
28493
28828
|
__proto__: null,
|
|
28829
|
+
RING_STROKE_WIDTH: RING_STROKE_WIDTH,
|
|
28494
28830
|
featureHasIcon: featureHasIcon,
|
|
28495
28831
|
getAnchorSymbolRadius: getAnchorSymbolRadius,
|
|
28496
28832
|
getIconRadius: getIconRadius,
|
|
@@ -29627,16 +29963,14 @@
|
|
|
29627
29963
|
// a <tspan> inside a <textPath> advances along the path instead of
|
|
29628
29964
|
// stacking below it, so the lines are joined rather than dropping
|
|
29629
29965
|
// everything after the first one
|
|
29630
|
-
text = text.split(labelNewlineRxp).join(' ');
|
|
29631
29966
|
addToReport(report, 'joined', id);
|
|
29632
29967
|
}
|
|
29633
|
-
textPath = {
|
|
29968
|
+
textPath = applyRunsToSvgObject({
|
|
29634
29969
|
tag: 'textPath',
|
|
29635
|
-
value: text,
|
|
29636
29970
|
properties: {
|
|
29637
29971
|
startOffset: rec['label-start-offset'] || getDefaultStartOffset(rec)
|
|
29638
29972
|
}
|
|
29639
|
-
};
|
|
29973
|
+
}, joinLineRuns(splitLabelLineRuns(text)));
|
|
29640
29974
|
// the caller replaces this with a reference to a <defs> entry
|
|
29641
29975
|
textPath.properties[LABEL_PATH_PROPERTY] = d;
|
|
29642
29976
|
if (rec['label-side']) {
|
|
@@ -29700,6 +30034,28 @@
|
|
|
29700
30034
|
return '50%';
|
|
29701
30035
|
}
|
|
29702
30036
|
|
|
30037
|
+
// A path label's lines as one line of runs, with a space for each break. The
|
|
30038
|
+
// space takes the weight of the text before it, and runs of one weight that
|
|
30039
|
+
// the join brings together are merged, so that text with no bold in it is a
|
|
30040
|
+
// single run.
|
|
30041
|
+
function joinLineRuns(lines) {
|
|
30042
|
+
var out = [];
|
|
30043
|
+
lines.forEach(function(runs, i) {
|
|
30044
|
+
if (i > 0) appendRun(out, {text: ' ', bold: out.length > 0 && out[out.length - 1].bold});
|
|
30045
|
+
runs.forEach(function(run) { appendRun(out, run); });
|
|
30046
|
+
});
|
|
30047
|
+
return out;
|
|
30048
|
+
}
|
|
30049
|
+
|
|
30050
|
+
function appendRun(runs, run) {
|
|
30051
|
+
var last = runs[runs.length - 1];
|
|
30052
|
+
if (last && last.bold == run.bold) {
|
|
30053
|
+
last.text += run.text;
|
|
30054
|
+
} else {
|
|
30055
|
+
runs.push({text: run.text, bold: run.bold});
|
|
30056
|
+
}
|
|
30057
|
+
}
|
|
30058
|
+
|
|
29703
30059
|
function addToReport(report, key, id) {
|
|
29704
30060
|
if (report && report[key]) report[key].push(id);
|
|
29705
30061
|
}
|
|
@@ -29913,6 +30269,198 @@
|
|
|
29913
30269
|
}
|
|
29914
30270
|
*/
|
|
29915
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
|
+
|
|
29916
30464
|
var geojsonImporters = {
|
|
29917
30465
|
Point: importPoint,
|
|
29918
30466
|
Polygon: importPolygon,
|
|
@@ -29935,6 +30483,9 @@
|
|
|
29935
30483
|
// rather than several labels at several points
|
|
29936
30484
|
svgObj = renderPathLabel(d, geom.coordinates,
|
|
29937
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);
|
|
29938
30489
|
} else if (geomType && geom.coordinates) {
|
|
29939
30490
|
svgObj = geojsonImporters[geomType](geom.coordinates, d);
|
|
29940
30491
|
}
|
|
@@ -40716,6 +41267,22 @@ ${svg}
|
|
|
40716
41267
|
}
|
|
40717
41268
|
}
|
|
40718
41269
|
|
|
41270
|
+
// -labels adds a label when given coordinates=, and styles the target's labels
|
|
41271
|
+
// otherwise. Options that belong to one of these are refused in the other,
|
|
41272
|
+
// rather than ignored.
|
|
41273
|
+
function validateLabelsOpts(cmd) {
|
|
41274
|
+
var o = cmd.options;
|
|
41275
|
+
if ('coordinates' in o) {
|
|
41276
|
+
if (o.where || o.ids) {
|
|
41277
|
+
stop$1('where= and ids= select labels to style, and can not be used with coordinates=');
|
|
41278
|
+
}
|
|
41279
|
+
} else if (o.properties) {
|
|
41280
|
+
stop$1('properties= requires coordinates=');
|
|
41281
|
+
} else if ('name' in o && !o.no_replace) {
|
|
41282
|
+
stop$1('name= requires coordinates= or +');
|
|
41283
|
+
}
|
|
41284
|
+
}
|
|
41285
|
+
|
|
40719
41286
|
function validateExpressionOpt(cmd) {
|
|
40720
41287
|
if (!cmd.options.expression) {
|
|
40721
41288
|
error('Command requires a JavaScript expression');
|
|
@@ -41696,17 +42263,17 @@ ${svg}
|
|
|
41696
42263
|
nameOpt2 = { // for -calc and -info
|
|
41697
42264
|
describe: 'name the output layer'
|
|
41698
42265
|
},
|
|
41699
|
-
// label style properties accepted by -add-label
|
|
41700
|
-
//
|
|
41701
|
-
//
|
|
42266
|
+
// label style properties accepted by -labels and -add-label. -style
|
|
42267
|
+
// accepts these too, for backward compatibility, but leaves them out of
|
|
42268
|
+
// its help.
|
|
41702
42269
|
labelStyleOpts = {
|
|
41703
|
-
'font-family': {describe: '
|
|
41704
|
-
'font-size': {describe: '
|
|
42270
|
+
'font-family': {describe: 'CSS font family, e.g. Georgia (default is sans-serif)'},
|
|
42271
|
+
'font-size': {describe: 'font size (default is 12)'},
|
|
41705
42272
|
'font-style': {describe: 'normal or italic'},
|
|
41706
42273
|
'font-weight': {describe: 'normal, bold or a numeric weight'},
|
|
41707
42274
|
'font-stretch': {describe: 'e.g. condensed'},
|
|
41708
42275
|
'letter-spacing': {describe: 'extra space between letters'},
|
|
41709
|
-
'line-height': {describe: 'spacing between lines of a multi-line label; a bare number is a multiple of the font size'},
|
|
42276
|
+
'line-height': {describe: 'spacing between lines of a multi-line label; a bare number is a multiple of the font size (default is 1.1)'},
|
|
41710
42277
|
'text-anchor': {describe: 'start, middle or end'},
|
|
41711
42278
|
'label-align': {describe: 'how the lines of a multi-line label align: left, center or right'},
|
|
41712
42279
|
'dominant-baseline': {describe: 'vertical alignment, e.g. central'},
|
|
@@ -41717,23 +42284,30 @@ ${svg}
|
|
|
41717
42284
|
dy: {describe: 'vertical offset from the anchor'},
|
|
41718
42285
|
fill: {describe: 'text color'},
|
|
41719
42286
|
opacity: {describe: 'text opacity'},
|
|
41720
|
-
'halo-width': {describe: 'width of a halo around the text in px'},
|
|
42287
|
+
'halo-width': {describe: 'width of a halo around the text in px (default is 0, no halo)'},
|
|
41721
42288
|
'halo-color': {describe: 'halo color (default is white)'},
|
|
41722
42289
|
'halo-opacity': {describe: 'halo opacity, 0-1'},
|
|
41723
|
-
'label-width': {describe: 'width of a fixed-width text block in px'},
|
|
42290
|
+
'label-width': {describe: 'width of a fixed-width text block in px (lines are wrapped in the web UI)'},
|
|
41724
42291
|
callout: {describe: 'line from the anchor to the text: line, elbow or curve'},
|
|
41725
42292
|
'callout-end': {describe: 'arrowhead at the anchor end: arrow, open-arrow or none'},
|
|
41726
|
-
'callout-end-size': {describe: 'length of an arrowhead\'s sides in px'},
|
|
42293
|
+
'callout-end-size': {describe: 'length of an arrowhead\'s sides in px (default is 10)'},
|
|
41727
42294
|
'callout-via': {describe: 'x,y of an elbow\'s corner or a point on a curve, in px'},
|
|
41728
42295
|
'callout-attach': {describe: 'where the callout meets the text, as x,y fractions'},
|
|
41729
42296
|
'callout-gap': {describe: 'px between the callout and the anchor'},
|
|
41730
|
-
'callout-padding': {describe: 'px between the callout and the text'},
|
|
42297
|
+
'callout-padding': {describe: 'px between the callout and the text (default is 3)'},
|
|
41731
42298
|
'callout-color': {describe: 'callout color (defaults to the text color)'},
|
|
41732
|
-
'callout-width': {describe: 'callout line width in px'},
|
|
42299
|
+
'callout-width': {describe: 'callout line width in px (default is 1)'},
|
|
41733
42300
|
'callout-opacity': {describe: 'callout opacity, 0-1'},
|
|
41734
42301
|
css: {describe: 'inline css style'},
|
|
41735
42302
|
class: {describe: 'name of CSS class or classes (space-separated)'}
|
|
41736
42303
|
},
|
|
42304
|
+
// a label can carry a symbol at its anchor
|
|
42305
|
+
labelIconOpts = {
|
|
42306
|
+
icon: {describe: 'symbol drawn at the label anchor: circle, square, ring, star'},
|
|
42307
|
+
'icon-size': {describe: 'size of the anchor symbol in px'},
|
|
42308
|
+
'icon-color': {describe: 'color of the anchor symbol (defaults to the text color)'},
|
|
42309
|
+
'icon-opacity': {describe: 'opacity of the anchor symbol, 0-1'}
|
|
42310
|
+
},
|
|
41737
42311
|
noReplaceOpt2 = { // for -calc and -info
|
|
41738
42312
|
alias: '+',
|
|
41739
42313
|
type: 'flag',
|
|
@@ -41813,6 +42387,18 @@ ${svg}
|
|
|
41813
42387
|
type: 'distance'
|
|
41814
42388
|
};
|
|
41815
42389
|
|
|
42390
|
+
// The label options -style used to document, before -labels took them over.
|
|
42391
|
+
// fill, opacity, css and class are left out because -style documents them
|
|
42392
|
+
// for every kind of feature.
|
|
42393
|
+
function getHiddenLabelStyleOpts() {
|
|
42394
|
+
var shared = ['fill', 'opacity', 'css', 'class'];
|
|
42395
|
+
var opts = {'label-text': {}};
|
|
42396
|
+
Object.keys(labelStyleOpts).forEach(function(name) {
|
|
42397
|
+
if (shared.indexOf(name) == -1) opts[name] = {};
|
|
42398
|
+
});
|
|
42399
|
+
return opts;
|
|
42400
|
+
}
|
|
42401
|
+
|
|
41816
42402
|
var parser = new CommandParser();
|
|
41817
42403
|
parser.usage('Usage: mapshaper -<command> [options] ...');
|
|
41818
42404
|
|
|
@@ -43181,6 +43767,32 @@ ${svg}
|
|
|
43181
43767
|
.option('encoding', encodingOpt)
|
|
43182
43768
|
.option('target', targetOpt);
|
|
43183
43769
|
|
|
43770
|
+
parser.command('labels')
|
|
43771
|
+
.describe('style labels, convert points to labels or add a label')
|
|
43772
|
+
.validate(validateLabelsOpts)
|
|
43773
|
+
.option('text', {
|
|
43774
|
+
describe: 'label text (a field, JS expression or literal value)'
|
|
43775
|
+
})
|
|
43776
|
+
.option('label-text', {
|
|
43777
|
+
alias_to: 'text'
|
|
43778
|
+
})
|
|
43779
|
+
.option('coordinates', {
|
|
43780
|
+
describe: 'add a label at x,y, or a path label along x,y,x,y,...'
|
|
43781
|
+
})
|
|
43782
|
+
.option('where', whereOpt)
|
|
43783
|
+
.option('ids', {
|
|
43784
|
+
describe: 'comma-sep. list of ids of the labels to style',
|
|
43785
|
+
type: 'numbers'
|
|
43786
|
+
})
|
|
43787
|
+
.options(labelStyleOpts)
|
|
43788
|
+
.options(labelIconOpts)
|
|
43789
|
+
.option('properties', {
|
|
43790
|
+
describe: 'with coordinates=, other attributes as a JSON object'
|
|
43791
|
+
})
|
|
43792
|
+
.option('name', nameOpt)
|
|
43793
|
+
.option('no-replace', noReplaceOpt)
|
|
43794
|
+
.option('target', targetOpt);
|
|
43795
|
+
|
|
43184
43796
|
parser.command('lines')
|
|
43185
43797
|
.describe('convert a polygon or point layer to a polyline layer')
|
|
43186
43798
|
.option('fields', {
|
|
@@ -43728,6 +44340,15 @@ ${svg}
|
|
|
43728
44340
|
.option('stroke-opacity', {
|
|
43729
44341
|
describe: 'stroke opacity'
|
|
43730
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
|
+
})
|
|
43731
44352
|
.option('opacity', {
|
|
43732
44353
|
describe: 'opacity; example: 0.5'
|
|
43733
44354
|
})
|
|
@@ -43744,99 +44365,11 @@ ${svg}
|
|
|
43744
44365
|
describe: 'point icon color (defaults to fill color, then black)'
|
|
43745
44366
|
})
|
|
43746
44367
|
.option('icon-opacity', {
|
|
43747
|
-
describe: 'point icon opacity, 0-1
|
|
43748
|
-
})
|
|
43749
|
-
.option('label-text', {
|
|
43750
|
-
describe: 'label text (set this to export points as labels)'
|
|
43751
|
-
})
|
|
43752
|
-
.option('label-pos', {
|
|
43753
|
-
describe: 'label position; one of: n, s, e, w, ne, se, nw, sw, c'
|
|
43754
|
-
})
|
|
43755
|
-
.option('text-anchor', {
|
|
43756
|
-
describe: 'label alignment; one of: start, end, middle (default)'
|
|
43757
|
-
})
|
|
43758
|
-
.option('label-align', {
|
|
43759
|
-
describe: 'alignment of the lines of multi-line labels; left, center or right'
|
|
43760
|
-
})
|
|
43761
|
-
.option('halo-width', {
|
|
43762
|
-
describe: 'width of a halo around label text in px (default is 0, no halo)'
|
|
43763
|
-
})
|
|
43764
|
-
.option('halo-color', {
|
|
43765
|
-
describe: 'color of label halos (default is white)'
|
|
43766
|
-
})
|
|
43767
|
-
.option('halo-opacity', {
|
|
43768
|
-
describe: 'opacity of label halos, 0-1'
|
|
43769
|
-
})
|
|
43770
|
-
.option('dx', {
|
|
43771
|
-
describe: 'x offset of labels (default is 0)'
|
|
43772
|
-
})
|
|
43773
|
-
.option('dy', {
|
|
43774
|
-
describe: 'y offset of labels (default is 0/baseline-aligned)'
|
|
43775
|
-
})
|
|
43776
|
-
.option('font-size', {
|
|
43777
|
-
describe: 'size of label text (default is 12)'
|
|
43778
|
-
})
|
|
43779
|
-
.option('font-family', {
|
|
43780
|
-
describe: 'CSS font family of labels (default is sans-serif)'
|
|
44368
|
+
describe: 'point icon opacity, 0-1'
|
|
43781
44369
|
})
|
|
43782
|
-
|
|
43783
|
-
|
|
43784
|
-
|
|
43785
|
-
.option('font-style', {
|
|
43786
|
-
describe: 'CSS font style property of labels (e.g. italic)'
|
|
43787
|
-
})
|
|
43788
|
-
.option('font-stretch', {
|
|
43789
|
-
describe: 'CSS font stretch property of labels (e.g. condensed)'
|
|
43790
|
-
})
|
|
43791
|
-
.option('letter-spacing', {
|
|
43792
|
-
describe: 'CSS letter-spacing property of labels'
|
|
43793
|
-
})
|
|
43794
|
-
.option('line-height', {
|
|
43795
|
-
describe: 'line spacing of multi-line labels; a bare number is a multiple of the font size, as in CSS (default is 1.1)'
|
|
43796
|
-
})
|
|
43797
|
-
.option('dominant-baseline', {
|
|
43798
|
-
describe: 'vertical alignment of labels (e.g. central)'
|
|
43799
|
-
})
|
|
43800
|
-
.option('label-side', {
|
|
43801
|
-
describe: 'which side of its path a label sits on: left or right'
|
|
43802
|
-
})
|
|
43803
|
-
.option('label-start-offset', {
|
|
43804
|
-
describe: 'where label text starts along its path, e.g. 50%'
|
|
43805
|
-
})
|
|
43806
|
-
.option('label-width', {
|
|
43807
|
-
describe: 'width of a fixed-width text block in px (lines are wrapped in the web UI)'
|
|
43808
|
-
})
|
|
43809
|
-
.option('callout', {
|
|
43810
|
-
describe: 'line from a label\'s anchor to its text: line, elbow or curve'
|
|
43811
|
-
})
|
|
43812
|
-
.option('callout-end', {
|
|
43813
|
-
describe: 'arrowhead at the anchor end of a callout: arrow (solid), open-arrow or none'
|
|
43814
|
-
})
|
|
43815
|
-
.option('callout-end-size', {
|
|
43816
|
-
describe: 'length of an arrowhead\'s sides in px (default 10, more for a thicker line)'
|
|
43817
|
-
})
|
|
43818
|
-
.option('callout-via', {
|
|
43819
|
-
describe: 'x,y of an elbow\'s corner or a point on a curve, in px from the anchor'
|
|
43820
|
-
})
|
|
43821
|
-
.option('callout-attach', {
|
|
43822
|
-
describe: 'where a callout meets its text, as x,y fractions of the text box'
|
|
43823
|
-
})
|
|
43824
|
-
.option('callout-gap', {
|
|
43825
|
-
describe: 'px between a callout and its anchor (default: a line meets the icon, an arrow clears it)'
|
|
43826
|
-
})
|
|
43827
|
-
.option('callout-padding', {
|
|
43828
|
-
describe: 'px between a callout and its text (default is 3)'
|
|
43829
|
-
})
|
|
43830
|
-
.option('callout-color', {
|
|
43831
|
-
describe: 'callout color (defaults to the text color)'
|
|
43832
|
-
})
|
|
43833
|
-
.option('callout-width', {
|
|
43834
|
-
describe: 'callout line width in px (default is 1)'
|
|
43835
|
-
})
|
|
43836
|
-
.option('callout-opacity', {
|
|
43837
|
-
describe: 'callout opacity, 0-1'
|
|
43838
|
-
})
|
|
43839
|
-
.option('target', targetOpt);
|
|
44370
|
+
// deprecated in favor of -labels; accepted, but left out of the help
|
|
44371
|
+
.options(getHiddenLabelStyleOpts())
|
|
44372
|
+
.option('target', targetOpt);
|
|
43840
44373
|
|
|
43841
44374
|
parser.command('repel')
|
|
43842
44375
|
.describe('move overlapping point symbols apart')
|
|
@@ -44082,23 +44615,10 @@ ${svg}
|
|
|
44082
44615
|
.option('text', {
|
|
44083
44616
|
describe: 'label text'
|
|
44084
44617
|
})
|
|
44085
|
-
//
|
|
44086
|
-
//
|
|
44618
|
+
// superseded by -labels coordinates=; kept so that older session
|
|
44619
|
+
// histories still run
|
|
44087
44620
|
.options(labelStyleOpts)
|
|
44088
|
-
|
|
44089
|
-
// though they are not text properties
|
|
44090
|
-
.option('icon', {
|
|
44091
|
-
describe: 'symbol drawn at the label anchor: circle, square, ring, star'
|
|
44092
|
-
})
|
|
44093
|
-
.option('icon-size', {
|
|
44094
|
-
describe: 'size of the anchor symbol in px'
|
|
44095
|
-
})
|
|
44096
|
-
.option('icon-color', {
|
|
44097
|
-
describe: 'color of the anchor symbol (defaults to the text color)'
|
|
44098
|
-
})
|
|
44099
|
-
.option('icon-opacity', {
|
|
44100
|
-
describe: 'opacity of the anchor symbol, 0-1'
|
|
44101
|
-
})
|
|
44621
|
+
.options(labelIconOpts)
|
|
44102
44622
|
.option('properties', {
|
|
44103
44623
|
describe: 'other attributes, as a JSON object'
|
|
44104
44624
|
})
|
|
@@ -49441,9 +49961,28 @@ ${svg}
|
|
|
49441
49961
|
props[key] = val;
|
|
49442
49962
|
}
|
|
49443
49963
|
}
|
|
49964
|
+
importPatternFill(props);
|
|
49444
49965
|
return props;
|
|
49445
49966
|
}
|
|
49446
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
|
+
|
|
49447
49986
|
function getNodeStyles(node) {
|
|
49448
49987
|
var styles = {};
|
|
49449
49988
|
var attrs = node.attributes || [];
|
|
@@ -51395,6 +51934,626 @@ ${svg}
|
|
|
51395
51934
|
return d;
|
|
51396
51935
|
}
|
|
51397
51936
|
|
|
51937
|
+
// Insert cutting points in arcs, where bbox intersects other shapes
|
|
51938
|
+
// Return a polygon layer containing the bounding box vectors, divided at cutting points.
|
|
51939
|
+
function divideDatasetByBBox(dataset, bbox) {
|
|
51940
|
+
var arcs = dataset.arcs;
|
|
51941
|
+
var data = findBBoxCutPoints(arcs, bbox);
|
|
51942
|
+
var map = insertCutPoints(data.cutPoints, arcs);
|
|
51943
|
+
arcs.dedupCoords();
|
|
51944
|
+
remapDividedArcs(dataset, map);
|
|
51945
|
+
// merge bbox dataset with target dataset,
|
|
51946
|
+
// so arcs are shared between target layers and bbox layer
|
|
51947
|
+
var clipDataset = bboxPointsToClipDataset(data.bboxPoints);
|
|
51948
|
+
var mergedDataset = mergeDatasets([dataset, clipDataset]);
|
|
51949
|
+
// TODO: detect if we need to rebuild topology (unlikely), like with the full clip command
|
|
51950
|
+
// buildTopology(mergedDataset);
|
|
51951
|
+
var clipLyr = mergedDataset.layers.pop();
|
|
51952
|
+
dataset.arcs = mergedDataset.arcs;
|
|
51953
|
+
dataset.layers = mergedDataset.layers;
|
|
51954
|
+
return clipLyr;
|
|
51955
|
+
}
|
|
51956
|
+
|
|
51957
|
+
function bboxPointsToClipDataset(arr) {
|
|
51958
|
+
var arcs = [];
|
|
51959
|
+
var shape = [];
|
|
51960
|
+
var layer = {geometry_type: 'polygon', shapes: [[shape]]};
|
|
51961
|
+
var p1, p2;
|
|
51962
|
+
for (var i=0, n=arr.length - 1; i<n; i++) {
|
|
51963
|
+
p1 = arr[i];
|
|
51964
|
+
p2 = arr[i+1];
|
|
51965
|
+
arcs.push([[p1.x, p1.y], [p2.x, p2.y]]);
|
|
51966
|
+
shape.push(i);
|
|
51967
|
+
}
|
|
51968
|
+
return {
|
|
51969
|
+
arcs: new ArcCollection(arcs),
|
|
51970
|
+
layers: [layer]
|
|
51971
|
+
};
|
|
51972
|
+
}
|
|
51973
|
+
|
|
51974
|
+
function findBBoxCutPoints(arcs, bbox) {
|
|
51975
|
+
var left = bbox[0],
|
|
51976
|
+
bottom = bbox[1],
|
|
51977
|
+
right = bbox[2],
|
|
51978
|
+
top = bbox[3];
|
|
51979
|
+
|
|
51980
|
+
// arrays of intersection points along each bbox edge
|
|
51981
|
+
var tt = [],
|
|
51982
|
+
rr = [],
|
|
51983
|
+
bb = [],
|
|
51984
|
+
ll = [];
|
|
51985
|
+
|
|
51986
|
+
arcs.forEachSegment(function(i, j, xx, yy) {
|
|
51987
|
+
var ax = xx[i],
|
|
51988
|
+
ay = yy[i],
|
|
51989
|
+
bx = xx[j],
|
|
51990
|
+
by = yy[j];
|
|
51991
|
+
var hit;
|
|
51992
|
+
if (segmentOutsideBBox(ax, ay, bx, by, left, bottom, right, top)) return;
|
|
51993
|
+
if (segmentInsideBBox(ax, ay, bx, by, left, bottom, right, top)) return;
|
|
51994
|
+
|
|
51995
|
+
hit = geom.segmentIntersection(left, top, right, top, ax, ay, bx, by);
|
|
51996
|
+
if (hit) addHit(tt, hit, i, j, xx, yy);
|
|
51997
|
+
|
|
51998
|
+
hit = geom.segmentIntersection(left, bottom, right, bottom, ax, ay, bx, by);
|
|
51999
|
+
if (hit) addHit(bb, hit, i, j, xx, yy);
|
|
52000
|
+
|
|
52001
|
+
hit = geom.segmentIntersection(left, bottom, left, top, ax, ay, bx, by);
|
|
52002
|
+
if (hit) addHit(ll, hit, i, j, xx, yy);
|
|
52003
|
+
|
|
52004
|
+
hit = geom.segmentIntersection(right, bottom, right, top, ax, ay, bx, by);
|
|
52005
|
+
if (hit) addHit(rr, hit, i, j, xx, yy);
|
|
52006
|
+
});
|
|
52007
|
+
|
|
52008
|
+
return {
|
|
52009
|
+
cutPoints: ll.concat(bb, rr, tt),
|
|
52010
|
+
bboxPoints: getDividedBBoxPoints(bbox, ll, tt, rr, bb)
|
|
52011
|
+
};
|
|
52012
|
+
|
|
52013
|
+
function addHit(arr, hit, i, j, xx, yy) {
|
|
52014
|
+
if (!hit) return;
|
|
52015
|
+
arr.push(formatHit(hit[0], hit[1], i, j, xx, yy));
|
|
52016
|
+
if (hit.length == 4) {
|
|
52017
|
+
arr.push(formatHit(hit[2], hit[3], i, j, xx, yy));
|
|
52018
|
+
}
|
|
52019
|
+
}
|
|
52020
|
+
|
|
52021
|
+
function formatHit(x, y, i, j, xx, yy) {
|
|
52022
|
+
var ids = formatIntersectingSegment(x, y, i, j, xx, yy);
|
|
52023
|
+
return getCutPoint(x, y, ids[0], ids[1]);
|
|
52024
|
+
}
|
|
52025
|
+
}
|
|
52026
|
+
|
|
52027
|
+
function segmentOutsideBBox(ax, ay, bx, by, xmin, ymin, xmax, ymax) {
|
|
52028
|
+
return ax < xmin && bx < xmin || ax > xmax && bx > xmax ||
|
|
52029
|
+
ay < ymin && by < ymin || ay > ymax && by > ymax;
|
|
52030
|
+
}
|
|
52031
|
+
|
|
52032
|
+
function segmentInsideBBox(ax, ay, bx, by, xmin, ymin, xmax, ymax) {
|
|
52033
|
+
return ax > xmin && bx > xmin && ax < xmax && bx < xmax &&
|
|
52034
|
+
ay > ymin && by > ymin && ay < ymax && by < ymax;
|
|
52035
|
+
}
|
|
52036
|
+
|
|
52037
|
+
// Returns an array of points representing the vertices in
|
|
52038
|
+
// the bbox with cutting points inserted.
|
|
52039
|
+
function getDividedBBoxPoints(bbox, ll, tt, rr, bb) {
|
|
52040
|
+
var bl = {x: bbox[0], y: bbox[1]},
|
|
52041
|
+
tl = {x: bbox[0], y: bbox[3]},
|
|
52042
|
+
tr = {x: bbox[2], y: bbox[3]},
|
|
52043
|
+
br = {x: bbox[2], y: bbox[1]};
|
|
52044
|
+
ll = utils.sortOn(ll.concat([bl, tl]), 'y', true);
|
|
52045
|
+
tt = utils.sortOn(tt.concat([tl, tr]), 'x', true);
|
|
52046
|
+
rr = utils.sortOn(rr.concat([tr, br]), 'y', false);
|
|
52047
|
+
bb = utils.sortOn(bb.concat([br, bl]), 'x', false);
|
|
52048
|
+
return ll.concat(tt, rr, bb).reduce(function(memo, p2) {
|
|
52049
|
+
var p1 = memo.length > 0 ? memo[memo.length-1] : null;
|
|
52050
|
+
if (p1 === null || p1.x != p2.x || p1.y != p2.y) memo.push(p2);
|
|
52051
|
+
return memo;
|
|
52052
|
+
}, []);
|
|
52053
|
+
}
|
|
52054
|
+
|
|
52055
|
+
var Bbox2Clipping = /*#__PURE__*/Object.freeze({
|
|
52056
|
+
__proto__: null,
|
|
52057
|
+
divideDatasetByBBox: divideDatasetByBBox,
|
|
52058
|
+
segmentInsideBBox: segmentInsideBBox,
|
|
52059
|
+
segmentOutsideBBox: segmentOutsideBBox
|
|
52060
|
+
});
|
|
52061
|
+
|
|
52062
|
+
cmd.filterGeom = function(lyr, arcs, opts) {
|
|
52063
|
+
if (!layerHasGeometry$1(lyr)) {
|
|
52064
|
+
stop$1("Layer is missing geometry");
|
|
52065
|
+
}
|
|
52066
|
+
if (opts.bbox) {
|
|
52067
|
+
filterByBoundsIntersection(lyr, arcs, opts);
|
|
52068
|
+
}
|
|
52069
|
+
cmd.filterFeatures(lyr, arcs, {remove_empty: true, verbose: false});
|
|
52070
|
+
};
|
|
52071
|
+
|
|
52072
|
+
function filterByBoundsIntersection(lyr, arcs, opts) {
|
|
52073
|
+
var filter = getBoundsIntersectionFilter(opts.bbox, lyr, arcs);
|
|
52074
|
+
noteLayerWillChange(lyr, {operation: 'filter-geom', unit: 'shapes'});
|
|
52075
|
+
editShapes(lyr.shapes, filter);
|
|
52076
|
+
markLayerChanged(lyr, {operation: 'filter-geom', unit: 'shapes'});
|
|
52077
|
+
}
|
|
52078
|
+
|
|
52079
|
+
function getBoundsIntersectionFilter(bbox, lyr, arcs) {
|
|
52080
|
+
var bounds = new Bounds(bbox);
|
|
52081
|
+
var filter = lyr.geometry_type == 'point' ?
|
|
52082
|
+
getPointInBoundsTest(bounds) :
|
|
52083
|
+
getPathBoundsIntersectionTest(bounds, arcs);
|
|
52084
|
+
return filter;
|
|
52085
|
+
}
|
|
52086
|
+
|
|
52087
|
+
function getPointInBoundsTest(bounds) {
|
|
52088
|
+
return function(xy) {
|
|
52089
|
+
var contains = bounds.containsPoint(xy[0], xy[1]);
|
|
52090
|
+
return contains ? xy : null;
|
|
52091
|
+
};
|
|
52092
|
+
}
|
|
52093
|
+
|
|
52094
|
+
// V1 too-simple test: bounding-box intersection
|
|
52095
|
+
// internal.getPathBoundsIntersectionTest = function(bounds, arcs) {
|
|
52096
|
+
// return function(path) {
|
|
52097
|
+
// return bounds.intersects(arcs.getSimpleShapeBounds(path)) ? path : null;
|
|
52098
|
+
// };
|
|
52099
|
+
// };
|
|
52100
|
+
|
|
52101
|
+
function getPathBoundsIntersectionTest(bounds, arcs) {
|
|
52102
|
+
var bbox = bounds.toArray(),
|
|
52103
|
+
left = bbox[0],
|
|
52104
|
+
bottom = bbox[1],
|
|
52105
|
+
right = bbox[2],
|
|
52106
|
+
top = bbox[3];
|
|
52107
|
+
|
|
52108
|
+
return function(path) {
|
|
52109
|
+
// case: bounding boxes don't intersect -> the path doesn't intersect the box
|
|
52110
|
+
if (!bounds.intersects(arcs.getSimpleShapeBounds(path))) {
|
|
52111
|
+
return null;
|
|
52112
|
+
}
|
|
52113
|
+
var intersects = false;
|
|
52114
|
+
var ax, ay, bx, by;
|
|
52115
|
+
var iter = arcs.getShapeIter(path);
|
|
52116
|
+
|
|
52117
|
+
if (iter.hasNext()) {
|
|
52118
|
+
ax = iter.x;
|
|
52119
|
+
ay = iter.y;
|
|
52120
|
+
}
|
|
52121
|
+
while (iter.hasNext()) {
|
|
52122
|
+
bx = ax;
|
|
52123
|
+
by = ay;
|
|
52124
|
+
ax = iter.x;
|
|
52125
|
+
ay = iter.y;
|
|
52126
|
+
if (segmentOutsideBBox(ax, ay, bx, by, left, bottom, right, top)) continue;
|
|
52127
|
+
if (segmentInsideBBox(ax, ay, bx, by, left, bottom, right, top)) {
|
|
52128
|
+
intersects = true;
|
|
52129
|
+
break;
|
|
52130
|
+
}
|
|
52131
|
+
if (geom.segmentIntersection(left, top, right, top, ax, ay, bx, by) ||
|
|
52132
|
+
geom.segmentIntersection(left, bottom, right, bottom, ax, ay, bx, by) ||
|
|
52133
|
+
geom.segmentIntersection(left, bottom, left, top, ax, ay, bx, by) ||
|
|
52134
|
+
geom.segmentIntersection(right, bottom, right, top, ax, ay, bx, by)) {
|
|
52135
|
+
intersects = true;
|
|
52136
|
+
break;
|
|
52137
|
+
}
|
|
52138
|
+
}
|
|
52139
|
+
|
|
52140
|
+
// case: bbox is entirely inside this ring
|
|
52141
|
+
if (!intersects && geom.testPointInRing(left, bottom, path, arcs)) {
|
|
52142
|
+
intersects = true;
|
|
52143
|
+
}
|
|
52144
|
+
return intersects ? path : null;
|
|
52145
|
+
};
|
|
52146
|
+
}
|
|
52147
|
+
|
|
52148
|
+
// Return a function for testing if a shape (path or point) intersects a bounding box
|
|
52149
|
+
// TODO: move this function to a different file
|
|
52150
|
+
function getBBoxIntersectionTest(bbox, lyr, arcs) {
|
|
52151
|
+
var filter = getBoundsIntersectionFilter(bbox, lyr, arcs);
|
|
52152
|
+
return function(shapeId) {
|
|
52153
|
+
var shp = lyr.shapes[shapeId];
|
|
52154
|
+
if (!shp) return false;
|
|
52155
|
+
for (var i=0; i<shp.length; i++) {
|
|
52156
|
+
if (filter(shp[i])) return true;
|
|
52157
|
+
}
|
|
52158
|
+
return false;
|
|
52159
|
+
};
|
|
52160
|
+
}
|
|
52161
|
+
|
|
52162
|
+
// return array of shape ids
|
|
52163
|
+
function findShapesIntersectingBBox(bbox, lyr, arcs) {
|
|
52164
|
+
var test = getBBoxIntersectionTest(bbox, lyr, arcs);
|
|
52165
|
+
var ids = [];
|
|
52166
|
+
for (var i=0; i<lyr.shapes.length; i++) {
|
|
52167
|
+
if (test(i)) ids.push(i);
|
|
52168
|
+
}
|
|
52169
|
+
return ids;
|
|
52170
|
+
}
|
|
52171
|
+
|
|
52172
|
+
var FilterGeom = /*#__PURE__*/Object.freeze({
|
|
52173
|
+
__proto__: null,
|
|
52174
|
+
findShapesIntersectingBBox: findShapesIntersectingBBox,
|
|
52175
|
+
getBBoxIntersectionTest: getBBoxIntersectionTest
|
|
52176
|
+
});
|
|
52177
|
+
|
|
52178
|
+
cmd.filterFeatures = function(lyr, arcs, opts) {
|
|
52179
|
+
var records = lyr.data ? lyr.data.getRecords() : null,
|
|
52180
|
+
shapes = lyr.shapes || null,
|
|
52181
|
+
n = getFeatureCount(lyr),
|
|
52182
|
+
filteredShapes = shapes ? [] : null,
|
|
52183
|
+
filteredRecords = records ? [] : null,
|
|
52184
|
+
filteredLyr = getOutputLayer(lyr, opts),
|
|
52185
|
+
invert = !!opts.invert,
|
|
52186
|
+
filter;
|
|
52187
|
+
|
|
52188
|
+
if (opts.expression) {
|
|
52189
|
+
filter = compileFeatureExpression(opts.expression, lyr, arcs);
|
|
52190
|
+
}
|
|
52191
|
+
|
|
52192
|
+
if (opts.ids) {
|
|
52193
|
+
filter = combineFilters(filter, getIdFilter(opts.ids));
|
|
52194
|
+
}
|
|
52195
|
+
|
|
52196
|
+
if (opts.remove_empty) {
|
|
52197
|
+
filter = combineFilters(filter, getNullGeometryFilter(lyr, arcs));
|
|
52198
|
+
}
|
|
52199
|
+
|
|
52200
|
+
if (opts.bbox) {
|
|
52201
|
+
filter = combineFilters(filter, getBBoxIntersectionTest(opts.bbox, lyr, arcs));
|
|
52202
|
+
}
|
|
52203
|
+
|
|
52204
|
+
if (!filter) {
|
|
52205
|
+
stop$1("Missing a filter criterion");
|
|
52206
|
+
}
|
|
52207
|
+
|
|
52208
|
+
utils.repeat(n, function(shapeId) {
|
|
52209
|
+
var result = filter(shapeId);
|
|
52210
|
+
requireBooleanResult(result);
|
|
52211
|
+
if (invert) result = !result;
|
|
52212
|
+
if (result === true) {
|
|
52213
|
+
if (shapes) filteredShapes.push(shapes[shapeId] || null);
|
|
52214
|
+
if (records) filteredRecords.push(records[shapeId] || null);
|
|
52215
|
+
}
|
|
52216
|
+
});
|
|
52217
|
+
|
|
52218
|
+
if (filteredLyr == lyr) {
|
|
52219
|
+
noteLayerWillChange(lyr, {operation: 'filter'});
|
|
52220
|
+
}
|
|
52221
|
+
filteredLyr.shapes = filteredShapes;
|
|
52222
|
+
filteredLyr.data = filteredRecords ? new DataTable(filteredRecords) : null;
|
|
52223
|
+
if (filteredLyr == lyr) {
|
|
52224
|
+
markLayerChanged(lyr, {operation: 'filter'});
|
|
52225
|
+
}
|
|
52226
|
+
if (opts.no_replace) {
|
|
52227
|
+
// if adding a layer, don't share objects between source and filtered layer
|
|
52228
|
+
filteredLyr = copyLayer(filteredLyr);
|
|
52229
|
+
}
|
|
52230
|
+
|
|
52231
|
+
if (opts.verbose !== false && !opts.quiet) {
|
|
52232
|
+
message(utils.format('Retained %,d of %,d features', getFeatureCount(filteredLyr), n));
|
|
52233
|
+
}
|
|
52234
|
+
|
|
52235
|
+
return filteredLyr;
|
|
52236
|
+
};
|
|
52237
|
+
|
|
52238
|
+
// TODO: update filter command to use this function
|
|
52239
|
+
function filterLayerInPlace(lyr, filter, invert) {
|
|
52240
|
+
var records = lyr.data ? lyr.data.getRecords() : null,
|
|
52241
|
+
shapes = lyr.shapes || null,
|
|
52242
|
+
n = getFeatureCount(lyr),
|
|
52243
|
+
filteredShapes = shapes ? [] : null,
|
|
52244
|
+
filteredRecords = records ? [] : null;
|
|
52245
|
+
utils.repeat(n, function(shapeId) {
|
|
52246
|
+
var result = filter(shapeId);
|
|
52247
|
+
requireBooleanResult(result);
|
|
52248
|
+
if (result === true) {
|
|
52249
|
+
if (shapes) filteredShapes.push(shapes[shapeId] || null);
|
|
52250
|
+
if (records) filteredRecords.push(records[shapeId] || null);
|
|
52251
|
+
}
|
|
52252
|
+
});
|
|
52253
|
+
noteLayerWillChange(lyr, {operation: 'filterLayerInPlace'});
|
|
52254
|
+
lyr.shapes = filteredShapes;
|
|
52255
|
+
lyr.data = filteredRecords ? new DataTable(filteredRecords) : null;
|
|
52256
|
+
markLayerChanged(lyr, {operation: 'filterLayerInPlace'});
|
|
52257
|
+
}
|
|
52258
|
+
|
|
52259
|
+
function getIdFilter(ids) {
|
|
52260
|
+
var set = new Set(ids);
|
|
52261
|
+
return function(i) {
|
|
52262
|
+
return set.has(i);
|
|
52263
|
+
};
|
|
52264
|
+
}
|
|
52265
|
+
|
|
52266
|
+
function getNullGeometryFilter(lyr, arcs) {
|
|
52267
|
+
var shapes = lyr.shapes;
|
|
52268
|
+
if (lyr.geometry_type == 'polygon') {
|
|
52269
|
+
return getEmptyPolygonFilter(shapes, arcs);
|
|
52270
|
+
}
|
|
52271
|
+
return function(i) {return !!shapes[i];};
|
|
52272
|
+
}
|
|
52273
|
+
|
|
52274
|
+
function getEmptyPolygonFilter(shapes, arcs) {
|
|
52275
|
+
return function(i) {
|
|
52276
|
+
var shp = shapes[i];
|
|
52277
|
+
return !!shp && geom.getPlanarShapeArea(shapes[i], arcs) > 0;
|
|
52278
|
+
};
|
|
52279
|
+
}
|
|
52280
|
+
|
|
52281
|
+
function combineFilters(a, b) {
|
|
52282
|
+
return a && b ? function(id) {
|
|
52283
|
+
return a(id) && b(id);
|
|
52284
|
+
} : (a || b);
|
|
52285
|
+
}
|
|
52286
|
+
|
|
52287
|
+
// The icon names accepted by the icon= property of the -style command.
|
|
52288
|
+
// Kept in a module of its own so that both the renderer (svg-symbols.mjs) and
|
|
52289
|
+
// the -style command can use it without forming an import cycle.
|
|
52290
|
+
|
|
52291
|
+
var iconNames = ['circle', 'square', 'ring', 'star'];
|
|
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
|
+
|
|
52298
|
+
function isSupportedIconName(name) {
|
|
52299
|
+
return iconNames.indexOf(name) > -1 || unlistedIconNames.indexOf(name) > -1;
|
|
52300
|
+
}
|
|
52301
|
+
|
|
52302
|
+
cmd.svgStyle = function(lyr, dataset, opts) {
|
|
52303
|
+
var filterFn, table, fields, hasNewFields, optFields, clearedByPosition, fieldsBefore;
|
|
52304
|
+
|
|
52305
|
+
function hadField(field) {
|
|
52306
|
+
return field in fieldsBefore;
|
|
52307
|
+
}
|
|
52308
|
+
|
|
52309
|
+
if (getFeatureCount(lyr) === 0) {
|
|
52310
|
+
return;
|
|
52311
|
+
}
|
|
52312
|
+
if (!lyr.data) {
|
|
52313
|
+
initDataTable(lyr);
|
|
52314
|
+
}
|
|
52315
|
+
if (opts.where) {
|
|
52316
|
+
filterFn = compileFeatureExpression(opts.where, lyr, dataset.arcs);
|
|
52317
|
+
}
|
|
52318
|
+
if (opts.ids) {
|
|
52319
|
+
filterFn = combineFilters(filterFn, getIdFilter(opts.ids));
|
|
52320
|
+
}
|
|
52321
|
+
if (opts.clear) {
|
|
52322
|
+
lyr.data.getFields().filter(isSupportedSvgStyleProperty).forEach(lyr.data.deleteField, lyr.data);
|
|
52323
|
+
}
|
|
52324
|
+
table = getLayerDataTable(lyr);
|
|
52325
|
+
optFields = getOptionFields(opts);
|
|
52326
|
+
fields = getStyleFields(optFields);
|
|
52327
|
+
// Which of dx/dy/text-anchor a label-pos in this command clears: not the ones
|
|
52328
|
+
// the same command also sets, so that `label-pos=n dx=3` keeps the nudge it
|
|
52329
|
+
// was given instead of clearing it a moment later.
|
|
52330
|
+
//
|
|
52331
|
+
// Clearing at all is what keeps the shorthand usable, since a value on the
|
|
52332
|
+
// record wins over the position: without it, setting a position on a label
|
|
52333
|
+
// that had been dragged would appear to do nothing.
|
|
52334
|
+
clearedByPosition = labelPositionDerivedFields.filter(function(field) {
|
|
52335
|
+
return optFields.indexOf(field) == -1;
|
|
52336
|
+
});
|
|
52337
|
+
hasNewFields = fields.some(function(field) {
|
|
52338
|
+
return !table.fieldExists(field);
|
|
52339
|
+
});
|
|
52340
|
+
// The table's columns as this command found them. Taken once, because the
|
|
52341
|
+
// command adds to them as it runs, and a blanked property must be judged
|
|
52342
|
+
// against what was there before rather than against what it has just made.
|
|
52343
|
+
fieldsBefore = utils.arrayToIndex(table.getFields());
|
|
52344
|
+
if (fields.length > 0) {
|
|
52345
|
+
if (hasNewFields) {
|
|
52346
|
+
table.captureSchemaBefore({operation: 'style', fields: fields});
|
|
52347
|
+
} else {
|
|
52348
|
+
table.captureFieldsBefore(fields, {operation: 'style'});
|
|
52349
|
+
}
|
|
52350
|
+
}
|
|
52351
|
+
Object.keys(opts).forEach(function(optName) {
|
|
52352
|
+
// undo cli parser name conversion; the regex must be global, or a
|
|
52353
|
+
// property with more than one hyphen (e.g. label-start-offset) is silently
|
|
52354
|
+
// skipped rather than applied
|
|
52355
|
+
var svgName = optName.replace(/_/g, '-');
|
|
52356
|
+
if (!isSupportedSvgStyleProperty(svgName)) {
|
|
52357
|
+
return;
|
|
52358
|
+
}
|
|
52359
|
+
var strVal = opts[optName].trim();
|
|
52360
|
+
// An empty value removes the property, rather than being rejected as an
|
|
52361
|
+
// unparseable one. This is how a control gives a property back: a label
|
|
52362
|
+
// dragged off its position clears label-pos, and there is otherwise no
|
|
52363
|
+
// per-property unset -- only -style clear, which clears all of them.
|
|
52364
|
+
var unset = strVal === '' && emptyValueUnsetsProperty(svgName);
|
|
52365
|
+
var accessor = unset ? null : getSymbolPropertyAccessor(strVal, svgName, lyr);
|
|
52366
|
+
var badIcons = svgName == 'icon' ? [] : null;
|
|
52367
|
+
// Removing a position is not setting one, so it neither validates the
|
|
52368
|
+
// value nor clears the offsets the position would have stood for.
|
|
52369
|
+
var posOnPaths = svgName == 'label-pos' && !unset ? [] : null;
|
|
52370
|
+
table.getRecords().forEach(function(rec, i) {
|
|
52371
|
+
if (filterFn && !filterFn(i)) {
|
|
52372
|
+
// make sure field exists if record is excluded by filter
|
|
52373
|
+
setUndefinedFields(rec, [svgName]);
|
|
52374
|
+
if (svgName == 'label-pos') {
|
|
52375
|
+
// ...but a field the position would only have cleared is one this
|
|
52376
|
+
// command is not writing anywhere, so an excluded record has nothing
|
|
52377
|
+
// to stay consistent with
|
|
52378
|
+
setUndefinedFields(rec, labelPositionDerivedFields, {has: hadField});
|
|
52379
|
+
}
|
|
52380
|
+
} else if (unset) {
|
|
52381
|
+
// Nothing to remove, and so nothing to create: removing a property no
|
|
52382
|
+
// record has would otherwise add an empty column for it.
|
|
52383
|
+
if (hadField(svgName) || svgName in rec) rec[svgName] = undefined;
|
|
52384
|
+
} else {
|
|
52385
|
+
rec[svgName] = accessor(i);
|
|
52386
|
+
if (badIcons) {
|
|
52387
|
+
addUnsupportedIconName(badIcons, rec.icon);
|
|
52388
|
+
}
|
|
52389
|
+
if (posOnPaths) {
|
|
52390
|
+
if (!parseLabelPosition(rec['label-pos'])) {
|
|
52391
|
+
stop$1('Unexpected value for label-pos:', rec['label-pos']);
|
|
52392
|
+
}
|
|
52393
|
+
if (shapeIsPathLabel(lyr.shapes && lyr.shapes[i], rec)) {
|
|
52394
|
+
// Not stored, so that ignoring it means ignoring it: a stored
|
|
52395
|
+
// position would show up in the style panel and would start
|
|
52396
|
+
// applying if the label ever lost all but one of its knots. Its
|
|
52397
|
+
// text-anchor is left alone too -- that one does place text along a
|
|
52398
|
+
// path, so a position that had no effect must not clear it.
|
|
52399
|
+
posOnPaths.push(i);
|
|
52400
|
+
rec['label-pos'] = undefined;
|
|
52401
|
+
} else {
|
|
52402
|
+
setUndefinedFields(rec, clearedByPosition, {overwrite: true, has: hadField});
|
|
52403
|
+
}
|
|
52404
|
+
}
|
|
52405
|
+
}
|
|
52406
|
+
});
|
|
52407
|
+
if (badIcons && badIcons.length > 0) {
|
|
52408
|
+
warn(formatUnsupportedIconMessage(badIcons));
|
|
52409
|
+
}
|
|
52410
|
+
if (posOnPaths && posOnPaths.length > 0) {
|
|
52411
|
+
warn(formatPositionOnPathMessage(posOnPaths));
|
|
52412
|
+
}
|
|
52413
|
+
});
|
|
52414
|
+
if (fields.length > 0) {
|
|
52415
|
+
if (hasNewFields) {
|
|
52416
|
+
table.markSchemaChanged({operation: 'style'});
|
|
52417
|
+
} else {
|
|
52418
|
+
table.markFieldsChanged(fields, {operation: 'style'});
|
|
52419
|
+
}
|
|
52420
|
+
}
|
|
52421
|
+
};
|
|
52422
|
+
|
|
52423
|
+
// The style properties this command was given, in SVG spelling.
|
|
52424
|
+
function getOptionFields(opts) {
|
|
52425
|
+
var fields = [];
|
|
52426
|
+
Object.keys(opts).forEach(function(optName) {
|
|
52427
|
+
var svgName = optName.replace(/_/g, '-');
|
|
52428
|
+
if (isSupportedSvgStyleProperty(svgName)) addField(fields, svgName);
|
|
52429
|
+
});
|
|
52430
|
+
return fields;
|
|
52431
|
+
}
|
|
52432
|
+
|
|
52433
|
+
// The fields the command will write, which is what the undo capture covers.
|
|
52434
|
+
// label-pos reaches dx/dy/text-anchor as well -- it no longer stores values in
|
|
52435
|
+
// them, but it does clear them.
|
|
52436
|
+
function getStyleFields(optFields) {
|
|
52437
|
+
var fields = [];
|
|
52438
|
+
optFields.forEach(function(svgName) {
|
|
52439
|
+
addField(fields, svgName);
|
|
52440
|
+
if (svgName == 'label-pos') {
|
|
52441
|
+
labelPositionFields.forEach(function(field) {
|
|
52442
|
+
addField(fields, field);
|
|
52443
|
+
});
|
|
52444
|
+
}
|
|
52445
|
+
});
|
|
52446
|
+
return fields;
|
|
52447
|
+
}
|
|
52448
|
+
|
|
52449
|
+
function addField(fields, field) {
|
|
52450
|
+
if (fields.indexOf(field) == -1) {
|
|
52451
|
+
fields.push(field);
|
|
52452
|
+
}
|
|
52453
|
+
}
|
|
52454
|
+
|
|
52455
|
+
// Icon names can not be validated when options are parsed, because an icon=
|
|
52456
|
+
// value may be a field name or an expression. The assigned values are checked
|
|
52457
|
+
// instead, so that computed names are covered too. Unsupported names are a
|
|
52458
|
+
// warning, not an error -- features with an unsupported name render without an
|
|
52459
|
+
// icon.
|
|
52460
|
+
var maxReportedIconNames = 4;
|
|
52461
|
+
|
|
52462
|
+
function addUnsupportedIconName(names, val) {
|
|
52463
|
+
var name;
|
|
52464
|
+
if (!val) return; // a blank value removes the icon
|
|
52465
|
+
name = String(val);
|
|
52466
|
+
if (isSupportedIconName(name) || names.indexOf(name) > -1) return;
|
|
52467
|
+
names.push(name);
|
|
52468
|
+
}
|
|
52469
|
+
|
|
52470
|
+
function formatUnsupportedIconMessage(names) {
|
|
52471
|
+
var extra = names.length - maxReportedIconNames;
|
|
52472
|
+
var listed = extra > 0 ? names.slice(0, maxReportedIconNames) : names;
|
|
52473
|
+
var str = 'Unsupported icon ' + (names.length > 1 ? 'names' : 'name') + ': ' +
|
|
52474
|
+
listed.join(', ');
|
|
52475
|
+
if (extra > 0) {
|
|
52476
|
+
str += ' (and ' + extra + ' more)';
|
|
52477
|
+
}
|
|
52478
|
+
return str + '. Expected one of: ' + iconNames.join(', ');
|
|
52479
|
+
}
|
|
52480
|
+
|
|
52481
|
+
// Adds @fields to @rec with no value, so that a record the filter excluded
|
|
52482
|
+
// still has the same schema as the ones it kept. With overwrite, also blanks a
|
|
52483
|
+
// value already there -- which is how setting a position takes back the offsets
|
|
52484
|
+
// a label was carrying.
|
|
52485
|
+
//
|
|
52486
|
+
// @has: optional test for whether the layer carries a field at all. A field
|
|
52487
|
+
// nobody has is not created in order to be blanked: -style label-pos=n clears
|
|
52488
|
+
// dx, dy and text-anchor because a value on the record wins over the position,
|
|
52489
|
+
// and there is nothing to win with when the column does not exist. Without
|
|
52490
|
+
// this, clicking a position in the style panel put three empty columns in the
|
|
52491
|
+
// user's table, and three empty columns in their CSV.
|
|
52492
|
+
function setUndefinedFields(rec, fields, opts) {
|
|
52493
|
+
var overwrite = !!(opts && opts.overwrite);
|
|
52494
|
+
var has = opts && opts.has;
|
|
52495
|
+
fields.forEach(function(field) {
|
|
52496
|
+
if (has && !has(field) && field in rec === false) return;
|
|
52497
|
+
if (overwrite || field in rec === false) {
|
|
52498
|
+
rec[field] = undefined;
|
|
52499
|
+
}
|
|
52500
|
+
});
|
|
52501
|
+
}
|
|
52502
|
+
|
|
52503
|
+
// label-pos places text around an anchor point, which a label strung along a
|
|
52504
|
+
// path does not have: its text runs from a start offset in the direction the
|
|
52505
|
+
// path goes. A warning rather than an error, because a layer can hold both
|
|
52506
|
+
// kinds of label and styling all of it at once is reasonable.
|
|
52507
|
+
function formatPositionOnPathMessage(ids) {
|
|
52508
|
+
var extra = ids.length - maxReportedIds;
|
|
52509
|
+
var listed = (extra > 0 ? ids.slice(0, maxReportedIds) : ids).join(', ');
|
|
52510
|
+
return 'Ignoring label-pos on ' + ids.length + ' path ' +
|
|
52511
|
+
(ids.length > 1 ? 'labels' : 'label') + ' (' + listed +
|
|
52512
|
+
(extra > 0 ? ' and ' + extra + ' more' : '') + '). ' +
|
|
52513
|
+
'Use label-start-offset= and text-anchor= to place text along a path.';
|
|
52514
|
+
}
|
|
52515
|
+
|
|
52516
|
+
var maxReportedIds = 4;
|
|
52517
|
+
|
|
52518
|
+
// -labels styles the labels in a point layer, or turns a point layer into
|
|
52519
|
+
// labels by giving it text=. With coordinates=, it adds one label instead; that
|
|
52520
|
+
// case is cmd.addLabel(), dispatched separately because it accepts an empty
|
|
52521
|
+
// target.
|
|
52522
|
+
//
|
|
52523
|
+
// Styling writes the same properties -style writes, through the same code, so
|
|
52524
|
+
// a label styled by either command is the same label.
|
|
52525
|
+
cmd.labels = function(targetLayers, dataset, opts) {
|
|
52526
|
+
var styleOpts = getStyleOpts(opts);
|
|
52527
|
+
var output = targetLayers.map(function(lyr) {
|
|
52528
|
+
var out;
|
|
52529
|
+
requirePointLayer$1(lyr);
|
|
52530
|
+
out = opts.no_replace ? copyLayer(lyr) : lyr;
|
|
52531
|
+
cmd.svgStyle(out, dataset, styleOpts);
|
|
52532
|
+
return out;
|
|
52533
|
+
});
|
|
52534
|
+
return opts.no_replace ? output : null;
|
|
52535
|
+
};
|
|
52536
|
+
|
|
52537
|
+
// text= is label-text= under the name that reads naturally in this command.
|
|
52538
|
+
function getStyleOpts(opts) {
|
|
52539
|
+
var o = Object.assign({}, opts);
|
|
52540
|
+
if ('text' in o) {
|
|
52541
|
+
o.label_text = o.text;
|
|
52542
|
+
delete o.text;
|
|
52543
|
+
}
|
|
52544
|
+
return o;
|
|
52545
|
+
}
|
|
52546
|
+
|
|
52547
|
+
// Labels are drawn at points. A layer with no geometry is let through, since
|
|
52548
|
+
// it has nothing to conflict with.
|
|
52549
|
+
function requirePointLayer$1(lyr) {
|
|
52550
|
+
if (lyr.geometry_type && lyr.geometry_type != 'point') {
|
|
52551
|
+
stop$1('Labels can only be applied to a point layer; layer "' +
|
|
52552
|
+
(lyr.name || '[unnamed]') + '" contains ' + lyr.geometry_type + 's. ' +
|
|
52553
|
+
'Use -points to make a point layer to label.');
|
|
52554
|
+
}
|
|
52555
|
+
}
|
|
52556
|
+
|
|
51398
52557
|
cmd.addLayer = addLayer;
|
|
51399
52558
|
|
|
51400
52559
|
var GEOMETRY_TYPES = ['point', 'polygon', 'polyline'];
|
|
@@ -52297,13 +53456,17 @@ ${svg}
|
|
|
52297
53456
|
var sw;
|
|
52298
53457
|
if (!(r > 0)) return null;
|
|
52299
53458
|
if (type == 'ring') {
|
|
52300
|
-
// drawn as a stroke,
|
|
53459
|
+
// drawn as a stroke, RING_STROKE_WIDTH wide unless stroke-width says otherwise
|
|
52301
53460
|
sw = +d['stroke-width'];
|
|
52302
|
-
return getSquareBox(r + (sw > 0 ? sw :
|
|
53461
|
+
return getSquareBox(r + (sw > 0 ? sw : RING_STROKE_WIDTH) / 2, 0, 0);
|
|
52303
53462
|
}
|
|
52304
53463
|
if (type == 'circle' || type == 'square' || type == 'star') {
|
|
52305
53464
|
return getSquareBox(r + getStrokeOutset(d), 0, 0);
|
|
52306
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
|
+
}
|
|
52307
53470
|
return null; // unsupported icons are not drawn
|
|
52308
53471
|
}
|
|
52309
53472
|
|
|
@@ -53263,10 +54426,10 @@ ${svg}
|
|
|
53263
54426
|
}
|
|
53264
54427
|
|
|
53265
54428
|
function isClosedPath$1(arr) {
|
|
53266
|
-
return isArrayOfPoints(arr) && arr.length > 3 && samePoint$
|
|
54429
|
+
return isArrayOfPoints(arr) && arr.length > 3 && samePoint$3(arr[0], arr[arr.length - 1]);
|
|
53267
54430
|
}
|
|
53268
54431
|
|
|
53269
|
-
function samePoint$
|
|
54432
|
+
function samePoint$3(a, b) {
|
|
53270
54433
|
return a[0] == b[0] && a[1] == b[1];
|
|
53271
54434
|
}
|
|
53272
54435
|
|
|
@@ -54072,7 +55235,7 @@ ${svg}
|
|
|
54072
55235
|
}
|
|
54073
55236
|
|
|
54074
55237
|
cmd.alphaShapes = function(pointLyr, targetDataset, opts) {
|
|
54075
|
-
requirePointLayer$
|
|
55238
|
+
requirePointLayer$2(pointLyr);
|
|
54076
55239
|
if (opts.interval > 0 === false) {
|
|
54077
55240
|
stop$1('Expected a non-negative interval parameter');
|
|
54078
55241
|
}
|
|
@@ -55453,12 +56616,12 @@ ${svg}
|
|
|
55453
56616
|
return arr[arr.length - 1];
|
|
55454
56617
|
}
|
|
55455
56618
|
|
|
55456
|
-
function samePoint$
|
|
56619
|
+
function samePoint$2(a, b) {
|
|
55457
56620
|
return a && b && a[0] === b[0] && a[1] === b[1];
|
|
55458
56621
|
}
|
|
55459
56622
|
|
|
55460
56623
|
function isClosedPath(arr) {
|
|
55461
|
-
return samePoint$
|
|
56624
|
+
return samePoint$2(arr[0], lastEl(arr));
|
|
55462
56625
|
}
|
|
55463
56626
|
|
|
55464
56627
|
// remove likely rounding errors
|
|
@@ -55619,7 +56782,7 @@ ${svg}
|
|
|
55619
56782
|
if (!nextPart) {
|
|
55620
56783
|
return null;
|
|
55621
56784
|
}
|
|
55622
|
-
if (samePoint$
|
|
56785
|
+
if (samePoint$2(ring[0], nextPart[0])) {
|
|
55623
56786
|
// done!
|
|
55624
56787
|
ring.push(ring[0]); // close the ring
|
|
55625
56788
|
return ring;
|
|
@@ -55668,7 +56831,7 @@ ${svg}
|
|
|
55668
56831
|
|
|
55669
56832
|
function findPartStartingAt(parts, firstPoint) {
|
|
55670
56833
|
for (var i=0; i<parts.length; i++) {
|
|
55671
|
-
if (samePoint$
|
|
56834
|
+
if (samePoint$2(parts[i][0], firstPoint)) {
|
|
55672
56835
|
return parts[i];
|
|
55673
56836
|
}
|
|
55674
56837
|
}
|
|
@@ -55692,7 +56855,7 @@ ${svg}
|
|
|
55692
56855
|
var part = [];
|
|
55693
56856
|
var firstPoint = path[0];
|
|
55694
56857
|
var lastPoint = lastEl(path);
|
|
55695
|
-
var closed = samePoint$
|
|
56858
|
+
var closed = samePoint$2(firstPoint, lastPoint);
|
|
55696
56859
|
var p, pp, y;
|
|
55697
56860
|
for (var i=0, n=path.length; i<n; i++) {
|
|
55698
56861
|
p = path[i];
|
|
@@ -60358,10 +61521,10 @@ ${svg}
|
|
|
60358
61521
|
var p = recenteredVertex(ctx, points[i]);
|
|
60359
61522
|
// Successive off-center vertices can re-center onto the same neck point;
|
|
60360
61523
|
// keep one copy so the chain has no zero-length segments.
|
|
60361
|
-
if (!samePoint(p, out[out.length - 1])) out.push(p);
|
|
61524
|
+
if (!samePoint$1(p, out[out.length - 1])) out.push(p);
|
|
60362
61525
|
}
|
|
60363
61526
|
var last = points[points.length - 1];
|
|
60364
|
-
if (!samePoint(last, out[out.length - 1])) out.push(last);
|
|
61527
|
+
if (!samePoint$1(last, out[out.length - 1])) out.push(last);
|
|
60365
61528
|
return out.length >= 2 ? out : points;
|
|
60366
61529
|
}
|
|
60367
61530
|
|
|
@@ -60464,7 +61627,7 @@ ${svg}
|
|
|
60464
61627
|
out[1] = ay + t * dy;
|
|
60465
61628
|
}
|
|
60466
61629
|
|
|
60467
|
-
function samePoint(a, b) {
|
|
61630
|
+
function samePoint$1(a, b) {
|
|
60468
61631
|
return a[0] === b[0] && a[1] === b[1];
|
|
60469
61632
|
}
|
|
60470
61633
|
|
|
@@ -61713,131 +62876,6 @@ ${svg}
|
|
|
61713
62876
|
normalizeOverlaySource: normalizeOverlaySource
|
|
61714
62877
|
});
|
|
61715
62878
|
|
|
61716
|
-
// Insert cutting points in arcs, where bbox intersects other shapes
|
|
61717
|
-
// Return a polygon layer containing the bounding box vectors, divided at cutting points.
|
|
61718
|
-
function divideDatasetByBBox(dataset, bbox) {
|
|
61719
|
-
var arcs = dataset.arcs;
|
|
61720
|
-
var data = findBBoxCutPoints(arcs, bbox);
|
|
61721
|
-
var map = insertCutPoints(data.cutPoints, arcs);
|
|
61722
|
-
arcs.dedupCoords();
|
|
61723
|
-
remapDividedArcs(dataset, map);
|
|
61724
|
-
// merge bbox dataset with target dataset,
|
|
61725
|
-
// so arcs are shared between target layers and bbox layer
|
|
61726
|
-
var clipDataset = bboxPointsToClipDataset(data.bboxPoints);
|
|
61727
|
-
var mergedDataset = mergeDatasets([dataset, clipDataset]);
|
|
61728
|
-
// TODO: detect if we need to rebuild topology (unlikely), like with the full clip command
|
|
61729
|
-
// buildTopology(mergedDataset);
|
|
61730
|
-
var clipLyr = mergedDataset.layers.pop();
|
|
61731
|
-
dataset.arcs = mergedDataset.arcs;
|
|
61732
|
-
dataset.layers = mergedDataset.layers;
|
|
61733
|
-
return clipLyr;
|
|
61734
|
-
}
|
|
61735
|
-
|
|
61736
|
-
function bboxPointsToClipDataset(arr) {
|
|
61737
|
-
var arcs = [];
|
|
61738
|
-
var shape = [];
|
|
61739
|
-
var layer = {geometry_type: 'polygon', shapes: [[shape]]};
|
|
61740
|
-
var p1, p2;
|
|
61741
|
-
for (var i=0, n=arr.length - 1; i<n; i++) {
|
|
61742
|
-
p1 = arr[i];
|
|
61743
|
-
p2 = arr[i+1];
|
|
61744
|
-
arcs.push([[p1.x, p1.y], [p2.x, p2.y]]);
|
|
61745
|
-
shape.push(i);
|
|
61746
|
-
}
|
|
61747
|
-
return {
|
|
61748
|
-
arcs: new ArcCollection(arcs),
|
|
61749
|
-
layers: [layer]
|
|
61750
|
-
};
|
|
61751
|
-
}
|
|
61752
|
-
|
|
61753
|
-
function findBBoxCutPoints(arcs, bbox) {
|
|
61754
|
-
var left = bbox[0],
|
|
61755
|
-
bottom = bbox[1],
|
|
61756
|
-
right = bbox[2],
|
|
61757
|
-
top = bbox[3];
|
|
61758
|
-
|
|
61759
|
-
// arrays of intersection points along each bbox edge
|
|
61760
|
-
var tt = [],
|
|
61761
|
-
rr = [],
|
|
61762
|
-
bb = [],
|
|
61763
|
-
ll = [];
|
|
61764
|
-
|
|
61765
|
-
arcs.forEachSegment(function(i, j, xx, yy) {
|
|
61766
|
-
var ax = xx[i],
|
|
61767
|
-
ay = yy[i],
|
|
61768
|
-
bx = xx[j],
|
|
61769
|
-
by = yy[j];
|
|
61770
|
-
var hit;
|
|
61771
|
-
if (segmentOutsideBBox(ax, ay, bx, by, left, bottom, right, top)) return;
|
|
61772
|
-
if (segmentInsideBBox(ax, ay, bx, by, left, bottom, right, top)) return;
|
|
61773
|
-
|
|
61774
|
-
hit = geom.segmentIntersection(left, top, right, top, ax, ay, bx, by);
|
|
61775
|
-
if (hit) addHit(tt, hit, i, j, xx, yy);
|
|
61776
|
-
|
|
61777
|
-
hit = geom.segmentIntersection(left, bottom, right, bottom, ax, ay, bx, by);
|
|
61778
|
-
if (hit) addHit(bb, hit, i, j, xx, yy);
|
|
61779
|
-
|
|
61780
|
-
hit = geom.segmentIntersection(left, bottom, left, top, ax, ay, bx, by);
|
|
61781
|
-
if (hit) addHit(ll, hit, i, j, xx, yy);
|
|
61782
|
-
|
|
61783
|
-
hit = geom.segmentIntersection(right, bottom, right, top, ax, ay, bx, by);
|
|
61784
|
-
if (hit) addHit(rr, hit, i, j, xx, yy);
|
|
61785
|
-
});
|
|
61786
|
-
|
|
61787
|
-
return {
|
|
61788
|
-
cutPoints: ll.concat(bb, rr, tt),
|
|
61789
|
-
bboxPoints: getDividedBBoxPoints(bbox, ll, tt, rr, bb)
|
|
61790
|
-
};
|
|
61791
|
-
|
|
61792
|
-
function addHit(arr, hit, i, j, xx, yy) {
|
|
61793
|
-
if (!hit) return;
|
|
61794
|
-
arr.push(formatHit(hit[0], hit[1], i, j, xx, yy));
|
|
61795
|
-
if (hit.length == 4) {
|
|
61796
|
-
arr.push(formatHit(hit[2], hit[3], i, j, xx, yy));
|
|
61797
|
-
}
|
|
61798
|
-
}
|
|
61799
|
-
|
|
61800
|
-
function formatHit(x, y, i, j, xx, yy) {
|
|
61801
|
-
var ids = formatIntersectingSegment(x, y, i, j, xx, yy);
|
|
61802
|
-
return getCutPoint(x, y, ids[0], ids[1]);
|
|
61803
|
-
}
|
|
61804
|
-
}
|
|
61805
|
-
|
|
61806
|
-
function segmentOutsideBBox(ax, ay, bx, by, xmin, ymin, xmax, ymax) {
|
|
61807
|
-
return ax < xmin && bx < xmin || ax > xmax && bx > xmax ||
|
|
61808
|
-
ay < ymin && by < ymin || ay > ymax && by > ymax;
|
|
61809
|
-
}
|
|
61810
|
-
|
|
61811
|
-
function segmentInsideBBox(ax, ay, bx, by, xmin, ymin, xmax, ymax) {
|
|
61812
|
-
return ax > xmin && bx > xmin && ax < xmax && bx < xmax &&
|
|
61813
|
-
ay > ymin && by > ymin && ay < ymax && by < ymax;
|
|
61814
|
-
}
|
|
61815
|
-
|
|
61816
|
-
// Returns an array of points representing the vertices in
|
|
61817
|
-
// the bbox with cutting points inserted.
|
|
61818
|
-
function getDividedBBoxPoints(bbox, ll, tt, rr, bb) {
|
|
61819
|
-
var bl = {x: bbox[0], y: bbox[1]},
|
|
61820
|
-
tl = {x: bbox[0], y: bbox[3]},
|
|
61821
|
-
tr = {x: bbox[2], y: bbox[3]},
|
|
61822
|
-
br = {x: bbox[2], y: bbox[1]};
|
|
61823
|
-
ll = utils.sortOn(ll.concat([bl, tl]), 'y', true);
|
|
61824
|
-
tt = utils.sortOn(tt.concat([tl, tr]), 'x', true);
|
|
61825
|
-
rr = utils.sortOn(rr.concat([tr, br]), 'y', false);
|
|
61826
|
-
bb = utils.sortOn(bb.concat([br, bl]), 'x', false);
|
|
61827
|
-
return ll.concat(tt, rr, bb).reduce(function(memo, p2) {
|
|
61828
|
-
var p1 = memo.length > 0 ? memo[memo.length-1] : null;
|
|
61829
|
-
if (p1 === null || p1.x != p2.x || p1.y != p2.y) memo.push(p2);
|
|
61830
|
-
return memo;
|
|
61831
|
-
}, []);
|
|
61832
|
-
}
|
|
61833
|
-
|
|
61834
|
-
var Bbox2Clipping = /*#__PURE__*/Object.freeze({
|
|
61835
|
-
__proto__: null,
|
|
61836
|
-
divideDatasetByBBox: divideDatasetByBBox,
|
|
61837
|
-
segmentInsideBBox: segmentInsideBBox,
|
|
61838
|
-
segmentOutsideBBox: segmentOutsideBBox
|
|
61839
|
-
});
|
|
61840
|
-
|
|
61841
62879
|
cmd.clipLayers = function(target, src, dataset, opts) {
|
|
61842
62880
|
return clipLayers(target, src, dataset, "clip", opts);
|
|
61843
62881
|
};
|
|
@@ -64949,7 +65987,7 @@ ${svg}
|
|
|
64949
65987
|
debug('Short ring', coords);
|
|
64950
65988
|
return;
|
|
64951
65989
|
}
|
|
64952
|
-
if (!samePoint$
|
|
65990
|
+
if (!samePoint$2(coords[0], lastEl(coords))) {
|
|
64953
65991
|
error('Open polygon ring');
|
|
64954
65992
|
}
|
|
64955
65993
|
rings.push(coords); // accumulate rings
|
|
@@ -64982,7 +66020,7 @@ ${svg}
|
|
|
64982
66020
|
cmd.lines = function(lyr, dataset, opts) {
|
|
64983
66021
|
opts = opts || {};
|
|
64984
66022
|
if (opts.callouts) {
|
|
64985
|
-
requirePointLayer$
|
|
66023
|
+
requirePointLayer$2(lyr);
|
|
64986
66024
|
return pointsToCallouts(lyr, dataset, opts);
|
|
64987
66025
|
} else if (lyr.geometry_type == 'point') {
|
|
64988
66026
|
return pointsToLines(lyr, dataset, opts);
|
|
@@ -75469,231 +76507,6 @@ ${svg}
|
|
|
75469
76507
|
return api;
|
|
75470
76508
|
}
|
|
75471
76509
|
|
|
75472
|
-
cmd.filterGeom = function(lyr, arcs, opts) {
|
|
75473
|
-
if (!layerHasGeometry$1(lyr)) {
|
|
75474
|
-
stop$1("Layer is missing geometry");
|
|
75475
|
-
}
|
|
75476
|
-
if (opts.bbox) {
|
|
75477
|
-
filterByBoundsIntersection(lyr, arcs, opts);
|
|
75478
|
-
}
|
|
75479
|
-
cmd.filterFeatures(lyr, arcs, {remove_empty: true, verbose: false});
|
|
75480
|
-
};
|
|
75481
|
-
|
|
75482
|
-
function filterByBoundsIntersection(lyr, arcs, opts) {
|
|
75483
|
-
var filter = getBoundsIntersectionFilter(opts.bbox, lyr, arcs);
|
|
75484
|
-
noteLayerWillChange(lyr, {operation: 'filter-geom', unit: 'shapes'});
|
|
75485
|
-
editShapes(lyr.shapes, filter);
|
|
75486
|
-
markLayerChanged(lyr, {operation: 'filter-geom', unit: 'shapes'});
|
|
75487
|
-
}
|
|
75488
|
-
|
|
75489
|
-
function getBoundsIntersectionFilter(bbox, lyr, arcs) {
|
|
75490
|
-
var bounds = new Bounds(bbox);
|
|
75491
|
-
var filter = lyr.geometry_type == 'point' ?
|
|
75492
|
-
getPointInBoundsTest(bounds) :
|
|
75493
|
-
getPathBoundsIntersectionTest(bounds, arcs);
|
|
75494
|
-
return filter;
|
|
75495
|
-
}
|
|
75496
|
-
|
|
75497
|
-
function getPointInBoundsTest(bounds) {
|
|
75498
|
-
return function(xy) {
|
|
75499
|
-
var contains = bounds.containsPoint(xy[0], xy[1]);
|
|
75500
|
-
return contains ? xy : null;
|
|
75501
|
-
};
|
|
75502
|
-
}
|
|
75503
|
-
|
|
75504
|
-
// V1 too-simple test: bounding-box intersection
|
|
75505
|
-
// internal.getPathBoundsIntersectionTest = function(bounds, arcs) {
|
|
75506
|
-
// return function(path) {
|
|
75507
|
-
// return bounds.intersects(arcs.getSimpleShapeBounds(path)) ? path : null;
|
|
75508
|
-
// };
|
|
75509
|
-
// };
|
|
75510
|
-
|
|
75511
|
-
function getPathBoundsIntersectionTest(bounds, arcs) {
|
|
75512
|
-
var bbox = bounds.toArray(),
|
|
75513
|
-
left = bbox[0],
|
|
75514
|
-
bottom = bbox[1],
|
|
75515
|
-
right = bbox[2],
|
|
75516
|
-
top = bbox[3];
|
|
75517
|
-
|
|
75518
|
-
return function(path) {
|
|
75519
|
-
// case: bounding boxes don't intersect -> the path doesn't intersect the box
|
|
75520
|
-
if (!bounds.intersects(arcs.getSimpleShapeBounds(path))) {
|
|
75521
|
-
return null;
|
|
75522
|
-
}
|
|
75523
|
-
var intersects = false;
|
|
75524
|
-
var ax, ay, bx, by;
|
|
75525
|
-
var iter = arcs.getShapeIter(path);
|
|
75526
|
-
|
|
75527
|
-
if (iter.hasNext()) {
|
|
75528
|
-
ax = iter.x;
|
|
75529
|
-
ay = iter.y;
|
|
75530
|
-
}
|
|
75531
|
-
while (iter.hasNext()) {
|
|
75532
|
-
bx = ax;
|
|
75533
|
-
by = ay;
|
|
75534
|
-
ax = iter.x;
|
|
75535
|
-
ay = iter.y;
|
|
75536
|
-
if (segmentOutsideBBox(ax, ay, bx, by, left, bottom, right, top)) continue;
|
|
75537
|
-
if (segmentInsideBBox(ax, ay, bx, by, left, bottom, right, top)) {
|
|
75538
|
-
intersects = true;
|
|
75539
|
-
break;
|
|
75540
|
-
}
|
|
75541
|
-
if (geom.segmentIntersection(left, top, right, top, ax, ay, bx, by) ||
|
|
75542
|
-
geom.segmentIntersection(left, bottom, right, bottom, ax, ay, bx, by) ||
|
|
75543
|
-
geom.segmentIntersection(left, bottom, left, top, ax, ay, bx, by) ||
|
|
75544
|
-
geom.segmentIntersection(right, bottom, right, top, ax, ay, bx, by)) {
|
|
75545
|
-
intersects = true;
|
|
75546
|
-
break;
|
|
75547
|
-
}
|
|
75548
|
-
}
|
|
75549
|
-
|
|
75550
|
-
// case: bbox is entirely inside this ring
|
|
75551
|
-
if (!intersects && geom.testPointInRing(left, bottom, path, arcs)) {
|
|
75552
|
-
intersects = true;
|
|
75553
|
-
}
|
|
75554
|
-
return intersects ? path : null;
|
|
75555
|
-
};
|
|
75556
|
-
}
|
|
75557
|
-
|
|
75558
|
-
// Return a function for testing if a shape (path or point) intersects a bounding box
|
|
75559
|
-
// TODO: move this function to a different file
|
|
75560
|
-
function getBBoxIntersectionTest(bbox, lyr, arcs) {
|
|
75561
|
-
var filter = getBoundsIntersectionFilter(bbox, lyr, arcs);
|
|
75562
|
-
return function(shapeId) {
|
|
75563
|
-
var shp = lyr.shapes[shapeId];
|
|
75564
|
-
if (!shp) return false;
|
|
75565
|
-
for (var i=0; i<shp.length; i++) {
|
|
75566
|
-
if (filter(shp[i])) return true;
|
|
75567
|
-
}
|
|
75568
|
-
return false;
|
|
75569
|
-
};
|
|
75570
|
-
}
|
|
75571
|
-
|
|
75572
|
-
// return array of shape ids
|
|
75573
|
-
function findShapesIntersectingBBox(bbox, lyr, arcs) {
|
|
75574
|
-
var test = getBBoxIntersectionTest(bbox, lyr, arcs);
|
|
75575
|
-
var ids = [];
|
|
75576
|
-
for (var i=0; i<lyr.shapes.length; i++) {
|
|
75577
|
-
if (test(i)) ids.push(i);
|
|
75578
|
-
}
|
|
75579
|
-
return ids;
|
|
75580
|
-
}
|
|
75581
|
-
|
|
75582
|
-
var FilterGeom = /*#__PURE__*/Object.freeze({
|
|
75583
|
-
__proto__: null,
|
|
75584
|
-
findShapesIntersectingBBox: findShapesIntersectingBBox,
|
|
75585
|
-
getBBoxIntersectionTest: getBBoxIntersectionTest
|
|
75586
|
-
});
|
|
75587
|
-
|
|
75588
|
-
cmd.filterFeatures = function(lyr, arcs, opts) {
|
|
75589
|
-
var records = lyr.data ? lyr.data.getRecords() : null,
|
|
75590
|
-
shapes = lyr.shapes || null,
|
|
75591
|
-
n = getFeatureCount(lyr),
|
|
75592
|
-
filteredShapes = shapes ? [] : null,
|
|
75593
|
-
filteredRecords = records ? [] : null,
|
|
75594
|
-
filteredLyr = getOutputLayer(lyr, opts),
|
|
75595
|
-
invert = !!opts.invert,
|
|
75596
|
-
filter;
|
|
75597
|
-
|
|
75598
|
-
if (opts.expression) {
|
|
75599
|
-
filter = compileFeatureExpression(opts.expression, lyr, arcs);
|
|
75600
|
-
}
|
|
75601
|
-
|
|
75602
|
-
if (opts.ids) {
|
|
75603
|
-
filter = combineFilters(filter, getIdFilter(opts.ids));
|
|
75604
|
-
}
|
|
75605
|
-
|
|
75606
|
-
if (opts.remove_empty) {
|
|
75607
|
-
filter = combineFilters(filter, getNullGeometryFilter(lyr, arcs));
|
|
75608
|
-
}
|
|
75609
|
-
|
|
75610
|
-
if (opts.bbox) {
|
|
75611
|
-
filter = combineFilters(filter, getBBoxIntersectionTest(opts.bbox, lyr, arcs));
|
|
75612
|
-
}
|
|
75613
|
-
|
|
75614
|
-
if (!filter) {
|
|
75615
|
-
stop$1("Missing a filter criterion");
|
|
75616
|
-
}
|
|
75617
|
-
|
|
75618
|
-
utils.repeat(n, function(shapeId) {
|
|
75619
|
-
var result = filter(shapeId);
|
|
75620
|
-
requireBooleanResult(result);
|
|
75621
|
-
if (invert) result = !result;
|
|
75622
|
-
if (result === true) {
|
|
75623
|
-
if (shapes) filteredShapes.push(shapes[shapeId] || null);
|
|
75624
|
-
if (records) filteredRecords.push(records[shapeId] || null);
|
|
75625
|
-
}
|
|
75626
|
-
});
|
|
75627
|
-
|
|
75628
|
-
if (filteredLyr == lyr) {
|
|
75629
|
-
noteLayerWillChange(lyr, {operation: 'filter'});
|
|
75630
|
-
}
|
|
75631
|
-
filteredLyr.shapes = filteredShapes;
|
|
75632
|
-
filteredLyr.data = filteredRecords ? new DataTable(filteredRecords) : null;
|
|
75633
|
-
if (filteredLyr == lyr) {
|
|
75634
|
-
markLayerChanged(lyr, {operation: 'filter'});
|
|
75635
|
-
}
|
|
75636
|
-
if (opts.no_replace) {
|
|
75637
|
-
// if adding a layer, don't share objects between source and filtered layer
|
|
75638
|
-
filteredLyr = copyLayer(filteredLyr);
|
|
75639
|
-
}
|
|
75640
|
-
|
|
75641
|
-
if (opts.verbose !== false && !opts.quiet) {
|
|
75642
|
-
message(utils.format('Retained %,d of %,d features', getFeatureCount(filteredLyr), n));
|
|
75643
|
-
}
|
|
75644
|
-
|
|
75645
|
-
return filteredLyr;
|
|
75646
|
-
};
|
|
75647
|
-
|
|
75648
|
-
// TODO: update filter command to use this function
|
|
75649
|
-
function filterLayerInPlace(lyr, filter, invert) {
|
|
75650
|
-
var records = lyr.data ? lyr.data.getRecords() : null,
|
|
75651
|
-
shapes = lyr.shapes || null,
|
|
75652
|
-
n = getFeatureCount(lyr),
|
|
75653
|
-
filteredShapes = shapes ? [] : null,
|
|
75654
|
-
filteredRecords = records ? [] : null;
|
|
75655
|
-
utils.repeat(n, function(shapeId) {
|
|
75656
|
-
var result = filter(shapeId);
|
|
75657
|
-
requireBooleanResult(result);
|
|
75658
|
-
if (result === true) {
|
|
75659
|
-
if (shapes) filteredShapes.push(shapes[shapeId] || null);
|
|
75660
|
-
if (records) filteredRecords.push(records[shapeId] || null);
|
|
75661
|
-
}
|
|
75662
|
-
});
|
|
75663
|
-
noteLayerWillChange(lyr, {operation: 'filterLayerInPlace'});
|
|
75664
|
-
lyr.shapes = filteredShapes;
|
|
75665
|
-
lyr.data = filteredRecords ? new DataTable(filteredRecords) : null;
|
|
75666
|
-
markLayerChanged(lyr, {operation: 'filterLayerInPlace'});
|
|
75667
|
-
}
|
|
75668
|
-
|
|
75669
|
-
function getIdFilter(ids) {
|
|
75670
|
-
var set = new Set(ids);
|
|
75671
|
-
return function(i) {
|
|
75672
|
-
return set.has(i);
|
|
75673
|
-
};
|
|
75674
|
-
}
|
|
75675
|
-
|
|
75676
|
-
function getNullGeometryFilter(lyr, arcs) {
|
|
75677
|
-
var shapes = lyr.shapes;
|
|
75678
|
-
if (lyr.geometry_type == 'polygon') {
|
|
75679
|
-
return getEmptyPolygonFilter(shapes, arcs);
|
|
75680
|
-
}
|
|
75681
|
-
return function(i) {return !!shapes[i];};
|
|
75682
|
-
}
|
|
75683
|
-
|
|
75684
|
-
function getEmptyPolygonFilter(shapes, arcs) {
|
|
75685
|
-
return function(i) {
|
|
75686
|
-
var shp = shapes[i];
|
|
75687
|
-
return !!shp && geom.getPlanarShapeArea(shapes[i], arcs) > 0;
|
|
75688
|
-
};
|
|
75689
|
-
}
|
|
75690
|
-
|
|
75691
|
-
function combineFilters(a, b) {
|
|
75692
|
-
return a && b ? function(id) {
|
|
75693
|
-
return a(id) && b(id);
|
|
75694
|
-
} : (a || b);
|
|
75695
|
-
}
|
|
75696
|
-
|
|
75697
76510
|
cmd.evaluateEachFeature = function(lyr, dataset, expArg, opts) {
|
|
75698
76511
|
var n = getFeatureCount(lyr),
|
|
75699
76512
|
arcs = dataset.arcs,
|
|
@@ -79540,7 +80353,7 @@ ${svg}
|
|
|
79540
80353
|
}
|
|
79541
80354
|
|
|
79542
80355
|
cmd.pointToGrid = function(targetLayers, targetDataset, opts) {
|
|
79543
|
-
targetLayers.forEach(requirePointLayer$
|
|
80356
|
+
targetLayers.forEach(requirePointLayer$2);
|
|
79544
80357
|
if (opts.interval > 0 === false) {
|
|
79545
80358
|
stop$1('Expected a non-negative interval parameter');
|
|
79546
80359
|
}
|
|
@@ -82325,232 +83138,6 @@ ${svg}
|
|
|
82325
83138
|
stopJob(job);
|
|
82326
83139
|
};
|
|
82327
83140
|
|
|
82328
|
-
// The icon names accepted by the icon= property of the -style command.
|
|
82329
|
-
// Kept in a module of its own so that both the renderer (svg-symbols.mjs) and
|
|
82330
|
-
// the -style command can use it without forming an import cycle.
|
|
82331
|
-
|
|
82332
|
-
var iconNames = ['circle', 'square', 'ring', 'star'];
|
|
82333
|
-
|
|
82334
|
-
function isSupportedIconName(name) {
|
|
82335
|
-
return iconNames.indexOf(name) > -1;
|
|
82336
|
-
}
|
|
82337
|
-
|
|
82338
|
-
cmd.svgStyle = function(lyr, dataset, opts) {
|
|
82339
|
-
var filterFn, table, fields, hasNewFields, optFields, clearedByPosition, fieldsBefore;
|
|
82340
|
-
|
|
82341
|
-
function hadField(field) {
|
|
82342
|
-
return field in fieldsBefore;
|
|
82343
|
-
}
|
|
82344
|
-
|
|
82345
|
-
if (getFeatureCount(lyr) === 0) {
|
|
82346
|
-
return;
|
|
82347
|
-
}
|
|
82348
|
-
if (!lyr.data) {
|
|
82349
|
-
initDataTable(lyr);
|
|
82350
|
-
}
|
|
82351
|
-
if (opts.where) {
|
|
82352
|
-
filterFn = compileFeatureExpression(opts.where, lyr, dataset.arcs);
|
|
82353
|
-
}
|
|
82354
|
-
if (opts.ids) {
|
|
82355
|
-
filterFn = combineFilters(filterFn, getIdFilter(opts.ids));
|
|
82356
|
-
}
|
|
82357
|
-
if (opts.clear) {
|
|
82358
|
-
lyr.data.getFields().filter(isSupportedSvgStyleProperty).forEach(lyr.data.deleteField, lyr.data);
|
|
82359
|
-
}
|
|
82360
|
-
table = getLayerDataTable(lyr);
|
|
82361
|
-
optFields = getOptionFields(opts);
|
|
82362
|
-
fields = getStyleFields(optFields);
|
|
82363
|
-
// Which of dx/dy/text-anchor a label-pos in this command clears: not the ones
|
|
82364
|
-
// the same command also sets, so that `label-pos=n dx=3` keeps the nudge it
|
|
82365
|
-
// was given instead of clearing it a moment later.
|
|
82366
|
-
//
|
|
82367
|
-
// Clearing at all is what keeps the shorthand usable, since a value on the
|
|
82368
|
-
// record wins over the position: without it, setting a position on a label
|
|
82369
|
-
// that had been dragged would appear to do nothing.
|
|
82370
|
-
clearedByPosition = labelPositionDerivedFields.filter(function(field) {
|
|
82371
|
-
return optFields.indexOf(field) == -1;
|
|
82372
|
-
});
|
|
82373
|
-
hasNewFields = fields.some(function(field) {
|
|
82374
|
-
return !table.fieldExists(field);
|
|
82375
|
-
});
|
|
82376
|
-
// The table's columns as this command found them. Taken once, because the
|
|
82377
|
-
// command adds to them as it runs, and a blanked property must be judged
|
|
82378
|
-
// against what was there before rather than against what it has just made.
|
|
82379
|
-
fieldsBefore = utils.arrayToIndex(table.getFields());
|
|
82380
|
-
if (fields.length > 0) {
|
|
82381
|
-
if (hasNewFields) {
|
|
82382
|
-
table.captureSchemaBefore({operation: 'style', fields: fields});
|
|
82383
|
-
} else {
|
|
82384
|
-
table.captureFieldsBefore(fields, {operation: 'style'});
|
|
82385
|
-
}
|
|
82386
|
-
}
|
|
82387
|
-
Object.keys(opts).forEach(function(optName) {
|
|
82388
|
-
// undo cli parser name conversion; the regex must be global, or a
|
|
82389
|
-
// property with more than one hyphen (e.g. label-start-offset) is silently
|
|
82390
|
-
// skipped rather than applied
|
|
82391
|
-
var svgName = optName.replace(/_/g, '-');
|
|
82392
|
-
if (!isSupportedSvgStyleProperty(svgName)) {
|
|
82393
|
-
return;
|
|
82394
|
-
}
|
|
82395
|
-
var strVal = opts[optName].trim();
|
|
82396
|
-
// An empty value removes the property, rather than being rejected as an
|
|
82397
|
-
// unparseable one. This is how a control gives a property back: a label
|
|
82398
|
-
// dragged off its position clears label-pos, and there is otherwise no
|
|
82399
|
-
// per-property unset -- only -style clear, which clears all of them.
|
|
82400
|
-
var unset = strVal === '' && emptyValueUnsetsProperty(svgName);
|
|
82401
|
-
var accessor = unset ? null : getSymbolPropertyAccessor(strVal, svgName, lyr);
|
|
82402
|
-
var badIcons = svgName == 'icon' ? [] : null;
|
|
82403
|
-
// Removing a position is not setting one, so it neither validates the
|
|
82404
|
-
// value nor clears the offsets the position would have stood for.
|
|
82405
|
-
var posOnPaths = svgName == 'label-pos' && !unset ? [] : null;
|
|
82406
|
-
table.getRecords().forEach(function(rec, i) {
|
|
82407
|
-
if (filterFn && !filterFn(i)) {
|
|
82408
|
-
// make sure field exists if record is excluded by filter
|
|
82409
|
-
setUndefinedFields(rec, [svgName]);
|
|
82410
|
-
if (svgName == 'label-pos') {
|
|
82411
|
-
// ...but a field the position would only have cleared is one this
|
|
82412
|
-
// command is not writing anywhere, so an excluded record has nothing
|
|
82413
|
-
// to stay consistent with
|
|
82414
|
-
setUndefinedFields(rec, labelPositionDerivedFields, {has: hadField});
|
|
82415
|
-
}
|
|
82416
|
-
} else if (unset) {
|
|
82417
|
-
// Nothing to remove, and so nothing to create: removing a property no
|
|
82418
|
-
// record has would otherwise add an empty column for it.
|
|
82419
|
-
if (hadField(svgName) || svgName in rec) rec[svgName] = undefined;
|
|
82420
|
-
} else {
|
|
82421
|
-
rec[svgName] = accessor(i);
|
|
82422
|
-
if (badIcons) {
|
|
82423
|
-
addUnsupportedIconName(badIcons, rec.icon);
|
|
82424
|
-
}
|
|
82425
|
-
if (posOnPaths) {
|
|
82426
|
-
if (!parseLabelPosition(rec['label-pos'])) {
|
|
82427
|
-
stop$1('Unexpected value for label-pos:', rec['label-pos']);
|
|
82428
|
-
}
|
|
82429
|
-
if (shapeIsPathLabel(lyr.shapes && lyr.shapes[i], rec)) {
|
|
82430
|
-
// Not stored, so that ignoring it means ignoring it: a stored
|
|
82431
|
-
// position would show up in the style panel and would start
|
|
82432
|
-
// applying if the label ever lost all but one of its knots. Its
|
|
82433
|
-
// text-anchor is left alone too -- that one does place text along a
|
|
82434
|
-
// path, so a position that had no effect must not clear it.
|
|
82435
|
-
posOnPaths.push(i);
|
|
82436
|
-
rec['label-pos'] = undefined;
|
|
82437
|
-
} else {
|
|
82438
|
-
setUndefinedFields(rec, clearedByPosition, {overwrite: true, has: hadField});
|
|
82439
|
-
}
|
|
82440
|
-
}
|
|
82441
|
-
}
|
|
82442
|
-
});
|
|
82443
|
-
if (badIcons && badIcons.length > 0) {
|
|
82444
|
-
warn(formatUnsupportedIconMessage(badIcons));
|
|
82445
|
-
}
|
|
82446
|
-
if (posOnPaths && posOnPaths.length > 0) {
|
|
82447
|
-
warn(formatPositionOnPathMessage(posOnPaths));
|
|
82448
|
-
}
|
|
82449
|
-
});
|
|
82450
|
-
if (fields.length > 0) {
|
|
82451
|
-
if (hasNewFields) {
|
|
82452
|
-
table.markSchemaChanged({operation: 'style'});
|
|
82453
|
-
} else {
|
|
82454
|
-
table.markFieldsChanged(fields, {operation: 'style'});
|
|
82455
|
-
}
|
|
82456
|
-
}
|
|
82457
|
-
};
|
|
82458
|
-
|
|
82459
|
-
// The style properties this command was given, in SVG spelling.
|
|
82460
|
-
function getOptionFields(opts) {
|
|
82461
|
-
var fields = [];
|
|
82462
|
-
Object.keys(opts).forEach(function(optName) {
|
|
82463
|
-
var svgName = optName.replace(/_/g, '-');
|
|
82464
|
-
if (isSupportedSvgStyleProperty(svgName)) addField(fields, svgName);
|
|
82465
|
-
});
|
|
82466
|
-
return fields;
|
|
82467
|
-
}
|
|
82468
|
-
|
|
82469
|
-
// The fields the command will write, which is what the undo capture covers.
|
|
82470
|
-
// label-pos reaches dx/dy/text-anchor as well -- it no longer stores values in
|
|
82471
|
-
// them, but it does clear them.
|
|
82472
|
-
function getStyleFields(optFields) {
|
|
82473
|
-
var fields = [];
|
|
82474
|
-
optFields.forEach(function(svgName) {
|
|
82475
|
-
addField(fields, svgName);
|
|
82476
|
-
if (svgName == 'label-pos') {
|
|
82477
|
-
labelPositionFields.forEach(function(field) {
|
|
82478
|
-
addField(fields, field);
|
|
82479
|
-
});
|
|
82480
|
-
}
|
|
82481
|
-
});
|
|
82482
|
-
return fields;
|
|
82483
|
-
}
|
|
82484
|
-
|
|
82485
|
-
function addField(fields, field) {
|
|
82486
|
-
if (fields.indexOf(field) == -1) {
|
|
82487
|
-
fields.push(field);
|
|
82488
|
-
}
|
|
82489
|
-
}
|
|
82490
|
-
|
|
82491
|
-
// Icon names can not be validated when options are parsed, because an icon=
|
|
82492
|
-
// value may be a field name or an expression. The assigned values are checked
|
|
82493
|
-
// instead, so that computed names are covered too. Unsupported names are a
|
|
82494
|
-
// warning, not an error -- features with an unsupported name render without an
|
|
82495
|
-
// icon.
|
|
82496
|
-
var maxReportedIconNames = 4;
|
|
82497
|
-
|
|
82498
|
-
function addUnsupportedIconName(names, val) {
|
|
82499
|
-
var name;
|
|
82500
|
-
if (!val) return; // a blank value removes the icon
|
|
82501
|
-
name = String(val);
|
|
82502
|
-
if (isSupportedIconName(name) || names.indexOf(name) > -1) return;
|
|
82503
|
-
names.push(name);
|
|
82504
|
-
}
|
|
82505
|
-
|
|
82506
|
-
function formatUnsupportedIconMessage(names) {
|
|
82507
|
-
var extra = names.length - maxReportedIconNames;
|
|
82508
|
-
var listed = extra > 0 ? names.slice(0, maxReportedIconNames) : names;
|
|
82509
|
-
var str = 'Unsupported icon ' + (names.length > 1 ? 'names' : 'name') + ': ' +
|
|
82510
|
-
listed.join(', ');
|
|
82511
|
-
if (extra > 0) {
|
|
82512
|
-
str += ' (and ' + extra + ' more)';
|
|
82513
|
-
}
|
|
82514
|
-
return str + '. Expected one of: ' + iconNames.join(', ');
|
|
82515
|
-
}
|
|
82516
|
-
|
|
82517
|
-
// Adds @fields to @rec with no value, so that a record the filter excluded
|
|
82518
|
-
// still has the same schema as the ones it kept. With overwrite, also blanks a
|
|
82519
|
-
// value already there -- which is how setting a position takes back the offsets
|
|
82520
|
-
// a label was carrying.
|
|
82521
|
-
//
|
|
82522
|
-
// @has: optional test for whether the layer carries a field at all. A field
|
|
82523
|
-
// nobody has is not created in order to be blanked: -style label-pos=n clears
|
|
82524
|
-
// dx, dy and text-anchor because a value on the record wins over the position,
|
|
82525
|
-
// and there is nothing to win with when the column does not exist. Without
|
|
82526
|
-
// this, clicking a position in the style panel put three empty columns in the
|
|
82527
|
-
// user's table, and three empty columns in their CSV.
|
|
82528
|
-
function setUndefinedFields(rec, fields, opts) {
|
|
82529
|
-
var overwrite = !!(opts && opts.overwrite);
|
|
82530
|
-
var has = opts && opts.has;
|
|
82531
|
-
fields.forEach(function(field) {
|
|
82532
|
-
if (has && !has(field) && field in rec === false) return;
|
|
82533
|
-
if (overwrite || field in rec === false) {
|
|
82534
|
-
rec[field] = undefined;
|
|
82535
|
-
}
|
|
82536
|
-
});
|
|
82537
|
-
}
|
|
82538
|
-
|
|
82539
|
-
// label-pos places text around an anchor point, which a label strung along a
|
|
82540
|
-
// path does not have: its text runs from a start offset in the direction the
|
|
82541
|
-
// path goes. A warning rather than an error, because a layer can hold both
|
|
82542
|
-
// kinds of label and styling all of it at once is reasonable.
|
|
82543
|
-
function formatPositionOnPathMessage(ids) {
|
|
82544
|
-
var extra = ids.length - maxReportedIds;
|
|
82545
|
-
var listed = (extra > 0 ? ids.slice(0, maxReportedIds) : ids).join(', ');
|
|
82546
|
-
return 'Ignoring label-pos on ' + ids.length + ' path ' +
|
|
82547
|
-
(ids.length > 1 ? 'labels' : 'label') + ' (' + listed +
|
|
82548
|
-
(extra > 0 ? ' and ' + extra + ' more' : '') + '). ' +
|
|
82549
|
-
'Use label-start-offset= and text-anchor= to place text along a path.';
|
|
82550
|
-
}
|
|
82551
|
-
|
|
82552
|
-
var maxReportedIds = 4;
|
|
82553
|
-
|
|
82554
83141
|
function getStickArrowCoords(d) {
|
|
82555
83142
|
return getArrowCoords(d, 'stick');
|
|
82556
83143
|
}
|
|
@@ -83082,7 +83669,7 @@ ${svg}
|
|
|
83082
83669
|
|
|
83083
83670
|
// TODO: refactor to remove duplication in mapshaper-svg-style.js
|
|
83084
83671
|
cmd.symbols = function(inputLyr, dataset, opts) {
|
|
83085
|
-
requirePointLayer$
|
|
83672
|
+
requirePointLayer$2(inputLyr);
|
|
83086
83673
|
var lyr = opts.no_replace ? copyLayer(inputLyr) : inputLyr;
|
|
83087
83674
|
var shapeMode = !!opts.geographic;
|
|
83088
83675
|
var metersPerPx;
|
|
@@ -83683,6 +84270,9 @@ ${svg}
|
|
|
83683
84270
|
if (name == 'rectangle') {
|
|
83684
84271
|
return !opts.source && !opts.bbox;
|
|
83685
84272
|
}
|
|
84273
|
+
if (name == 'labels') {
|
|
84274
|
+
return !('coordinates' in opts);
|
|
84275
|
+
}
|
|
83686
84276
|
return [
|
|
83687
84277
|
'affine', 'alpha-shapes', 'blur', 'buffer', 'calc', 'check-geometry',
|
|
83688
84278
|
'classify', 'clean', 'clip', 'cluster', 'contours', 'dashlines', 'data-fill',
|
|
@@ -83697,7 +84287,10 @@ ${svg}
|
|
|
83697
84287
|
].indexOf(name) > -1;
|
|
83698
84288
|
}
|
|
83699
84289
|
|
|
83700
|
-
function commandAcceptsEmptyTarget(name) {
|
|
84290
|
+
function commandAcceptsEmptyTarget(name, opts) {
|
|
84291
|
+
if (name == 'labels') {
|
|
84292
|
+
return 'coordinates' in opts;
|
|
84293
|
+
}
|
|
83701
84294
|
return name == 'graticule' || name == 'i' || name == 'help' ||
|
|
83702
84295
|
name == 'point-grid' || name == 'shape' || name == 'rectangle' || name == 'frame' ||
|
|
83703
84296
|
name == 'require' || name == 'run' || name == 'define' ||
|
|
@@ -83793,7 +84386,7 @@ ${svg}
|
|
|
83793
84386
|
stop$1(utils.format('Missing target: %s\nAvailable layers: %s',
|
|
83794
84387
|
opts.target, getFormattedLayerList(job.catalog)));
|
|
83795
84388
|
}
|
|
83796
|
-
if (!commandAcceptsEmptyTarget(name)) {
|
|
84389
|
+
if (!commandAcceptsEmptyTarget(name, opts)) {
|
|
83797
84390
|
stop$1("No data is available");
|
|
83798
84391
|
}
|
|
83799
84392
|
}
|
|
@@ -83816,7 +84409,7 @@ ${svg}
|
|
|
83816
84409
|
}
|
|
83817
84410
|
outputLayers = cmd.addShape(targetLayers, targetDataset, opts);
|
|
83818
84411
|
|
|
83819
|
-
} else if (name == 'add-label') {
|
|
84412
|
+
} else if (name == 'add-label' || name == 'labels' && 'coordinates' in opts) {
|
|
83820
84413
|
if (!targetDataset) {
|
|
83821
84414
|
targetDataset = {info: {}, layers: []};
|
|
83822
84415
|
targetLayers = targetDataset.layers;
|
|
@@ -83824,6 +84417,9 @@ ${svg}
|
|
|
83824
84417
|
}
|
|
83825
84418
|
outputLayers = cmd.addLabel(targetLayers, targetDataset, opts);
|
|
83826
84419
|
|
|
84420
|
+
} else if (name == 'labels') {
|
|
84421
|
+
outputLayers = cmd.labels(targetLayers, targetDataset, opts);
|
|
84422
|
+
|
|
83827
84423
|
} else if (name == 'add-layer') {
|
|
83828
84424
|
// The new layer arrives in a dataset of its own, so it is added to the
|
|
83829
84425
|
// catalog here rather than integrated into the target below.
|
|
@@ -84401,7 +84997,7 @@ ${svg}
|
|
|
84401
84997
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
84402
84998
|
}
|
|
84403
84999
|
|
|
84404
|
-
var version = "0.7.
|
|
85000
|
+
var version = "0.7.71";
|
|
84405
85001
|
|
|
84406
85002
|
// Parse command line args into commands and run them
|
|
84407
85003
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -85216,7 +85812,7 @@ ${svg}
|
|
|
85216
85812
|
// parse. Null is what every reader already falls back from.
|
|
85217
85813
|
function measureLabelText(rec) {
|
|
85218
85814
|
var text = toLabelString(rec && rec['label-text']);
|
|
85219
|
-
var font, fontSize, spacing, width;
|
|
85815
|
+
var font, fontSize, spacing, width, failed = false;
|
|
85220
85816
|
if (!text) return null;
|
|
85221
85817
|
font = getFontForRecord(rec);
|
|
85222
85818
|
if (!font) return null;
|
|
@@ -85225,10 +85821,22 @@ ${svg}
|
|
|
85225
85821
|
if (!(fontSize > 0)) return null;
|
|
85226
85822
|
// The widest line, which is what the block of a multi-line label is as wide
|
|
85227
85823
|
// as, and what the browser's getBBox() reports for the same text.
|
|
85228
|
-
|
|
85229
|
-
|
|
85824
|
+
//
|
|
85825
|
+
// A line with bold words in it is measured a run at a time, each in its own
|
|
85826
|
+
// face, which loses the kerning between the last letter of one run and the
|
|
85827
|
+
// first of the next -- a fraction of a pixel at a weight change.
|
|
85828
|
+
width = splitLabelLineRuns(text).reduce(function(max, runs) {
|
|
85829
|
+
var w = runs.reduce(function(sum, run) {
|
|
85830
|
+
var face = run.bold ? getFontForRecord(rec, true) : font;
|
|
85831
|
+
if (!face) {
|
|
85832
|
+
failed = true;
|
|
85833
|
+
return sum;
|
|
85834
|
+
}
|
|
85835
|
+
return sum + measureLine(face, run.text, fontSize, spacing);
|
|
85836
|
+
}, 0);
|
|
85230
85837
|
return w > max ? w : max;
|
|
85231
85838
|
}, 0);
|
|
85839
|
+
if (failed) return null;
|
|
85232
85840
|
return width > 0 ? width : null;
|
|
85233
85841
|
}
|
|
85234
85842
|
|
|
@@ -85252,9 +85860,12 @@ ${svg}
|
|
|
85252
85860
|
// The font a record is drawn in, opened and remembered. A record with no
|
|
85253
85861
|
// font-family is drawn in the layer group's default, the same one the GUI
|
|
85254
85862
|
// measures against -- see getLabelTextDefaults().
|
|
85255
|
-
|
|
85863
|
+
//
|
|
85864
|
+
// @bold: the face its bold runs are drawn in, which is the keyword's 700
|
|
85865
|
+
// whatever the label's own weight is, as it is in CSS.
|
|
85866
|
+
function getFontForRecord(rec, bold) {
|
|
85256
85867
|
var family = rec['font-family'] || 'sans-serif';
|
|
85257
|
-
var weight = getFontWeight(rec);
|
|
85868
|
+
var weight = bold ? WEIGHT_NAMES[LABEL_BOLD_WEIGHT] : getFontWeight(rec);
|
|
85258
85869
|
var italic = isItalic(rec);
|
|
85259
85870
|
var stretch = rec['font-stretch'] || '';
|
|
85260
85871
|
var key = [family, weight, italic ? 'i' : 'n', stretch].join('|');
|
|
@@ -86994,6 +87605,257 @@ ${svg}
|
|
|
86994
87605
|
restoreCapturedUnits: restoreCapturedUnits
|
|
86995
87606
|
});
|
|
86996
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
|
+
|
|
86997
87859
|
// Attach functions exported by modules to the "internal" object,
|
|
86998
87860
|
// so they can be run by tests and by the GUI.
|
|
86999
87861
|
// TODO: rewrite tests to import functions directly from modules,
|
|
@@ -87003,7 +87865,7 @@ ${svg}
|
|
|
87003
87865
|
internal.svg = Object.assign({}, SvgStringify, SvgPathUtils, GeojsonToSvg,
|
|
87004
87866
|
SvgFeatureUtils,
|
|
87005
87867
|
SvgLabels, SvgSymbols, SvgLabelPaths, SvgLabelFit, SvgLabelAlign,
|
|
87006
|
-
SvgLabelMetrics, SvgLabelHalo, SvgLabelCallout);
|
|
87868
|
+
SvgLabelMetrics, SvgLabelHalo, SvgLabelCallout, SvgLabelMarkup, SvgLineArrows);
|
|
87007
87869
|
|
|
87008
87870
|
// Reached through the bundle rather than imported from source, unlike most of
|
|
87009
87871
|
// what tests use, because these modules load fs and fontkit through the
|
|
@@ -87128,6 +87990,7 @@ ${svg}
|
|
|
87128
87990
|
PathExport,
|
|
87129
87991
|
Pathfinder,
|
|
87130
87992
|
PathfinderUtils,
|
|
87993
|
+
PencilGaussian,
|
|
87131
87994
|
PathImport,
|
|
87132
87995
|
PathRepair,
|
|
87133
87996
|
PathUtils,
|