mapshaper 0.7.55 → 0.7.58
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 +1361 -246
- package/package.json +1 -1
- package/www/mapshaper.js +1361 -246
- package/www/sponsor.html +2 -0
package/mapshaper.js
CHANGED
|
@@ -351,7 +351,7 @@
|
|
|
351
351
|
// Calc sum, skip falsy and NaN values
|
|
352
352
|
// Assumes: no other non-numeric objects in array
|
|
353
353
|
//
|
|
354
|
-
function sum$
|
|
354
|
+
function sum$2(arr, info) {
|
|
355
355
|
if (!isArrayLike(arr)) error$1 ("sum() expects an array, received:", arr);
|
|
356
356
|
var tot = 0,
|
|
357
357
|
nan = 0,
|
|
@@ -1117,7 +1117,7 @@
|
|
|
1117
1117
|
quicksort: quicksort$1, quicksortPartition,
|
|
1118
1118
|
range, regexEscape, reorderArray: reorderArray$1, repeat: repeat$1, repeatString,
|
|
1119
1119
|
replaceArray, rpad, rtrim,
|
|
1120
|
-
shuffle, some, sortArrayIndex, sortOn, splitLines, sum: sum$
|
|
1120
|
+
shuffle, some, sortArrayIndex, sortOn, splitLines, sum: sum$2,
|
|
1121
1121
|
toArray: toArray$1, toBuffer, trim, trimQuotes,
|
|
1122
1122
|
uniq, uniqifyNames,
|
|
1123
1123
|
wildcardToRegExp
|
|
@@ -5793,10 +5793,13 @@
|
|
|
5793
5793
|
|
|
5794
5794
|
function layerOnlyHasRectangles(lyr, arcs) {
|
|
5795
5795
|
if (!layerHasPaths(lyr)) return false;
|
|
5796
|
-
if (countMultiPartFeatures(lyr) > 0) return false;
|
|
5797
5796
|
return lyr.shapes.every(function(shp) {
|
|
5798
5797
|
if (!shp) return true;
|
|
5799
|
-
|
|
5798
|
+
// Every part is tested: a shape whose first ring is rectangular may well
|
|
5799
|
+
// have others that are not.
|
|
5800
|
+
return shp.every(function(path) {
|
|
5801
|
+
return pathIsRectangle(path, arcs);
|
|
5802
|
+
});
|
|
5800
5803
|
});
|
|
5801
5804
|
}
|
|
5802
5805
|
|
|
@@ -5886,7 +5889,7 @@
|
|
|
5886
5889
|
|
|
5887
5890
|
function requireSinglePointLayer(lyr, msg) {
|
|
5888
5891
|
requirePointLayer(lyr);
|
|
5889
|
-
if (countMultiPartFeatures(lyr) > 0) {
|
|
5892
|
+
if (countMultiPartFeatures(lyr.shapes) > 0) {
|
|
5890
5893
|
stop$1(msg || 'This command requires single points; layer contains multi-point features.');
|
|
5891
5894
|
}
|
|
5892
5895
|
}
|
|
@@ -6030,8 +6033,16 @@
|
|
|
6030
6033
|
return copy;
|
|
6031
6034
|
}
|
|
6032
6035
|
|
|
6036
|
+
// shapes: one layer's "shapes" array
|
|
6037
|
+
// Guards against being passed a layer instead: several callers made that
|
|
6038
|
+
// mistake, and reading .length off a layer object silently returned 0, which
|
|
6039
|
+
// disabled the multi-part checks that depend on this function.
|
|
6033
6040
|
function countMultiPartFeatures(shapes) {
|
|
6034
6041
|
var count = 0;
|
|
6042
|
+
if (!shapes) return 0;
|
|
6043
|
+
if (!Array.isArray(shapes)) {
|
|
6044
|
+
error('countMultiPartFeatures() expects an array of shapes');
|
|
6045
|
+
}
|
|
6035
6046
|
for (var i=0, n=shapes.length; i<n; i++) {
|
|
6036
6047
|
if (shapes[i] && shapes[i].length > 1) count++;
|
|
6037
6048
|
}
|
|
@@ -9869,7 +9880,7 @@
|
|
|
9869
9880
|
id: piece.id,
|
|
9870
9881
|
source_region: piece.source_region,
|
|
9871
9882
|
copy: piece.copy,
|
|
9872
|
-
projected_boundary: closeRing$
|
|
9883
|
+
projected_boundary: closeRing$2(piece.boundary)
|
|
9873
9884
|
};
|
|
9874
9885
|
}
|
|
9875
9886
|
|
|
@@ -9877,7 +9888,7 @@
|
|
|
9877
9888
|
return {
|
|
9878
9889
|
id: piece.id,
|
|
9879
9890
|
source_region: piece.source_region,
|
|
9880
|
-
projected_boundary: closeRing$
|
|
9891
|
+
projected_boundary: closeRing$2(piece.boundary)
|
|
9881
9892
|
};
|
|
9882
9893
|
}
|
|
9883
9894
|
|
|
@@ -9959,7 +9970,7 @@
|
|
|
9959
9970
|
return paths;
|
|
9960
9971
|
}
|
|
9961
9972
|
|
|
9962
|
-
function closeRing$
|
|
9973
|
+
function closeRing$2(ring) {
|
|
9963
9974
|
var copy = ring.map(function(p) { return p.concat(); });
|
|
9964
9975
|
if (copy.length && !pointsEqual$2(copy[0], copy[copy.length - 1])) {
|
|
9965
9976
|
copy.push(copy[0].concat());
|
|
@@ -14944,6 +14955,27 @@
|
|
|
14944
14955
|
return fflatePostprocess(obj);
|
|
14945
14956
|
}
|
|
14946
14957
|
|
|
14958
|
+
// Return the entry names in a zip archive, without decompressing them.
|
|
14959
|
+
// Used to tell a KMZ from an ordinary zip when there is no filename to go by.
|
|
14960
|
+
// input: A file path or a buffer
|
|
14961
|
+
function listZipEntryNames(input) {
|
|
14962
|
+
if (input instanceof ArrayBuffer) {
|
|
14963
|
+
input = new Uint8Array(input);
|
|
14964
|
+
}
|
|
14965
|
+
if (!runningInBrowser()) {
|
|
14966
|
+
var AdmZip = require('adm-zip');
|
|
14967
|
+
return new AdmZip(input).getEntries().map(function(entry) {
|
|
14968
|
+
return entry.entryName;
|
|
14969
|
+
});
|
|
14970
|
+
}
|
|
14971
|
+
var names = [];
|
|
14972
|
+
unzipSync$1(input, {filter: function(file) {
|
|
14973
|
+
names.push(file.name);
|
|
14974
|
+
return false; // collect names only
|
|
14975
|
+
}});
|
|
14976
|
+
return names;
|
|
14977
|
+
}
|
|
14978
|
+
|
|
14947
14979
|
function unzipAsync(buf, cb) {
|
|
14948
14980
|
if (!runningInBrowser()) {
|
|
14949
14981
|
error('Async unzipping only supported in the browser');
|
|
@@ -15038,6 +15070,7 @@
|
|
|
15038
15070
|
var Zip = /*#__PURE__*/Object.freeze({
|
|
15039
15071
|
__proto__: null,
|
|
15040
15072
|
isImportableZipPath: isImportableZipPath,
|
|
15073
|
+
listZipEntryNames: listZipEntryNames,
|
|
15041
15074
|
unzipAsync: unzipAsync,
|
|
15042
15075
|
unzipSync: unzipSync,
|
|
15043
15076
|
zipAsync: zipAsync,
|
|
@@ -18618,14 +18651,15 @@
|
|
|
18618
18651
|
function parseHexColor(str) {
|
|
18619
18652
|
var hex = hexRxp.exec(str)[1];
|
|
18620
18653
|
if (hex.length == 3 || hex.length == 4) {
|
|
18621
|
-
|
|
18654
|
+
// expand shorthand, e.g. #f90 -> #ffcc00
|
|
18655
|
+
hex = hex.split('').map(function(c) { return c + c; }).join('');
|
|
18622
18656
|
}
|
|
18623
18657
|
if (hex.length != 6 && hex.length != 8) return null;
|
|
18624
18658
|
return {
|
|
18625
18659
|
r: parseInt(hex.substr(0, 2), 16),
|
|
18626
18660
|
g: parseInt(hex.substr(2, 2), 16),
|
|
18627
18661
|
b: parseInt(hex.substr(4, 2), 16),
|
|
18628
|
-
a: hex.length == 8 ? parseInt(hex.substr(
|
|
18662
|
+
a: hex.length == 8 ? parseInt(hex.substr(6, 2), 16) / 255 : 1
|
|
18629
18663
|
};
|
|
18630
18664
|
}
|
|
18631
18665
|
|
|
@@ -25733,6 +25767,173 @@
|
|
|
25733
25767
|
return text || text === 0; // accept numerical 0 as label text
|
|
25734
25768
|
}
|
|
25735
25769
|
|
|
25770
|
+
var assignmentRxp = /^([a-z0-9_+-]+)=(?!=)(.*)$/i; // exclude ==
|
|
25771
|
+
|
|
25772
|
+
function splitShellTokens(str) {
|
|
25773
|
+
var BAREWORD = `([^'"\\s])+`;
|
|
25774
|
+
var DOUBLE_QUOTE = `"((\\\\"|[^"])*?)"`;
|
|
25775
|
+
var SINGLE_QUOTE = `'((\\\\'|[^'])*?)'`;
|
|
25776
|
+
var rxp = new RegExp('(' + BAREWORD + '|' + SINGLE_QUOTE + '|' + DOUBLE_QUOTE + ')*', 'g');
|
|
25777
|
+
var matches = str.match(rxp) || [];
|
|
25778
|
+
var chunks = matches.filter(function(chunk) {
|
|
25779
|
+
// single backslashes may be present in multiline commands pasted from a makefile, e.g.
|
|
25780
|
+
return !!chunk && chunk != '\\';
|
|
25781
|
+
}).map(utils.trimQuotes);
|
|
25782
|
+
return chunks;
|
|
25783
|
+
}
|
|
25784
|
+
|
|
25785
|
+
function parsePercent(o) {
|
|
25786
|
+
var str = String(o);
|
|
25787
|
+
var isPct = str.indexOf('%') > 0;
|
|
25788
|
+
var pct;
|
|
25789
|
+
if (isPct) {
|
|
25790
|
+
pct = Number(str.replace('%', '')) / 100;
|
|
25791
|
+
} else {
|
|
25792
|
+
pct = Number(str);
|
|
25793
|
+
}
|
|
25794
|
+
if (!(pct >= 0 && pct <= 1)) {
|
|
25795
|
+
stop$1(utils.format("Invalid percentage: %s", str));
|
|
25796
|
+
}
|
|
25797
|
+
return pct;
|
|
25798
|
+
}
|
|
25799
|
+
|
|
25800
|
+
function parseNumberList(token) {
|
|
25801
|
+
return token.split(',').map(parseFloat);
|
|
25802
|
+
}
|
|
25803
|
+
|
|
25804
|
+
// Split comma-delimited list, trim quotes from entire list and
|
|
25805
|
+
// individual members
|
|
25806
|
+
function parseStringList(token) {
|
|
25807
|
+
var delim = ',';
|
|
25808
|
+
var list = splitOptionList(token, delim);
|
|
25809
|
+
if (list.length == 1) {
|
|
25810
|
+
list = splitOptionList(list[0], delim);
|
|
25811
|
+
}
|
|
25812
|
+
return list;
|
|
25813
|
+
}
|
|
25814
|
+
|
|
25815
|
+
// Accept spaces and/or commas as delimiters
|
|
25816
|
+
function parseColorList(token) {
|
|
25817
|
+
var delim = ', ';
|
|
25818
|
+
// accept rgb(0 0 0) rgb(0,0,0) rgb(0, 0, 0)
|
|
25819
|
+
var token2 = token.replace(/[ ,] *(?=[^(]*\))/g, '~~~'); // kludge: protect rgba() functions from being split apart
|
|
25820
|
+
var list = splitOptionList(token2, delim);
|
|
25821
|
+
if (list.length == 1) {
|
|
25822
|
+
list = splitOptionList(list[0], delim);
|
|
25823
|
+
}
|
|
25824
|
+
list = list.map(function(str) {
|
|
25825
|
+
return str.replace(/~~~/g, ',');
|
|
25826
|
+
});
|
|
25827
|
+
return list;
|
|
25828
|
+
}
|
|
25829
|
+
|
|
25830
|
+
// Splits a comma-delimited list into items, for the list-valued options of the
|
|
25831
|
+
// -symbols command (radii=, values=, fills=). Unlike parseStringList(), empty
|
|
25832
|
+
// items are kept, so that a blank item in one list still lines up with its
|
|
25833
|
+
// counterpart in another. Commas nested inside parentheses, brackets, braces
|
|
25834
|
+
// or quotes are not delimiters, so items like rgba(0,0,0,0.5), Math.max(A,B)
|
|
25835
|
+
// and [R, R * 0.5] survive as single items.
|
|
25836
|
+
function splitListItems(str) {
|
|
25837
|
+
var items = [];
|
|
25838
|
+
var start = 0;
|
|
25839
|
+
var depth = 0;
|
|
25840
|
+
var quote = '';
|
|
25841
|
+
var c;
|
|
25842
|
+
for (var i=0, n=str.length; i<n; i++) {
|
|
25843
|
+
c = str[i];
|
|
25844
|
+
if (quote) {
|
|
25845
|
+
if (c === quote) quote = '';
|
|
25846
|
+
} else if (c === '"' || c === "'") {
|
|
25847
|
+
quote = c;
|
|
25848
|
+
} else if (c === '(' || c === '[' || c === '{') {
|
|
25849
|
+
depth++;
|
|
25850
|
+
} else if (c === ')' || c === ']' || c === '}') {
|
|
25851
|
+
depth--;
|
|
25852
|
+
} else if (c === ',' && depth <= 0) {
|
|
25853
|
+
items.push(str.slice(start, i));
|
|
25854
|
+
start = i + 1;
|
|
25855
|
+
}
|
|
25856
|
+
}
|
|
25857
|
+
items.push(str.slice(start));
|
|
25858
|
+
return items.map(function(item) {
|
|
25859
|
+
return utils.trimQuotes(item.trim());
|
|
25860
|
+
});
|
|
25861
|
+
}
|
|
25862
|
+
|
|
25863
|
+
function cleanArgv(argv) {
|
|
25864
|
+
// Note: original trim caused some quoted spaces to be removed
|
|
25865
|
+
// (e.g. bash shell seems to convert [delimiter=" "] to [delimiter= ],
|
|
25866
|
+
// which then got trimmed to [delimiter=] below)
|
|
25867
|
+
//// argv = argv.map(function(s) {return s.trim();}); // trim whitespace
|
|
25868
|
+
|
|
25869
|
+
// Updated: don't trim space from tokens like [delimeter= ]
|
|
25870
|
+
argv = argv.map(function(s) {
|
|
25871
|
+
if (!/= $/.test(s)) {
|
|
25872
|
+
s = utils.rtrim(s);
|
|
25873
|
+
}
|
|
25874
|
+
s = utils.ltrim(s);
|
|
25875
|
+
return s;
|
|
25876
|
+
});
|
|
25877
|
+
argv = argv.filter(function(s) {return s !== '';}); // remove empty tokens
|
|
25878
|
+
// Note: removing trimQuotes() call... now, strings like 'name="Meg"' will no longer
|
|
25879
|
+
// be parsed the same way as name=Meg and name="Meg"
|
|
25880
|
+
//// argv = argv.map(utils.trimQuotes); // remove one level of single or dbl quotes
|
|
25881
|
+
return argv;
|
|
25882
|
+
}
|
|
25883
|
+
|
|
25884
|
+
function splitOptionList(str, delimChars) {
|
|
25885
|
+
var BAREWORD = '([^' + delimChars + '\'"][^' + delimChars + ']*)'; // TODO: make safer
|
|
25886
|
+
var DOUBLE_QUOTE = '"((\\\\"|[^"])*?)"';
|
|
25887
|
+
var SINGLE_QUOTE = '\'((\\\\\'|[^\'])*?)\'';
|
|
25888
|
+
var rxp = new RegExp('^(' + BAREWORD + '|' + SINGLE_QUOTE + '|' + DOUBLE_QUOTE + ')([' + delimChars + ']+|$)');
|
|
25889
|
+
var chunks = [];
|
|
25890
|
+
var match;
|
|
25891
|
+
while ((match = rxp.exec(str)) !== null) {
|
|
25892
|
+
chunks.push(match[1]);
|
|
25893
|
+
str = str.substr(match[0].length);
|
|
25894
|
+
}
|
|
25895
|
+
return chunks.filter(function(chunk) {
|
|
25896
|
+
return !!chunk && chunk != '\\';
|
|
25897
|
+
}).map(utils.trimQuotes);
|
|
25898
|
+
}
|
|
25899
|
+
|
|
25900
|
+
// Prepare a value to be used as an option value.
|
|
25901
|
+
// Places quotes around strings containing spaces.
|
|
25902
|
+
// e.g. converts Layer 1 -> "Layer 1"
|
|
25903
|
+
// for use in contexts like: name="Layer 1"
|
|
25904
|
+
function formatOptionValue(val) {
|
|
25905
|
+
val = String(val);
|
|
25906
|
+
if (val.indexOf(' ') > -1) {
|
|
25907
|
+
val = JSON.stringify(val); // quote ids with spaces
|
|
25908
|
+
}
|
|
25909
|
+
return val;
|
|
25910
|
+
}
|
|
25911
|
+
|
|
25912
|
+
function isAssignment(token) {
|
|
25913
|
+
return assignmentRxp.test(token);
|
|
25914
|
+
}
|
|
25915
|
+
|
|
25916
|
+
function splitAssignment(token) {
|
|
25917
|
+
var match = assignmentRxp.exec(token),
|
|
25918
|
+
name = match[1],
|
|
25919
|
+
val = utils.trimQuotes(match[2]);
|
|
25920
|
+
return [name, val];
|
|
25921
|
+
}
|
|
25922
|
+
|
|
25923
|
+
var OptionParsingUtils = /*#__PURE__*/Object.freeze({
|
|
25924
|
+
__proto__: null,
|
|
25925
|
+
cleanArgv: cleanArgv,
|
|
25926
|
+
formatOptionValue: formatOptionValue,
|
|
25927
|
+
isAssignment: isAssignment,
|
|
25928
|
+
parseColorList: parseColorList,
|
|
25929
|
+
parseNumberList: parseNumberList,
|
|
25930
|
+
parsePercent: parsePercent,
|
|
25931
|
+
parseStringList: parseStringList,
|
|
25932
|
+
splitAssignment: splitAssignment,
|
|
25933
|
+
splitListItems: splitListItems,
|
|
25934
|
+
splitShellTokens: splitShellTokens
|
|
25935
|
+
});
|
|
25936
|
+
|
|
25736
25937
|
/* example patterns
|
|
25737
25938
|
hatches 1px black 1px red 1px white
|
|
25738
25939
|
1px black 1px red 1px white // same as above (hatches is default)
|
|
@@ -26024,7 +26225,10 @@
|
|
|
26024
26225
|
length: 'number', // e.g. arrow length
|
|
26025
26226
|
rotation: 'number',
|
|
26026
26227
|
radius: 'number',
|
|
26027
|
-
radii: null, //
|
|
26228
|
+
radii: null, // (ring) list, see symbolListProperties
|
|
26229
|
+
hole: 'number', // (pie) radius of the hole in a donut
|
|
26230
|
+
values: null, // (pie) list, see symbolListProperties
|
|
26231
|
+
fills: null, // (pie) list, see symbolListProperties
|
|
26028
26232
|
flipped: 'boolean',
|
|
26029
26233
|
rotated: 'boolean',
|
|
26030
26234
|
direction: 'number',
|
|
@@ -26043,6 +26247,18 @@
|
|
|
26043
26247
|
effect: null // e.g. "fade"
|
|
26044
26248
|
}, stylePropertyTypes);
|
|
26045
26249
|
|
|
26250
|
+
// Options that take a list of values -- one for each part of a multi-part
|
|
26251
|
+
// symbol -- instead of a single value. Each item in the list is resolved
|
|
26252
|
+
// separately, so an item can be a literal value, a field name or an
|
|
26253
|
+
// expression. The type hint for fills= is null on purpose: an item that isn't
|
|
26254
|
+
// a recognizable color is passed along as a literal string, so that a symbol
|
|
26255
|
+
// can leave that part unfilled instead of the command failing.
|
|
26256
|
+
var symbolListProperties = {
|
|
26257
|
+
radii: 'number',
|
|
26258
|
+
values: 'number',
|
|
26259
|
+
fills: null
|
|
26260
|
+
};
|
|
26261
|
+
|
|
26046
26262
|
var commonProperties = 'css,class,opacity,stroke,stroke-width,stroke-dasharray,stroke-opacity,fill-opacity,vector-effect'.split(',');
|
|
26047
26263
|
|
|
26048
26264
|
var propertiesBySymbolType = {
|
|
@@ -26118,7 +26334,9 @@
|
|
|
26118
26334
|
return;
|
|
26119
26335
|
}
|
|
26120
26336
|
var val = opts[optName];
|
|
26121
|
-
functions[svgName] =
|
|
26337
|
+
functions[svgName] = svgName in symbolListProperties ?
|
|
26338
|
+
getSymbolListAccessor(val, svgName, lyr) :
|
|
26339
|
+
getSymbolPropertyAccessor(val, svgName, lyr);
|
|
26122
26340
|
properties.push(svgName);
|
|
26123
26341
|
});
|
|
26124
26342
|
|
|
@@ -26145,8 +26363,35 @@
|
|
|
26145
26363
|
}
|
|
26146
26364
|
|
|
26147
26365
|
function getSymbolPropertyAccessor(val, svgName, lyr) {
|
|
26366
|
+
return getPropertyAccessor(val, symbolPropertyTypes[svgName], lyr, svgName);
|
|
26367
|
+
}
|
|
26368
|
+
|
|
26369
|
+
// Returns a function that maps a feature id to an array of values, for options
|
|
26370
|
+
// like values= and fills= that describe the parts of a multi-part symbol.
|
|
26371
|
+
// The option may be given as an array or as a comma-separated list.
|
|
26372
|
+
function getSymbolListAccessor(val, svgName, lyr) {
|
|
26373
|
+
var typeHint = symbolListProperties[svgName];
|
|
26374
|
+
var items = Array.isArray(val) ? val : splitListItems(String(val));
|
|
26375
|
+
var accessors = items.map(function(item) {
|
|
26376
|
+
return getPropertyAccessor(item, typeHint, lyr, svgName);
|
|
26377
|
+
});
|
|
26378
|
+
return function(id) {
|
|
26379
|
+
return accessors.map(function(accessor) {
|
|
26380
|
+
return accessor(id);
|
|
26381
|
+
});
|
|
26382
|
+
};
|
|
26383
|
+
}
|
|
26384
|
+
// Returns a function that maps a feature id to a property value. The value may
|
|
26385
|
+
// be given as a literal, as the name of a data field, or as a JS expression
|
|
26386
|
+
// evaluated against each feature.
|
|
26387
|
+
// typeHint: a type understood by parseSvgLiteralValue(), or null to accept any
|
|
26388
|
+
// string as a literal value
|
|
26389
|
+
// name: used in error messages
|
|
26390
|
+
// Callers outside the SVG property system pass a type hint directly, rather
|
|
26391
|
+
// than registering an option name in symbolPropertyTypes -- that index also
|
|
26392
|
+
// decides which options the -symbols command treats as symbol properties.
|
|
26393
|
+
function getPropertyAccessor(val, typeHint, lyr, name) {
|
|
26148
26394
|
var strVal = String(val).trim();
|
|
26149
|
-
var typeHint = symbolPropertyTypes[svgName];
|
|
26150
26395
|
var fields = lyr.data ? lyr.data.getFields() : [];
|
|
26151
26396
|
var literalVal = null;
|
|
26152
26397
|
var accessor;
|
|
@@ -26164,7 +26409,7 @@
|
|
|
26164
26409
|
}
|
|
26165
26410
|
if (accessor) return accessor;
|
|
26166
26411
|
if (literalVal !== null) return function(id) {return literalVal;};
|
|
26167
|
-
stop$1('Unexpected value for',
|
|
26412
|
+
stop$1('Unexpected value for', name + ':', strVal);
|
|
26168
26413
|
}
|
|
26169
26414
|
|
|
26170
26415
|
function parseStyleExpression(strVal, lyr) {
|
|
@@ -26270,7 +26515,9 @@
|
|
|
26270
26515
|
applyStyleAttributes: applyStyleAttributes,
|
|
26271
26516
|
findStylePropertiesBySymbolGeom: findStylePropertiesBySymbolGeom,
|
|
26272
26517
|
getLabelPositionStyle: getLabelPositionStyle,
|
|
26518
|
+
getPropertyAccessor: getPropertyAccessor,
|
|
26273
26519
|
getSymbolDataAccessor: getSymbolDataAccessor,
|
|
26520
|
+
getSymbolListAccessor: getSymbolListAccessor,
|
|
26274
26521
|
getSymbolPropertyAccessor: getSymbolPropertyAccessor,
|
|
26275
26522
|
isSupportedSvgStyleProperty: isSupportedSvgStyleProperty,
|
|
26276
26523
|
isSvgClassName: isSvgClassName,
|
|
@@ -37954,139 +38201,6 @@ ${svg}
|
|
|
37954
38201
|
}
|
|
37955
38202
|
}
|
|
37956
38203
|
|
|
37957
|
-
var assignmentRxp = /^([a-z0-9_+-]+)=(?!=)(.*)$/i; // exclude ==
|
|
37958
|
-
|
|
37959
|
-
function splitShellTokens(str) {
|
|
37960
|
-
var BAREWORD = `([^'"\\s])+`;
|
|
37961
|
-
var DOUBLE_QUOTE = `"((\\\\"|[^"])*?)"`;
|
|
37962
|
-
var SINGLE_QUOTE = `'((\\\\'|[^'])*?)'`;
|
|
37963
|
-
var rxp = new RegExp('(' + BAREWORD + '|' + SINGLE_QUOTE + '|' + DOUBLE_QUOTE + ')*', 'g');
|
|
37964
|
-
var matches = str.match(rxp) || [];
|
|
37965
|
-
var chunks = matches.filter(function(chunk) {
|
|
37966
|
-
// single backslashes may be present in multiline commands pasted from a makefile, e.g.
|
|
37967
|
-
return !!chunk && chunk != '\\';
|
|
37968
|
-
}).map(utils.trimQuotes);
|
|
37969
|
-
return chunks;
|
|
37970
|
-
}
|
|
37971
|
-
|
|
37972
|
-
function parsePercent(o) {
|
|
37973
|
-
var str = String(o);
|
|
37974
|
-
var isPct = str.indexOf('%') > 0;
|
|
37975
|
-
var pct;
|
|
37976
|
-
if (isPct) {
|
|
37977
|
-
pct = Number(str.replace('%', '')) / 100;
|
|
37978
|
-
} else {
|
|
37979
|
-
pct = Number(str);
|
|
37980
|
-
}
|
|
37981
|
-
if (!(pct >= 0 && pct <= 1)) {
|
|
37982
|
-
stop$1(utils.format("Invalid percentage: %s", str));
|
|
37983
|
-
}
|
|
37984
|
-
return pct;
|
|
37985
|
-
}
|
|
37986
|
-
|
|
37987
|
-
function parseNumberList(token) {
|
|
37988
|
-
return token.split(',').map(parseFloat);
|
|
37989
|
-
}
|
|
37990
|
-
|
|
37991
|
-
// Split comma-delimited list, trim quotes from entire list and
|
|
37992
|
-
// individual members
|
|
37993
|
-
function parseStringList(token) {
|
|
37994
|
-
var delim = ',';
|
|
37995
|
-
var list = splitOptionList(token, delim);
|
|
37996
|
-
if (list.length == 1) {
|
|
37997
|
-
list = splitOptionList(list[0], delim);
|
|
37998
|
-
}
|
|
37999
|
-
return list;
|
|
38000
|
-
}
|
|
38001
|
-
|
|
38002
|
-
// Accept spaces and/or commas as delimiters
|
|
38003
|
-
function parseColorList(token) {
|
|
38004
|
-
var delim = ', ';
|
|
38005
|
-
// accept rgb(0 0 0) rgb(0,0,0) rgb(0, 0, 0)
|
|
38006
|
-
var token2 = token.replace(/[ ,] *(?=[^(]*\))/g, '~~~'); // kludge: protect rgba() functions from being split apart
|
|
38007
|
-
var list = splitOptionList(token2, delim);
|
|
38008
|
-
if (list.length == 1) {
|
|
38009
|
-
list = splitOptionList(list[0], delim);
|
|
38010
|
-
}
|
|
38011
|
-
list = list.map(function(str) {
|
|
38012
|
-
return str.replace(/~~~/g, ',');
|
|
38013
|
-
});
|
|
38014
|
-
return list;
|
|
38015
|
-
}
|
|
38016
|
-
|
|
38017
|
-
function cleanArgv(argv) {
|
|
38018
|
-
// Note: original trim caused some quoted spaces to be removed
|
|
38019
|
-
// (e.g. bash shell seems to convert [delimiter=" "] to [delimiter= ],
|
|
38020
|
-
// which then got trimmed to [delimiter=] below)
|
|
38021
|
-
//// argv = argv.map(function(s) {return s.trim();}); // trim whitespace
|
|
38022
|
-
|
|
38023
|
-
// Updated: don't trim space from tokens like [delimeter= ]
|
|
38024
|
-
argv = argv.map(function(s) {
|
|
38025
|
-
if (!/= $/.test(s)) {
|
|
38026
|
-
s = utils.rtrim(s);
|
|
38027
|
-
}
|
|
38028
|
-
s = utils.ltrim(s);
|
|
38029
|
-
return s;
|
|
38030
|
-
});
|
|
38031
|
-
argv = argv.filter(function(s) {return s !== '';}); // remove empty tokens
|
|
38032
|
-
// Note: removing trimQuotes() call... now, strings like 'name="Meg"' will no longer
|
|
38033
|
-
// be parsed the same way as name=Meg and name="Meg"
|
|
38034
|
-
//// argv = argv.map(utils.trimQuotes); // remove one level of single or dbl quotes
|
|
38035
|
-
return argv;
|
|
38036
|
-
}
|
|
38037
|
-
|
|
38038
|
-
function splitOptionList(str, delimChars) {
|
|
38039
|
-
var BAREWORD = '([^' + delimChars + '\'"][^' + delimChars + ']*)'; // TODO: make safer
|
|
38040
|
-
var DOUBLE_QUOTE = '"((\\\\"|[^"])*?)"';
|
|
38041
|
-
var SINGLE_QUOTE = '\'((\\\\\'|[^\'])*?)\'';
|
|
38042
|
-
var rxp = new RegExp('^(' + BAREWORD + '|' + SINGLE_QUOTE + '|' + DOUBLE_QUOTE + ')([' + delimChars + ']+|$)');
|
|
38043
|
-
var chunks = [];
|
|
38044
|
-
var match;
|
|
38045
|
-
while ((match = rxp.exec(str)) !== null) {
|
|
38046
|
-
chunks.push(match[1]);
|
|
38047
|
-
str = str.substr(match[0].length);
|
|
38048
|
-
}
|
|
38049
|
-
return chunks.filter(function(chunk) {
|
|
38050
|
-
return !!chunk && chunk != '\\';
|
|
38051
|
-
}).map(utils.trimQuotes);
|
|
38052
|
-
}
|
|
38053
|
-
|
|
38054
|
-
// Prepare a value to be used as an option value.
|
|
38055
|
-
// Places quotes around strings containing spaces.
|
|
38056
|
-
// e.g. converts Layer 1 -> "Layer 1"
|
|
38057
|
-
// for use in contexts like: name="Layer 1"
|
|
38058
|
-
function formatOptionValue(val) {
|
|
38059
|
-
val = String(val);
|
|
38060
|
-
if (val.indexOf(' ') > -1) {
|
|
38061
|
-
val = JSON.stringify(val); // quote ids with spaces
|
|
38062
|
-
}
|
|
38063
|
-
return val;
|
|
38064
|
-
}
|
|
38065
|
-
|
|
38066
|
-
function isAssignment(token) {
|
|
38067
|
-
return assignmentRxp.test(token);
|
|
38068
|
-
}
|
|
38069
|
-
|
|
38070
|
-
function splitAssignment(token) {
|
|
38071
|
-
var match = assignmentRxp.exec(token),
|
|
38072
|
-
name = match[1],
|
|
38073
|
-
val = utils.trimQuotes(match[2]);
|
|
38074
|
-
return [name, val];
|
|
38075
|
-
}
|
|
38076
|
-
|
|
38077
|
-
var OptionParsingUtils = /*#__PURE__*/Object.freeze({
|
|
38078
|
-
__proto__: null,
|
|
38079
|
-
cleanArgv: cleanArgv,
|
|
38080
|
-
formatOptionValue: formatOptionValue,
|
|
38081
|
-
isAssignment: isAssignment,
|
|
38082
|
-
parseColorList: parseColorList,
|
|
38083
|
-
parseNumberList: parseNumberList,
|
|
38084
|
-
parsePercent: parsePercent,
|
|
38085
|
-
parseStringList: parseStringList,
|
|
38086
|
-
splitAssignment: splitAssignment,
|
|
38087
|
-
splitShellTokens: splitShellTokens
|
|
38088
|
-
});
|
|
38089
|
-
|
|
38090
38204
|
function CommandParser() {
|
|
38091
38205
|
var commandRxp = /^--?([a-z][\w-]*)$/i,
|
|
38092
38206
|
invalidCommandRxp = /^--?[a-z][\w-]*[=]/i, // e.g. -target=A // could be more general
|
|
@@ -38266,6 +38380,7 @@ ${svg}
|
|
|
38266
38380
|
// Try to read an option for command @cmdDef from @argv
|
|
38267
38381
|
function readOption(cmd, argv, cmdDef) {
|
|
38268
38382
|
var token = argv.shift(),
|
|
38383
|
+
assignedValue = null,
|
|
38269
38384
|
optName, optDef, parts;
|
|
38270
38385
|
|
|
38271
38386
|
if (isAssignment(token)) {
|
|
@@ -38275,7 +38390,10 @@ ${svg}
|
|
|
38275
38390
|
if (!optDef) ; else if (optDef.type == 'flag' || optDef.assign_to) {
|
|
38276
38391
|
stop$1("-" + cmdDef.name + " " + parts[0] + " option doesn't take a value");
|
|
38277
38392
|
} else {
|
|
38278
|
-
argv
|
|
38393
|
+
// Keep the value out of argv: it was written as part of the token,
|
|
38394
|
+
// so it is a value even when it looks like a command name
|
|
38395
|
+
// (e.g. hole=-size, dx=-OFFSET).
|
|
38396
|
+
assignedValue = parts[1];
|
|
38279
38397
|
}
|
|
38280
38398
|
} else {
|
|
38281
38399
|
// try to parse as a flag option,
|
|
@@ -38300,6 +38418,8 @@ ${svg}
|
|
|
38300
38418
|
cmd.options[optDef.assign_to] = optDef.name;
|
|
38301
38419
|
} else if (optDef.type == 'flag') {
|
|
38302
38420
|
cmd.options[optName] = true;
|
|
38421
|
+
} else if (assignedValue !== null) {
|
|
38422
|
+
cmd.options[optName] = parseOptionValue(assignedValue, optDef);
|
|
38303
38423
|
} else {
|
|
38304
38424
|
cmd.options[optName] = readOptionValue(argv, optDef);
|
|
38305
38425
|
}
|
|
@@ -41003,10 +41123,41 @@ ${svg}
|
|
|
41003
41123
|
})
|
|
41004
41124
|
.option('target', targetOpt);
|
|
41005
41125
|
|
|
41126
|
+
parser.command('repel')
|
|
41127
|
+
.describe('move overlapping point symbols apart')
|
|
41128
|
+
.option('width', {
|
|
41129
|
+
describe: 'display width of the layer in pixels (required if no frame)',
|
|
41130
|
+
type: 'number'
|
|
41131
|
+
})
|
|
41132
|
+
.option('max-shift', {
|
|
41133
|
+
describe: 'max displacement in pixels; may vary by symbol (default is 20)'
|
|
41134
|
+
})
|
|
41135
|
+
.option('padding', {
|
|
41136
|
+
describe: 'pixels of clearance around each symbol; may vary (default is 0)'
|
|
41137
|
+
})
|
|
41138
|
+
.option('margin', {
|
|
41139
|
+
describe: 'pixels of clearance between two symbols; may vary (default is 0)'
|
|
41140
|
+
})
|
|
41141
|
+
.option('ticks', {
|
|
41142
|
+
describe: 'number of solver passes (default is 100)',
|
|
41143
|
+
type: 'number'
|
|
41144
|
+
})
|
|
41145
|
+
.option('strength', {
|
|
41146
|
+
describe: 'portion of each overlap resolved per pass, 0-1 (default is 0.4)',
|
|
41147
|
+
type: 'number'
|
|
41148
|
+
})
|
|
41149
|
+
.option('radius', {
|
|
41150
|
+
describe: 'field or expression giving symbol radius in pixels',
|
|
41151
|
+
})
|
|
41152
|
+
.option('polygons', {
|
|
41153
|
+
describe: 'layer or file of polygons that symbols must stay inside'
|
|
41154
|
+
})
|
|
41155
|
+
.option('target', targetOpt);
|
|
41156
|
+
|
|
41006
41157
|
parser.command('symbols')
|
|
41007
41158
|
.describe('symbolize points as arrows, circles, stars, polygons, etc.')
|
|
41008
41159
|
.option('type', {
|
|
41009
|
-
describe: 'types: arrow, circle, square, star, polygon, ring'
|
|
41160
|
+
describe: 'types: arrow, circle, square, star, polygon, ring, pie'
|
|
41010
41161
|
})
|
|
41011
41162
|
.option('stroke', {})
|
|
41012
41163
|
.option('stroke-width', {})
|
|
@@ -41065,6 +41216,15 @@ ${svg}
|
|
|
41065
41216
|
.option('radii', {
|
|
41066
41217
|
describe: '(ring) comma-sep. list of concentric radii, ascending order'
|
|
41067
41218
|
})
|
|
41219
|
+
.option('hole', {
|
|
41220
|
+
describe: '(pie) radius of the hole in a donut symbol'
|
|
41221
|
+
})
|
|
41222
|
+
.option('values', {
|
|
41223
|
+
describe: '(pie) comma-sep. list of wedge values (numbers or fields)'
|
|
41224
|
+
})
|
|
41225
|
+
.option('fills', {
|
|
41226
|
+
describe: '(pie) comma-sep. list of wedge fill colors'
|
|
41227
|
+
})
|
|
41068
41228
|
.option('arrow-style', {
|
|
41069
41229
|
describe: '(arrow) options: stick, standard (default is standard)'
|
|
41070
41230
|
})
|
|
@@ -44316,9 +44476,10 @@ ${svg}
|
|
|
44316
44476
|
({gpkg, tmpPath} = await openGeoPackage(input, geopackage));
|
|
44317
44477
|
var availableLayers;
|
|
44318
44478
|
var filterApplied;
|
|
44479
|
+
var unreadableLayers;
|
|
44319
44480
|
try {
|
|
44320
44481
|
try {
|
|
44321
|
-
({datasets, availableLayers, filterApplied} = readFeatureTableDatasets(gpkg, opts));
|
|
44482
|
+
({datasets, availableLayers, filterApplied, unreadableLayers} = readFeatureTableDatasets(gpkg, opts));
|
|
44322
44483
|
} catch (e) {
|
|
44323
44484
|
if (!runningInBrowser() && isLocalCsProjError(e)) {
|
|
44324
44485
|
gpkg.close();
|
|
@@ -44331,7 +44492,7 @@ ${svg}
|
|
|
44331
44492
|
throw e;
|
|
44332
44493
|
}
|
|
44333
44494
|
gpkg = await geopackage.GeoPackageAPI.open(tmpPath);
|
|
44334
|
-
({datasets, availableLayers, filterApplied} = readFeatureTableDatasets(gpkg, opts));
|
|
44495
|
+
({datasets, availableLayers, filterApplied, unreadableLayers} = readFeatureTableDatasets(gpkg, opts));
|
|
44335
44496
|
} else {
|
|
44336
44497
|
throw e;
|
|
44337
44498
|
}
|
|
@@ -44346,6 +44507,7 @@ ${svg}
|
|
|
44346
44507
|
layers: [{name: '', data: null}],
|
|
44347
44508
|
info: {
|
|
44348
44509
|
_gpkg_available_layers: availableLayers,
|
|
44510
|
+
_gpkg_unreadable_layers: unreadableLayers,
|
|
44349
44511
|
_gpkg_placeholder: !!filterApplied
|
|
44350
44512
|
}
|
|
44351
44513
|
};
|
|
@@ -44357,6 +44519,7 @@ ${svg}
|
|
|
44357
44519
|
mergedArr.forEach(function(ds) {
|
|
44358
44520
|
ds.info = ds.info || {};
|
|
44359
44521
|
ds.info._gpkg_available_layers = availableLayers;
|
|
44522
|
+
ds.info._gpkg_unreadable_layers = unreadableLayers;
|
|
44360
44523
|
});
|
|
44361
44524
|
return merged;
|
|
44362
44525
|
}
|
|
@@ -44482,10 +44645,38 @@ ${svg}
|
|
|
44482
44645
|
var availableLayers = gpkg.getFeatureTables() || [];
|
|
44483
44646
|
var tables = filterGeoPackageTables(availableLayers, selected);
|
|
44484
44647
|
var filterApplied = Array.isArray(selected) && selected.length > 0;
|
|
44485
|
-
var
|
|
44486
|
-
|
|
44648
|
+
var unreadableLayers = [];
|
|
44649
|
+
var datasets = [];
|
|
44650
|
+
tables.forEach(function(table) {
|
|
44651
|
+
try {
|
|
44652
|
+
datasets.push(readFeatureTable(gpkg, table, opts));
|
|
44653
|
+
} catch (e) {
|
|
44654
|
+
// The caller recovers from a CRS parsing failure by rewriting the file's
|
|
44655
|
+
// CRS metadata and re-reading, so that error must not be swallowed here.
|
|
44656
|
+
if (isLocalCsProjError(e)) throw e;
|
|
44657
|
+
unreadableLayers.push(table);
|
|
44658
|
+
warn(formatUnreadableTableMessage(table, e));
|
|
44659
|
+
}
|
|
44487
44660
|
});
|
|
44488
|
-
return {
|
|
44661
|
+
return {
|
|
44662
|
+
datasets: datasets,
|
|
44663
|
+
availableLayers: availableLayers,
|
|
44664
|
+
filterApplied: filterApplied,
|
|
44665
|
+
unreadableLayers: unreadableLayers
|
|
44666
|
+
};
|
|
44667
|
+
}
|
|
44668
|
+
|
|
44669
|
+
// A GeoPackage can advertise feature tables that aren't readable. USGS National
|
|
44670
|
+
// Map vector products, for instance, list a CLIPPOLY table in gpkg_contents and
|
|
44671
|
+
// gpkg_geometry_columns after dropping the table itself from the file. GDAL
|
|
44672
|
+
// skips such layers with a warning; do the same rather than failing the whole
|
|
44673
|
+
// import over one bad table.
|
|
44674
|
+
function formatUnreadableTableMessage(table, err) {
|
|
44675
|
+
var msg = err && err.message ? String(err.message) : '';
|
|
44676
|
+
var reason = /^Table does not exist:/.test(msg) ?
|
|
44677
|
+
'the table is listed in the GeoPackage metadata but is missing from the file' :
|
|
44678
|
+
msg || 'unknown error';
|
|
44679
|
+
return 'Skipping GeoPackage layer "' + table + '": ' + reason;
|
|
44489
44680
|
}
|
|
44490
44681
|
|
|
44491
44682
|
function getSelectedGeoPackageLayers(opts) {
|
|
@@ -46458,12 +46649,12 @@ ${svg}
|
|
|
46458
46649
|
var coords = path.coords;
|
|
46459
46650
|
if (coords.length < 2) return;
|
|
46460
46651
|
if (forcePolylinePaths) {
|
|
46461
|
-
lines.push(path.closed || pointsEqual$1(coords[0], coords[coords.length - 1]) ? closeRing(coords) : coords);
|
|
46652
|
+
lines.push(path.closed || pointsEqual$1(coords[0], coords[coords.length - 1]) ? closeRing$1(coords) : coords);
|
|
46462
46653
|
return;
|
|
46463
46654
|
}
|
|
46464
46655
|
if (path.closed || pointsEqual$1(coords[0], coords[coords.length - 1])) {
|
|
46465
46656
|
if (coords.length >= 3) {
|
|
46466
|
-
rings.push(closeRing(coords));
|
|
46657
|
+
rings.push(closeRing$1(coords));
|
|
46467
46658
|
}
|
|
46468
46659
|
} else {
|
|
46469
46660
|
lines.push(coords);
|
|
@@ -46767,7 +46958,7 @@ ${svg}
|
|
|
46767
46958
|
return /translate\(/i.test(String(str || ''));
|
|
46768
46959
|
}
|
|
46769
46960
|
|
|
46770
|
-
function closeRing(coords) {
|
|
46961
|
+
function closeRing$1(coords) {
|
|
46771
46962
|
var ring = coords.map(function(p) { return [p[0], p[1]]; });
|
|
46772
46963
|
if (!pointsEqual$1(ring[0], ring[ring.length - 1])) {
|
|
46773
46964
|
ring.push([ring[0][0], ring[0][1]]);
|
|
@@ -47244,15 +47435,12 @@ ${svg}
|
|
|
47244
47435
|
var files = opts.files || [];
|
|
47245
47436
|
var dataset, datasets, target;
|
|
47246
47437
|
|
|
47438
|
+
// copy opts, so parameters can be modified within this command
|
|
47439
|
+
opts = Object.assign({}, opts);
|
|
47440
|
+
opts.input = Object.assign({}, opts.input); // make sure we have a cache
|
|
47441
|
+
|
|
47247
47442
|
if (opts.stdin) {
|
|
47248
|
-
|
|
47249
|
-
catalog.addDatasets(datasets);
|
|
47250
|
-
if (datasets.length > 1) {
|
|
47251
|
-
catalog.setDefaultTargets(datasets.map(function(ds) {
|
|
47252
|
-
return {dataset: ds, layers: ds.layers};
|
|
47253
|
-
}));
|
|
47254
|
-
}
|
|
47255
|
-
return normalizeImportedTarget(datasets);
|
|
47443
|
+
files = [readStdinAsFile(opts.input)];
|
|
47256
47444
|
}
|
|
47257
47445
|
|
|
47258
47446
|
if (files.length > 0 === false) {
|
|
@@ -47261,10 +47449,6 @@ ${svg}
|
|
|
47261
47449
|
|
|
47262
47450
|
verbose("Importing: " + files.join(' '));
|
|
47263
47451
|
|
|
47264
|
-
// copy opts, so parameters can be modified within this command
|
|
47265
|
-
opts = Object.assign({}, opts);
|
|
47266
|
-
opts.input = Object.assign({}, opts.input); // make sure we have a cache
|
|
47267
|
-
|
|
47268
47452
|
convertDataObjects(files, opts.input);
|
|
47269
47453
|
|
|
47270
47454
|
files = expandFiles(files, opts.input);
|
|
@@ -47345,6 +47529,41 @@ ${svg}
|
|
|
47345
47529
|
return obj.target;
|
|
47346
47530
|
}
|
|
47347
47531
|
|
|
47532
|
+
// Read the stdin stream into @cache and return the filename to import it as.
|
|
47533
|
+
// A zipped dataset is given a .zip or .kmz name, so that it is unpacked by
|
|
47534
|
+
// expandFiles() like an archive passed as a file argument. Stdin carries no
|
|
47535
|
+
// filename to identify its contents, so an archive is detected from the ZIP
|
|
47536
|
+
// magic number, and a KMZ is told from an ordinary zip by its doc.kml entry.
|
|
47537
|
+
function readStdinAsFile(cache) {
|
|
47538
|
+
var path = '/dev/stdin';
|
|
47539
|
+
var content = cli.readFile(path, null, cache);
|
|
47540
|
+
var name = path;
|
|
47541
|
+
if (contentLooksZipped(content)) {
|
|
47542
|
+
name = zipContainsDocKml(content) ? 'stdin.kmz' : 'stdin.zip';
|
|
47543
|
+
}
|
|
47544
|
+
cache[name] = content;
|
|
47545
|
+
return name;
|
|
47546
|
+
}
|
|
47547
|
+
|
|
47548
|
+
// Detect the ZIP local file header signature ("PK\x03\x04")
|
|
47549
|
+
function contentLooksZipped(content) {
|
|
47550
|
+
var bytes = content instanceof ArrayBuffer ? new Uint8Array(content) : content;
|
|
47551
|
+
if (utils.isString(bytes) || !bytes || bytes.length < 4) return false;
|
|
47552
|
+
return bytes[0] == 0x50 && bytes[1] == 0x4b && bytes[2] == 0x03 && bytes[3] == 0x04;
|
|
47553
|
+
}
|
|
47554
|
+
|
|
47555
|
+
function zipContainsDocKml(content) {
|
|
47556
|
+
var names;
|
|
47557
|
+
try {
|
|
47558
|
+
names = listZipEntryNames(content);
|
|
47559
|
+
} catch(e) {
|
|
47560
|
+
return false; // let the unzipping step report a malformed archive
|
|
47561
|
+
}
|
|
47562
|
+
return names.some(function(name) {
|
|
47563
|
+
return parseLocalPath(name).filename.toLowerCase() == 'doc.kml';
|
|
47564
|
+
});
|
|
47565
|
+
}
|
|
47566
|
+
|
|
47348
47567
|
function expandFiles(files, cache) {
|
|
47349
47568
|
var files2 = [];
|
|
47350
47569
|
files.forEach(function(file) {
|
|
@@ -47476,6 +47695,7 @@ ${svg}
|
|
|
47476
47695
|
content = cli.readFile(path, encoding || 'utf-8', cache);
|
|
47477
47696
|
fileType = guessInputContentType(content);
|
|
47478
47697
|
if (fileType == 'text' && content.indexOf('\ufffd') > -1) {
|
|
47698
|
+
// invalidate string data that contains the 'replacement character'
|
|
47479
47699
|
fileType = null;
|
|
47480
47700
|
}
|
|
47481
47701
|
}
|
|
@@ -47484,7 +47704,7 @@ ${svg}
|
|
|
47484
47704
|
stop$1(getUnsupportedFileMessage(path));
|
|
47485
47705
|
}
|
|
47486
47706
|
input[fileType] = {filename: path, content: content};
|
|
47487
|
-
content = null;
|
|
47707
|
+
content = null; // for g.c.
|
|
47488
47708
|
if (fileType == 'shp' || fileType == 'dbf') {
|
|
47489
47709
|
readShapefileAuxFiles(path, input, cache);
|
|
47490
47710
|
} else if (isRasterImageInputType(fileType)) {
|
|
@@ -47529,12 +47749,14 @@ ${svg}
|
|
|
47529
47749
|
function validateAndCleanGpkgSelection(datasets, opts) {
|
|
47530
47750
|
var availableSet = new Set();
|
|
47531
47751
|
var importedSet = new Set();
|
|
47752
|
+
var unreadableSet = new Set();
|
|
47532
47753
|
var sawGpkg = false;
|
|
47533
47754
|
datasets.forEach(function(ds) {
|
|
47534
47755
|
var info = ds && ds.info;
|
|
47535
47756
|
if (!info || !Array.isArray(info._gpkg_available_layers)) return;
|
|
47536
47757
|
sawGpkg = true;
|
|
47537
47758
|
info._gpkg_available_layers.forEach(function(name) { availableSet.add(name); });
|
|
47759
|
+
(info._gpkg_unreadable_layers || []).forEach(function(name) { unreadableSet.add(name); });
|
|
47538
47760
|
if (!info._gpkg_placeholder) {
|
|
47539
47761
|
(ds.layers || []).forEach(function(lyr) {
|
|
47540
47762
|
if (lyr && lyr.name) importedSet.add(lyr.name);
|
|
@@ -47542,9 +47764,17 @@ ${svg}
|
|
|
47542
47764
|
}
|
|
47543
47765
|
});
|
|
47544
47766
|
if (sawGpkg && Array.isArray(opts.layers) && opts.layers.length > 0) {
|
|
47545
|
-
var
|
|
47767
|
+
var absent = opts.layers.filter(function(name) {
|
|
47546
47768
|
return !importedSet.has(name);
|
|
47547
47769
|
});
|
|
47770
|
+
// A layer that the file advertises but that can't be parsed is skipped with
|
|
47771
|
+
// a warning during import; report it separately here, because saying it is
|
|
47772
|
+
// missing while also listing it as an existing layer is contradictory.
|
|
47773
|
+
var unreadable = absent.filter(function(name) { return unreadableSet.has(name); });
|
|
47774
|
+
var missing = absent.filter(function(name) { return !unreadableSet.has(name); });
|
|
47775
|
+
if (unreadable.length > 0) {
|
|
47776
|
+
stop$1('Unable to read GeoPackage layer(s): ' + unreadable.join(', '));
|
|
47777
|
+
}
|
|
47548
47778
|
if (missing.length > 0) {
|
|
47549
47779
|
stop$1(
|
|
47550
47780
|
'Missing GeoPackage layer(s): ' + missing.join(', ') + '\n' +
|
|
@@ -47558,6 +47788,7 @@ ${svg}
|
|
|
47558
47788
|
cleaned.forEach(function(ds) {
|
|
47559
47789
|
if (ds && ds.info) {
|
|
47560
47790
|
delete ds.info._gpkg_available_layers;
|
|
47791
|
+
delete ds.info._gpkg_unreadable_layers;
|
|
47561
47792
|
delete ds.info._gpkg_placeholder;
|
|
47562
47793
|
}
|
|
47563
47794
|
});
|
|
@@ -48368,7 +48599,7 @@ ${svg}
|
|
|
48368
48599
|
const resulterrbound = (3 + 8 * epsilon) * epsilon;
|
|
48369
48600
|
|
|
48370
48601
|
// fast_expansion_sum_zeroelim routine from oritinal code
|
|
48371
|
-
function sum(elen, e, flen, f, h) {
|
|
48602
|
+
function sum$1(elen, e, flen, f, h) {
|
|
48372
48603
|
let Q, Qnew, hh, bvirt;
|
|
48373
48604
|
let enow = e[0];
|
|
48374
48605
|
let fnow = f[0];
|
|
@@ -48551,7 +48782,7 @@ ${svg}
|
|
|
48551
48782
|
bvirt = u3 - _j;
|
|
48552
48783
|
u[2] = _j - (u3 - bvirt) + (_i - bvirt);
|
|
48553
48784
|
u[3] = u3;
|
|
48554
|
-
const C1len = sum(4, B$2, 4, u, C1);
|
|
48785
|
+
const C1len = sum$1(4, B$2, 4, u, C1);
|
|
48555
48786
|
|
|
48556
48787
|
s1 = acx * bcytail;
|
|
48557
48788
|
c = splitter * acx;
|
|
@@ -48582,7 +48813,7 @@ ${svg}
|
|
|
48582
48813
|
bvirt = u3 - _j;
|
|
48583
48814
|
u[2] = _j - (u3 - bvirt) + (_i - bvirt);
|
|
48584
48815
|
u[3] = u3;
|
|
48585
|
-
const C2len = sum(C1len, C1, 4, u, C2);
|
|
48816
|
+
const C2len = sum$1(C1len, C1, 4, u, C2);
|
|
48586
48817
|
|
|
48587
48818
|
s1 = acxtail * bcytail;
|
|
48588
48819
|
c = splitter * acxtail;
|
|
@@ -48613,7 +48844,7 @@ ${svg}
|
|
|
48613
48844
|
bvirt = u3 - _j;
|
|
48614
48845
|
u[2] = _j - (u3 - bvirt) + (_i - bvirt);
|
|
48615
48846
|
u[3] = u3;
|
|
48616
|
-
const Dlen = sum(C2len, C2, 4, u, D$1);
|
|
48847
|
+
const Dlen = sum$1(C2len, C2, 4, u, D$1);
|
|
48617
48848
|
|
|
48618
48849
|
return D$1[Dlen - 1];
|
|
48619
48850
|
}
|
|
@@ -74984,7 +75215,7 @@ ${svg}
|
|
|
74984
75215
|
|
|
74985
75216
|
var datasets = [targetDataset];
|
|
74986
75217
|
var outputLayers = targetLayers.map(function(pointLyr) {
|
|
74987
|
-
if (countMultiPartFeatures(pointLyr) > 0) {
|
|
75218
|
+
if (countMultiPartFeatures(pointLyr.shapes) > 0) {
|
|
74988
75219
|
stop$1('This command requires single points');
|
|
74989
75220
|
}
|
|
74990
75221
|
var dataset = getPolygonDataset(pointLyr, bbox, opts);
|
|
@@ -75300,6 +75531,761 @@ ${svg}
|
|
|
75300
75531
|
}, {});
|
|
75301
75532
|
}
|
|
75302
75533
|
|
|
75534
|
+
// Keeps each symbol's center inside the polygon that contained it to begin
|
|
75535
|
+
// with, so that a symbol representing one area can't drift into a neighboring
|
|
75536
|
+
// one. Used by -repel via its polygons= option.
|
|
75537
|
+
//
|
|
75538
|
+
// Nodes are laid out in pixel space, at a scale of @pixelsPerUnit pixels per
|
|
75539
|
+
// unit of the polygons' coordinate system. That transform is a pure scale (see
|
|
75540
|
+
// how -repel builds its nodes), so dividing a node position by it gives a
|
|
75541
|
+
// position that can be tested against the polygons directly.
|
|
75542
|
+
|
|
75543
|
+
// Bisection steps used to find how far a symbol can travel before it leaves its
|
|
75544
|
+
// polygon. Six steps locate the crossing to within 1/64 of the attempted move;
|
|
75545
|
+
// finer searches were measured and change the result by a fraction of a pixel.
|
|
75546
|
+
var BISECT_STEPS = 6;
|
|
75547
|
+
|
|
75548
|
+
// Assigns each node the id of the polygon containing its anchor, or -1 if the
|
|
75549
|
+
// anchor isn't inside any of them. Returns the number of unassigned nodes,
|
|
75550
|
+
// which are left free to move.
|
|
75551
|
+
function assignContainingPolygons(nodes, lyr, arcs, pixelsPerUnit) {
|
|
75552
|
+
var index = new PathIndex(lyr.shapes, arcs);
|
|
75553
|
+
var unassigned = 0;
|
|
75554
|
+
nodes.forEach(function(node) {
|
|
75555
|
+
node.polygonId = index.findEnclosingShape([node.x0 / pixelsPerUnit, node.y0 / pixelsPerUnit]);
|
|
75556
|
+
node.insideX = node.x0;
|
|
75557
|
+
node.insideY = node.y0;
|
|
75558
|
+
if (node.polygonId == -1) unassigned++;
|
|
75559
|
+
});
|
|
75560
|
+
return unassigned;
|
|
75561
|
+
}
|
|
75562
|
+
|
|
75563
|
+
// Returns a function that the solver runs after each pass: any symbol that has
|
|
75564
|
+
// left its polygon is pulled back along the path it just travelled, to the
|
|
75565
|
+
// furthest point that is still inside.
|
|
75566
|
+
//
|
|
75567
|
+
// This can't violate the max-shift limit. The position it assigns always lies
|
|
75568
|
+
// between two positions the node has already held, both of which the solver
|
|
75569
|
+
// clamped to within max-shift of the anchor, and a disk is convex.
|
|
75570
|
+
function getContainmentConstraint(lyr, arcs, pixelsPerUnit) {
|
|
75571
|
+
var shapes = lyr.shapes;
|
|
75572
|
+
|
|
75573
|
+
function isInside(node, x, y) {
|
|
75574
|
+
return testPointInPolygon(x / pixelsPerUnit, y / pixelsPerUnit,
|
|
75575
|
+
shapes[node.polygonId], arcs);
|
|
75576
|
+
}
|
|
75577
|
+
|
|
75578
|
+
return function(nodes) {
|
|
75579
|
+
var node, lo, hi, mid, x, y, i, j;
|
|
75580
|
+
for (i=0; i<nodes.length; i++) {
|
|
75581
|
+
node = nodes[i];
|
|
75582
|
+
if (node.polygonId == -1) continue;
|
|
75583
|
+
// A node that hasn't moved since it was last checked is still inside,
|
|
75584
|
+
// so most symbols in a sparse layout cost nothing after the early passes
|
|
75585
|
+
if (node.x === node.insideX && node.y === node.insideY) continue;
|
|
75586
|
+
if (isInside(node, node.x, node.y)) {
|
|
75587
|
+
node.insideX = node.x;
|
|
75588
|
+
node.insideY = node.y;
|
|
75589
|
+
continue;
|
|
75590
|
+
}
|
|
75591
|
+
lo = 0; // known inside
|
|
75592
|
+
hi = 1; // known outside
|
|
75593
|
+
for (j=0; j<BISECT_STEPS; j++) {
|
|
75594
|
+
mid = (lo + hi) / 2;
|
|
75595
|
+
x = node.insideX + (node.x - node.insideX) * mid;
|
|
75596
|
+
y = node.insideY + (node.y - node.insideY) * mid;
|
|
75597
|
+
if (isInside(node, x, y)) lo = mid; else hi = mid;
|
|
75598
|
+
}
|
|
75599
|
+
node.x = node.insideX + (node.x - node.insideX) * lo;
|
|
75600
|
+
node.y = node.insideY + (node.y - node.insideY) * lo;
|
|
75601
|
+
node.insideX = node.x;
|
|
75602
|
+
node.insideY = node.y;
|
|
75603
|
+
}
|
|
75604
|
+
};
|
|
75605
|
+
}
|
|
75606
|
+
|
|
75607
|
+
var roundCoord$1 = getRoundingFunction(0.01);
|
|
75608
|
+
|
|
75609
|
+
function getSymbolFillColor(d) {
|
|
75610
|
+
return d.fill || 'magenta';
|
|
75611
|
+
}
|
|
75612
|
+
|
|
75613
|
+
function getSymbolStrokeColor(d) {
|
|
75614
|
+
return d.stroke || d.fill || 'magenta';
|
|
75615
|
+
}
|
|
75616
|
+
|
|
75617
|
+
function applySymbolStyles(sym, d) {
|
|
75618
|
+
if (sym.type == 'polyline') {
|
|
75619
|
+
sym.stroke = getSymbolStrokeColor(d);
|
|
75620
|
+
} else {
|
|
75621
|
+
sym.fill = getSymbolFillColor(d);
|
|
75622
|
+
}
|
|
75623
|
+
if (d.opacity) {
|
|
75624
|
+
sym.opacity = d.opacity;
|
|
75625
|
+
}
|
|
75626
|
+
return sym;
|
|
75627
|
+
}
|
|
75628
|
+
|
|
75629
|
+
function getSymbolRadius(d) {
|
|
75630
|
+
if (d.radius === 0 || d.length === 0 || d.r === 0) return 0;
|
|
75631
|
+
return d.radius || d.length || d.r || 5; // use a default value
|
|
75632
|
+
}
|
|
75633
|
+
|
|
75634
|
+
// Converts the value of a list-valued option into an array of items. The value
|
|
75635
|
+
// arrives either as a whole string (from a direct call, rather than from the
|
|
75636
|
+
// -symbols option accessor) or as an array of items already resolved one by
|
|
75637
|
+
// one. An item that holds an array of its own, as an expression like
|
|
75638
|
+
// [R, R * 0.5] does, is flattened into the list. Items themselves are left
|
|
75639
|
+
// unsplit, because a single item can contain a comma, as rgba() colors do.
|
|
75640
|
+
function toItemList(val) {
|
|
75641
|
+
var items = utils.isString(val) ? splitListItems(val) : [].concat(val);
|
|
75642
|
+
var list = [];
|
|
75643
|
+
for (var i=0; i<items.length; i++) {
|
|
75644
|
+
if (Array.isArray(items[i])) {
|
|
75645
|
+
list = list.concat(items[i]);
|
|
75646
|
+
} else {
|
|
75647
|
+
list.push(items[i]);
|
|
75648
|
+
}
|
|
75649
|
+
}
|
|
75650
|
+
return list;
|
|
75651
|
+
}
|
|
75652
|
+
|
|
75653
|
+
// Like toItemList(), for options that take numbers. An item that is a string
|
|
75654
|
+
// containing commas is split, so that a data field holding a value like "2,4"
|
|
75655
|
+
// works as a list. Values that aren't numbers become NaN, for the caller to
|
|
75656
|
+
// report or ignore.
|
|
75657
|
+
function toNumberList(val) {
|
|
75658
|
+
var list = [];
|
|
75659
|
+
toItemList(val).forEach(function(item) {
|
|
75660
|
+
if (utils.isString(item) && item.indexOf(',') > -1) {
|
|
75661
|
+
list = list.concat(parseNumberList(item));
|
|
75662
|
+
} else {
|
|
75663
|
+
list.push(Number(item));
|
|
75664
|
+
}
|
|
75665
|
+
});
|
|
75666
|
+
return list;
|
|
75667
|
+
}
|
|
75668
|
+
|
|
75669
|
+
// Returns the radius of the smallest circle centered on a symbol's point that
|
|
75670
|
+
// covers the symbol, or null if the symbol contains a part whose reach can't be
|
|
75671
|
+
// measured.
|
|
75672
|
+
//
|
|
75673
|
+
// Only a symbol drawn around its point can be described this way: a circle, or
|
|
75674
|
+
// a group of the circles and polygons that make up ring, pie and donut symbols.
|
|
75675
|
+
// An arrow is drawn from its point outward, so the circle around its point that
|
|
75676
|
+
// covers it says nothing useful about the space it occupies -- arrows are plain
|
|
75677
|
+
// polygon symbols, which reach this function only as group parts, which they
|
|
75678
|
+
// never are.
|
|
75679
|
+
function getSymbolBoundingRadius(sym) {
|
|
75680
|
+
var max = 0;
|
|
75681
|
+
var parts, r, i;
|
|
75682
|
+
if (!sym) return null;
|
|
75683
|
+
if (sym.type == 'circle') {
|
|
75684
|
+
// Half of a stroke lies outside the circle it follows, which is how a ring
|
|
75685
|
+
// symbol paints its bands.
|
|
75686
|
+
return getPositiveNumber(sym.r) + getStrokeOutset(sym);
|
|
75687
|
+
}
|
|
75688
|
+
if (sym.type == 'polygon') {
|
|
75689
|
+
forEachSymbolCoord(sym.coordinates || [], function(p) {
|
|
75690
|
+
var dist = Math.sqrt(p[0] * p[0] + p[1] * p[1]);
|
|
75691
|
+
if (dist > max) max = dist;
|
|
75692
|
+
});
|
|
75693
|
+
return max + getStrokeOutset(sym);
|
|
75694
|
+
}
|
|
75695
|
+
if (sym.type == 'group') {
|
|
75696
|
+
parts = sym.parts || [];
|
|
75697
|
+
for (i=0; i<parts.length; i++) {
|
|
75698
|
+
r = getSymbolBoundingRadius(parts[i]);
|
|
75699
|
+
if (r === null) return null;
|
|
75700
|
+
if (r > max) max = r;
|
|
75701
|
+
}
|
|
75702
|
+
return max;
|
|
75703
|
+
}
|
|
75704
|
+
return null;
|
|
75705
|
+
}
|
|
75706
|
+
|
|
75707
|
+
// A stroke straddles the path it is drawn on, so half of its width extends
|
|
75708
|
+
// beyond the shape.
|
|
75709
|
+
function getStrokeOutset(sym) {
|
|
75710
|
+
var width = sym['stroke-width'];
|
|
75711
|
+
if (!sym.stroke || sym.stroke == 'none') return 0;
|
|
75712
|
+
// an SVG stroke is one pixel wide unless stroke-width says otherwise
|
|
75713
|
+
if (width === undefined || width === null || width === '') return 0.5;
|
|
75714
|
+
return getPositiveNumber(width) / 2;
|
|
75715
|
+
}
|
|
75716
|
+
|
|
75717
|
+
function getPositiveNumber(val) {
|
|
75718
|
+
var num = +val;
|
|
75719
|
+
return num > 0 ? num : 0;
|
|
75720
|
+
}
|
|
75721
|
+
|
|
75722
|
+
function forEachSymbolCoord(coords, cb) {
|
|
75723
|
+
var isPoint = coords && utils.isNumber(coords[0]);
|
|
75724
|
+
var isNested = !isPoint && coords && Array.isArray(coords[0]);
|
|
75725
|
+
if (isPoint) return cb(coords);
|
|
75726
|
+
for (var i=0; i<coords.length; i++) {
|
|
75727
|
+
if (isNested) forEachSymbolCoord(coords[i], cb);
|
|
75728
|
+
}
|
|
75729
|
+
}
|
|
75730
|
+
|
|
75731
|
+
function flipY(coords) {
|
|
75732
|
+
forEachSymbolCoord(coords, function(p) {
|
|
75733
|
+
p[1] = -p[1];
|
|
75734
|
+
});
|
|
75735
|
+
}
|
|
75736
|
+
|
|
75737
|
+
function scaleAndShiftCoords(coords, scale, shift) {
|
|
75738
|
+
forEachSymbolCoord(coords, function(xy) {
|
|
75739
|
+
xy[0] = xy[0] * scale + shift[0];
|
|
75740
|
+
xy[1] = xy[1] * scale + shift[1];
|
|
75741
|
+
});
|
|
75742
|
+
}
|
|
75743
|
+
|
|
75744
|
+
function roundCoordsForSVG(coords) {
|
|
75745
|
+
forEachSymbolCoord(coords, function(p) {
|
|
75746
|
+
p[0] = roundCoord$1(p[0]);
|
|
75747
|
+
p[1] = roundCoord$1(p[1]);
|
|
75748
|
+
});
|
|
75749
|
+
}
|
|
75750
|
+
|
|
75751
|
+
function rotateCoords(coords, rotation) {
|
|
75752
|
+
if (!rotation) return;
|
|
75753
|
+
var f = getAffineTransform(rotation, 1, [0, 0], [0, 0]);
|
|
75754
|
+
forEachSymbolCoord(coords, function(p) {
|
|
75755
|
+
var p2 = f(p[0], p[1]);
|
|
75756
|
+
p[0] = p2[0];
|
|
75757
|
+
p[1] = p2[1];
|
|
75758
|
+
});
|
|
75759
|
+
}
|
|
75760
|
+
|
|
75761
|
+
// Collision reduction for circular symbols, in the spirit of d3-force's
|
|
75762
|
+
// forceCollide. Overlapping symbols are pushed apart, but each one is kept
|
|
75763
|
+
// within a fixed distance of its true position.
|
|
75764
|
+
//
|
|
75765
|
+
// Nodes are laid out in pixel space; converting to and from map coordinates is
|
|
75766
|
+
// the caller's job. A node is {i, x, y, x0, y0, r, maxShift, margin}, where
|
|
75767
|
+
// x0,y0 is the anchor (the symbol's true position), r includes any padding and
|
|
75768
|
+
// maxShift is the furthest this symbol may be displaced. All three of r,
|
|
75769
|
+
// maxShift and margin are per-node, so the caller can vary them from symbol to
|
|
75770
|
+
// symbol; a node with a maxShift of 0 never moves and so acts as a fixed
|
|
75771
|
+
// obstacle.
|
|
75772
|
+
//
|
|
75773
|
+
// A pair of nodes is kept a.r + b.r + max(a.margin, b.margin) apart, so a
|
|
75774
|
+
// margin is a gap that one symbol claims from every neighbor, whatever the
|
|
75775
|
+
// neighbor asks for, and the larger of the two claims wins. Padding is the
|
|
75776
|
+
// alternative already folded into r, where a pair ends up with the sum of the
|
|
75777
|
+
// two symbols' padding between them. margin is optional; treated as 0 if absent.
|
|
75778
|
+
//
|
|
75779
|
+
// A negative margin lets a pair overlap, but only where both nodes permit it:
|
|
75780
|
+
// max() means one node's 0 outranks another's -5.
|
|
75781
|
+
|
|
75782
|
+
// max_shift is not read by the solver (it is a per-node property); it is here
|
|
75783
|
+
// as the default for callers to apply when building nodes.
|
|
75784
|
+
//
|
|
75785
|
+
// strength is the damping factor of what is essentially Gauss-Seidel
|
|
75786
|
+
// relaxation. A symbol in a cluster is corrected once per overlapping neighbor
|
|
75787
|
+
// in a single pass, so the corrections sum to more than its own overlap and
|
|
75788
|
+
// values near 1 overshoot and oscillate. Given the tick budget below, anything
|
|
75789
|
+
// from 0.3 to 1.0 resolves a layout that can be resolved at all; the value only
|
|
75790
|
+
// matters where crowding exceeds what max-shift can fix, and there the lower
|
|
75791
|
+
// end leaves shallower overlaps and holds symbols closer to their true
|
|
75792
|
+
// positions. Below about 0.4 the solver stops reliably breaking up stacks of
|
|
75793
|
+
// coincident symbols.
|
|
75794
|
+
var symbolCollisionDefaults = {
|
|
75795
|
+
ticks: 100,
|
|
75796
|
+
max_shift: 20,
|
|
75797
|
+
strength: 0.4
|
|
75798
|
+
};
|
|
75799
|
+
|
|
75800
|
+
// Mutates the x,y properties of @nodes. Returns the number of nodes that moved.
|
|
75801
|
+
//
|
|
75802
|
+
// opts.constrain is an optional function(nodes) applied after each pass, for
|
|
75803
|
+
// callers that need to restrict where symbols may go (see
|
|
75804
|
+
// mapshaper-symbol-containment.mjs). It must only move a node to a position
|
|
75805
|
+
// between ones the node has already held, or the max-shift limit no longer
|
|
75806
|
+
// holds.
|
|
75807
|
+
function resolveSymbolCollisions(nodes, opts) {
|
|
75808
|
+
var ticks = opts.ticks,
|
|
75809
|
+
strength = opts.strength,
|
|
75810
|
+
constrain = opts.constrain || null,
|
|
75811
|
+
moved = 0,
|
|
75812
|
+
i;
|
|
75813
|
+
if (nodes.length < 2 || !(ticks > 0) || !anyNodeCanMove(nodes)) return 0;
|
|
75814
|
+
for (i=0; i<ticks; i++) {
|
|
75815
|
+
resolveCollisions(nodes, strength);
|
|
75816
|
+
// Limiting the displacement on every pass (instead of only at the end)
|
|
75817
|
+
// avoids pulling symbols back into the overlaps they just escaped.
|
|
75818
|
+
limitDisplacement(nodes);
|
|
75819
|
+
if (constrain) constrain(nodes);
|
|
75820
|
+
}
|
|
75821
|
+
for (i=0; i<nodes.length; i++) {
|
|
75822
|
+
if (nodes[i].x !== nodes[i].x0 || nodes[i].y !== nodes[i].y0) moved++;
|
|
75823
|
+
}
|
|
75824
|
+
return moved;
|
|
75825
|
+
}
|
|
75826
|
+
|
|
75827
|
+
// Collisions shallower than this are not reported. A converged layout doesn't
|
|
75828
|
+
// leave pairs at exactly the separation they need, it leaves a tail of pairs
|
|
75829
|
+
// short of it by a fraction of a pixel -- on a solved 435-symbol layout, 55
|
|
75830
|
+
// pairs fall short by more than a hundredth of a pixel, 6 by more than half a
|
|
75831
|
+
// pixel, and none by more than one. So a threshold of a pixel is where this
|
|
75832
|
+
// solver's convergence residue ends and genuine unresolved crowding begins, as
|
|
75833
|
+
// well as being about the point where the shortfall becomes visible.
|
|
75834
|
+
var VISIBLE_COLLISION = 1; // pixels
|
|
75835
|
+
|
|
75836
|
+
// Number of pairs that fall visibly short of the separation they need, for
|
|
75837
|
+
// reporting how much of the job got done. Deliberately not a count of every
|
|
75838
|
+
// collision: falling short by a 100th of a pixel and by 15 pixels are not the
|
|
75839
|
+
// same result, and treating them alike makes a layout that is visually finished
|
|
75840
|
+
// look like a failure.
|
|
75841
|
+
//
|
|
75842
|
+
// Note this counts collisions, not overlaps, and padding or a margin can put
|
|
75843
|
+
// the two apart: symbols asked to keep 4px between them and left 3px apart are
|
|
75844
|
+
// colliding, in that the layout hasn't done what was asked, but not overlapping.
|
|
75845
|
+
function countSymbolCollisions(nodes) {
|
|
75846
|
+
var count = 0;
|
|
75847
|
+
forEachCollision(nodes, function(a, b, dx, dy, distSq, sum) {
|
|
75848
|
+
if (sum - Math.sqrt(distSq) > VISIBLE_COLLISION) count++;
|
|
75849
|
+
});
|
|
75850
|
+
return count;
|
|
75851
|
+
}
|
|
75852
|
+
|
|
75853
|
+
// Push colliding nodes apart, in proportion to how far short of their required
|
|
75854
|
+
// separation they are. Larger nodes move less than smaller ones. Which of the
|
|
75855
|
+
// two yields is decided by their radii alone, so asking for a wide margin does
|
|
75856
|
+
// not also make a symbol harder to push.
|
|
75857
|
+
function resolveCollisions(nodes, strength) {
|
|
75858
|
+
forEachCollision(nodes, function(a, b, dx, dy, distSq, sum) {
|
|
75859
|
+
separate(a, b, dx, dy, distSq, sum, strength);
|
|
75860
|
+
});
|
|
75861
|
+
}
|
|
75862
|
+
|
|
75863
|
+
// Sweep along the x axis, using the greatest reach still ahead of the current
|
|
75864
|
+
// node to know when to stop looking for neighbors. A node's reach is its radius
|
|
75865
|
+
// plus its margin if that margin is positive: only a positive margin widens the
|
|
75866
|
+
// area a symbol needs, and clamping is what makes the bound below safe.
|
|
75867
|
+
//
|
|
75868
|
+
// Breaking out of the inner loop is valid because the separation a pair needs,
|
|
75869
|
+
// a.r + b.r + max(a.margin, b.margin), never exceeds a.reach + b.reach. With
|
|
75870
|
+
// margins clamped at 0 that holds, since max(ma, mb) <= max(ma, 0) + max(mb, 0).
|
|
75871
|
+
// Without the clamp it fails for two negative margins -- max(-3, -3) is -3, not
|
|
75872
|
+
// -6 -- and the sweep quietly stops short of pairs that are still colliding.
|
|
75873
|
+
//
|
|
75874
|
+
// Margins are collected into an array in the same pass that builds the bound,
|
|
75875
|
+
// which normalizes a missing margin to 0 once per node instead of guarding every
|
|
75876
|
+
// comparison in the loop below.
|
|
75877
|
+
//
|
|
75878
|
+
// Note the sort happens once per pass while the callback moves nodes as it goes,
|
|
75879
|
+
// so the bound is applied to positions that have already shifted and a pair that
|
|
75880
|
+
// drifts into range mid-pass can be missed until the next pass re-sorts. A
|
|
75881
|
+
// consequence is that padding and an equivalent margin, which ask for the same
|
|
75882
|
+
// separation, don't produce identical output: the margin gives a node a longer
|
|
75883
|
+
// reach, which prunes less and catches a few more of those pairs.
|
|
75884
|
+
function forEachCollision(nodes, cb) {
|
|
75885
|
+
var n = nodes.length,
|
|
75886
|
+
maxAhead = new Float64Array(n),
|
|
75887
|
+
margins = new Float64Array(n),
|
|
75888
|
+
max = 0,
|
|
75889
|
+
a, b, dx, dy, sum, distSq, reach, ma, mb, i, j;
|
|
75890
|
+
nodes.sort(compareX);
|
|
75891
|
+
for (i=n-1; i>=0; i--) {
|
|
75892
|
+
ma = nodes[i].margin || 0;
|
|
75893
|
+
margins[i] = ma;
|
|
75894
|
+
reach = nodes[i].r + (ma > 0 ? ma : 0);
|
|
75895
|
+
if (reach > max) max = reach;
|
|
75896
|
+
maxAhead[i] = max;
|
|
75897
|
+
}
|
|
75898
|
+
for (i=0; i<n; i++) {
|
|
75899
|
+
a = nodes[i];
|
|
75900
|
+
ma = margins[i];
|
|
75901
|
+
reach = a.r + (ma > 0 ? ma : 0);
|
|
75902
|
+
for (j=i+1; j<n; j++) {
|
|
75903
|
+
b = nodes[j];
|
|
75904
|
+
dx = b.x - a.x;
|
|
75905
|
+
if (dx > reach + maxAhead[j]) break;
|
|
75906
|
+
dy = b.y - a.y;
|
|
75907
|
+
mb = margins[j];
|
|
75908
|
+
sum = a.r + b.r + (ma > mb ? ma : mb);
|
|
75909
|
+
// Margins can cancel the pair's radii entirely, which separate() would
|
|
75910
|
+
// read as an overlap and answer by pulling the two nodes together.
|
|
75911
|
+
if (sum <= 0) continue;
|
|
75912
|
+
if (dy > sum || dy < -sum) continue;
|
|
75913
|
+
distSq = dx * dx + dy * dy;
|
|
75914
|
+
if (distSq >= sum * sum) continue;
|
|
75915
|
+
cb(a, b, dx, dy, distSq, sum);
|
|
75916
|
+
}
|
|
75917
|
+
}
|
|
75918
|
+
}
|
|
75919
|
+
|
|
75920
|
+
function compareX(a, b) {
|
|
75921
|
+
// Break ties by original index, so the order of equally positioned nodes
|
|
75922
|
+
// doesn't depend on the engine's sort implementation.
|
|
75923
|
+
return a.x - b.x || a.i - b.i;
|
|
75924
|
+
}
|
|
75925
|
+
|
|
75926
|
+
function separate(a, b, dx, dy, distSq, sum, strength) {
|
|
75927
|
+
var ux, uy, overlap, dist, dir, shift, share;
|
|
75928
|
+
if (distSq > 0) {
|
|
75929
|
+
dist = Math.sqrt(distSq);
|
|
75930
|
+
ux = dx / dist;
|
|
75931
|
+
uy = dy / dist;
|
|
75932
|
+
overlap = sum - dist;
|
|
75933
|
+
} else {
|
|
75934
|
+
// Coincident nodes: pick a repeatable arbitrary direction
|
|
75935
|
+
dir = getPseudoDirection(a.i, b.i);
|
|
75936
|
+
ux = dir[0];
|
|
75937
|
+
uy = dir[1];
|
|
75938
|
+
overlap = sum;
|
|
75939
|
+
}
|
|
75940
|
+
shift = overlap * strength;
|
|
75941
|
+
share = b.r * b.r / (a.r * a.r + b.r * b.r); // portion moved by a
|
|
75942
|
+
a.x -= ux * shift * share;
|
|
75943
|
+
a.y -= uy * shift * share;
|
|
75944
|
+
b.x += ux * shift * (1 - share);
|
|
75945
|
+
b.y += uy * shift * (1 - share);
|
|
75946
|
+
}
|
|
75947
|
+
|
|
75948
|
+
// A deterministic stand-in for d3-force's jiggle(), which uses Math.random()
|
|
75949
|
+
function getPseudoDirection(i, j) {
|
|
75950
|
+
var hash = (Math.imul(i + 1, 2654435761) ^ Math.imul(j + 1, 1597334677)) >>> 0,
|
|
75951
|
+
x = (hash & 0xffff) / 0x8000 - 1,
|
|
75952
|
+
y = ((hash >>> 16) & 0xffff) / 0x8000 - 1,
|
|
75953
|
+
dist = Math.sqrt(x * x + y * y);
|
|
75954
|
+
return dist > 0 ? [x / dist, y / dist] : [1, 0];
|
|
75955
|
+
}
|
|
75956
|
+
|
|
75957
|
+
function anyNodeCanMove(nodes) {
|
|
75958
|
+
for (var i=0; i<nodes.length; i++) {
|
|
75959
|
+
if (nodes[i].maxShift > 0) return true;
|
|
75960
|
+
}
|
|
75961
|
+
return false;
|
|
75962
|
+
}
|
|
75963
|
+
|
|
75964
|
+
// Pull each node back onto the circle of radius node.maxShift around its anchor.
|
|
75965
|
+
function limitDisplacement(nodes) {
|
|
75966
|
+
var node, maxShift, dx, dy, distSq, k, i;
|
|
75967
|
+
for (i=0; i<nodes.length; i++) {
|
|
75968
|
+
node = nodes[i];
|
|
75969
|
+
maxShift = node.maxShift;
|
|
75970
|
+
dx = node.x - node.x0;
|
|
75971
|
+
dy = node.y - node.y0;
|
|
75972
|
+
distSq = dx * dx + dy * dy;
|
|
75973
|
+
if (distSq <= maxShift * maxShift) continue;
|
|
75974
|
+
k = maxShift / Math.sqrt(distSq);
|
|
75975
|
+
node.x = node.x0 + dx * k;
|
|
75976
|
+
node.y = node.y0 + dy * k;
|
|
75977
|
+
}
|
|
75978
|
+
}
|
|
75979
|
+
|
|
75980
|
+
var SymbolCollisions = /*#__PURE__*/Object.freeze({
|
|
75981
|
+
__proto__: null,
|
|
75982
|
+
VISIBLE_COLLISION: VISIBLE_COLLISION,
|
|
75983
|
+
countSymbolCollisions: countSymbolCollisions,
|
|
75984
|
+
resolveSymbolCollisions: resolveSymbolCollisions,
|
|
75985
|
+
symbolCollisionDefaults: symbolCollisionDefaults
|
|
75986
|
+
});
|
|
75987
|
+
|
|
75988
|
+
// Move symbols apart to reduce overlaps, keeping each one within a fixed pixel
|
|
75989
|
+
// distance of its true position. Each symbol is laid out as the circle that
|
|
75990
|
+
// covers it, so the command works on the symbol types that are drawn around
|
|
75991
|
+
// their point (see getSymbolLayoutRadius()).
|
|
75992
|
+
//
|
|
75993
|
+
// The layout runs in the pixel space of the map that the symbols will be
|
|
75994
|
+
// rendered in, so the command needs to know the display scale: either from a
|
|
75995
|
+
// frame associated with the target, or from a width= option.
|
|
75996
|
+
//
|
|
75997
|
+
// All targeted layers are laid out in a single simulation, so symbols in
|
|
75998
|
+
// different layers are moved apart from each other as well.
|
|
75999
|
+
//
|
|
76000
|
+
// An optional polygons= layer confines each symbol to the polygon it started
|
|
76001
|
+
// in, so that a symbol standing for one area can't drift into a neighboring
|
|
76002
|
+
// one. Symbols in an area too small to hold them barely move as a result, and
|
|
76003
|
+
// their collisions go unresolved -- that is the point of the option, but it does
|
|
76004
|
+
// mean fewer collisions get fixed.
|
|
76005
|
+
//
|
|
76006
|
+
// Clearance between symbols can be asked for in two ways. padding= surrounds
|
|
76007
|
+
// each symbol, so a pair ends up with the sum of their two paddings between
|
|
76008
|
+
// them; margin= is a gap a symbol claims from every neighbor, so a pair ends up
|
|
76009
|
+
// with the greater of their two margins. Use padding= to make a symbol keep its
|
|
76010
|
+
// distance in proportion to how much of it there is, and margin= to set a gap
|
|
76011
|
+
// that holds however the neighbor is configured.
|
|
76012
|
+
var DEFAULT_PADDING = 0; // pixels
|
|
76013
|
+
var DEFAULT_MARGIN = 0; // pixels
|
|
76014
|
+
|
|
76015
|
+
cmd.repel = function(targetLayers, dataset, catalog, polygonSource, opts) {
|
|
76016
|
+
var solverOpts = {
|
|
76017
|
+
ticks: opts.ticks > 0 ? opts.ticks : symbolCollisionDefaults.ticks,
|
|
76018
|
+
strength: opts.strength > 0 ? opts.strength : symbolCollisionDefaults.strength
|
|
76019
|
+
};
|
|
76020
|
+
var nodes = [];
|
|
76021
|
+
|
|
76022
|
+
// Validated before the display scale is resolved, so that an unusable target
|
|
76023
|
+
// is reported as such instead of as a missing width= option.
|
|
76024
|
+
requireProjectedDataset(dataset);
|
|
76025
|
+
targetLayers.forEach(function(lyr) {
|
|
76026
|
+
requireRepelTarget(lyr, opts);
|
|
76027
|
+
});
|
|
76028
|
+
var pixelsPerUnit = getDisplayScale(targetLayers, dataset, catalog, opts);
|
|
76029
|
+
targetLayers.forEach(function(lyr) {
|
|
76030
|
+
addLayerNodes(nodes, lyr, pixelsPerUnit, opts);
|
|
76031
|
+
});
|
|
76032
|
+
if (nodes.length === 0) {
|
|
76033
|
+
stop$1('Targeted layer(s) contain no symbols to move.');
|
|
76034
|
+
}
|
|
76035
|
+
var unconstrained = addContainment(nodes, dataset, polygonSource, pixelsPerUnit, solverOpts);
|
|
76036
|
+
|
|
76037
|
+
var collisionsBefore = countSymbolCollisions(nodes);
|
|
76038
|
+
var moved = resolveSymbolCollisions(nodes, solverOpts);
|
|
76039
|
+
var collisionsAfter = countSymbolCollisions(nodes);
|
|
76040
|
+
applyDisplacement(nodes, pixelsPerUnit);
|
|
76041
|
+
reportResults(nodes, moved, collisionsBefore, collisionsAfter, !!polygonSource);
|
|
76042
|
+
if (unconstrained > 0) {
|
|
76043
|
+
message(utils.format('%d symbol%s not inside any polygon in %s, and %s left free to move',
|
|
76044
|
+
unconstrained, unconstrained == 1 ? ' is' : 's are', polygonSource.layer.name || 'the polygons= layer',
|
|
76045
|
+
unconstrained == 1 ? 'was' : 'were'));
|
|
76046
|
+
}
|
|
76047
|
+
};
|
|
76048
|
+
|
|
76049
|
+
// Confines symbols to the polygons they start inside. Returns the number of
|
|
76050
|
+
// symbols that no polygon contains, which are left unconstrained.
|
|
76051
|
+
function addContainment(nodes, dataset, polygonSource, pixelsPerUnit, solverOpts) {
|
|
76052
|
+
if (!polygonSource) return 0;
|
|
76053
|
+
var lyr = polygonSource.layer;
|
|
76054
|
+
var arcs = polygonSource.dataset.arcs;
|
|
76055
|
+
requirePolygonLayer(lyr, 'The polygons= option requires a polygon layer.');
|
|
76056
|
+
// Only catches mixing projected with unprojected data; two different
|
|
76057
|
+
// projections are the caller's problem, as with -join
|
|
76058
|
+
requireDatasetsHaveCompatibleCRS([dataset, polygonSource.dataset]);
|
|
76059
|
+
var unconstrained = assignContainingPolygons(nodes, lyr, arcs, pixelsPerUnit);
|
|
76060
|
+
solverOpts.constrain = getContainmentConstraint(lyr, arcs, pixelsPerUnit);
|
|
76061
|
+
return unconstrained;
|
|
76062
|
+
}
|
|
76063
|
+
|
|
76064
|
+
// Symbols are displaced by adding an offset to their original coordinates, so
|
|
76065
|
+
// that symbols that didn't move keep bit-for-bit identical coordinates.
|
|
76066
|
+
// Displaced symbols get a new coordinate array rather than an updated one,
|
|
76067
|
+
// because the GeoJSON importer reuses the arrays it is given and mutating them
|
|
76068
|
+
// would reach back into a caller's input object.
|
|
76069
|
+
function applyDisplacement(nodes, pixelsPerUnit) {
|
|
76070
|
+
var movedNodes = nodes.filter(function(node) {
|
|
76071
|
+
return node.x !== node.x0 || node.y !== node.y0;
|
|
76072
|
+
});
|
|
76073
|
+
var movedLayers = [];
|
|
76074
|
+
movedNodes.forEach(function(node) {
|
|
76075
|
+
if (movedLayers.indexOf(node.lyr) == -1) movedLayers.push(node.lyr);
|
|
76076
|
+
});
|
|
76077
|
+
movedLayers.forEach(function(lyr) {
|
|
76078
|
+
noteLayerWillChange(lyr, {operation: 'repel', unit: 'shapes'});
|
|
76079
|
+
});
|
|
76080
|
+
movedNodes.forEach(function(node) {
|
|
76081
|
+
node.shp[0] = [
|
|
76082
|
+
node.shp[0][0] + (node.x - node.x0) / pixelsPerUnit,
|
|
76083
|
+
node.shp[0][1] + (node.y - node.y0) / pixelsPerUnit
|
|
76084
|
+
];
|
|
76085
|
+
});
|
|
76086
|
+
movedLayers.forEach(function(lyr) {
|
|
76087
|
+
markLayerChanged(lyr, {operation: 'repel', unit: 'shapes'});
|
|
76088
|
+
});
|
|
76089
|
+
}
|
|
76090
|
+
|
|
76091
|
+
function reportResults(nodes, moved, before, after, contained) {
|
|
76092
|
+
var total = nodes.length;
|
|
76093
|
+
var msg = utils.format('Moved %d of %d symbol%s', moved, total, total == 1 ? '' : 's');
|
|
76094
|
+
// Counts are of visible collisions only, so a layout whose symbols end up at
|
|
76095
|
+
// or a fraction of a pixel short of the separation they need is reported as
|
|
76096
|
+
// finished, and the advice to raise max-shift= is not given when nothing is
|
|
76097
|
+
// left to fix. A collision is not the same as an overlap: two symbols asked to
|
|
76098
|
+
// keep a gap between them collide as soon as the gap closes.
|
|
76099
|
+
if (after > 0) {
|
|
76100
|
+
msg += utils.format('; %d of %d visible collision%s remain (%s)',
|
|
76101
|
+
after, before, before == 1 ? '' : 's',
|
|
76102
|
+
contained ? getMaxShiftAdvice(nodes) + ', or drop polygons=' : getMaxShiftAdvice(nodes));
|
|
76103
|
+
} else if (before > 0) {
|
|
76104
|
+
msg += utils.format('; resolved %d visible collision%s', before, before == 1 ? '' : 's');
|
|
76105
|
+
} else {
|
|
76106
|
+
msg += ' (no visible collisions found)';
|
|
76107
|
+
}
|
|
76108
|
+
message(msg);
|
|
76109
|
+
}
|
|
76110
|
+
|
|
76111
|
+
// max-shift= may vary from symbol to symbol, so report the range in use.
|
|
76112
|
+
function getMaxShiftAdvice(nodes) {
|
|
76113
|
+
var min = Infinity, max = -Infinity;
|
|
76114
|
+
nodes.forEach(function(node) {
|
|
76115
|
+
if (node.maxShift < min) min = node.maxShift;
|
|
76116
|
+
if (node.maxShift > max) max = node.maxShift;
|
|
76117
|
+
});
|
|
76118
|
+
if (min === max) {
|
|
76119
|
+
return utils.format('try a larger max-shift= than %s', min);
|
|
76120
|
+
}
|
|
76121
|
+
return utils.format('try larger max-shift= values, now %s-%s', min, max);
|
|
76122
|
+
}
|
|
76123
|
+
|
|
76124
|
+
function requireRepelTarget(lyr, opts) {
|
|
76125
|
+
requireSinglePointLayer(lyr,
|
|
76126
|
+
'-repel requires single points; layer contains multi-point features.');
|
|
76127
|
+
if (!opts.radius && !layerHasSymbols(lyr)) {
|
|
76128
|
+
stop$1('-repel requires a layer containing symbols ' +
|
|
76129
|
+
'(see the -symbols and -style commands), or a radius= option.');
|
|
76130
|
+
}
|
|
76131
|
+
}
|
|
76132
|
+
|
|
76133
|
+
function layerHasSymbols(lyr) {
|
|
76134
|
+
return !!lyr.data && (lyr.data.fieldExists('svg-symbol') || lyr.data.fieldExists('r'));
|
|
76135
|
+
}
|
|
76136
|
+
|
|
76137
|
+
function addLayerNodes(nodes, lyr, pixelsPerUnit, opts) {
|
|
76138
|
+
var getRadius = getRadiusAccessor(lyr, opts);
|
|
76139
|
+
var getPadding = getPixelValueAccessor(opts.padding, 'padding', DEFAULT_PADDING, lyr);
|
|
76140
|
+
var getMargin = getPixelValueAccessor(opts.margin, 'margin', DEFAULT_MARGIN, lyr, true);
|
|
76141
|
+
var getMaxShift = getPixelValueAccessor(opts.max_shift, 'max-shift',
|
|
76142
|
+
symbolCollisionDefaults.max_shift, lyr);
|
|
76143
|
+
var i, shp, p, r;
|
|
76144
|
+
for (i=0; i<lyr.shapes.length; i++) {
|
|
76145
|
+
shp = lyr.shapes[i];
|
|
76146
|
+
p = shp ? shp[0] : null;
|
|
76147
|
+
if (!p) continue;
|
|
76148
|
+
r = getRadius(i);
|
|
76149
|
+
if (!(r > 0) || !isFinite(p[0]) || !isFinite(p[1])) continue;
|
|
76150
|
+
nodes.push({
|
|
76151
|
+
lyr: lyr,
|
|
76152
|
+
shp: shp,
|
|
76153
|
+
i: nodes.length,
|
|
76154
|
+
x: p[0] * pixelsPerUnit,
|
|
76155
|
+
y: p[1] * pixelsPerUnit,
|
|
76156
|
+
x0: p[0] * pixelsPerUnit,
|
|
76157
|
+
y0: p[1] * pixelsPerUnit,
|
|
76158
|
+
// Padding is added to the radius, so a pair of symbols ends up with
|
|
76159
|
+
// (padding a + padding b) pixels of clearance between them. A margin is
|
|
76160
|
+
// kept separate, because the solver has to compare the two symbols'
|
|
76161
|
+
// margins to find the gap a pair needs.
|
|
76162
|
+
r: r + getPadding(i),
|
|
76163
|
+
margin: getMargin(i),
|
|
76164
|
+
maxShift: getMaxShift(i)
|
|
76165
|
+
});
|
|
76166
|
+
}
|
|
76167
|
+
}
|
|
76168
|
+
|
|
76169
|
+
// padding=, margin= and max-shift= accept a number, a field name or an
|
|
76170
|
+
// expression, and so are resolved for each symbol.
|
|
76171
|
+
//
|
|
76172
|
+
// @allowNegative is for margin=, where a negative value asks that a pair of
|
|
76173
|
+
// symbols be allowed to overlap. Elsewhere a negative value is an error: it
|
|
76174
|
+
// would shrink a radius or a shift limit, neither of which is a coherent
|
|
76175
|
+
// request. A margin still has to be a finite number, so NaN is rejected either
|
|
76176
|
+
// way.
|
|
76177
|
+
function getPixelValueAccessor(optVal, name, defaultVal, lyr, allowNegative) {
|
|
76178
|
+
if (optVal === undefined || optVal === null || optVal === '') {
|
|
76179
|
+
return function(i) {return defaultVal;};
|
|
76180
|
+
}
|
|
76181
|
+
var accessor = getPropertyAccessor(optVal, 'number', lyr, name);
|
|
76182
|
+
return function(i) {
|
|
76183
|
+
var val = +accessor(i);
|
|
76184
|
+
if (allowNegative ? !isFinite(val) : !(val >= 0)) {
|
|
76185
|
+
stop$1(utils.format('Invalid %s= value: %s', name, accessor(i)));
|
|
76186
|
+
}
|
|
76187
|
+
return val;
|
|
76188
|
+
};
|
|
76189
|
+
}
|
|
76190
|
+
|
|
76191
|
+
// Radii come from one of three places: an explicit radius= option, the
|
|
76192
|
+
// svg-symbol field written by -symbols, or the r field written by -style.
|
|
76193
|
+
// Deliberately not using getSymbolRadius(), whose default of 5 would invent
|
|
76194
|
+
// radii for symbols that don't exist.
|
|
76195
|
+
function getRadiusAccessor(lyr, opts) {
|
|
76196
|
+
if (opts.radius) {
|
|
76197
|
+
return getSymbolPropertyAccessor(opts.radius, 'radius', lyr);
|
|
76198
|
+
}
|
|
76199
|
+
var records = lyr.data.getRecords();
|
|
76200
|
+
return function(i) {
|
|
76201
|
+
var rec = records[i];
|
|
76202
|
+
if (!rec) return 0;
|
|
76203
|
+
if (rec['svg-symbol']) return getSymbolLayoutRadius(rec['svg-symbol']);
|
|
76204
|
+
return rec.r > 0 ? +rec.r : 0;
|
|
76205
|
+
};
|
|
76206
|
+
}
|
|
76207
|
+
|
|
76208
|
+
// A symbol is laid out as the circle that covers it, so a symbol type is
|
|
76209
|
+
// supported if it is drawn around its point: a circle, or a group of parts,
|
|
76210
|
+
// which is how ring, pie and donut symbols are made. A symbol drawn from its
|
|
76211
|
+
// point outward, like an arrow, has no meaningful radius and needs radius=.
|
|
76212
|
+
function getSymbolLayoutRadius(sym) {
|
|
76213
|
+
var r;
|
|
76214
|
+
if (utils.isString(sym)) {
|
|
76215
|
+
try {
|
|
76216
|
+
sym = JSON.parse(sym);
|
|
76217
|
+
} catch(e) {
|
|
76218
|
+
return 0;
|
|
76219
|
+
}
|
|
76220
|
+
}
|
|
76221
|
+
if (!sym) return 0;
|
|
76222
|
+
r = sym.type == 'circle' || sym.type == 'group' ?
|
|
76223
|
+
getSymbolBoundingRadius(sym) : null;
|
|
76224
|
+
if (r === null) {
|
|
76225
|
+
stop$1('-repel is unable to size a ' + (sym.type || 'symbol') +
|
|
76226
|
+
' symbol; use a radius= option to give it a radius.');
|
|
76227
|
+
}
|
|
76228
|
+
return r;
|
|
76229
|
+
}
|
|
76230
|
+
|
|
76231
|
+
// Returns the number of display pixels per map coordinate unit.
|
|
76232
|
+
function getDisplayScale(targetLayers, dataset, catalog, opts) {
|
|
76233
|
+
var frame, bounds, extent;
|
|
76234
|
+
if (opts.width > 0) {
|
|
76235
|
+
bounds = getCombinedBounds(targetLayers, dataset);
|
|
76236
|
+
// A layer of collinear points has no width; fall back to its height so that
|
|
76237
|
+
// width= still gives a usable scale.
|
|
76238
|
+
extent = bounds.width() || bounds.height();
|
|
76239
|
+
if (!(extent > 0)) {
|
|
76240
|
+
stop$1('Unable to calculate a display scale: targeted symbols have no extent.');
|
|
76241
|
+
}
|
|
76242
|
+
return opts.width / extent;
|
|
76243
|
+
}
|
|
76244
|
+
frame = findRepelFrame(dataset, catalog);
|
|
76245
|
+
if (!frame) {
|
|
76246
|
+
stop$1('-repel requires a width= option when the target has no frame.');
|
|
76247
|
+
}
|
|
76248
|
+
warnIfFrameMismatched(frame, targetLayers, dataset);
|
|
76249
|
+
return getFramePixelsPerUnit(frame);
|
|
76250
|
+
}
|
|
76251
|
+
|
|
76252
|
+
function findRepelFrame(dataset, catalog) {
|
|
76253
|
+
var lyr = findFrameLayerInDataset(dataset);
|
|
76254
|
+
var target;
|
|
76255
|
+
if (lyr) return getFrameLayerData(lyr, dataset.arcs);
|
|
76256
|
+
// -frame adds the frame it creates to the catalog as a separate dataset, so a
|
|
76257
|
+
// catalog-wide search is needed to find it (-scalebar does the same).
|
|
76258
|
+
target = catalog ? findFrame(catalog) : null;
|
|
76259
|
+
return target ? getFrameLayerData(target.layer, target.dataset.arcs) : null;
|
|
76260
|
+
}
|
|
76261
|
+
|
|
76262
|
+
// Mirrors the transform applied by fitDatasetToFrame(), so that the layout
|
|
76263
|
+
// matches the pixels that get rendered. A naive width / bbox width differs
|
|
76264
|
+
// whenever the frame bbox and its pixel dimensions have different aspect ratios.
|
|
76265
|
+
function getFramePixelsPerUnit(frameData) {
|
|
76266
|
+
var bounds = new Bounds(frameData.bbox);
|
|
76267
|
+
var bounds2 = frameData.bbox2 ? new Bounds(frameData.bbox2) :
|
|
76268
|
+
new Bounds(0, 0, frameData.width, frameData.height);
|
|
76269
|
+
bounds.fillOut(bounds2.width() / bounds2.height());
|
|
76270
|
+
return bounds.getTransform(bounds2).mx;
|
|
76271
|
+
}
|
|
76272
|
+
|
|
76273
|
+
function warnIfFrameMismatched(frame, targetLayers, dataset) {
|
|
76274
|
+
var bounds = getCombinedBounds(targetLayers, dataset);
|
|
76275
|
+
if (!bounds.hasBounds()) return;
|
|
76276
|
+
if (!new Bounds(frame.bbox).intersects(bounds)) {
|
|
76277
|
+
message('[repel] Warning: the frame does not overlap the targeted symbols. ' +
|
|
76278
|
+
'If the data was projected after the frame was made, the display scale is wrong.');
|
|
76279
|
+
}
|
|
76280
|
+
}
|
|
76281
|
+
|
|
76282
|
+
function getCombinedBounds(targetLayers, dataset) {
|
|
76283
|
+
return targetLayers.reduce(function(memo, lyr) {
|
|
76284
|
+
var bounds = getLayerBounds(lyr, dataset.arcs);
|
|
76285
|
+
return bounds ? memo.mergeBounds(bounds) : memo;
|
|
76286
|
+
}, new Bounds());
|
|
76287
|
+
}
|
|
76288
|
+
|
|
75303
76289
|
// Support for evaluating expressions embedded in curly-brace templates
|
|
75304
76290
|
|
|
75305
76291
|
// Returns: a string (e.g. a command string used by the -run command)
|
|
@@ -77287,72 +78273,6 @@ ${svg}
|
|
|
77287
78273
|
});
|
|
77288
78274
|
}
|
|
77289
78275
|
|
|
77290
|
-
var roundCoord$1 = getRoundingFunction(0.01);
|
|
77291
|
-
|
|
77292
|
-
function getSymbolFillColor(d) {
|
|
77293
|
-
return d.fill || 'magenta';
|
|
77294
|
-
}
|
|
77295
|
-
|
|
77296
|
-
function getSymbolStrokeColor(d) {
|
|
77297
|
-
return d.stroke || d.fill || 'magenta';
|
|
77298
|
-
}
|
|
77299
|
-
|
|
77300
|
-
function applySymbolStyles(sym, d) {
|
|
77301
|
-
if (sym.type == 'polyline') {
|
|
77302
|
-
sym.stroke = getSymbolStrokeColor(d);
|
|
77303
|
-
} else {
|
|
77304
|
-
sym.fill = getSymbolFillColor(d);
|
|
77305
|
-
}
|
|
77306
|
-
if (d.opacity) {
|
|
77307
|
-
sym.opacity = d.opacity;
|
|
77308
|
-
}
|
|
77309
|
-
return sym;
|
|
77310
|
-
}
|
|
77311
|
-
|
|
77312
|
-
function getSymbolRadius(d) {
|
|
77313
|
-
if (d.radius === 0 || d.length === 0 || d.r === 0) return 0;
|
|
77314
|
-
return d.radius || d.length || d.r || 5; // use a default value
|
|
77315
|
-
}
|
|
77316
|
-
|
|
77317
|
-
function forEachSymbolCoord(coords, cb) {
|
|
77318
|
-
var isPoint = coords && utils.isNumber(coords[0]);
|
|
77319
|
-
var isNested = !isPoint && coords && Array.isArray(coords[0]);
|
|
77320
|
-
if (isPoint) return cb(coords);
|
|
77321
|
-
for (var i=0; i<coords.length; i++) {
|
|
77322
|
-
if (isNested) forEachSymbolCoord(coords[i], cb);
|
|
77323
|
-
}
|
|
77324
|
-
}
|
|
77325
|
-
|
|
77326
|
-
function flipY(coords) {
|
|
77327
|
-
forEachSymbolCoord(coords, function(p) {
|
|
77328
|
-
p[1] = -p[1];
|
|
77329
|
-
});
|
|
77330
|
-
}
|
|
77331
|
-
|
|
77332
|
-
function scaleAndShiftCoords(coords, scale, shift) {
|
|
77333
|
-
forEachSymbolCoord(coords, function(xy) {
|
|
77334
|
-
xy[0] = xy[0] * scale + shift[0];
|
|
77335
|
-
xy[1] = xy[1] * scale + shift[1];
|
|
77336
|
-
});
|
|
77337
|
-
}
|
|
77338
|
-
|
|
77339
|
-
function roundCoordsForSVG(coords) {
|
|
77340
|
-
forEachSymbolCoord(coords, function(p) {
|
|
77341
|
-
p[0] = roundCoord$1(p[0]);
|
|
77342
|
-
p[1] = roundCoord$1(p[1]);
|
|
77343
|
-
});
|
|
77344
|
-
}
|
|
77345
|
-
|
|
77346
|
-
function rotateCoords(coords, rotation) {
|
|
77347
|
-
if (!rotation) return;
|
|
77348
|
-
var f = getAffineTransform(rotation, 1, [0, 0], [0, 0]);
|
|
77349
|
-
forEachSymbolCoord(coords, function(p) {
|
|
77350
|
-
var p2 = f(p[0], p[1]);
|
|
77351
|
-
p[0] = p2[0];
|
|
77352
|
-
p[1] = p2[1];
|
|
77353
|
-
});
|
|
77354
|
-
}
|
|
77355
|
-
|
|
77356
78276
|
function getStickArrowCoords(d) {
|
|
77357
78277
|
return getArrowCoords(d, 'stick');
|
|
77358
78278
|
}
|
|
@@ -77690,11 +78610,176 @@ ${svg}
|
|
|
77690
78610
|
}
|
|
77691
78611
|
|
|
77692
78612
|
function parseRings(arg) {
|
|
77693
|
-
var arr =
|
|
78613
|
+
var arr = toNumberList(arg);
|
|
77694
78614
|
utils.genericSort(arr, true);
|
|
77695
78615
|
return utils.uniq(arr);
|
|
77696
78616
|
}
|
|
77697
78617
|
|
|
78618
|
+
// Matches the vertex density of the 72-sided circle made by getPolygonCoords()
|
|
78619
|
+
var DEGREES_PER_SEGMENT = 5;
|
|
78620
|
+
|
|
78621
|
+
// Returns an svg-symbol object containing one polygon part per wedge, or null
|
|
78622
|
+
// if the symbol has nothing to draw.
|
|
78623
|
+
function makePieSymbol(d, opts) {
|
|
78624
|
+
var wedges = getPieWedges(d, +opts.scale || 1);
|
|
78625
|
+
if (wedges.length === 0) return null;
|
|
78626
|
+
var parts = wedges.map(function(wedge) {
|
|
78627
|
+
flipY(wedge.coordinates); // the SVG y-axis points down
|
|
78628
|
+
roundCoordsForSVG(wedge.coordinates);
|
|
78629
|
+
var part = {
|
|
78630
|
+
type: 'polygon',
|
|
78631
|
+
coordinates: wedge.coordinates,
|
|
78632
|
+
fill: wedge.fill
|
|
78633
|
+
};
|
|
78634
|
+
if (d.stroke) part.stroke = d.stroke;
|
|
78635
|
+
if (d['stroke-width']) part['stroke-width'] = d['stroke-width'];
|
|
78636
|
+
// opacity is applied per-part, not to the group: renderComplexSymbol()
|
|
78637
|
+
// assumes a group symbol has a properties object, which group() only
|
|
78638
|
+
// provides for single-part groups.
|
|
78639
|
+
if (d.opacity) part.opacity = d.opacity;
|
|
78640
|
+
return part;
|
|
78641
|
+
});
|
|
78642
|
+
return {type: 'group', parts: parts};
|
|
78643
|
+
}
|
|
78644
|
+
|
|
78645
|
+
// Returns an array of {fill, coordinates} objects, in the y-up coordinate space
|
|
78646
|
+
// shared by the symbol generators, centered on [0, 0]. Coordinates are an array
|
|
78647
|
+
// of rings, like flattened GeoJSON MultiPolygon coordinates.
|
|
78648
|
+
//
|
|
78649
|
+
// The first wedge starts at the top of the symbol and the rest follow in
|
|
78650
|
+
// clockwise order. A wedge with no value, or without a usable fill color, is
|
|
78651
|
+
// left out -- an unfilled wedge becomes a gap in the pie without shifting the
|
|
78652
|
+
// wedges that follow it.
|
|
78653
|
+
function getPieWedges(d, scale) {
|
|
78654
|
+
var values = getPieValues(d);
|
|
78655
|
+
var fills = getPieFills(d);
|
|
78656
|
+
var radii = getPieRadii(d, scale);
|
|
78657
|
+
var rotation = +d.rotation || 0;
|
|
78658
|
+
var total = sum(values);
|
|
78659
|
+
var wedges = [];
|
|
78660
|
+
var start = rotation;
|
|
78661
|
+
var fill, end;
|
|
78662
|
+
if (total > 0 === false || radii.outer > 0 === false) return wedges;
|
|
78663
|
+
for (var i=0; i<values.length; i++) {
|
|
78664
|
+
end = start + values[i] / total * 360;
|
|
78665
|
+
fill = getWedgeFill(fills[i]);
|
|
78666
|
+
if (fill && end > start) {
|
|
78667
|
+
wedges.push({
|
|
78668
|
+
fill: fill,
|
|
78669
|
+
coordinates: getWedgeCoords(start, end, radii.inner, radii.outer)
|
|
78670
|
+
});
|
|
78671
|
+
}
|
|
78672
|
+
start = end;
|
|
78673
|
+
}
|
|
78674
|
+
return wedges;
|
|
78675
|
+
}
|
|
78676
|
+
|
|
78677
|
+
// Returns an array of rings outlining a wedge spanning two bearings.
|
|
78678
|
+
function getWedgeCoords(startAngle, endAngle, inner, outer) {
|
|
78679
|
+
var ring;
|
|
78680
|
+
if (endAngle - startAngle >= 360) {
|
|
78681
|
+
// A single non-zero value covers the whole symbol: a circle, or a pair of
|
|
78682
|
+
// concentric rings if the symbol is a donut.
|
|
78683
|
+
ring = closeRing(getArcCoords(startAngle, startAngle + 360, outer));
|
|
78684
|
+
if (inner > 0) {
|
|
78685
|
+
// wind the hole in the opposite direction from the outer ring
|
|
78686
|
+
return [ring, closeRing(getArcCoords(startAngle + 360, startAngle, inner))];
|
|
78687
|
+
}
|
|
78688
|
+
return [ring];
|
|
78689
|
+
}
|
|
78690
|
+
ring = getArcCoords(startAngle, endAngle, outer);
|
|
78691
|
+
if (inner > 0) {
|
|
78692
|
+
// A wedge of a donut is a simple polygon, so the hole needs no ring of
|
|
78693
|
+
// its own -- the inner arc doubles back inside the outer one.
|
|
78694
|
+
ring = ring.concat(getArcCoords(endAngle, startAngle, inner));
|
|
78695
|
+
} else {
|
|
78696
|
+
ring.push([0, 0]);
|
|
78697
|
+
}
|
|
78698
|
+
ring.push(ring[0].concat());
|
|
78699
|
+
return [ring];
|
|
78700
|
+
}
|
|
78701
|
+
|
|
78702
|
+
// A 360 degree arc starts and ends at the same angle, but rounding leaves its
|
|
78703
|
+
// endpoints a hair apart, so the ring is closed explicitly.
|
|
78704
|
+
function closeRing(ring) {
|
|
78705
|
+
ring[ring.length - 1] = ring[0].concat();
|
|
78706
|
+
return ring;
|
|
78707
|
+
}
|
|
78708
|
+
|
|
78709
|
+
// Returns points along an arc, including both endpoints. Angles are bearings,
|
|
78710
|
+
// which run clockwise from the top of the symbol -- the same convention as
|
|
78711
|
+
// rotateCoords(), so the rotation= option is just an offset to the first angle.
|
|
78712
|
+
function getArcCoords(startAngle, endAngle, radius) {
|
|
78713
|
+
var sweep = endAngle - startAngle;
|
|
78714
|
+
var segments = Math.max(1, Math.ceil(Math.abs(sweep) / DEGREES_PER_SEGMENT));
|
|
78715
|
+
var coords = [];
|
|
78716
|
+
for (var i=0; i<=segments; i++) {
|
|
78717
|
+
coords.push(getPlanarSegmentEndpoint(0, 0, startAngle + sweep * i / segments, radius));
|
|
78718
|
+
}
|
|
78719
|
+
return coords;
|
|
78720
|
+
}
|
|
78721
|
+
|
|
78722
|
+
// Missing, non-numeric and negative values all count as zero.
|
|
78723
|
+
function getPieValues(d) {
|
|
78724
|
+
if (!d.values) {
|
|
78725
|
+
stop$1('A pie symbol requires a values= option.');
|
|
78726
|
+
}
|
|
78727
|
+
return toNumberList(d.values).map(function(num) {
|
|
78728
|
+
return num > 0 ? num : 0;
|
|
78729
|
+
});
|
|
78730
|
+
}
|
|
78731
|
+
|
|
78732
|
+
function getPieFills(d) {
|
|
78733
|
+
if (!d.fills) {
|
|
78734
|
+
stop$1('A pie symbol requires a fills= option.');
|
|
78735
|
+
}
|
|
78736
|
+
return toItemList(d.fills);
|
|
78737
|
+
}
|
|
78738
|
+
|
|
78739
|
+
// Returns the color to fill a wedge with, or null if the wedge should be left
|
|
78740
|
+
// out of the symbol.
|
|
78741
|
+
function getWedgeFill(val) {
|
|
78742
|
+
var str = val === null || val === undefined ? '' : String(val).trim();
|
|
78743
|
+
var name = str.toLowerCase();
|
|
78744
|
+
var rgb;
|
|
78745
|
+
if (!str || name == 'none' || name == 'transparent') {
|
|
78746
|
+
return null;
|
|
78747
|
+
}
|
|
78748
|
+
rgb = parseColor(str);
|
|
78749
|
+
if (!rgb || rgb.a === 0) return null;
|
|
78750
|
+
return str;
|
|
78751
|
+
}
|
|
78752
|
+
|
|
78753
|
+
// Returns {inner, outer} radii in pixels. The hole= option makes a donut. A
|
|
78754
|
+
// negative hole is measured inward from the outer radius, which gives a band
|
|
78755
|
+
// of wedges of a fixed width without having to repeat a data-driven radius=
|
|
78756
|
+
// expression. A hole as wide as the symbol leaves a plain pie, and a
|
|
78757
|
+
// non-positive outer radius is left for the caller to skip, like radius=0 on
|
|
78758
|
+
// the other symbol types.
|
|
78759
|
+
function getPieRadii(d, scale) {
|
|
78760
|
+
var outer = getSymbolRadius(d);
|
|
78761
|
+
var hole = +d.hole;
|
|
78762
|
+
var inner = 0;
|
|
78763
|
+
if (hole > 0) {
|
|
78764
|
+
inner = hole;
|
|
78765
|
+
} else if (hole < 0) {
|
|
78766
|
+
inner = outer + hole;
|
|
78767
|
+
}
|
|
78768
|
+
if (!(inner > 0) || inner >= outer) inner = 0;
|
|
78769
|
+
return {
|
|
78770
|
+
inner: inner * scale,
|
|
78771
|
+
outer: outer * scale
|
|
78772
|
+
};
|
|
78773
|
+
}
|
|
78774
|
+
|
|
78775
|
+
function sum(arr) {
|
|
78776
|
+
var total = 0;
|
|
78777
|
+
for (var i=0; i<arr.length; i++) {
|
|
78778
|
+
total += arr[i];
|
|
78779
|
+
}
|
|
78780
|
+
return total;
|
|
78781
|
+
}
|
|
78782
|
+
|
|
77698
78783
|
// Returns an svg-symbol data object for one symbol
|
|
77699
78784
|
function makePathSymbol(coords, properties, geojsonType) {
|
|
77700
78785
|
var sym;
|
|
@@ -77719,11 +78804,16 @@ ${svg}
|
|
|
77719
78804
|
|
|
77720
78805
|
// TODO: refactor to remove duplication in mapshaper-svg-style.js
|
|
77721
78806
|
cmd.symbols = function(inputLyr, dataset, opts) {
|
|
77722
|
-
|
|
78807
|
+
requirePointLayer(inputLyr);
|
|
77723
78808
|
var lyr = opts.no_replace ? copyLayer(inputLyr) : inputLyr;
|
|
77724
78809
|
var shapeMode = !!opts.geographic;
|
|
77725
78810
|
var metersPerPx;
|
|
77726
78811
|
if (shapeMode) {
|
|
78812
|
+
// Only the first point of each feature is used to place a generated shape,
|
|
78813
|
+
// so the other points of a multi-point feature would be dropped. In SVG
|
|
78814
|
+
// mode geometry is untouched and every point gets a symbol, so multi-point
|
|
78815
|
+
// features are fine.
|
|
78816
|
+
requireSinglePointLayer(inputLyr);
|
|
77727
78817
|
requireProjectedDataset(dataset);
|
|
77728
78818
|
metersPerPx = opts.pixel_scale || getMetersPerPixel(lyr);
|
|
77729
78819
|
}
|
|
@@ -77746,6 +78836,17 @@ ${svg}
|
|
|
77746
78836
|
var d = getSymbolData(i);
|
|
77747
78837
|
var rec = records[i] || {};
|
|
77748
78838
|
|
|
78839
|
+
// A pie has a different fill for each of its wedges, which the single
|
|
78840
|
+
// fill field of a generated shape can't express.
|
|
78841
|
+
if (d.type == 'pie') {
|
|
78842
|
+
if (shapeMode) {
|
|
78843
|
+
stop$1('The pie symbol type does not support the geographic option.');
|
|
78844
|
+
}
|
|
78845
|
+
var pie = makePieSymbol(d, opts);
|
|
78846
|
+
if (pie) rec['svg-symbol'] = pie;
|
|
78847
|
+
return;
|
|
78848
|
+
}
|
|
78849
|
+
|
|
77749
78850
|
// non-polygon symbols
|
|
77750
78851
|
if (!shapeMode && d.type == 'circle') {
|
|
77751
78852
|
rec['svg-symbol'] = makeCircleSymbol(d, opts);
|
|
@@ -78313,7 +79414,7 @@ ${svg}
|
|
|
78313
79414
|
'filter-islands2', 'filter-points', 'filter-slivers', 'grid', 'grid2',
|
|
78314
79415
|
'fuzzy-join', 'ignore', 'inlay', 'innerlines', 'inspect', 'join',
|
|
78315
79416
|
'lines', 'mosaic', 'points', 'polygons', 'rectangles', 'rename-fields',
|
|
78316
|
-
'shapes', 'simplify', 'slice', 'smooth', 'sort', 'split',
|
|
79417
|
+
'repel', 'shapes', 'simplify', 'slice', 'smooth', 'sort', 'split',
|
|
78317
79418
|
'split-on-grid', 'stitch', 'style', 'subdivide', 'symbols', 'uniq'
|
|
78318
79419
|
].indexOf(name) > -1;
|
|
78319
79420
|
}
|
|
@@ -78673,6 +79774,10 @@ ${svg}
|
|
|
78673
79774
|
} else if (name == 'rename-layers') {
|
|
78674
79775
|
cmd.renameLayers(targetLayers, opts.names);
|
|
78675
79776
|
|
|
79777
|
+
} else if (name == 'repel') {
|
|
79778
|
+
cmd.repel(targetLayers, targetDataset, job.catalog,
|
|
79779
|
+
await findRepelPolygons(opts, job.catalog, targets), opts);
|
|
79780
|
+
|
|
78676
79781
|
} else if (name == 'require') {
|
|
78677
79782
|
await cmd.require(opts);
|
|
78678
79783
|
|
|
@@ -78865,6 +79970,15 @@ ${svg}
|
|
|
78865
79970
|
return job;
|
|
78866
79971
|
}
|
|
78867
79972
|
|
|
79973
|
+
// -repel's polygons= option names a second layer, like source= elsewhere, but
|
|
79974
|
+
// is resolved here rather than by the shared source= machinery: the layer is
|
|
79975
|
+
// only read (never merged into the target's topology), so it needs neither a
|
|
79976
|
+
// topology merge nor the per-target copies that clip and erase require.
|
|
79977
|
+
async function findRepelPolygons(opts, catalog, targets) {
|
|
79978
|
+
if (!opts.polygons) return null;
|
|
79979
|
+
return await findCommandSourceAsync(convertSourceName(opts.polygons, targets), catalog, opts);
|
|
79980
|
+
}
|
|
79981
|
+
|
|
78868
79982
|
async function resolveSourceForTargets(command, job, targets) {
|
|
78869
79983
|
job.startCommand(command);
|
|
78870
79984
|
try {
|
|
@@ -78970,7 +80084,7 @@ ${svg}
|
|
|
78970
80084
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
78971
80085
|
}
|
|
78972
80086
|
|
|
78973
|
-
var version = "0.7.
|
|
80087
|
+
var version = "0.7.58";
|
|
78974
80088
|
|
|
78975
80089
|
// Parse command line args into commands and run them
|
|
78976
80090
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -81175,6 +82289,7 @@ ${svg}
|
|
|
81175
82289
|
Simplify,
|
|
81176
82290
|
SimplifyFast,
|
|
81177
82291
|
SimplifyPct,
|
|
82292
|
+
SymbolCollisions,
|
|
81178
82293
|
Slivers,
|
|
81179
82294
|
Snapping,
|
|
81180
82295
|
SourceUtils,
|