mapshaper 0.7.69 → 0.7.70
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 +925 -736
- package/package.json +1 -1
- package/www/mapshaper-gui.js +1152 -141
- package/www/mapshaper.js +925 -736
- package/www/page.css +19 -3
package/www/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,
|
|
@@ -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
|
|
|
@@ -26388,7 +26388,10 @@
|
|
|
26388
26388
|
function makeSVGPatternFill(str, id) {
|
|
26389
26389
|
var o = parsePattern(str);
|
|
26390
26390
|
var svg;
|
|
26391
|
-
if (!o)
|
|
26391
|
+
if (!o) {
|
|
26392
|
+
message('Invalid pattern, ignoring:', str);
|
|
26393
|
+
return null;
|
|
26394
|
+
}
|
|
26392
26395
|
if (o.type == 'hatches') {
|
|
26393
26396
|
svg = makeHatchPatternSVG(o);
|
|
26394
26397
|
} else if (o.type == 'dots' || o.type == 'squares') {
|
|
@@ -27383,6 +27386,126 @@
|
|
|
27383
27386
|
splitLabelHalos: splitLabelHalos
|
|
27384
27387
|
});
|
|
27385
27388
|
|
|
27389
|
+
// Bold words inside a label: label-text='Israel <b>restricted access</b> here'.
|
|
27390
|
+
//
|
|
27391
|
+
// The markup is part of the text rather than a column beside it, so that the
|
|
27392
|
+
// words and their weight cannot drift apart: an expression, a join or a -labels
|
|
27393
|
+
// text= that rewrites the text rewrites the bold with it. The GUI editor never
|
|
27394
|
+
// shows the tags -- it reads them into ranges and writes them back.
|
|
27395
|
+
//
|
|
27396
|
+
// Only a matched <b>...</b> pair with no other b tag inside it is markup.
|
|
27397
|
+
// Anything else -- an unclosed <b>, a stray </b>, "a < b" -- is text, and is
|
|
27398
|
+
// drawn as typed.
|
|
27399
|
+
//
|
|
27400
|
+
// See docs/development/label-tool-design.md.
|
|
27401
|
+
|
|
27402
|
+
var BOLD_PAIR_RXP = /<b>((?:(?!<\/?b>)[\s\S])*?)<\/b>/gi;
|
|
27403
|
+
var BOLD_TAG_RXP = /<\/?b>/i;
|
|
27404
|
+
|
|
27405
|
+
// The font-weight a bold run is drawn with: the keyword, which a browser and
|
|
27406
|
+
// the Node text measurer both resolve to the family's own bold face.
|
|
27407
|
+
var LABEL_BOLD_WEIGHT = 'bold';
|
|
27408
|
+
|
|
27409
|
+
// @str -> {text, bold}: the text with its bold pairs removed, and the bold
|
|
27410
|
+
// stretches as ascending, non-overlapping [start, end) offsets into it.
|
|
27411
|
+
function parseBoldMarkup(str) {
|
|
27412
|
+
var s = str === null || str === undefined ? '' : String(str);
|
|
27413
|
+
var text = '', bold = [], last = 0, m;
|
|
27414
|
+
if (!BOLD_TAG_RXP.test(s)) return {text: s, bold: bold};
|
|
27415
|
+
BOLD_PAIR_RXP.lastIndex = 0;
|
|
27416
|
+
while ((m = BOLD_PAIR_RXP.exec(s))) {
|
|
27417
|
+
text += s.substring(last, m.index);
|
|
27418
|
+
if (m[1]) {
|
|
27419
|
+
bold.push([text.length, text.length + m[1].length]);
|
|
27420
|
+
text += m[1];
|
|
27421
|
+
}
|
|
27422
|
+
last = m.index + m[0].length;
|
|
27423
|
+
}
|
|
27424
|
+
text += s.substring(last);
|
|
27425
|
+
return {text: text, bold: normalizeBoldRanges(bold)};
|
|
27426
|
+
}
|
|
27427
|
+
|
|
27428
|
+
function stripBoldMarkup(str) {
|
|
27429
|
+
return parseBoldMarkup(str).text;
|
|
27430
|
+
}
|
|
27431
|
+
|
|
27432
|
+
// Sorted, clipped to non-empty, with touching and overlapping ranges merged.
|
|
27433
|
+
// Returns a new array.
|
|
27434
|
+
function normalizeBoldRanges(ranges) {
|
|
27435
|
+
var sorted = (ranges || []).filter(function(r) {
|
|
27436
|
+
return r && r[1] > r[0];
|
|
27437
|
+
}).map(function(r) {
|
|
27438
|
+
return [r[0], r[1]];
|
|
27439
|
+
}).sort(function(a, b) {
|
|
27440
|
+
return a[0] - b[0];
|
|
27441
|
+
});
|
|
27442
|
+
var out = [];
|
|
27443
|
+
sorted.forEach(function(r) {
|
|
27444
|
+
var prev = out[out.length - 1];
|
|
27445
|
+
if (prev && r[0] <= prev[1]) {
|
|
27446
|
+
prev[1] = Math.max(prev[1], r[1]);
|
|
27447
|
+
} else {
|
|
27448
|
+
out.push(r);
|
|
27449
|
+
}
|
|
27450
|
+
});
|
|
27451
|
+
return out;
|
|
27452
|
+
}
|
|
27453
|
+
|
|
27454
|
+
// The characters [start, end) of @text as runs of one weight:
|
|
27455
|
+
// [{text, bold}, ...], with nothing for an empty stretch.
|
|
27456
|
+
function getBoldRuns(text, bold, start, end) {
|
|
27457
|
+
var runs = [], pos = start, i, r, a, b;
|
|
27458
|
+
for (i = 0; i < bold.length && pos < end; i++) {
|
|
27459
|
+
r = bold[i];
|
|
27460
|
+
a = Math.max(r[0], pos);
|
|
27461
|
+
b = Math.min(r[1], end);
|
|
27462
|
+
if (b <= a) continue;
|
|
27463
|
+
if (a > pos) runs.push({text: text.substring(pos, a), bold: false});
|
|
27464
|
+
runs.push({text: text.substring(a, b), bold: true});
|
|
27465
|
+
pos = b;
|
|
27466
|
+
}
|
|
27467
|
+
if (pos < end) runs.push({text: text.substring(pos, end), bold: false});
|
|
27468
|
+
return runs;
|
|
27469
|
+
}
|
|
27470
|
+
|
|
27471
|
+
// @runs -> @obj with the first plain run as its value and the rest as <tspan>s,
|
|
27472
|
+
// which is the shape the stringifier writes as mixed content. A label with no
|
|
27473
|
+
// bold in it comes out exactly as it did before bold existed: one value and no
|
|
27474
|
+
// children. Returns @obj.
|
|
27475
|
+
function applyRunsToSvgObject(obj, runs) {
|
|
27476
|
+
var i = 0;
|
|
27477
|
+
var children = [];
|
|
27478
|
+
if (runs.length > 0 && !runs[0].bold) {
|
|
27479
|
+
obj.value = runs[0].text;
|
|
27480
|
+
i = 1;
|
|
27481
|
+
} else {
|
|
27482
|
+
obj.value = '';
|
|
27483
|
+
}
|
|
27484
|
+
for (; i < runs.length; i++) {
|
|
27485
|
+
children.push(renderRun(runs[i]));
|
|
27486
|
+
}
|
|
27487
|
+
if (children.length > 0) {
|
|
27488
|
+
obj.children = children.concat(obj.children || []);
|
|
27489
|
+
}
|
|
27490
|
+
return obj;
|
|
27491
|
+
}
|
|
27492
|
+
|
|
27493
|
+
function renderRun(run) {
|
|
27494
|
+
var o = {tag: 'tspan', value: run.text};
|
|
27495
|
+
if (run.bold) o.properties = {'font-weight': LABEL_BOLD_WEIGHT};
|
|
27496
|
+
return o;
|
|
27497
|
+
}
|
|
27498
|
+
|
|
27499
|
+
var SvgLabelMarkup = /*#__PURE__*/Object.freeze({
|
|
27500
|
+
__proto__: null,
|
|
27501
|
+
LABEL_BOLD_WEIGHT: LABEL_BOLD_WEIGHT,
|
|
27502
|
+
applyRunsToSvgObject: applyRunsToSvgObject,
|
|
27503
|
+
getBoldRuns: getBoldRuns,
|
|
27504
|
+
normalizeBoldRanges: normalizeBoldRanges,
|
|
27505
|
+
parseBoldMarkup: parseBoldMarkup,
|
|
27506
|
+
stripBoldMarkup: stripBoldMarkup
|
|
27507
|
+
});
|
|
27508
|
+
|
|
27386
27509
|
// Accepting \n (two chars) as an alternative to the newline character
|
|
27387
27510
|
// (sometimes, '\n' is not converted to newline, e.g. in a Makefile)
|
|
27388
27511
|
// Also accepting <br>
|
|
@@ -27399,21 +27522,45 @@
|
|
|
27399
27522
|
var anyLabelBreakRxp = /(\n|\\n|<br>|<wbr>)/i;
|
|
27400
27523
|
var softBreakRxp = /^<wbr>$/i;
|
|
27401
27524
|
|
|
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.
|
|
27525
|
+
// The lines a label's text is drawn as, without its bold markup.
|
|
27405
27526
|
function splitLabelLines(text) {
|
|
27406
|
-
|
|
27407
|
-
|
|
27527
|
+
return splitLabelLineRuns(text).map(function(runs) {
|
|
27528
|
+
return runs.map(function(run) { return run.text; }).join('');
|
|
27529
|
+
});
|
|
27530
|
+
}
|
|
27531
|
+
|
|
27532
|
+
// The lines a label's text is drawn as, each one a list of runs of one weight:
|
|
27533
|
+
// [[{text, bold}, ...], ...]. A bold stretch can cross a line break, and is
|
|
27534
|
+
// then a run on each line.
|
|
27535
|
+
//
|
|
27536
|
+
// Whitespace before a soft break is dropped: a trailing space would move an
|
|
27537
|
+
// end- or middle-justified line by a space's width.
|
|
27538
|
+
function splitLabelLineRuns(text) {
|
|
27539
|
+
var o = parseBoldMarkup(String(text));
|
|
27540
|
+
var parts = o.text.split(anyLabelBreakRxp);
|
|
27541
|
+
var pos = parts[0].length;
|
|
27542
|
+
var lines = [getBoldRuns(o.text, o.bold, 0, pos)];
|
|
27408
27543
|
for (var i = 1; i < parts.length; i += 2) {
|
|
27409
27544
|
if (softBreakRxp.test(parts[i])) {
|
|
27410
|
-
lines[lines.length - 1]
|
|
27545
|
+
trimTrailingSpace(lines[lines.length - 1]);
|
|
27411
27546
|
}
|
|
27412
|
-
|
|
27547
|
+
pos += parts[i].length;
|
|
27548
|
+
lines.push(getBoldRuns(o.text, o.bold, pos, pos + parts[i + 1].length));
|
|
27549
|
+
pos += parts[i + 1].length;
|
|
27413
27550
|
}
|
|
27414
27551
|
return lines;
|
|
27415
27552
|
}
|
|
27416
27553
|
|
|
27554
|
+
function trimTrailingSpace(runs) {
|
|
27555
|
+
var last;
|
|
27556
|
+
while (runs.length > 0) {
|
|
27557
|
+
last = runs[runs.length - 1];
|
|
27558
|
+
last.text = last.text.replace(/\s+$/, '');
|
|
27559
|
+
if (last.text) return;
|
|
27560
|
+
runs.pop();
|
|
27561
|
+
}
|
|
27562
|
+
}
|
|
27563
|
+
|
|
27417
27564
|
function removeSoftBreaks(text) {
|
|
27418
27565
|
return String(text).replace(/<wbr>/gi, '');
|
|
27419
27566
|
}
|
|
@@ -27464,7 +27611,7 @@
|
|
|
27464
27611
|
// already been through it, so calling it here as well costs nothing and means
|
|
27465
27612
|
// every way into the renderer draws a label in the position it is stored in.
|
|
27466
27613
|
var rec = resolveLabelPosition(recArg);
|
|
27467
|
-
var morelines =
|
|
27614
|
+
var morelines = splitLabelLineRuns(toLabelString(rec['label-text']));
|
|
27468
27615
|
var line = morelines.shift();
|
|
27469
27616
|
var obj;
|
|
27470
27617
|
var dx = applyAlignmentShift(rec);
|
|
@@ -27475,23 +27622,21 @@
|
|
|
27475
27622
|
y: dy,
|
|
27476
27623
|
x: dx
|
|
27477
27624
|
};
|
|
27478
|
-
obj = {
|
|
27625
|
+
obj = applyRunsToSvgObject({
|
|
27479
27626
|
tag: 'text',
|
|
27480
|
-
value: line,
|
|
27481
27627
|
properties: properties
|
|
27482
|
-
};
|
|
27628
|
+
}, line);
|
|
27483
27629
|
if (morelines.length > 0) {
|
|
27484
27630
|
// multiline label
|
|
27485
|
-
obj.children = [];
|
|
27631
|
+
obj.children = obj.children || [];
|
|
27486
27632
|
morelines.forEach(function(line) {
|
|
27487
|
-
var tspan = {
|
|
27633
|
+
var tspan = applyRunsToSvgObject({
|
|
27488
27634
|
tag: 'tspan',
|
|
27489
|
-
value: line,
|
|
27490
27635
|
properties: {
|
|
27491
27636
|
x: dx,
|
|
27492
27637
|
dy: getLineHeightDy(rec['line-height'])
|
|
27493
27638
|
}
|
|
27494
|
-
};
|
|
27639
|
+
}, line);
|
|
27495
27640
|
obj.children.push(tspan);
|
|
27496
27641
|
});
|
|
27497
27642
|
}
|
|
@@ -27591,6 +27736,7 @@
|
|
|
27591
27736
|
removeSoftBreaks: removeSoftBreaks,
|
|
27592
27737
|
renderLabel: renderLabel$1,
|
|
27593
27738
|
renderStyledLabel: renderStyledLabel,
|
|
27739
|
+
splitLabelLineRuns: splitLabelLineRuns,
|
|
27594
27740
|
splitLabelLines: splitLabelLines,
|
|
27595
27741
|
toLabelString: toLabelString,
|
|
27596
27742
|
toPixels: toPixels
|
|
@@ -29627,16 +29773,14 @@
|
|
|
29627
29773
|
// a <tspan> inside a <textPath> advances along the path instead of
|
|
29628
29774
|
// stacking below it, so the lines are joined rather than dropping
|
|
29629
29775
|
// everything after the first one
|
|
29630
|
-
text = text.split(labelNewlineRxp).join(' ');
|
|
29631
29776
|
addToReport(report, 'joined', id);
|
|
29632
29777
|
}
|
|
29633
|
-
textPath = {
|
|
29778
|
+
textPath = applyRunsToSvgObject({
|
|
29634
29779
|
tag: 'textPath',
|
|
29635
|
-
value: text,
|
|
29636
29780
|
properties: {
|
|
29637
29781
|
startOffset: rec['label-start-offset'] || getDefaultStartOffset(rec)
|
|
29638
29782
|
}
|
|
29639
|
-
};
|
|
29783
|
+
}, joinLineRuns(splitLabelLineRuns(text)));
|
|
29640
29784
|
// the caller replaces this with a reference to a <defs> entry
|
|
29641
29785
|
textPath.properties[LABEL_PATH_PROPERTY] = d;
|
|
29642
29786
|
if (rec['label-side']) {
|
|
@@ -29700,6 +29844,28 @@
|
|
|
29700
29844
|
return '50%';
|
|
29701
29845
|
}
|
|
29702
29846
|
|
|
29847
|
+
// A path label's lines as one line of runs, with a space for each break. The
|
|
29848
|
+
// space takes the weight of the text before it, and runs of one weight that
|
|
29849
|
+
// the join brings together are merged, so that text with no bold in it is a
|
|
29850
|
+
// single run.
|
|
29851
|
+
function joinLineRuns(lines) {
|
|
29852
|
+
var out = [];
|
|
29853
|
+
lines.forEach(function(runs, i) {
|
|
29854
|
+
if (i > 0) appendRun(out, {text: ' ', bold: out.length > 0 && out[out.length - 1].bold});
|
|
29855
|
+
runs.forEach(function(run) { appendRun(out, run); });
|
|
29856
|
+
});
|
|
29857
|
+
return out;
|
|
29858
|
+
}
|
|
29859
|
+
|
|
29860
|
+
function appendRun(runs, run) {
|
|
29861
|
+
var last = runs[runs.length - 1];
|
|
29862
|
+
if (last && last.bold == run.bold) {
|
|
29863
|
+
last.text += run.text;
|
|
29864
|
+
} else {
|
|
29865
|
+
runs.push({text: run.text, bold: run.bold});
|
|
29866
|
+
}
|
|
29867
|
+
}
|
|
29868
|
+
|
|
29703
29869
|
function addToReport(report, key, id) {
|
|
29704
29870
|
if (report && report[key]) report[key].push(id);
|
|
29705
29871
|
}
|
|
@@ -40716,6 +40882,22 @@ ${svg}
|
|
|
40716
40882
|
}
|
|
40717
40883
|
}
|
|
40718
40884
|
|
|
40885
|
+
// -labels adds a label when given coordinates=, and styles the target's labels
|
|
40886
|
+
// otherwise. Options that belong to one of these are refused in the other,
|
|
40887
|
+
// rather than ignored.
|
|
40888
|
+
function validateLabelsOpts(cmd) {
|
|
40889
|
+
var o = cmd.options;
|
|
40890
|
+
if ('coordinates' in o) {
|
|
40891
|
+
if (o.where || o.ids) {
|
|
40892
|
+
stop$1('where= and ids= select labels to style, and can not be used with coordinates=');
|
|
40893
|
+
}
|
|
40894
|
+
} else if (o.properties) {
|
|
40895
|
+
stop$1('properties= requires coordinates=');
|
|
40896
|
+
} else if ('name' in o && !o.no_replace) {
|
|
40897
|
+
stop$1('name= requires coordinates= or +');
|
|
40898
|
+
}
|
|
40899
|
+
}
|
|
40900
|
+
|
|
40719
40901
|
function validateExpressionOpt(cmd) {
|
|
40720
40902
|
if (!cmd.options.expression) {
|
|
40721
40903
|
error('Command requires a JavaScript expression');
|
|
@@ -41696,17 +41878,17 @@ ${svg}
|
|
|
41696
41878
|
nameOpt2 = { // for -calc and -info
|
|
41697
41879
|
describe: 'name the output layer'
|
|
41698
41880
|
},
|
|
41699
|
-
// label style properties accepted by -add-label
|
|
41700
|
-
//
|
|
41701
|
-
//
|
|
41881
|
+
// label style properties accepted by -labels and -add-label. -style
|
|
41882
|
+
// accepts these too, for backward compatibility, but leaves them out of
|
|
41883
|
+
// its help.
|
|
41702
41884
|
labelStyleOpts = {
|
|
41703
|
-
'font-family': {describe: '
|
|
41704
|
-
'font-size': {describe: '
|
|
41885
|
+
'font-family': {describe: 'CSS font family, e.g. Georgia (default is sans-serif)'},
|
|
41886
|
+
'font-size': {describe: 'font size (default is 12)'},
|
|
41705
41887
|
'font-style': {describe: 'normal or italic'},
|
|
41706
41888
|
'font-weight': {describe: 'normal, bold or a numeric weight'},
|
|
41707
41889
|
'font-stretch': {describe: 'e.g. condensed'},
|
|
41708
41890
|
'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'},
|
|
41891
|
+
'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
41892
|
'text-anchor': {describe: 'start, middle or end'},
|
|
41711
41893
|
'label-align': {describe: 'how the lines of a multi-line label align: left, center or right'},
|
|
41712
41894
|
'dominant-baseline': {describe: 'vertical alignment, e.g. central'},
|
|
@@ -41717,23 +41899,30 @@ ${svg}
|
|
|
41717
41899
|
dy: {describe: 'vertical offset from the anchor'},
|
|
41718
41900
|
fill: {describe: 'text color'},
|
|
41719
41901
|
opacity: {describe: 'text opacity'},
|
|
41720
|
-
'halo-width': {describe: 'width of a halo around the text in px'},
|
|
41902
|
+
'halo-width': {describe: 'width of a halo around the text in px (default is 0, no halo)'},
|
|
41721
41903
|
'halo-color': {describe: 'halo color (default is white)'},
|
|
41722
41904
|
'halo-opacity': {describe: 'halo opacity, 0-1'},
|
|
41723
|
-
'label-width': {describe: 'width of a fixed-width text block in px'},
|
|
41905
|
+
'label-width': {describe: 'width of a fixed-width text block in px (lines are wrapped in the web UI)'},
|
|
41724
41906
|
callout: {describe: 'line from the anchor to the text: line, elbow or curve'},
|
|
41725
41907
|
'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'},
|
|
41908
|
+
'callout-end-size': {describe: 'length of an arrowhead\'s sides in px (default is 10)'},
|
|
41727
41909
|
'callout-via': {describe: 'x,y of an elbow\'s corner or a point on a curve, in px'},
|
|
41728
41910
|
'callout-attach': {describe: 'where the callout meets the text, as x,y fractions'},
|
|
41729
41911
|
'callout-gap': {describe: 'px between the callout and the anchor'},
|
|
41730
|
-
'callout-padding': {describe: 'px between the callout and the text'},
|
|
41912
|
+
'callout-padding': {describe: 'px between the callout and the text (default is 3)'},
|
|
41731
41913
|
'callout-color': {describe: 'callout color (defaults to the text color)'},
|
|
41732
|
-
'callout-width': {describe: 'callout line width in px'},
|
|
41914
|
+
'callout-width': {describe: 'callout line width in px (default is 1)'},
|
|
41733
41915
|
'callout-opacity': {describe: 'callout opacity, 0-1'},
|
|
41734
41916
|
css: {describe: 'inline css style'},
|
|
41735
41917
|
class: {describe: 'name of CSS class or classes (space-separated)'}
|
|
41736
41918
|
},
|
|
41919
|
+
// a label can carry a symbol at its anchor
|
|
41920
|
+
labelIconOpts = {
|
|
41921
|
+
icon: {describe: 'symbol drawn at the label anchor: circle, square, ring, star'},
|
|
41922
|
+
'icon-size': {describe: 'size of the anchor symbol in px'},
|
|
41923
|
+
'icon-color': {describe: 'color of the anchor symbol (defaults to the text color)'},
|
|
41924
|
+
'icon-opacity': {describe: 'opacity of the anchor symbol, 0-1'}
|
|
41925
|
+
},
|
|
41737
41926
|
noReplaceOpt2 = { // for -calc and -info
|
|
41738
41927
|
alias: '+',
|
|
41739
41928
|
type: 'flag',
|
|
@@ -41813,6 +42002,18 @@ ${svg}
|
|
|
41813
42002
|
type: 'distance'
|
|
41814
42003
|
};
|
|
41815
42004
|
|
|
42005
|
+
// The label options -style used to document, before -labels took them over.
|
|
42006
|
+
// fill, opacity, css and class are left out because -style documents them
|
|
42007
|
+
// for every kind of feature.
|
|
42008
|
+
function getHiddenLabelStyleOpts() {
|
|
42009
|
+
var shared = ['fill', 'opacity', 'css', 'class'];
|
|
42010
|
+
var opts = {'label-text': {}};
|
|
42011
|
+
Object.keys(labelStyleOpts).forEach(function(name) {
|
|
42012
|
+
if (shared.indexOf(name) == -1) opts[name] = {};
|
|
42013
|
+
});
|
|
42014
|
+
return opts;
|
|
42015
|
+
}
|
|
42016
|
+
|
|
41816
42017
|
var parser = new CommandParser();
|
|
41817
42018
|
parser.usage('Usage: mapshaper -<command> [options] ...');
|
|
41818
42019
|
|
|
@@ -43181,6 +43382,32 @@ ${svg}
|
|
|
43181
43382
|
.option('encoding', encodingOpt)
|
|
43182
43383
|
.option('target', targetOpt);
|
|
43183
43384
|
|
|
43385
|
+
parser.command('labels')
|
|
43386
|
+
.describe('style labels, convert points to labels or add a label')
|
|
43387
|
+
.validate(validateLabelsOpts)
|
|
43388
|
+
.option('text', {
|
|
43389
|
+
describe: 'label text (a field, JS expression or literal value)'
|
|
43390
|
+
})
|
|
43391
|
+
.option('label-text', {
|
|
43392
|
+
alias_to: 'text'
|
|
43393
|
+
})
|
|
43394
|
+
.option('coordinates', {
|
|
43395
|
+
describe: 'add a label at x,y, or a path label along x,y,x,y,...'
|
|
43396
|
+
})
|
|
43397
|
+
.option('where', whereOpt)
|
|
43398
|
+
.option('ids', {
|
|
43399
|
+
describe: 'comma-sep. list of ids of the labels to style',
|
|
43400
|
+
type: 'numbers'
|
|
43401
|
+
})
|
|
43402
|
+
.options(labelStyleOpts)
|
|
43403
|
+
.options(labelIconOpts)
|
|
43404
|
+
.option('properties', {
|
|
43405
|
+
describe: 'with coordinates=, other attributes as a JSON object'
|
|
43406
|
+
})
|
|
43407
|
+
.option('name', nameOpt)
|
|
43408
|
+
.option('no-replace', noReplaceOpt)
|
|
43409
|
+
.option('target', targetOpt);
|
|
43410
|
+
|
|
43184
43411
|
parser.command('lines')
|
|
43185
43412
|
.describe('convert a polygon or point layer to a polyline layer')
|
|
43186
43413
|
.option('fields', {
|
|
@@ -43744,99 +43971,11 @@ ${svg}
|
|
|
43744
43971
|
describe: 'point icon color (defaults to fill color, then black)'
|
|
43745
43972
|
})
|
|
43746
43973
|
.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)'
|
|
43781
|
-
})
|
|
43782
|
-
.option('font-weight', {
|
|
43783
|
-
describe: 'CSS font weight property of labels (e.g. bold, 700)'
|
|
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'
|
|
43974
|
+
describe: 'point icon opacity, 0-1'
|
|
43820
43975
|
})
|
|
43821
|
-
|
|
43822
|
-
|
|
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);
|
|
43976
|
+
// deprecated in favor of -labels; accepted, but left out of the help
|
|
43977
|
+
.options(getHiddenLabelStyleOpts())
|
|
43978
|
+
.option('target', targetOpt);
|
|
43840
43979
|
|
|
43841
43980
|
parser.command('repel')
|
|
43842
43981
|
.describe('move overlapping point symbols apart')
|
|
@@ -44082,23 +44221,10 @@ ${svg}
|
|
|
44082
44221
|
.option('text', {
|
|
44083
44222
|
describe: 'label text'
|
|
44084
44223
|
})
|
|
44085
|
-
//
|
|
44086
|
-
//
|
|
44224
|
+
// superseded by -labels coordinates=; kept so that older session
|
|
44225
|
+
// histories still run
|
|
44087
44226
|
.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
|
-
})
|
|
44227
|
+
.options(labelIconOpts)
|
|
44102
44228
|
.option('properties', {
|
|
44103
44229
|
describe: 'other attributes, as a JSON object'
|
|
44104
44230
|
})
|
|
@@ -51395,6 +51521,621 @@ ${svg}
|
|
|
51395
51521
|
return d;
|
|
51396
51522
|
}
|
|
51397
51523
|
|
|
51524
|
+
// Insert cutting points in arcs, where bbox intersects other shapes
|
|
51525
|
+
// Return a polygon layer containing the bounding box vectors, divided at cutting points.
|
|
51526
|
+
function divideDatasetByBBox(dataset, bbox) {
|
|
51527
|
+
var arcs = dataset.arcs;
|
|
51528
|
+
var data = findBBoxCutPoints(arcs, bbox);
|
|
51529
|
+
var map = insertCutPoints(data.cutPoints, arcs);
|
|
51530
|
+
arcs.dedupCoords();
|
|
51531
|
+
remapDividedArcs(dataset, map);
|
|
51532
|
+
// merge bbox dataset with target dataset,
|
|
51533
|
+
// so arcs are shared between target layers and bbox layer
|
|
51534
|
+
var clipDataset = bboxPointsToClipDataset(data.bboxPoints);
|
|
51535
|
+
var mergedDataset = mergeDatasets([dataset, clipDataset]);
|
|
51536
|
+
// TODO: detect if we need to rebuild topology (unlikely), like with the full clip command
|
|
51537
|
+
// buildTopology(mergedDataset);
|
|
51538
|
+
var clipLyr = mergedDataset.layers.pop();
|
|
51539
|
+
dataset.arcs = mergedDataset.arcs;
|
|
51540
|
+
dataset.layers = mergedDataset.layers;
|
|
51541
|
+
return clipLyr;
|
|
51542
|
+
}
|
|
51543
|
+
|
|
51544
|
+
function bboxPointsToClipDataset(arr) {
|
|
51545
|
+
var arcs = [];
|
|
51546
|
+
var shape = [];
|
|
51547
|
+
var layer = {geometry_type: 'polygon', shapes: [[shape]]};
|
|
51548
|
+
var p1, p2;
|
|
51549
|
+
for (var i=0, n=arr.length - 1; i<n; i++) {
|
|
51550
|
+
p1 = arr[i];
|
|
51551
|
+
p2 = arr[i+1];
|
|
51552
|
+
arcs.push([[p1.x, p1.y], [p2.x, p2.y]]);
|
|
51553
|
+
shape.push(i);
|
|
51554
|
+
}
|
|
51555
|
+
return {
|
|
51556
|
+
arcs: new ArcCollection(arcs),
|
|
51557
|
+
layers: [layer]
|
|
51558
|
+
};
|
|
51559
|
+
}
|
|
51560
|
+
|
|
51561
|
+
function findBBoxCutPoints(arcs, bbox) {
|
|
51562
|
+
var left = bbox[0],
|
|
51563
|
+
bottom = bbox[1],
|
|
51564
|
+
right = bbox[2],
|
|
51565
|
+
top = bbox[3];
|
|
51566
|
+
|
|
51567
|
+
// arrays of intersection points along each bbox edge
|
|
51568
|
+
var tt = [],
|
|
51569
|
+
rr = [],
|
|
51570
|
+
bb = [],
|
|
51571
|
+
ll = [];
|
|
51572
|
+
|
|
51573
|
+
arcs.forEachSegment(function(i, j, xx, yy) {
|
|
51574
|
+
var ax = xx[i],
|
|
51575
|
+
ay = yy[i],
|
|
51576
|
+
bx = xx[j],
|
|
51577
|
+
by = yy[j];
|
|
51578
|
+
var hit;
|
|
51579
|
+
if (segmentOutsideBBox(ax, ay, bx, by, left, bottom, right, top)) return;
|
|
51580
|
+
if (segmentInsideBBox(ax, ay, bx, by, left, bottom, right, top)) return;
|
|
51581
|
+
|
|
51582
|
+
hit = geom.segmentIntersection(left, top, right, top, ax, ay, bx, by);
|
|
51583
|
+
if (hit) addHit(tt, hit, i, j, xx, yy);
|
|
51584
|
+
|
|
51585
|
+
hit = geom.segmentIntersection(left, bottom, right, bottom, ax, ay, bx, by);
|
|
51586
|
+
if (hit) addHit(bb, hit, i, j, xx, yy);
|
|
51587
|
+
|
|
51588
|
+
hit = geom.segmentIntersection(left, bottom, left, top, ax, ay, bx, by);
|
|
51589
|
+
if (hit) addHit(ll, hit, i, j, xx, yy);
|
|
51590
|
+
|
|
51591
|
+
hit = geom.segmentIntersection(right, bottom, right, top, ax, ay, bx, by);
|
|
51592
|
+
if (hit) addHit(rr, hit, i, j, xx, yy);
|
|
51593
|
+
});
|
|
51594
|
+
|
|
51595
|
+
return {
|
|
51596
|
+
cutPoints: ll.concat(bb, rr, tt),
|
|
51597
|
+
bboxPoints: getDividedBBoxPoints(bbox, ll, tt, rr, bb)
|
|
51598
|
+
};
|
|
51599
|
+
|
|
51600
|
+
function addHit(arr, hit, i, j, xx, yy) {
|
|
51601
|
+
if (!hit) return;
|
|
51602
|
+
arr.push(formatHit(hit[0], hit[1], i, j, xx, yy));
|
|
51603
|
+
if (hit.length == 4) {
|
|
51604
|
+
arr.push(formatHit(hit[2], hit[3], i, j, xx, yy));
|
|
51605
|
+
}
|
|
51606
|
+
}
|
|
51607
|
+
|
|
51608
|
+
function formatHit(x, y, i, j, xx, yy) {
|
|
51609
|
+
var ids = formatIntersectingSegment(x, y, i, j, xx, yy);
|
|
51610
|
+
return getCutPoint(x, y, ids[0], ids[1]);
|
|
51611
|
+
}
|
|
51612
|
+
}
|
|
51613
|
+
|
|
51614
|
+
function segmentOutsideBBox(ax, ay, bx, by, xmin, ymin, xmax, ymax) {
|
|
51615
|
+
return ax < xmin && bx < xmin || ax > xmax && bx > xmax ||
|
|
51616
|
+
ay < ymin && by < ymin || ay > ymax && by > ymax;
|
|
51617
|
+
}
|
|
51618
|
+
|
|
51619
|
+
function segmentInsideBBox(ax, ay, bx, by, xmin, ymin, xmax, ymax) {
|
|
51620
|
+
return ax > xmin && bx > xmin && ax < xmax && bx < xmax &&
|
|
51621
|
+
ay > ymin && by > ymin && ay < ymax && by < ymax;
|
|
51622
|
+
}
|
|
51623
|
+
|
|
51624
|
+
// Returns an array of points representing the vertices in
|
|
51625
|
+
// the bbox with cutting points inserted.
|
|
51626
|
+
function getDividedBBoxPoints(bbox, ll, tt, rr, bb) {
|
|
51627
|
+
var bl = {x: bbox[0], y: bbox[1]},
|
|
51628
|
+
tl = {x: bbox[0], y: bbox[3]},
|
|
51629
|
+
tr = {x: bbox[2], y: bbox[3]},
|
|
51630
|
+
br = {x: bbox[2], y: bbox[1]};
|
|
51631
|
+
ll = utils.sortOn(ll.concat([bl, tl]), 'y', true);
|
|
51632
|
+
tt = utils.sortOn(tt.concat([tl, tr]), 'x', true);
|
|
51633
|
+
rr = utils.sortOn(rr.concat([tr, br]), 'y', false);
|
|
51634
|
+
bb = utils.sortOn(bb.concat([br, bl]), 'x', false);
|
|
51635
|
+
return ll.concat(tt, rr, bb).reduce(function(memo, p2) {
|
|
51636
|
+
var p1 = memo.length > 0 ? memo[memo.length-1] : null;
|
|
51637
|
+
if (p1 === null || p1.x != p2.x || p1.y != p2.y) memo.push(p2);
|
|
51638
|
+
return memo;
|
|
51639
|
+
}, []);
|
|
51640
|
+
}
|
|
51641
|
+
|
|
51642
|
+
var Bbox2Clipping = /*#__PURE__*/Object.freeze({
|
|
51643
|
+
__proto__: null,
|
|
51644
|
+
divideDatasetByBBox: divideDatasetByBBox,
|
|
51645
|
+
segmentInsideBBox: segmentInsideBBox,
|
|
51646
|
+
segmentOutsideBBox: segmentOutsideBBox
|
|
51647
|
+
});
|
|
51648
|
+
|
|
51649
|
+
cmd.filterGeom = function(lyr, arcs, opts) {
|
|
51650
|
+
if (!layerHasGeometry$1(lyr)) {
|
|
51651
|
+
stop$1("Layer is missing geometry");
|
|
51652
|
+
}
|
|
51653
|
+
if (opts.bbox) {
|
|
51654
|
+
filterByBoundsIntersection(lyr, arcs, opts);
|
|
51655
|
+
}
|
|
51656
|
+
cmd.filterFeatures(lyr, arcs, {remove_empty: true, verbose: false});
|
|
51657
|
+
};
|
|
51658
|
+
|
|
51659
|
+
function filterByBoundsIntersection(lyr, arcs, opts) {
|
|
51660
|
+
var filter = getBoundsIntersectionFilter(opts.bbox, lyr, arcs);
|
|
51661
|
+
noteLayerWillChange(lyr, {operation: 'filter-geom', unit: 'shapes'});
|
|
51662
|
+
editShapes(lyr.shapes, filter);
|
|
51663
|
+
markLayerChanged(lyr, {operation: 'filter-geom', unit: 'shapes'});
|
|
51664
|
+
}
|
|
51665
|
+
|
|
51666
|
+
function getBoundsIntersectionFilter(bbox, lyr, arcs) {
|
|
51667
|
+
var bounds = new Bounds(bbox);
|
|
51668
|
+
var filter = lyr.geometry_type == 'point' ?
|
|
51669
|
+
getPointInBoundsTest(bounds) :
|
|
51670
|
+
getPathBoundsIntersectionTest(bounds, arcs);
|
|
51671
|
+
return filter;
|
|
51672
|
+
}
|
|
51673
|
+
|
|
51674
|
+
function getPointInBoundsTest(bounds) {
|
|
51675
|
+
return function(xy) {
|
|
51676
|
+
var contains = bounds.containsPoint(xy[0], xy[1]);
|
|
51677
|
+
return contains ? xy : null;
|
|
51678
|
+
};
|
|
51679
|
+
}
|
|
51680
|
+
|
|
51681
|
+
// V1 too-simple test: bounding-box intersection
|
|
51682
|
+
// internal.getPathBoundsIntersectionTest = function(bounds, arcs) {
|
|
51683
|
+
// return function(path) {
|
|
51684
|
+
// return bounds.intersects(arcs.getSimpleShapeBounds(path)) ? path : null;
|
|
51685
|
+
// };
|
|
51686
|
+
// };
|
|
51687
|
+
|
|
51688
|
+
function getPathBoundsIntersectionTest(bounds, arcs) {
|
|
51689
|
+
var bbox = bounds.toArray(),
|
|
51690
|
+
left = bbox[0],
|
|
51691
|
+
bottom = bbox[1],
|
|
51692
|
+
right = bbox[2],
|
|
51693
|
+
top = bbox[3];
|
|
51694
|
+
|
|
51695
|
+
return function(path) {
|
|
51696
|
+
// case: bounding boxes don't intersect -> the path doesn't intersect the box
|
|
51697
|
+
if (!bounds.intersects(arcs.getSimpleShapeBounds(path))) {
|
|
51698
|
+
return null;
|
|
51699
|
+
}
|
|
51700
|
+
var intersects = false;
|
|
51701
|
+
var ax, ay, bx, by;
|
|
51702
|
+
var iter = arcs.getShapeIter(path);
|
|
51703
|
+
|
|
51704
|
+
if (iter.hasNext()) {
|
|
51705
|
+
ax = iter.x;
|
|
51706
|
+
ay = iter.y;
|
|
51707
|
+
}
|
|
51708
|
+
while (iter.hasNext()) {
|
|
51709
|
+
bx = ax;
|
|
51710
|
+
by = ay;
|
|
51711
|
+
ax = iter.x;
|
|
51712
|
+
ay = iter.y;
|
|
51713
|
+
if (segmentOutsideBBox(ax, ay, bx, by, left, bottom, right, top)) continue;
|
|
51714
|
+
if (segmentInsideBBox(ax, ay, bx, by, left, bottom, right, top)) {
|
|
51715
|
+
intersects = true;
|
|
51716
|
+
break;
|
|
51717
|
+
}
|
|
51718
|
+
if (geom.segmentIntersection(left, top, right, top, ax, ay, bx, by) ||
|
|
51719
|
+
geom.segmentIntersection(left, bottom, right, bottom, ax, ay, bx, by) ||
|
|
51720
|
+
geom.segmentIntersection(left, bottom, left, top, ax, ay, bx, by) ||
|
|
51721
|
+
geom.segmentIntersection(right, bottom, right, top, ax, ay, bx, by)) {
|
|
51722
|
+
intersects = true;
|
|
51723
|
+
break;
|
|
51724
|
+
}
|
|
51725
|
+
}
|
|
51726
|
+
|
|
51727
|
+
// case: bbox is entirely inside this ring
|
|
51728
|
+
if (!intersects && geom.testPointInRing(left, bottom, path, arcs)) {
|
|
51729
|
+
intersects = true;
|
|
51730
|
+
}
|
|
51731
|
+
return intersects ? path : null;
|
|
51732
|
+
};
|
|
51733
|
+
}
|
|
51734
|
+
|
|
51735
|
+
// Return a function for testing if a shape (path or point) intersects a bounding box
|
|
51736
|
+
// TODO: move this function to a different file
|
|
51737
|
+
function getBBoxIntersectionTest(bbox, lyr, arcs) {
|
|
51738
|
+
var filter = getBoundsIntersectionFilter(bbox, lyr, arcs);
|
|
51739
|
+
return function(shapeId) {
|
|
51740
|
+
var shp = lyr.shapes[shapeId];
|
|
51741
|
+
if (!shp) return false;
|
|
51742
|
+
for (var i=0; i<shp.length; i++) {
|
|
51743
|
+
if (filter(shp[i])) return true;
|
|
51744
|
+
}
|
|
51745
|
+
return false;
|
|
51746
|
+
};
|
|
51747
|
+
}
|
|
51748
|
+
|
|
51749
|
+
// return array of shape ids
|
|
51750
|
+
function findShapesIntersectingBBox(bbox, lyr, arcs) {
|
|
51751
|
+
var test = getBBoxIntersectionTest(bbox, lyr, arcs);
|
|
51752
|
+
var ids = [];
|
|
51753
|
+
for (var i=0; i<lyr.shapes.length; i++) {
|
|
51754
|
+
if (test(i)) ids.push(i);
|
|
51755
|
+
}
|
|
51756
|
+
return ids;
|
|
51757
|
+
}
|
|
51758
|
+
|
|
51759
|
+
var FilterGeom = /*#__PURE__*/Object.freeze({
|
|
51760
|
+
__proto__: null,
|
|
51761
|
+
findShapesIntersectingBBox: findShapesIntersectingBBox,
|
|
51762
|
+
getBBoxIntersectionTest: getBBoxIntersectionTest
|
|
51763
|
+
});
|
|
51764
|
+
|
|
51765
|
+
cmd.filterFeatures = function(lyr, arcs, opts) {
|
|
51766
|
+
var records = lyr.data ? lyr.data.getRecords() : null,
|
|
51767
|
+
shapes = lyr.shapes || null,
|
|
51768
|
+
n = getFeatureCount(lyr),
|
|
51769
|
+
filteredShapes = shapes ? [] : null,
|
|
51770
|
+
filteredRecords = records ? [] : null,
|
|
51771
|
+
filteredLyr = getOutputLayer(lyr, opts),
|
|
51772
|
+
invert = !!opts.invert,
|
|
51773
|
+
filter;
|
|
51774
|
+
|
|
51775
|
+
if (opts.expression) {
|
|
51776
|
+
filter = compileFeatureExpression(opts.expression, lyr, arcs);
|
|
51777
|
+
}
|
|
51778
|
+
|
|
51779
|
+
if (opts.ids) {
|
|
51780
|
+
filter = combineFilters(filter, getIdFilter(opts.ids));
|
|
51781
|
+
}
|
|
51782
|
+
|
|
51783
|
+
if (opts.remove_empty) {
|
|
51784
|
+
filter = combineFilters(filter, getNullGeometryFilter(lyr, arcs));
|
|
51785
|
+
}
|
|
51786
|
+
|
|
51787
|
+
if (opts.bbox) {
|
|
51788
|
+
filter = combineFilters(filter, getBBoxIntersectionTest(opts.bbox, lyr, arcs));
|
|
51789
|
+
}
|
|
51790
|
+
|
|
51791
|
+
if (!filter) {
|
|
51792
|
+
stop$1("Missing a filter criterion");
|
|
51793
|
+
}
|
|
51794
|
+
|
|
51795
|
+
utils.repeat(n, function(shapeId) {
|
|
51796
|
+
var result = filter(shapeId);
|
|
51797
|
+
requireBooleanResult(result);
|
|
51798
|
+
if (invert) result = !result;
|
|
51799
|
+
if (result === true) {
|
|
51800
|
+
if (shapes) filteredShapes.push(shapes[shapeId] || null);
|
|
51801
|
+
if (records) filteredRecords.push(records[shapeId] || null);
|
|
51802
|
+
}
|
|
51803
|
+
});
|
|
51804
|
+
|
|
51805
|
+
if (filteredLyr == lyr) {
|
|
51806
|
+
noteLayerWillChange(lyr, {operation: 'filter'});
|
|
51807
|
+
}
|
|
51808
|
+
filteredLyr.shapes = filteredShapes;
|
|
51809
|
+
filteredLyr.data = filteredRecords ? new DataTable(filteredRecords) : null;
|
|
51810
|
+
if (filteredLyr == lyr) {
|
|
51811
|
+
markLayerChanged(lyr, {operation: 'filter'});
|
|
51812
|
+
}
|
|
51813
|
+
if (opts.no_replace) {
|
|
51814
|
+
// if adding a layer, don't share objects between source and filtered layer
|
|
51815
|
+
filteredLyr = copyLayer(filteredLyr);
|
|
51816
|
+
}
|
|
51817
|
+
|
|
51818
|
+
if (opts.verbose !== false && !opts.quiet) {
|
|
51819
|
+
message(utils.format('Retained %,d of %,d features', getFeatureCount(filteredLyr), n));
|
|
51820
|
+
}
|
|
51821
|
+
|
|
51822
|
+
return filteredLyr;
|
|
51823
|
+
};
|
|
51824
|
+
|
|
51825
|
+
// TODO: update filter command to use this function
|
|
51826
|
+
function filterLayerInPlace(lyr, filter, invert) {
|
|
51827
|
+
var records = lyr.data ? lyr.data.getRecords() : null,
|
|
51828
|
+
shapes = lyr.shapes || null,
|
|
51829
|
+
n = getFeatureCount(lyr),
|
|
51830
|
+
filteredShapes = shapes ? [] : null,
|
|
51831
|
+
filteredRecords = records ? [] : null;
|
|
51832
|
+
utils.repeat(n, function(shapeId) {
|
|
51833
|
+
var result = filter(shapeId);
|
|
51834
|
+
requireBooleanResult(result);
|
|
51835
|
+
if (result === true) {
|
|
51836
|
+
if (shapes) filteredShapes.push(shapes[shapeId] || null);
|
|
51837
|
+
if (records) filteredRecords.push(records[shapeId] || null);
|
|
51838
|
+
}
|
|
51839
|
+
});
|
|
51840
|
+
noteLayerWillChange(lyr, {operation: 'filterLayerInPlace'});
|
|
51841
|
+
lyr.shapes = filteredShapes;
|
|
51842
|
+
lyr.data = filteredRecords ? new DataTable(filteredRecords) : null;
|
|
51843
|
+
markLayerChanged(lyr, {operation: 'filterLayerInPlace'});
|
|
51844
|
+
}
|
|
51845
|
+
|
|
51846
|
+
function getIdFilter(ids) {
|
|
51847
|
+
var set = new Set(ids);
|
|
51848
|
+
return function(i) {
|
|
51849
|
+
return set.has(i);
|
|
51850
|
+
};
|
|
51851
|
+
}
|
|
51852
|
+
|
|
51853
|
+
function getNullGeometryFilter(lyr, arcs) {
|
|
51854
|
+
var shapes = lyr.shapes;
|
|
51855
|
+
if (lyr.geometry_type == 'polygon') {
|
|
51856
|
+
return getEmptyPolygonFilter(shapes, arcs);
|
|
51857
|
+
}
|
|
51858
|
+
return function(i) {return !!shapes[i];};
|
|
51859
|
+
}
|
|
51860
|
+
|
|
51861
|
+
function getEmptyPolygonFilter(shapes, arcs) {
|
|
51862
|
+
return function(i) {
|
|
51863
|
+
var shp = shapes[i];
|
|
51864
|
+
return !!shp && geom.getPlanarShapeArea(shapes[i], arcs) > 0;
|
|
51865
|
+
};
|
|
51866
|
+
}
|
|
51867
|
+
|
|
51868
|
+
function combineFilters(a, b) {
|
|
51869
|
+
return a && b ? function(id) {
|
|
51870
|
+
return a(id) && b(id);
|
|
51871
|
+
} : (a || b);
|
|
51872
|
+
}
|
|
51873
|
+
|
|
51874
|
+
// The icon names accepted by the icon= property of the -style command.
|
|
51875
|
+
// Kept in a module of its own so that both the renderer (svg-symbols.mjs) and
|
|
51876
|
+
// the -style command can use it without forming an import cycle.
|
|
51877
|
+
|
|
51878
|
+
var iconNames = ['circle', 'square', 'ring', 'star'];
|
|
51879
|
+
|
|
51880
|
+
function isSupportedIconName(name) {
|
|
51881
|
+
return iconNames.indexOf(name) > -1;
|
|
51882
|
+
}
|
|
51883
|
+
|
|
51884
|
+
cmd.svgStyle = function(lyr, dataset, opts) {
|
|
51885
|
+
var filterFn, table, fields, hasNewFields, optFields, clearedByPosition, fieldsBefore;
|
|
51886
|
+
|
|
51887
|
+
function hadField(field) {
|
|
51888
|
+
return field in fieldsBefore;
|
|
51889
|
+
}
|
|
51890
|
+
|
|
51891
|
+
if (getFeatureCount(lyr) === 0) {
|
|
51892
|
+
return;
|
|
51893
|
+
}
|
|
51894
|
+
if (!lyr.data) {
|
|
51895
|
+
initDataTable(lyr);
|
|
51896
|
+
}
|
|
51897
|
+
if (opts.where) {
|
|
51898
|
+
filterFn = compileFeatureExpression(opts.where, lyr, dataset.arcs);
|
|
51899
|
+
}
|
|
51900
|
+
if (opts.ids) {
|
|
51901
|
+
filterFn = combineFilters(filterFn, getIdFilter(opts.ids));
|
|
51902
|
+
}
|
|
51903
|
+
if (opts.clear) {
|
|
51904
|
+
lyr.data.getFields().filter(isSupportedSvgStyleProperty).forEach(lyr.data.deleteField, lyr.data);
|
|
51905
|
+
}
|
|
51906
|
+
table = getLayerDataTable(lyr);
|
|
51907
|
+
optFields = getOptionFields(opts);
|
|
51908
|
+
fields = getStyleFields(optFields);
|
|
51909
|
+
// Which of dx/dy/text-anchor a label-pos in this command clears: not the ones
|
|
51910
|
+
// the same command also sets, so that `label-pos=n dx=3` keeps the nudge it
|
|
51911
|
+
// was given instead of clearing it a moment later.
|
|
51912
|
+
//
|
|
51913
|
+
// Clearing at all is what keeps the shorthand usable, since a value on the
|
|
51914
|
+
// record wins over the position: without it, setting a position on a label
|
|
51915
|
+
// that had been dragged would appear to do nothing.
|
|
51916
|
+
clearedByPosition = labelPositionDerivedFields.filter(function(field) {
|
|
51917
|
+
return optFields.indexOf(field) == -1;
|
|
51918
|
+
});
|
|
51919
|
+
hasNewFields = fields.some(function(field) {
|
|
51920
|
+
return !table.fieldExists(field);
|
|
51921
|
+
});
|
|
51922
|
+
// The table's columns as this command found them. Taken once, because the
|
|
51923
|
+
// command adds to them as it runs, and a blanked property must be judged
|
|
51924
|
+
// against what was there before rather than against what it has just made.
|
|
51925
|
+
fieldsBefore = utils.arrayToIndex(table.getFields());
|
|
51926
|
+
if (fields.length > 0) {
|
|
51927
|
+
if (hasNewFields) {
|
|
51928
|
+
table.captureSchemaBefore({operation: 'style', fields: fields});
|
|
51929
|
+
} else {
|
|
51930
|
+
table.captureFieldsBefore(fields, {operation: 'style'});
|
|
51931
|
+
}
|
|
51932
|
+
}
|
|
51933
|
+
Object.keys(opts).forEach(function(optName) {
|
|
51934
|
+
// undo cli parser name conversion; the regex must be global, or a
|
|
51935
|
+
// property with more than one hyphen (e.g. label-start-offset) is silently
|
|
51936
|
+
// skipped rather than applied
|
|
51937
|
+
var svgName = optName.replace(/_/g, '-');
|
|
51938
|
+
if (!isSupportedSvgStyleProperty(svgName)) {
|
|
51939
|
+
return;
|
|
51940
|
+
}
|
|
51941
|
+
var strVal = opts[optName].trim();
|
|
51942
|
+
// An empty value removes the property, rather than being rejected as an
|
|
51943
|
+
// unparseable one. This is how a control gives a property back: a label
|
|
51944
|
+
// dragged off its position clears label-pos, and there is otherwise no
|
|
51945
|
+
// per-property unset -- only -style clear, which clears all of them.
|
|
51946
|
+
var unset = strVal === '' && emptyValueUnsetsProperty(svgName);
|
|
51947
|
+
var accessor = unset ? null : getSymbolPropertyAccessor(strVal, svgName, lyr);
|
|
51948
|
+
var badIcons = svgName == 'icon' ? [] : null;
|
|
51949
|
+
// Removing a position is not setting one, so it neither validates the
|
|
51950
|
+
// value nor clears the offsets the position would have stood for.
|
|
51951
|
+
var posOnPaths = svgName == 'label-pos' && !unset ? [] : null;
|
|
51952
|
+
table.getRecords().forEach(function(rec, i) {
|
|
51953
|
+
if (filterFn && !filterFn(i)) {
|
|
51954
|
+
// make sure field exists if record is excluded by filter
|
|
51955
|
+
setUndefinedFields(rec, [svgName]);
|
|
51956
|
+
if (svgName == 'label-pos') {
|
|
51957
|
+
// ...but a field the position would only have cleared is one this
|
|
51958
|
+
// command is not writing anywhere, so an excluded record has nothing
|
|
51959
|
+
// to stay consistent with
|
|
51960
|
+
setUndefinedFields(rec, labelPositionDerivedFields, {has: hadField});
|
|
51961
|
+
}
|
|
51962
|
+
} else if (unset) {
|
|
51963
|
+
// Nothing to remove, and so nothing to create: removing a property no
|
|
51964
|
+
// record has would otherwise add an empty column for it.
|
|
51965
|
+
if (hadField(svgName) || svgName in rec) rec[svgName] = undefined;
|
|
51966
|
+
} else {
|
|
51967
|
+
rec[svgName] = accessor(i);
|
|
51968
|
+
if (badIcons) {
|
|
51969
|
+
addUnsupportedIconName(badIcons, rec.icon);
|
|
51970
|
+
}
|
|
51971
|
+
if (posOnPaths) {
|
|
51972
|
+
if (!parseLabelPosition(rec['label-pos'])) {
|
|
51973
|
+
stop$1('Unexpected value for label-pos:', rec['label-pos']);
|
|
51974
|
+
}
|
|
51975
|
+
if (shapeIsPathLabel(lyr.shapes && lyr.shapes[i], rec)) {
|
|
51976
|
+
// Not stored, so that ignoring it means ignoring it: a stored
|
|
51977
|
+
// position would show up in the style panel and would start
|
|
51978
|
+
// applying if the label ever lost all but one of its knots. Its
|
|
51979
|
+
// text-anchor is left alone too -- that one does place text along a
|
|
51980
|
+
// path, so a position that had no effect must not clear it.
|
|
51981
|
+
posOnPaths.push(i);
|
|
51982
|
+
rec['label-pos'] = undefined;
|
|
51983
|
+
} else {
|
|
51984
|
+
setUndefinedFields(rec, clearedByPosition, {overwrite: true, has: hadField});
|
|
51985
|
+
}
|
|
51986
|
+
}
|
|
51987
|
+
}
|
|
51988
|
+
});
|
|
51989
|
+
if (badIcons && badIcons.length > 0) {
|
|
51990
|
+
warn(formatUnsupportedIconMessage(badIcons));
|
|
51991
|
+
}
|
|
51992
|
+
if (posOnPaths && posOnPaths.length > 0) {
|
|
51993
|
+
warn(formatPositionOnPathMessage(posOnPaths));
|
|
51994
|
+
}
|
|
51995
|
+
});
|
|
51996
|
+
if (fields.length > 0) {
|
|
51997
|
+
if (hasNewFields) {
|
|
51998
|
+
table.markSchemaChanged({operation: 'style'});
|
|
51999
|
+
} else {
|
|
52000
|
+
table.markFieldsChanged(fields, {operation: 'style'});
|
|
52001
|
+
}
|
|
52002
|
+
}
|
|
52003
|
+
};
|
|
52004
|
+
|
|
52005
|
+
// The style properties this command was given, in SVG spelling.
|
|
52006
|
+
function getOptionFields(opts) {
|
|
52007
|
+
var fields = [];
|
|
52008
|
+
Object.keys(opts).forEach(function(optName) {
|
|
52009
|
+
var svgName = optName.replace(/_/g, '-');
|
|
52010
|
+
if (isSupportedSvgStyleProperty(svgName)) addField(fields, svgName);
|
|
52011
|
+
});
|
|
52012
|
+
return fields;
|
|
52013
|
+
}
|
|
52014
|
+
|
|
52015
|
+
// The fields the command will write, which is what the undo capture covers.
|
|
52016
|
+
// label-pos reaches dx/dy/text-anchor as well -- it no longer stores values in
|
|
52017
|
+
// them, but it does clear them.
|
|
52018
|
+
function getStyleFields(optFields) {
|
|
52019
|
+
var fields = [];
|
|
52020
|
+
optFields.forEach(function(svgName) {
|
|
52021
|
+
addField(fields, svgName);
|
|
52022
|
+
if (svgName == 'label-pos') {
|
|
52023
|
+
labelPositionFields.forEach(function(field) {
|
|
52024
|
+
addField(fields, field);
|
|
52025
|
+
});
|
|
52026
|
+
}
|
|
52027
|
+
});
|
|
52028
|
+
return fields;
|
|
52029
|
+
}
|
|
52030
|
+
|
|
52031
|
+
function addField(fields, field) {
|
|
52032
|
+
if (fields.indexOf(field) == -1) {
|
|
52033
|
+
fields.push(field);
|
|
52034
|
+
}
|
|
52035
|
+
}
|
|
52036
|
+
|
|
52037
|
+
// Icon names can not be validated when options are parsed, because an icon=
|
|
52038
|
+
// value may be a field name or an expression. The assigned values are checked
|
|
52039
|
+
// instead, so that computed names are covered too. Unsupported names are a
|
|
52040
|
+
// warning, not an error -- features with an unsupported name render without an
|
|
52041
|
+
// icon.
|
|
52042
|
+
var maxReportedIconNames = 4;
|
|
52043
|
+
|
|
52044
|
+
function addUnsupportedIconName(names, val) {
|
|
52045
|
+
var name;
|
|
52046
|
+
if (!val) return; // a blank value removes the icon
|
|
52047
|
+
name = String(val);
|
|
52048
|
+
if (isSupportedIconName(name) || names.indexOf(name) > -1) return;
|
|
52049
|
+
names.push(name);
|
|
52050
|
+
}
|
|
52051
|
+
|
|
52052
|
+
function formatUnsupportedIconMessage(names) {
|
|
52053
|
+
var extra = names.length - maxReportedIconNames;
|
|
52054
|
+
var listed = extra > 0 ? names.slice(0, maxReportedIconNames) : names;
|
|
52055
|
+
var str = 'Unsupported icon ' + (names.length > 1 ? 'names' : 'name') + ': ' +
|
|
52056
|
+
listed.join(', ');
|
|
52057
|
+
if (extra > 0) {
|
|
52058
|
+
str += ' (and ' + extra + ' more)';
|
|
52059
|
+
}
|
|
52060
|
+
return str + '. Expected one of: ' + iconNames.join(', ');
|
|
52061
|
+
}
|
|
52062
|
+
|
|
52063
|
+
// Adds @fields to @rec with no value, so that a record the filter excluded
|
|
52064
|
+
// still has the same schema as the ones it kept. With overwrite, also blanks a
|
|
52065
|
+
// value already there -- which is how setting a position takes back the offsets
|
|
52066
|
+
// a label was carrying.
|
|
52067
|
+
//
|
|
52068
|
+
// @has: optional test for whether the layer carries a field at all. A field
|
|
52069
|
+
// nobody has is not created in order to be blanked: -style label-pos=n clears
|
|
52070
|
+
// dx, dy and text-anchor because a value on the record wins over the position,
|
|
52071
|
+
// and there is nothing to win with when the column does not exist. Without
|
|
52072
|
+
// this, clicking a position in the style panel put three empty columns in the
|
|
52073
|
+
// user's table, and three empty columns in their CSV.
|
|
52074
|
+
function setUndefinedFields(rec, fields, opts) {
|
|
52075
|
+
var overwrite = !!(opts && opts.overwrite);
|
|
52076
|
+
var has = opts && opts.has;
|
|
52077
|
+
fields.forEach(function(field) {
|
|
52078
|
+
if (has && !has(field) && field in rec === false) return;
|
|
52079
|
+
if (overwrite || field in rec === false) {
|
|
52080
|
+
rec[field] = undefined;
|
|
52081
|
+
}
|
|
52082
|
+
});
|
|
52083
|
+
}
|
|
52084
|
+
|
|
52085
|
+
// label-pos places text around an anchor point, which a label strung along a
|
|
52086
|
+
// path does not have: its text runs from a start offset in the direction the
|
|
52087
|
+
// path goes. A warning rather than an error, because a layer can hold both
|
|
52088
|
+
// kinds of label and styling all of it at once is reasonable.
|
|
52089
|
+
function formatPositionOnPathMessage(ids) {
|
|
52090
|
+
var extra = ids.length - maxReportedIds;
|
|
52091
|
+
var listed = (extra > 0 ? ids.slice(0, maxReportedIds) : ids).join(', ');
|
|
52092
|
+
return 'Ignoring label-pos on ' + ids.length + ' path ' +
|
|
52093
|
+
(ids.length > 1 ? 'labels' : 'label') + ' (' + listed +
|
|
52094
|
+
(extra > 0 ? ' and ' + extra + ' more' : '') + '). ' +
|
|
52095
|
+
'Use label-start-offset= and text-anchor= to place text along a path.';
|
|
52096
|
+
}
|
|
52097
|
+
|
|
52098
|
+
var maxReportedIds = 4;
|
|
52099
|
+
|
|
52100
|
+
// -labels styles the labels in a point layer, or turns a point layer into
|
|
52101
|
+
// labels by giving it text=. With coordinates=, it adds one label instead; that
|
|
52102
|
+
// case is cmd.addLabel(), dispatched separately because it accepts an empty
|
|
52103
|
+
// target.
|
|
52104
|
+
//
|
|
52105
|
+
// Styling writes the same properties -style writes, through the same code, so
|
|
52106
|
+
// a label styled by either command is the same label.
|
|
52107
|
+
cmd.labels = function(targetLayers, dataset, opts) {
|
|
52108
|
+
var styleOpts = getStyleOpts(opts);
|
|
52109
|
+
var output = targetLayers.map(function(lyr) {
|
|
52110
|
+
var out;
|
|
52111
|
+
requirePointLayer$1(lyr);
|
|
52112
|
+
out = opts.no_replace ? copyLayer(lyr) : lyr;
|
|
52113
|
+
cmd.svgStyle(out, dataset, styleOpts);
|
|
52114
|
+
return out;
|
|
52115
|
+
});
|
|
52116
|
+
return opts.no_replace ? output : null;
|
|
52117
|
+
};
|
|
52118
|
+
|
|
52119
|
+
// text= is label-text= under the name that reads naturally in this command.
|
|
52120
|
+
function getStyleOpts(opts) {
|
|
52121
|
+
var o = Object.assign({}, opts);
|
|
52122
|
+
if ('text' in o) {
|
|
52123
|
+
o.label_text = o.text;
|
|
52124
|
+
delete o.text;
|
|
52125
|
+
}
|
|
52126
|
+
return o;
|
|
52127
|
+
}
|
|
52128
|
+
|
|
52129
|
+
// Labels are drawn at points. A layer with no geometry is let through, since
|
|
52130
|
+
// it has nothing to conflict with.
|
|
52131
|
+
function requirePointLayer$1(lyr) {
|
|
52132
|
+
if (lyr.geometry_type && lyr.geometry_type != 'point') {
|
|
52133
|
+
stop$1('Labels can only be applied to a point layer; layer "' +
|
|
52134
|
+
(lyr.name || '[unnamed]') + '" contains ' + lyr.geometry_type + 's. ' +
|
|
52135
|
+
'Use -points to make a point layer to label.');
|
|
52136
|
+
}
|
|
52137
|
+
}
|
|
52138
|
+
|
|
51398
52139
|
cmd.addLayer = addLayer;
|
|
51399
52140
|
|
|
51400
52141
|
var GEOMETRY_TYPES = ['point', 'polygon', 'polyline'];
|
|
@@ -54072,7 +54813,7 @@ ${svg}
|
|
|
54072
54813
|
}
|
|
54073
54814
|
|
|
54074
54815
|
cmd.alphaShapes = function(pointLyr, targetDataset, opts) {
|
|
54075
|
-
requirePointLayer$
|
|
54816
|
+
requirePointLayer$2(pointLyr);
|
|
54076
54817
|
if (opts.interval > 0 === false) {
|
|
54077
54818
|
stop$1('Expected a non-negative interval parameter');
|
|
54078
54819
|
}
|
|
@@ -61713,131 +62454,6 @@ ${svg}
|
|
|
61713
62454
|
normalizeOverlaySource: normalizeOverlaySource
|
|
61714
62455
|
});
|
|
61715
62456
|
|
|
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
62457
|
cmd.clipLayers = function(target, src, dataset, opts) {
|
|
61842
62458
|
return clipLayers(target, src, dataset, "clip", opts);
|
|
61843
62459
|
};
|
|
@@ -64982,7 +65598,7 @@ ${svg}
|
|
|
64982
65598
|
cmd.lines = function(lyr, dataset, opts) {
|
|
64983
65599
|
opts = opts || {};
|
|
64984
65600
|
if (opts.callouts) {
|
|
64985
|
-
requirePointLayer$
|
|
65601
|
+
requirePointLayer$2(lyr);
|
|
64986
65602
|
return pointsToCallouts(lyr, dataset, opts);
|
|
64987
65603
|
} else if (lyr.geometry_type == 'point') {
|
|
64988
65604
|
return pointsToLines(lyr, dataset, opts);
|
|
@@ -75469,231 +76085,6 @@ ${svg}
|
|
|
75469
76085
|
return api;
|
|
75470
76086
|
}
|
|
75471
76087
|
|
|
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
76088
|
cmd.evaluateEachFeature = function(lyr, dataset, expArg, opts) {
|
|
75698
76089
|
var n = getFeatureCount(lyr),
|
|
75699
76090
|
arcs = dataset.arcs,
|
|
@@ -79540,7 +79931,7 @@ ${svg}
|
|
|
79540
79931
|
}
|
|
79541
79932
|
|
|
79542
79933
|
cmd.pointToGrid = function(targetLayers, targetDataset, opts) {
|
|
79543
|
-
targetLayers.forEach(requirePointLayer$
|
|
79934
|
+
targetLayers.forEach(requirePointLayer$2);
|
|
79544
79935
|
if (opts.interval > 0 === false) {
|
|
79545
79936
|
stop$1('Expected a non-negative interval parameter');
|
|
79546
79937
|
}
|
|
@@ -82325,232 +82716,6 @@ ${svg}
|
|
|
82325
82716
|
stopJob(job);
|
|
82326
82717
|
};
|
|
82327
82718
|
|
|
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
82719
|
function getStickArrowCoords(d) {
|
|
82555
82720
|
return getArrowCoords(d, 'stick');
|
|
82556
82721
|
}
|
|
@@ -83082,7 +83247,7 @@ ${svg}
|
|
|
83082
83247
|
|
|
83083
83248
|
// TODO: refactor to remove duplication in mapshaper-svg-style.js
|
|
83084
83249
|
cmd.symbols = function(inputLyr, dataset, opts) {
|
|
83085
|
-
requirePointLayer$
|
|
83250
|
+
requirePointLayer$2(inputLyr);
|
|
83086
83251
|
var lyr = opts.no_replace ? copyLayer(inputLyr) : inputLyr;
|
|
83087
83252
|
var shapeMode = !!opts.geographic;
|
|
83088
83253
|
var metersPerPx;
|
|
@@ -83683,6 +83848,9 @@ ${svg}
|
|
|
83683
83848
|
if (name == 'rectangle') {
|
|
83684
83849
|
return !opts.source && !opts.bbox;
|
|
83685
83850
|
}
|
|
83851
|
+
if (name == 'labels') {
|
|
83852
|
+
return !('coordinates' in opts);
|
|
83853
|
+
}
|
|
83686
83854
|
return [
|
|
83687
83855
|
'affine', 'alpha-shapes', 'blur', 'buffer', 'calc', 'check-geometry',
|
|
83688
83856
|
'classify', 'clean', 'clip', 'cluster', 'contours', 'dashlines', 'data-fill',
|
|
@@ -83697,7 +83865,10 @@ ${svg}
|
|
|
83697
83865
|
].indexOf(name) > -1;
|
|
83698
83866
|
}
|
|
83699
83867
|
|
|
83700
|
-
function commandAcceptsEmptyTarget(name) {
|
|
83868
|
+
function commandAcceptsEmptyTarget(name, opts) {
|
|
83869
|
+
if (name == 'labels') {
|
|
83870
|
+
return 'coordinates' in opts;
|
|
83871
|
+
}
|
|
83701
83872
|
return name == 'graticule' || name == 'i' || name == 'help' ||
|
|
83702
83873
|
name == 'point-grid' || name == 'shape' || name == 'rectangle' || name == 'frame' ||
|
|
83703
83874
|
name == 'require' || name == 'run' || name == 'define' ||
|
|
@@ -83793,7 +83964,7 @@ ${svg}
|
|
|
83793
83964
|
stop$1(utils.format('Missing target: %s\nAvailable layers: %s',
|
|
83794
83965
|
opts.target, getFormattedLayerList(job.catalog)));
|
|
83795
83966
|
}
|
|
83796
|
-
if (!commandAcceptsEmptyTarget(name)) {
|
|
83967
|
+
if (!commandAcceptsEmptyTarget(name, opts)) {
|
|
83797
83968
|
stop$1("No data is available");
|
|
83798
83969
|
}
|
|
83799
83970
|
}
|
|
@@ -83816,7 +83987,7 @@ ${svg}
|
|
|
83816
83987
|
}
|
|
83817
83988
|
outputLayers = cmd.addShape(targetLayers, targetDataset, opts);
|
|
83818
83989
|
|
|
83819
|
-
} else if (name == 'add-label') {
|
|
83990
|
+
} else if (name == 'add-label' || name == 'labels' && 'coordinates' in opts) {
|
|
83820
83991
|
if (!targetDataset) {
|
|
83821
83992
|
targetDataset = {info: {}, layers: []};
|
|
83822
83993
|
targetLayers = targetDataset.layers;
|
|
@@ -83824,6 +83995,9 @@ ${svg}
|
|
|
83824
83995
|
}
|
|
83825
83996
|
outputLayers = cmd.addLabel(targetLayers, targetDataset, opts);
|
|
83826
83997
|
|
|
83998
|
+
} else if (name == 'labels') {
|
|
83999
|
+
outputLayers = cmd.labels(targetLayers, targetDataset, opts);
|
|
84000
|
+
|
|
83827
84001
|
} else if (name == 'add-layer') {
|
|
83828
84002
|
// The new layer arrives in a dataset of its own, so it is added to the
|
|
83829
84003
|
// catalog here rather than integrated into the target below.
|
|
@@ -84401,7 +84575,7 @@ ${svg}
|
|
|
84401
84575
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
84402
84576
|
}
|
|
84403
84577
|
|
|
84404
|
-
var version = "0.7.
|
|
84578
|
+
var version = "0.7.70";
|
|
84405
84579
|
|
|
84406
84580
|
// Parse command line args into commands and run them
|
|
84407
84581
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -85216,7 +85390,7 @@ ${svg}
|
|
|
85216
85390
|
// parse. Null is what every reader already falls back from.
|
|
85217
85391
|
function measureLabelText(rec) {
|
|
85218
85392
|
var text = toLabelString(rec && rec['label-text']);
|
|
85219
|
-
var font, fontSize, spacing, width;
|
|
85393
|
+
var font, fontSize, spacing, width, failed = false;
|
|
85220
85394
|
if (!text) return null;
|
|
85221
85395
|
font = getFontForRecord(rec);
|
|
85222
85396
|
if (!font) return null;
|
|
@@ -85225,10 +85399,22 @@ ${svg}
|
|
|
85225
85399
|
if (!(fontSize > 0)) return null;
|
|
85226
85400
|
// The widest line, which is what the block of a multi-line label is as wide
|
|
85227
85401
|
// as, and what the browser's getBBox() reports for the same text.
|
|
85228
|
-
|
|
85229
|
-
|
|
85402
|
+
//
|
|
85403
|
+
// A line with bold words in it is measured a run at a time, each in its own
|
|
85404
|
+
// face, which loses the kerning between the last letter of one run and the
|
|
85405
|
+
// first of the next -- a fraction of a pixel at a weight change.
|
|
85406
|
+
width = splitLabelLineRuns(text).reduce(function(max, runs) {
|
|
85407
|
+
var w = runs.reduce(function(sum, run) {
|
|
85408
|
+
var face = run.bold ? getFontForRecord(rec, true) : font;
|
|
85409
|
+
if (!face) {
|
|
85410
|
+
failed = true;
|
|
85411
|
+
return sum;
|
|
85412
|
+
}
|
|
85413
|
+
return sum + measureLine(face, run.text, fontSize, spacing);
|
|
85414
|
+
}, 0);
|
|
85230
85415
|
return w > max ? w : max;
|
|
85231
85416
|
}, 0);
|
|
85417
|
+
if (failed) return null;
|
|
85232
85418
|
return width > 0 ? width : null;
|
|
85233
85419
|
}
|
|
85234
85420
|
|
|
@@ -85252,9 +85438,12 @@ ${svg}
|
|
|
85252
85438
|
// The font a record is drawn in, opened and remembered. A record with no
|
|
85253
85439
|
// font-family is drawn in the layer group's default, the same one the GUI
|
|
85254
85440
|
// measures against -- see getLabelTextDefaults().
|
|
85255
|
-
|
|
85441
|
+
//
|
|
85442
|
+
// @bold: the face its bold runs are drawn in, which is the keyword's 700
|
|
85443
|
+
// whatever the label's own weight is, as it is in CSS.
|
|
85444
|
+
function getFontForRecord(rec, bold) {
|
|
85256
85445
|
var family = rec['font-family'] || 'sans-serif';
|
|
85257
|
-
var weight = getFontWeight(rec);
|
|
85446
|
+
var weight = bold ? WEIGHT_NAMES[LABEL_BOLD_WEIGHT] : getFontWeight(rec);
|
|
85258
85447
|
var italic = isItalic(rec);
|
|
85259
85448
|
var stretch = rec['font-stretch'] || '';
|
|
85260
85449
|
var key = [family, weight, italic ? 'i' : 'n', stretch].join('|');
|
|
@@ -87003,7 +87192,7 @@ ${svg}
|
|
|
87003
87192
|
internal.svg = Object.assign({}, SvgStringify, SvgPathUtils, GeojsonToSvg,
|
|
87004
87193
|
SvgFeatureUtils,
|
|
87005
87194
|
SvgLabels, SvgSymbols, SvgLabelPaths, SvgLabelFit, SvgLabelAlign,
|
|
87006
|
-
SvgLabelMetrics, SvgLabelHalo, SvgLabelCallout);
|
|
87195
|
+
SvgLabelMetrics, SvgLabelHalo, SvgLabelCallout, SvgLabelMarkup);
|
|
87007
87196
|
|
|
87008
87197
|
// Reached through the bundle rather than imported from source, unlike most of
|
|
87009
87198
|
// what tests use, because these modules load fs and fontkit through the
|