mapshaper 0.7.56 → 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 +811 -292
- package/package.json +1 -1
- package/www/mapshaper.js +811 -292
- 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
|
|
@@ -9880,7 +9880,7 @@
|
|
|
9880
9880
|
id: piece.id,
|
|
9881
9881
|
source_region: piece.source_region,
|
|
9882
9882
|
copy: piece.copy,
|
|
9883
|
-
projected_boundary: closeRing$
|
|
9883
|
+
projected_boundary: closeRing$2(piece.boundary)
|
|
9884
9884
|
};
|
|
9885
9885
|
}
|
|
9886
9886
|
|
|
@@ -9888,7 +9888,7 @@
|
|
|
9888
9888
|
return {
|
|
9889
9889
|
id: piece.id,
|
|
9890
9890
|
source_region: piece.source_region,
|
|
9891
|
-
projected_boundary: closeRing$
|
|
9891
|
+
projected_boundary: closeRing$2(piece.boundary)
|
|
9892
9892
|
};
|
|
9893
9893
|
}
|
|
9894
9894
|
|
|
@@ -9970,7 +9970,7 @@
|
|
|
9970
9970
|
return paths;
|
|
9971
9971
|
}
|
|
9972
9972
|
|
|
9973
|
-
function closeRing$
|
|
9973
|
+
function closeRing$2(ring) {
|
|
9974
9974
|
var copy = ring.map(function(p) { return p.concat(); });
|
|
9975
9975
|
if (copy.length && !pointsEqual$2(copy[0], copy[copy.length - 1])) {
|
|
9976
9976
|
copy.push(copy[0].concat());
|
|
@@ -14955,6 +14955,27 @@
|
|
|
14955
14955
|
return fflatePostprocess(obj);
|
|
14956
14956
|
}
|
|
14957
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
|
+
|
|
14958
14979
|
function unzipAsync(buf, cb) {
|
|
14959
14980
|
if (!runningInBrowser()) {
|
|
14960
14981
|
error('Async unzipping only supported in the browser');
|
|
@@ -15049,6 +15070,7 @@
|
|
|
15049
15070
|
var Zip = /*#__PURE__*/Object.freeze({
|
|
15050
15071
|
__proto__: null,
|
|
15051
15072
|
isImportableZipPath: isImportableZipPath,
|
|
15073
|
+
listZipEntryNames: listZipEntryNames,
|
|
15052
15074
|
unzipAsync: unzipAsync,
|
|
15053
15075
|
unzipSync: unzipSync,
|
|
15054
15076
|
zipAsync: zipAsync,
|
|
@@ -18629,14 +18651,15 @@
|
|
|
18629
18651
|
function parseHexColor(str) {
|
|
18630
18652
|
var hex = hexRxp.exec(str)[1];
|
|
18631
18653
|
if (hex.length == 3 || hex.length == 4) {
|
|
18632
|
-
|
|
18654
|
+
// expand shorthand, e.g. #f90 -> #ffcc00
|
|
18655
|
+
hex = hex.split('').map(function(c) { return c + c; }).join('');
|
|
18633
18656
|
}
|
|
18634
18657
|
if (hex.length != 6 && hex.length != 8) return null;
|
|
18635
18658
|
return {
|
|
18636
18659
|
r: parseInt(hex.substr(0, 2), 16),
|
|
18637
18660
|
g: parseInt(hex.substr(2, 2), 16),
|
|
18638
18661
|
b: parseInt(hex.substr(4, 2), 16),
|
|
18639
|
-
a: hex.length == 8 ? parseInt(hex.substr(
|
|
18662
|
+
a: hex.length == 8 ? parseInt(hex.substr(6, 2), 16) / 255 : 1
|
|
18640
18663
|
};
|
|
18641
18664
|
}
|
|
18642
18665
|
|
|
@@ -25744,6 +25767,173 @@
|
|
|
25744
25767
|
return text || text === 0; // accept numerical 0 as label text
|
|
25745
25768
|
}
|
|
25746
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
|
+
|
|
25747
25937
|
/* example patterns
|
|
25748
25938
|
hatches 1px black 1px red 1px white
|
|
25749
25939
|
1px black 1px red 1px white // same as above (hatches is default)
|
|
@@ -26035,7 +26225,10 @@
|
|
|
26035
26225
|
length: 'number', // e.g. arrow length
|
|
26036
26226
|
rotation: 'number',
|
|
26037
26227
|
radius: 'number',
|
|
26038
|
-
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
|
|
26039
26232
|
flipped: 'boolean',
|
|
26040
26233
|
rotated: 'boolean',
|
|
26041
26234
|
direction: 'number',
|
|
@@ -26054,6 +26247,18 @@
|
|
|
26054
26247
|
effect: null // e.g. "fade"
|
|
26055
26248
|
}, stylePropertyTypes);
|
|
26056
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
|
+
|
|
26057
26262
|
var commonProperties = 'css,class,opacity,stroke,stroke-width,stroke-dasharray,stroke-opacity,fill-opacity,vector-effect'.split(',');
|
|
26058
26263
|
|
|
26059
26264
|
var propertiesBySymbolType = {
|
|
@@ -26129,7 +26334,9 @@
|
|
|
26129
26334
|
return;
|
|
26130
26335
|
}
|
|
26131
26336
|
var val = opts[optName];
|
|
26132
|
-
functions[svgName] =
|
|
26337
|
+
functions[svgName] = svgName in symbolListProperties ?
|
|
26338
|
+
getSymbolListAccessor(val, svgName, lyr) :
|
|
26339
|
+
getSymbolPropertyAccessor(val, svgName, lyr);
|
|
26133
26340
|
properties.push(svgName);
|
|
26134
26341
|
});
|
|
26135
26342
|
|
|
@@ -26159,6 +26366,21 @@
|
|
|
26159
26366
|
return getPropertyAccessor(val, symbolPropertyTypes[svgName], lyr, svgName);
|
|
26160
26367
|
}
|
|
26161
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
|
+
}
|
|
26162
26384
|
// Returns a function that maps a feature id to a property value. The value may
|
|
26163
26385
|
// be given as a literal, as the name of a data field, or as a JS expression
|
|
26164
26386
|
// evaluated against each feature.
|
|
@@ -26295,6 +26517,7 @@
|
|
|
26295
26517
|
getLabelPositionStyle: getLabelPositionStyle,
|
|
26296
26518
|
getPropertyAccessor: getPropertyAccessor,
|
|
26297
26519
|
getSymbolDataAccessor: getSymbolDataAccessor,
|
|
26520
|
+
getSymbolListAccessor: getSymbolListAccessor,
|
|
26298
26521
|
getSymbolPropertyAccessor: getSymbolPropertyAccessor,
|
|
26299
26522
|
isSupportedSvgStyleProperty: isSupportedSvgStyleProperty,
|
|
26300
26523
|
isSvgClassName: isSvgClassName,
|
|
@@ -37978,139 +38201,6 @@ ${svg}
|
|
|
37978
38201
|
}
|
|
37979
38202
|
}
|
|
37980
38203
|
|
|
37981
|
-
var assignmentRxp = /^([a-z0-9_+-]+)=(?!=)(.*)$/i; // exclude ==
|
|
37982
|
-
|
|
37983
|
-
function splitShellTokens(str) {
|
|
37984
|
-
var BAREWORD = `([^'"\\s])+`;
|
|
37985
|
-
var DOUBLE_QUOTE = `"((\\\\"|[^"])*?)"`;
|
|
37986
|
-
var SINGLE_QUOTE = `'((\\\\'|[^'])*?)'`;
|
|
37987
|
-
var rxp = new RegExp('(' + BAREWORD + '|' + SINGLE_QUOTE + '|' + DOUBLE_QUOTE + ')*', 'g');
|
|
37988
|
-
var matches = str.match(rxp) || [];
|
|
37989
|
-
var chunks = matches.filter(function(chunk) {
|
|
37990
|
-
// single backslashes may be present in multiline commands pasted from a makefile, e.g.
|
|
37991
|
-
return !!chunk && chunk != '\\';
|
|
37992
|
-
}).map(utils.trimQuotes);
|
|
37993
|
-
return chunks;
|
|
37994
|
-
}
|
|
37995
|
-
|
|
37996
|
-
function parsePercent(o) {
|
|
37997
|
-
var str = String(o);
|
|
37998
|
-
var isPct = str.indexOf('%') > 0;
|
|
37999
|
-
var pct;
|
|
38000
|
-
if (isPct) {
|
|
38001
|
-
pct = Number(str.replace('%', '')) / 100;
|
|
38002
|
-
} else {
|
|
38003
|
-
pct = Number(str);
|
|
38004
|
-
}
|
|
38005
|
-
if (!(pct >= 0 && pct <= 1)) {
|
|
38006
|
-
stop$1(utils.format("Invalid percentage: %s", str));
|
|
38007
|
-
}
|
|
38008
|
-
return pct;
|
|
38009
|
-
}
|
|
38010
|
-
|
|
38011
|
-
function parseNumberList(token) {
|
|
38012
|
-
return token.split(',').map(parseFloat);
|
|
38013
|
-
}
|
|
38014
|
-
|
|
38015
|
-
// Split comma-delimited list, trim quotes from entire list and
|
|
38016
|
-
// individual members
|
|
38017
|
-
function parseStringList(token) {
|
|
38018
|
-
var delim = ',';
|
|
38019
|
-
var list = splitOptionList(token, delim);
|
|
38020
|
-
if (list.length == 1) {
|
|
38021
|
-
list = splitOptionList(list[0], delim);
|
|
38022
|
-
}
|
|
38023
|
-
return list;
|
|
38024
|
-
}
|
|
38025
|
-
|
|
38026
|
-
// Accept spaces and/or commas as delimiters
|
|
38027
|
-
function parseColorList(token) {
|
|
38028
|
-
var delim = ', ';
|
|
38029
|
-
// accept rgb(0 0 0) rgb(0,0,0) rgb(0, 0, 0)
|
|
38030
|
-
var token2 = token.replace(/[ ,] *(?=[^(]*\))/g, '~~~'); // kludge: protect rgba() functions from being split apart
|
|
38031
|
-
var list = splitOptionList(token2, delim);
|
|
38032
|
-
if (list.length == 1) {
|
|
38033
|
-
list = splitOptionList(list[0], delim);
|
|
38034
|
-
}
|
|
38035
|
-
list = list.map(function(str) {
|
|
38036
|
-
return str.replace(/~~~/g, ',');
|
|
38037
|
-
});
|
|
38038
|
-
return list;
|
|
38039
|
-
}
|
|
38040
|
-
|
|
38041
|
-
function cleanArgv(argv) {
|
|
38042
|
-
// Note: original trim caused some quoted spaces to be removed
|
|
38043
|
-
// (e.g. bash shell seems to convert [delimiter=" "] to [delimiter= ],
|
|
38044
|
-
// which then got trimmed to [delimiter=] below)
|
|
38045
|
-
//// argv = argv.map(function(s) {return s.trim();}); // trim whitespace
|
|
38046
|
-
|
|
38047
|
-
// Updated: don't trim space from tokens like [delimeter= ]
|
|
38048
|
-
argv = argv.map(function(s) {
|
|
38049
|
-
if (!/= $/.test(s)) {
|
|
38050
|
-
s = utils.rtrim(s);
|
|
38051
|
-
}
|
|
38052
|
-
s = utils.ltrim(s);
|
|
38053
|
-
return s;
|
|
38054
|
-
});
|
|
38055
|
-
argv = argv.filter(function(s) {return s !== '';}); // remove empty tokens
|
|
38056
|
-
// Note: removing trimQuotes() call... now, strings like 'name="Meg"' will no longer
|
|
38057
|
-
// be parsed the same way as name=Meg and name="Meg"
|
|
38058
|
-
//// argv = argv.map(utils.trimQuotes); // remove one level of single or dbl quotes
|
|
38059
|
-
return argv;
|
|
38060
|
-
}
|
|
38061
|
-
|
|
38062
|
-
function splitOptionList(str, delimChars) {
|
|
38063
|
-
var BAREWORD = '([^' + delimChars + '\'"][^' + delimChars + ']*)'; // TODO: make safer
|
|
38064
|
-
var DOUBLE_QUOTE = '"((\\\\"|[^"])*?)"';
|
|
38065
|
-
var SINGLE_QUOTE = '\'((\\\\\'|[^\'])*?)\'';
|
|
38066
|
-
var rxp = new RegExp('^(' + BAREWORD + '|' + SINGLE_QUOTE + '|' + DOUBLE_QUOTE + ')([' + delimChars + ']+|$)');
|
|
38067
|
-
var chunks = [];
|
|
38068
|
-
var match;
|
|
38069
|
-
while ((match = rxp.exec(str)) !== null) {
|
|
38070
|
-
chunks.push(match[1]);
|
|
38071
|
-
str = str.substr(match[0].length);
|
|
38072
|
-
}
|
|
38073
|
-
return chunks.filter(function(chunk) {
|
|
38074
|
-
return !!chunk && chunk != '\\';
|
|
38075
|
-
}).map(utils.trimQuotes);
|
|
38076
|
-
}
|
|
38077
|
-
|
|
38078
|
-
// Prepare a value to be used as an option value.
|
|
38079
|
-
// Places quotes around strings containing spaces.
|
|
38080
|
-
// e.g. converts Layer 1 -> "Layer 1"
|
|
38081
|
-
// for use in contexts like: name="Layer 1"
|
|
38082
|
-
function formatOptionValue(val) {
|
|
38083
|
-
val = String(val);
|
|
38084
|
-
if (val.indexOf(' ') > -1) {
|
|
38085
|
-
val = JSON.stringify(val); // quote ids with spaces
|
|
38086
|
-
}
|
|
38087
|
-
return val;
|
|
38088
|
-
}
|
|
38089
|
-
|
|
38090
|
-
function isAssignment(token) {
|
|
38091
|
-
return assignmentRxp.test(token);
|
|
38092
|
-
}
|
|
38093
|
-
|
|
38094
|
-
function splitAssignment(token) {
|
|
38095
|
-
var match = assignmentRxp.exec(token),
|
|
38096
|
-
name = match[1],
|
|
38097
|
-
val = utils.trimQuotes(match[2]);
|
|
38098
|
-
return [name, val];
|
|
38099
|
-
}
|
|
38100
|
-
|
|
38101
|
-
var OptionParsingUtils = /*#__PURE__*/Object.freeze({
|
|
38102
|
-
__proto__: null,
|
|
38103
|
-
cleanArgv: cleanArgv,
|
|
38104
|
-
formatOptionValue: formatOptionValue,
|
|
38105
|
-
isAssignment: isAssignment,
|
|
38106
|
-
parseColorList: parseColorList,
|
|
38107
|
-
parseNumberList: parseNumberList,
|
|
38108
|
-
parsePercent: parsePercent,
|
|
38109
|
-
parseStringList: parseStringList,
|
|
38110
|
-
splitAssignment: splitAssignment,
|
|
38111
|
-
splitShellTokens: splitShellTokens
|
|
38112
|
-
});
|
|
38113
|
-
|
|
38114
38204
|
function CommandParser() {
|
|
38115
38205
|
var commandRxp = /^--?([a-z][\w-]*)$/i,
|
|
38116
38206
|
invalidCommandRxp = /^--?[a-z][\w-]*[=]/i, // e.g. -target=A // could be more general
|
|
@@ -38290,6 +38380,7 @@ ${svg}
|
|
|
38290
38380
|
// Try to read an option for command @cmdDef from @argv
|
|
38291
38381
|
function readOption(cmd, argv, cmdDef) {
|
|
38292
38382
|
var token = argv.shift(),
|
|
38383
|
+
assignedValue = null,
|
|
38293
38384
|
optName, optDef, parts;
|
|
38294
38385
|
|
|
38295
38386
|
if (isAssignment(token)) {
|
|
@@ -38299,7 +38390,10 @@ ${svg}
|
|
|
38299
38390
|
if (!optDef) ; else if (optDef.type == 'flag' || optDef.assign_to) {
|
|
38300
38391
|
stop$1("-" + cmdDef.name + " " + parts[0] + " option doesn't take a value");
|
|
38301
38392
|
} else {
|
|
38302
|
-
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];
|
|
38303
38397
|
}
|
|
38304
38398
|
} else {
|
|
38305
38399
|
// try to parse as a flag option,
|
|
@@ -38324,6 +38418,8 @@ ${svg}
|
|
|
38324
38418
|
cmd.options[optDef.assign_to] = optDef.name;
|
|
38325
38419
|
} else if (optDef.type == 'flag') {
|
|
38326
38420
|
cmd.options[optName] = true;
|
|
38421
|
+
} else if (assignedValue !== null) {
|
|
38422
|
+
cmd.options[optName] = parseOptionValue(assignedValue, optDef);
|
|
38327
38423
|
} else {
|
|
38328
38424
|
cmd.options[optName] = readOptionValue(argv, optDef);
|
|
38329
38425
|
}
|
|
@@ -41028,7 +41124,7 @@ ${svg}
|
|
|
41028
41124
|
.option('target', targetOpt);
|
|
41029
41125
|
|
|
41030
41126
|
parser.command('repel')
|
|
41031
|
-
.describe('move overlapping
|
|
41127
|
+
.describe('move overlapping point symbols apart')
|
|
41032
41128
|
.option('width', {
|
|
41033
41129
|
describe: 'display width of the layer in pixels (required if no frame)',
|
|
41034
41130
|
type: 'number'
|
|
@@ -41039,6 +41135,9 @@ ${svg}
|
|
|
41039
41135
|
.option('padding', {
|
|
41040
41136
|
describe: 'pixels of clearance around each symbol; may vary (default is 0)'
|
|
41041
41137
|
})
|
|
41138
|
+
.option('margin', {
|
|
41139
|
+
describe: 'pixels of clearance between two symbols; may vary (default is 0)'
|
|
41140
|
+
})
|
|
41042
41141
|
.option('ticks', {
|
|
41043
41142
|
describe: 'number of solver passes (default is 100)',
|
|
41044
41143
|
type: 'number'
|
|
@@ -41058,7 +41157,7 @@ ${svg}
|
|
|
41058
41157
|
parser.command('symbols')
|
|
41059
41158
|
.describe('symbolize points as arrows, circles, stars, polygons, etc.')
|
|
41060
41159
|
.option('type', {
|
|
41061
|
-
describe: 'types: arrow, circle, square, star, polygon, ring'
|
|
41160
|
+
describe: 'types: arrow, circle, square, star, polygon, ring, pie'
|
|
41062
41161
|
})
|
|
41063
41162
|
.option('stroke', {})
|
|
41064
41163
|
.option('stroke-width', {})
|
|
@@ -41117,6 +41216,15 @@ ${svg}
|
|
|
41117
41216
|
.option('radii', {
|
|
41118
41217
|
describe: '(ring) comma-sep. list of concentric radii, ascending order'
|
|
41119
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
|
+
})
|
|
41120
41228
|
.option('arrow-style', {
|
|
41121
41229
|
describe: '(arrow) options: stick, standard (default is standard)'
|
|
41122
41230
|
})
|
|
@@ -44368,9 +44476,10 @@ ${svg}
|
|
|
44368
44476
|
({gpkg, tmpPath} = await openGeoPackage(input, geopackage));
|
|
44369
44477
|
var availableLayers;
|
|
44370
44478
|
var filterApplied;
|
|
44479
|
+
var unreadableLayers;
|
|
44371
44480
|
try {
|
|
44372
44481
|
try {
|
|
44373
|
-
({datasets, availableLayers, filterApplied} = readFeatureTableDatasets(gpkg, opts));
|
|
44482
|
+
({datasets, availableLayers, filterApplied, unreadableLayers} = readFeatureTableDatasets(gpkg, opts));
|
|
44374
44483
|
} catch (e) {
|
|
44375
44484
|
if (!runningInBrowser() && isLocalCsProjError(e)) {
|
|
44376
44485
|
gpkg.close();
|
|
@@ -44383,7 +44492,7 @@ ${svg}
|
|
|
44383
44492
|
throw e;
|
|
44384
44493
|
}
|
|
44385
44494
|
gpkg = await geopackage.GeoPackageAPI.open(tmpPath);
|
|
44386
|
-
({datasets, availableLayers, filterApplied} = readFeatureTableDatasets(gpkg, opts));
|
|
44495
|
+
({datasets, availableLayers, filterApplied, unreadableLayers} = readFeatureTableDatasets(gpkg, opts));
|
|
44387
44496
|
} else {
|
|
44388
44497
|
throw e;
|
|
44389
44498
|
}
|
|
@@ -44398,6 +44507,7 @@ ${svg}
|
|
|
44398
44507
|
layers: [{name: '', data: null}],
|
|
44399
44508
|
info: {
|
|
44400
44509
|
_gpkg_available_layers: availableLayers,
|
|
44510
|
+
_gpkg_unreadable_layers: unreadableLayers,
|
|
44401
44511
|
_gpkg_placeholder: !!filterApplied
|
|
44402
44512
|
}
|
|
44403
44513
|
};
|
|
@@ -44409,6 +44519,7 @@ ${svg}
|
|
|
44409
44519
|
mergedArr.forEach(function(ds) {
|
|
44410
44520
|
ds.info = ds.info || {};
|
|
44411
44521
|
ds.info._gpkg_available_layers = availableLayers;
|
|
44522
|
+
ds.info._gpkg_unreadable_layers = unreadableLayers;
|
|
44412
44523
|
});
|
|
44413
44524
|
return merged;
|
|
44414
44525
|
}
|
|
@@ -44534,10 +44645,38 @@ ${svg}
|
|
|
44534
44645
|
var availableLayers = gpkg.getFeatureTables() || [];
|
|
44535
44646
|
var tables = filterGeoPackageTables(availableLayers, selected);
|
|
44536
44647
|
var filterApplied = Array.isArray(selected) && selected.length > 0;
|
|
44537
|
-
var
|
|
44538
|
-
|
|
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
|
+
}
|
|
44539
44660
|
});
|
|
44540
|
-
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;
|
|
44541
44680
|
}
|
|
44542
44681
|
|
|
44543
44682
|
function getSelectedGeoPackageLayers(opts) {
|
|
@@ -46510,12 +46649,12 @@ ${svg}
|
|
|
46510
46649
|
var coords = path.coords;
|
|
46511
46650
|
if (coords.length < 2) return;
|
|
46512
46651
|
if (forcePolylinePaths) {
|
|
46513
|
-
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);
|
|
46514
46653
|
return;
|
|
46515
46654
|
}
|
|
46516
46655
|
if (path.closed || pointsEqual$1(coords[0], coords[coords.length - 1])) {
|
|
46517
46656
|
if (coords.length >= 3) {
|
|
46518
|
-
rings.push(closeRing(coords));
|
|
46657
|
+
rings.push(closeRing$1(coords));
|
|
46519
46658
|
}
|
|
46520
46659
|
} else {
|
|
46521
46660
|
lines.push(coords);
|
|
@@ -46819,7 +46958,7 @@ ${svg}
|
|
|
46819
46958
|
return /translate\(/i.test(String(str || ''));
|
|
46820
46959
|
}
|
|
46821
46960
|
|
|
46822
|
-
function closeRing(coords) {
|
|
46961
|
+
function closeRing$1(coords) {
|
|
46823
46962
|
var ring = coords.map(function(p) { return [p[0], p[1]]; });
|
|
46824
46963
|
if (!pointsEqual$1(ring[0], ring[ring.length - 1])) {
|
|
46825
46964
|
ring.push([ring[0][0], ring[0][1]]);
|
|
@@ -47296,15 +47435,12 @@ ${svg}
|
|
|
47296
47435
|
var files = opts.files || [];
|
|
47297
47436
|
var dataset, datasets, target;
|
|
47298
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
|
+
|
|
47299
47442
|
if (opts.stdin) {
|
|
47300
|
-
|
|
47301
|
-
catalog.addDatasets(datasets);
|
|
47302
|
-
if (datasets.length > 1) {
|
|
47303
|
-
catalog.setDefaultTargets(datasets.map(function(ds) {
|
|
47304
|
-
return {dataset: ds, layers: ds.layers};
|
|
47305
|
-
}));
|
|
47306
|
-
}
|
|
47307
|
-
return normalizeImportedTarget(datasets);
|
|
47443
|
+
files = [readStdinAsFile(opts.input)];
|
|
47308
47444
|
}
|
|
47309
47445
|
|
|
47310
47446
|
if (files.length > 0 === false) {
|
|
@@ -47313,10 +47449,6 @@ ${svg}
|
|
|
47313
47449
|
|
|
47314
47450
|
verbose("Importing: " + files.join(' '));
|
|
47315
47451
|
|
|
47316
|
-
// copy opts, so parameters can be modified within this command
|
|
47317
|
-
opts = Object.assign({}, opts);
|
|
47318
|
-
opts.input = Object.assign({}, opts.input); // make sure we have a cache
|
|
47319
|
-
|
|
47320
47452
|
convertDataObjects(files, opts.input);
|
|
47321
47453
|
|
|
47322
47454
|
files = expandFiles(files, opts.input);
|
|
@@ -47397,6 +47529,41 @@ ${svg}
|
|
|
47397
47529
|
return obj.target;
|
|
47398
47530
|
}
|
|
47399
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
|
+
|
|
47400
47567
|
function expandFiles(files, cache) {
|
|
47401
47568
|
var files2 = [];
|
|
47402
47569
|
files.forEach(function(file) {
|
|
@@ -47528,6 +47695,7 @@ ${svg}
|
|
|
47528
47695
|
content = cli.readFile(path, encoding || 'utf-8', cache);
|
|
47529
47696
|
fileType = guessInputContentType(content);
|
|
47530
47697
|
if (fileType == 'text' && content.indexOf('\ufffd') > -1) {
|
|
47698
|
+
// invalidate string data that contains the 'replacement character'
|
|
47531
47699
|
fileType = null;
|
|
47532
47700
|
}
|
|
47533
47701
|
}
|
|
@@ -47536,7 +47704,7 @@ ${svg}
|
|
|
47536
47704
|
stop$1(getUnsupportedFileMessage(path));
|
|
47537
47705
|
}
|
|
47538
47706
|
input[fileType] = {filename: path, content: content};
|
|
47539
|
-
content = null;
|
|
47707
|
+
content = null; // for g.c.
|
|
47540
47708
|
if (fileType == 'shp' || fileType == 'dbf') {
|
|
47541
47709
|
readShapefileAuxFiles(path, input, cache);
|
|
47542
47710
|
} else if (isRasterImageInputType(fileType)) {
|
|
@@ -47581,12 +47749,14 @@ ${svg}
|
|
|
47581
47749
|
function validateAndCleanGpkgSelection(datasets, opts) {
|
|
47582
47750
|
var availableSet = new Set();
|
|
47583
47751
|
var importedSet = new Set();
|
|
47752
|
+
var unreadableSet = new Set();
|
|
47584
47753
|
var sawGpkg = false;
|
|
47585
47754
|
datasets.forEach(function(ds) {
|
|
47586
47755
|
var info = ds && ds.info;
|
|
47587
47756
|
if (!info || !Array.isArray(info._gpkg_available_layers)) return;
|
|
47588
47757
|
sawGpkg = true;
|
|
47589
47758
|
info._gpkg_available_layers.forEach(function(name) { availableSet.add(name); });
|
|
47759
|
+
(info._gpkg_unreadable_layers || []).forEach(function(name) { unreadableSet.add(name); });
|
|
47590
47760
|
if (!info._gpkg_placeholder) {
|
|
47591
47761
|
(ds.layers || []).forEach(function(lyr) {
|
|
47592
47762
|
if (lyr && lyr.name) importedSet.add(lyr.name);
|
|
@@ -47594,9 +47764,17 @@ ${svg}
|
|
|
47594
47764
|
}
|
|
47595
47765
|
});
|
|
47596
47766
|
if (sawGpkg && Array.isArray(opts.layers) && opts.layers.length > 0) {
|
|
47597
|
-
var
|
|
47767
|
+
var absent = opts.layers.filter(function(name) {
|
|
47598
47768
|
return !importedSet.has(name);
|
|
47599
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
|
+
}
|
|
47600
47778
|
if (missing.length > 0) {
|
|
47601
47779
|
stop$1(
|
|
47602
47780
|
'Missing GeoPackage layer(s): ' + missing.join(', ') + '\n' +
|
|
@@ -47610,6 +47788,7 @@ ${svg}
|
|
|
47610
47788
|
cleaned.forEach(function(ds) {
|
|
47611
47789
|
if (ds && ds.info) {
|
|
47612
47790
|
delete ds.info._gpkg_available_layers;
|
|
47791
|
+
delete ds.info._gpkg_unreadable_layers;
|
|
47613
47792
|
delete ds.info._gpkg_placeholder;
|
|
47614
47793
|
}
|
|
47615
47794
|
});
|
|
@@ -48420,7 +48599,7 @@ ${svg}
|
|
|
48420
48599
|
const resulterrbound = (3 + 8 * epsilon) * epsilon;
|
|
48421
48600
|
|
|
48422
48601
|
// fast_expansion_sum_zeroelim routine from oritinal code
|
|
48423
|
-
function sum(elen, e, flen, f, h) {
|
|
48602
|
+
function sum$1(elen, e, flen, f, h) {
|
|
48424
48603
|
let Q, Qnew, hh, bvirt;
|
|
48425
48604
|
let enow = e[0];
|
|
48426
48605
|
let fnow = f[0];
|
|
@@ -48603,7 +48782,7 @@ ${svg}
|
|
|
48603
48782
|
bvirt = u3 - _j;
|
|
48604
48783
|
u[2] = _j - (u3 - bvirt) + (_i - bvirt);
|
|
48605
48784
|
u[3] = u3;
|
|
48606
|
-
const C1len = sum(4, B$2, 4, u, C1);
|
|
48785
|
+
const C1len = sum$1(4, B$2, 4, u, C1);
|
|
48607
48786
|
|
|
48608
48787
|
s1 = acx * bcytail;
|
|
48609
48788
|
c = splitter * acx;
|
|
@@ -48634,7 +48813,7 @@ ${svg}
|
|
|
48634
48813
|
bvirt = u3 - _j;
|
|
48635
48814
|
u[2] = _j - (u3 - bvirt) + (_i - bvirt);
|
|
48636
48815
|
u[3] = u3;
|
|
48637
|
-
const C2len = sum(C1len, C1, 4, u, C2);
|
|
48816
|
+
const C2len = sum$1(C1len, C1, 4, u, C2);
|
|
48638
48817
|
|
|
48639
48818
|
s1 = acxtail * bcytail;
|
|
48640
48819
|
c = splitter * acxtail;
|
|
@@ -48665,7 +48844,7 @@ ${svg}
|
|
|
48665
48844
|
bvirt = u3 - _j;
|
|
48666
48845
|
u[2] = _j - (u3 - bvirt) + (_i - bvirt);
|
|
48667
48846
|
u[3] = u3;
|
|
48668
|
-
const Dlen = sum(C2len, C2, 4, u, D$1);
|
|
48847
|
+
const Dlen = sum$1(C2len, C2, 4, u, D$1);
|
|
48669
48848
|
|
|
48670
48849
|
return D$1[Dlen - 1];
|
|
48671
48850
|
}
|
|
@@ -75425,16 +75604,180 @@ ${svg}
|
|
|
75425
75604
|
};
|
|
75426
75605
|
}
|
|
75427
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
|
+
|
|
75428
75761
|
// Collision reduction for circular symbols, in the spirit of d3-force's
|
|
75429
75762
|
// forceCollide. Overlapping symbols are pushed apart, but each one is kept
|
|
75430
75763
|
// within a fixed distance of its true position.
|
|
75431
75764
|
//
|
|
75432
75765
|
// Nodes are laid out in pixel space; converting to and from map coordinates is
|
|
75433
|
-
// the caller's job. A node is {i, x, y, x0, y0, r, maxShift}, where
|
|
75434
|
-
// the anchor (the symbol's true position), r includes any padding and
|
|
75435
|
-
// is the furthest this symbol may be displaced.
|
|
75436
|
-
// per-node, so the caller can vary them from symbol to
|
|
75437
|
-
// maxShift of 0 never moves and so acts as a fixed
|
|
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.
|
|
75438
75781
|
|
|
75439
75782
|
// max_shift is not read by the solver (it is a per-node property); it is here
|
|
75440
75783
|
// as the default for callers to apply when building nodes.
|
|
@@ -75481,55 +75824,91 @@ ${svg}
|
|
|
75481
75824
|
return moved;
|
|
75482
75825
|
}
|
|
75483
75826
|
|
|
75484
|
-
//
|
|
75485
|
-
// leave
|
|
75486
|
-
// pixel
|
|
75487
|
-
//
|
|
75488
|
-
// So a threshold of a pixel is where this
|
|
75489
|
-
// genuine unresolved crowding begins, as
|
|
75490
|
-
//
|
|
75491
|
-
var
|
|
75492
|
-
|
|
75493
|
-
// Number of
|
|
75494
|
-
// done. Deliberately not a count of
|
|
75495
|
-
//
|
|
75496
|
-
// makes a layout that is visually finished
|
|
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.
|
|
75497
75845
|
function countSymbolCollisions(nodes) {
|
|
75498
75846
|
var count = 0;
|
|
75499
75847
|
forEachCollision(nodes, function(a, b, dx, dy, distSq, sum) {
|
|
75500
|
-
if (sum - Math.sqrt(distSq) >
|
|
75848
|
+
if (sum - Math.sqrt(distSq) > VISIBLE_COLLISION) count++;
|
|
75501
75849
|
});
|
|
75502
75850
|
return count;
|
|
75503
75851
|
}
|
|
75504
75852
|
|
|
75505
|
-
// Push
|
|
75506
|
-
// less than smaller ones.
|
|
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.
|
|
75507
75857
|
function resolveCollisions(nodes, strength) {
|
|
75508
75858
|
forEachCollision(nodes, function(a, b, dx, dy, distSq, sum) {
|
|
75509
75859
|
separate(a, b, dx, dy, distSq, sum, strength);
|
|
75510
75860
|
});
|
|
75511
75861
|
}
|
|
75512
75862
|
|
|
75513
|
-
// Sweep along the x axis, using the
|
|
75514
|
-
// node to know when to stop looking for neighbors.
|
|
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.
|
|
75515
75884
|
function forEachCollision(nodes, cb) {
|
|
75516
75885
|
var n = nodes.length,
|
|
75517
75886
|
maxAhead = new Float64Array(n),
|
|
75887
|
+
margins = new Float64Array(n),
|
|
75518
75888
|
max = 0,
|
|
75519
|
-
a, b, dx, dy, sum, distSq, i, j;
|
|
75889
|
+
a, b, dx, dy, sum, distSq, reach, ma, mb, i, j;
|
|
75520
75890
|
nodes.sort(compareX);
|
|
75521
75891
|
for (i=n-1; i>=0; i--) {
|
|
75522
|
-
|
|
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;
|
|
75523
75896
|
maxAhead[i] = max;
|
|
75524
75897
|
}
|
|
75525
75898
|
for (i=0; i<n; i++) {
|
|
75526
75899
|
a = nodes[i];
|
|
75900
|
+
ma = margins[i];
|
|
75901
|
+
reach = a.r + (ma > 0 ? ma : 0);
|
|
75527
75902
|
for (j=i+1; j<n; j++) {
|
|
75528
75903
|
b = nodes[j];
|
|
75529
75904
|
dx = b.x - a.x;
|
|
75530
|
-
if (dx >
|
|
75905
|
+
if (dx > reach + maxAhead[j]) break;
|
|
75531
75906
|
dy = b.y - a.y;
|
|
75532
|
-
|
|
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;
|
|
75533
75912
|
if (dy > sum || dy < -sum) continue;
|
|
75534
75913
|
distSq = dx * dx + dy * dy;
|
|
75535
75914
|
if (distSq >= sum * sum) continue;
|
|
@@ -75600,14 +75979,16 @@ ${svg}
|
|
|
75600
75979
|
|
|
75601
75980
|
var SymbolCollisions = /*#__PURE__*/Object.freeze({
|
|
75602
75981
|
__proto__: null,
|
|
75603
|
-
|
|
75982
|
+
VISIBLE_COLLISION: VISIBLE_COLLISION,
|
|
75604
75983
|
countSymbolCollisions: countSymbolCollisions,
|
|
75605
75984
|
resolveSymbolCollisions: resolveSymbolCollisions,
|
|
75606
75985
|
symbolCollisionDefaults: symbolCollisionDefaults
|
|
75607
75986
|
});
|
|
75608
75987
|
|
|
75609
|
-
// Move
|
|
75610
|
-
//
|
|
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()).
|
|
75611
75992
|
//
|
|
75612
75993
|
// The layout runs in the pixel space of the map that the symbols will be
|
|
75613
75994
|
// rendered in, so the command needs to know the display scale: either from a
|
|
@@ -75619,9 +76000,17 @@ ${svg}
|
|
|
75619
76000
|
// An optional polygons= layer confines each symbol to the polygon it started
|
|
75620
76001
|
// in, so that a symbol standing for one area can't drift into a neighboring
|
|
75621
76002
|
// one. Symbols in an area too small to hold them barely move as a result, and
|
|
75622
|
-
// their
|
|
75623
|
-
// mean fewer
|
|
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.
|
|
75624
76012
|
var DEFAULT_PADDING = 0; // pixels
|
|
76013
|
+
var DEFAULT_MARGIN = 0; // pixels
|
|
75625
76014
|
|
|
75626
76015
|
cmd.repel = function(targetLayers, dataset, catalog, polygonSource, opts) {
|
|
75627
76016
|
var solverOpts = {
|
|
@@ -75641,7 +76030,7 @@ ${svg}
|
|
|
75641
76030
|
addLayerNodes(nodes, lyr, pixelsPerUnit, opts);
|
|
75642
76031
|
});
|
|
75643
76032
|
if (nodes.length === 0) {
|
|
75644
|
-
stop$1('Targeted layer(s) contain no
|
|
76033
|
+
stop$1('Targeted layer(s) contain no symbols to move.');
|
|
75645
76034
|
}
|
|
75646
76035
|
var unconstrained = addContainment(nodes, dataset, polygonSource, pixelsPerUnit, solverOpts);
|
|
75647
76036
|
|
|
@@ -75702,17 +76091,19 @@ ${svg}
|
|
|
75702
76091
|
function reportResults(nodes, moved, before, after, contained) {
|
|
75703
76092
|
var total = nodes.length;
|
|
75704
76093
|
var msg = utils.format('Moved %d of %d symbol%s', moved, total, total == 1 ? '' : 's');
|
|
75705
|
-
// Counts are of visible
|
|
75706
|
-
//
|
|
75707
|
-
// advice to raise max-shift= is not given when nothing is
|
|
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.
|
|
75708
76099
|
if (after > 0) {
|
|
75709
|
-
msg += utils.format('; %d of %d visible
|
|
76100
|
+
msg += utils.format('; %d of %d visible collision%s remain (%s)',
|
|
75710
76101
|
after, before, before == 1 ? '' : 's',
|
|
75711
76102
|
contained ? getMaxShiftAdvice(nodes) + ', or drop polygons=' : getMaxShiftAdvice(nodes));
|
|
75712
76103
|
} else if (before > 0) {
|
|
75713
|
-
msg += utils.format('; resolved %d visible
|
|
76104
|
+
msg += utils.format('; resolved %d visible collision%s', before, before == 1 ? '' : 's');
|
|
75714
76105
|
} else {
|
|
75715
|
-
msg += ' (no visible
|
|
76106
|
+
msg += ' (no visible collisions found)';
|
|
75716
76107
|
}
|
|
75717
76108
|
message(msg);
|
|
75718
76109
|
}
|
|
@@ -75733,19 +76124,20 @@ ${svg}
|
|
|
75733
76124
|
function requireRepelTarget(lyr, opts) {
|
|
75734
76125
|
requireSinglePointLayer(lyr,
|
|
75735
76126
|
'-repel requires single points; layer contains multi-point features.');
|
|
75736
|
-
if (!opts.radius && !
|
|
75737
|
-
stop$1('-repel requires a layer containing
|
|
76127
|
+
if (!opts.radius && !layerHasSymbols(lyr)) {
|
|
76128
|
+
stop$1('-repel requires a layer containing symbols ' +
|
|
75738
76129
|
'(see the -symbols and -style commands), or a radius= option.');
|
|
75739
76130
|
}
|
|
75740
76131
|
}
|
|
75741
76132
|
|
|
75742
|
-
function
|
|
76133
|
+
function layerHasSymbols(lyr) {
|
|
75743
76134
|
return !!lyr.data && (lyr.data.fieldExists('svg-symbol') || lyr.data.fieldExists('r'));
|
|
75744
76135
|
}
|
|
75745
76136
|
|
|
75746
76137
|
function addLayerNodes(nodes, lyr, pixelsPerUnit, opts) {
|
|
75747
76138
|
var getRadius = getRadiusAccessor(lyr, opts);
|
|
75748
76139
|
var getPadding = getPixelValueAccessor(opts.padding, 'padding', DEFAULT_PADDING, lyr);
|
|
76140
|
+
var getMargin = getPixelValueAccessor(opts.margin, 'margin', DEFAULT_MARGIN, lyr, true);
|
|
75749
76141
|
var getMaxShift = getPixelValueAccessor(opts.max_shift, 'max-shift',
|
|
75750
76142
|
symbolCollisionDefaults.max_shift, lyr);
|
|
75751
76143
|
var i, shp, p, r;
|
|
@@ -75764,23 +76156,32 @@ ${svg}
|
|
|
75764
76156
|
x0: p[0] * pixelsPerUnit,
|
|
75765
76157
|
y0: p[1] * pixelsPerUnit,
|
|
75766
76158
|
// Padding is added to the radius, so a pair of symbols ends up with
|
|
75767
|
-
// (padding a + padding b) pixels of clearance between them.
|
|
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.
|
|
75768
76162
|
r: r + getPadding(i),
|
|
76163
|
+
margin: getMargin(i),
|
|
75769
76164
|
maxShift: getMaxShift(i)
|
|
75770
76165
|
});
|
|
75771
76166
|
}
|
|
75772
76167
|
}
|
|
75773
76168
|
|
|
75774
|
-
// padding= and max-shift= accept a number, a field name or an
|
|
75775
|
-
// so are resolved for each symbol.
|
|
75776
|
-
|
|
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) {
|
|
75777
76178
|
if (optVal === undefined || optVal === null || optVal === '') {
|
|
75778
76179
|
return function(i) {return defaultVal;};
|
|
75779
76180
|
}
|
|
75780
76181
|
var accessor = getPropertyAccessor(optVal, 'number', lyr, name);
|
|
75781
76182
|
return function(i) {
|
|
75782
76183
|
var val = +accessor(i);
|
|
75783
|
-
if (!(val >= 0)) {
|
|
76184
|
+
if (allowNegative ? !isFinite(val) : !(val >= 0)) {
|
|
75784
76185
|
stop$1(utils.format('Invalid %s= value: %s', name, accessor(i)));
|
|
75785
76186
|
}
|
|
75786
76187
|
return val;
|
|
@@ -75799,12 +76200,17 @@ ${svg}
|
|
|
75799
76200
|
return function(i) {
|
|
75800
76201
|
var rec = records[i];
|
|
75801
76202
|
if (!rec) return 0;
|
|
75802
|
-
if (rec['svg-symbol']) return
|
|
76203
|
+
if (rec['svg-symbol']) return getSymbolLayoutRadius(rec['svg-symbol']);
|
|
75803
76204
|
return rec.r > 0 ? +rec.r : 0;
|
|
75804
76205
|
};
|
|
75805
76206
|
}
|
|
75806
76207
|
|
|
75807
|
-
|
|
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;
|
|
75808
76214
|
if (utils.isString(sym)) {
|
|
75809
76215
|
try {
|
|
75810
76216
|
sym = JSON.parse(sym);
|
|
@@ -75812,11 +76218,14 @@ ${svg}
|
|
|
75812
76218
|
return 0;
|
|
75813
76219
|
}
|
|
75814
76220
|
}
|
|
75815
|
-
if (!sym
|
|
75816
|
-
|
|
75817
|
-
|
|
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.');
|
|
75818
76227
|
}
|
|
75819
|
-
return
|
|
76228
|
+
return r;
|
|
75820
76229
|
}
|
|
75821
76230
|
|
|
75822
76231
|
// Returns the number of display pixels per map coordinate unit.
|
|
@@ -77864,72 +78273,6 @@ ${svg}
|
|
|
77864
78273
|
});
|
|
77865
78274
|
}
|
|
77866
78275
|
|
|
77867
|
-
var roundCoord$1 = getRoundingFunction(0.01);
|
|
77868
|
-
|
|
77869
|
-
function getSymbolFillColor(d) {
|
|
77870
|
-
return d.fill || 'magenta';
|
|
77871
|
-
}
|
|
77872
|
-
|
|
77873
|
-
function getSymbolStrokeColor(d) {
|
|
77874
|
-
return d.stroke || d.fill || 'magenta';
|
|
77875
|
-
}
|
|
77876
|
-
|
|
77877
|
-
function applySymbolStyles(sym, d) {
|
|
77878
|
-
if (sym.type == 'polyline') {
|
|
77879
|
-
sym.stroke = getSymbolStrokeColor(d);
|
|
77880
|
-
} else {
|
|
77881
|
-
sym.fill = getSymbolFillColor(d);
|
|
77882
|
-
}
|
|
77883
|
-
if (d.opacity) {
|
|
77884
|
-
sym.opacity = d.opacity;
|
|
77885
|
-
}
|
|
77886
|
-
return sym;
|
|
77887
|
-
}
|
|
77888
|
-
|
|
77889
|
-
function getSymbolRadius(d) {
|
|
77890
|
-
if (d.radius === 0 || d.length === 0 || d.r === 0) return 0;
|
|
77891
|
-
return d.radius || d.length || d.r || 5; // use a default value
|
|
77892
|
-
}
|
|
77893
|
-
|
|
77894
|
-
function forEachSymbolCoord(coords, cb) {
|
|
77895
|
-
var isPoint = coords && utils.isNumber(coords[0]);
|
|
77896
|
-
var isNested = !isPoint && coords && Array.isArray(coords[0]);
|
|
77897
|
-
if (isPoint) return cb(coords);
|
|
77898
|
-
for (var i=0; i<coords.length; i++) {
|
|
77899
|
-
if (isNested) forEachSymbolCoord(coords[i], cb);
|
|
77900
|
-
}
|
|
77901
|
-
}
|
|
77902
|
-
|
|
77903
|
-
function flipY(coords) {
|
|
77904
|
-
forEachSymbolCoord(coords, function(p) {
|
|
77905
|
-
p[1] = -p[1];
|
|
77906
|
-
});
|
|
77907
|
-
}
|
|
77908
|
-
|
|
77909
|
-
function scaleAndShiftCoords(coords, scale, shift) {
|
|
77910
|
-
forEachSymbolCoord(coords, function(xy) {
|
|
77911
|
-
xy[0] = xy[0] * scale + shift[0];
|
|
77912
|
-
xy[1] = xy[1] * scale + shift[1];
|
|
77913
|
-
});
|
|
77914
|
-
}
|
|
77915
|
-
|
|
77916
|
-
function roundCoordsForSVG(coords) {
|
|
77917
|
-
forEachSymbolCoord(coords, function(p) {
|
|
77918
|
-
p[0] = roundCoord$1(p[0]);
|
|
77919
|
-
p[1] = roundCoord$1(p[1]);
|
|
77920
|
-
});
|
|
77921
|
-
}
|
|
77922
|
-
|
|
77923
|
-
function rotateCoords(coords, rotation) {
|
|
77924
|
-
if (!rotation) return;
|
|
77925
|
-
var f = getAffineTransform(rotation, 1, [0, 0], [0, 0]);
|
|
77926
|
-
forEachSymbolCoord(coords, function(p) {
|
|
77927
|
-
var p2 = f(p[0], p[1]);
|
|
77928
|
-
p[0] = p2[0];
|
|
77929
|
-
p[1] = p2[1];
|
|
77930
|
-
});
|
|
77931
|
-
}
|
|
77932
|
-
|
|
77933
78276
|
function getStickArrowCoords(d) {
|
|
77934
78277
|
return getArrowCoords(d, 'stick');
|
|
77935
78278
|
}
|
|
@@ -78267,11 +78610,176 @@ ${svg}
|
|
|
78267
78610
|
}
|
|
78268
78611
|
|
|
78269
78612
|
function parseRings(arg) {
|
|
78270
|
-
var arr =
|
|
78613
|
+
var arr = toNumberList(arg);
|
|
78271
78614
|
utils.genericSort(arr, true);
|
|
78272
78615
|
return utils.uniq(arr);
|
|
78273
78616
|
}
|
|
78274
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
|
+
|
|
78275
78783
|
// Returns an svg-symbol data object for one symbol
|
|
78276
78784
|
function makePathSymbol(coords, properties, geojsonType) {
|
|
78277
78785
|
var sym;
|
|
@@ -78328,6 +78836,17 @@ ${svg}
|
|
|
78328
78836
|
var d = getSymbolData(i);
|
|
78329
78837
|
var rec = records[i] || {};
|
|
78330
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
|
+
|
|
78331
78850
|
// non-polygon symbols
|
|
78332
78851
|
if (!shapeMode && d.type == 'circle') {
|
|
78333
78852
|
rec['svg-symbol'] = makeCircleSymbol(d, opts);
|
|
@@ -79565,7 +80084,7 @@ ${svg}
|
|
|
79565
80084
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
79566
80085
|
}
|
|
79567
80086
|
|
|
79568
|
-
var version = "0.7.
|
|
80087
|
+
var version = "0.7.58";
|
|
79569
80088
|
|
|
79570
80089
|
// Parse command line args into commands and run them
|
|
79571
80090
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|