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/www/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$1(arr, info) {
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$1,
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$1(piece.boundary)
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$1(piece.boundary)
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$1(ring) {
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
- hex = hex.split('').map(function(c) { return c + c; });
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(7, 2), 16) / 255 : 1
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, // string, parsed by function
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] = getSymbolPropertyAccessor(val, svgName, lyr);
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.unshift(parts[1]);
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 circle symbols apart')
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 datasets = tables.map(function(table) {
44538
- return readFeatureTable(gpkg, table, opts);
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 {datasets: datasets, availableLayers: availableLayers, filterApplied: filterApplied};
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
- datasets = await importDatasetsFromFile('/dev/stdin', opts);
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 missing = opts.layers.filter(function(name) {
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 x0,y0 is
75434
- // the anchor (the symbol's true position), r includes any padding and maxShift
75435
- // is the furthest this symbol may be displaced. Both r and maxShift are
75436
- // per-node, so the caller can vary them from symbol to symbol; a node with a
75437
- // maxShift of 0 never moves and so acts as a fixed obstacle.
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
- // Overlaps shallower than this are not reported. A converged layout doesn't
75485
- // leave symbols exactly touching, it leaves a tail of overlaps a fraction of a
75486
- // pixel deep -- on a solved 435-symbol layout, 55 pairs overlap by more than a
75487
- // hundredth of a pixel, 6 by more than half a pixel, and none by more than one.
75488
- // So a threshold of a pixel is where this solver's convergence residue ends and
75489
- // genuine unresolved crowding begins, as well as being about the point where an
75490
- // overlap becomes visible.
75491
- var VISIBLE_OVERLAP = 1; // pixels
75492
-
75493
- // Number of visibly overlapping pairs, for reporting how much of the job got
75494
- // done. Deliberately not a count of all overlaps: an overlap 100th of a pixel
75495
- // deep and one 15 pixels deep are not the same result, and treating them alike
75496
- // makes a layout that is visually finished look like a failure.
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) > VISIBLE_OVERLAP) count++;
75848
+ if (sum - Math.sqrt(distSq) > VISIBLE_COLLISION) count++;
75501
75849
  });
75502
75850
  return count;
75503
75851
  }
75504
75852
 
75505
- // Push overlapping nodes apart, in proportion to the overlap. Larger nodes move
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 largest radius still ahead of the current
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
- if (nodes[i].r > max) max = nodes[i].r;
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 > a.r + maxAhead[j]) break;
75905
+ if (dx > reach + maxAhead[j]) break;
75531
75906
  dy = b.y - a.y;
75532
- sum = a.r + b.r;
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
- VISIBLE_OVERLAP: VISIBLE_OVERLAP,
75982
+ VISIBLE_COLLISION: VISIBLE_COLLISION,
75604
75983
  countSymbolCollisions: countSymbolCollisions,
75605
75984
  resolveSymbolCollisions: resolveSymbolCollisions,
75606
75985
  symbolCollisionDefaults: symbolCollisionDefaults
75607
75986
  });
75608
75987
 
75609
- // Move circular symbols apart to reduce overlaps, keeping each one within a
75610
- // fixed pixel distance of its true position.
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 overlaps go unresolved -- that is the point of the option, but it does
75623
- // mean fewer overlaps get fixed.
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 circle symbols to move.');
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 overlaps only, so a layout whose symbols end up
75706
- // touching or a fraction of a pixel apart is reported as finished, and the
75707
- // advice to raise max-shift= is not given when nothing is left to fix.
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 overlap%s remain (%s)',
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 overlap%s', before, before == 1 ? '' : 's');
76104
+ msg += utils.format('; resolved %d visible collision%s', before, before == 1 ? '' : 's');
75714
76105
  } else {
75715
- msg += ' (no visible overlaps found)';
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 && !layerHasCircleSymbols(lyr)) {
75737
- stop$1('-repel requires a layer containing circle symbols ' +
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 layerHasCircleSymbols(lyr) {
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 expression, and
75775
- // so are resolved for each symbol.
75776
- function getPixelValueAccessor(optVal, name, defaultVal, lyr) {
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 getCircleSymbolRadius(rec['svg-symbol']);
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
- function getCircleSymbolRadius(sym) {
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 || sym.type != 'circle') {
75816
- stop$1('-repel currently supports circle symbols only' +
75817
- (sym && sym.type ? ' (found a ' + sym.type + ' symbol).' : '.'));
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 sym.r > 0 ? +sym.r : 0;
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 = Array.isArray(arg) ? arg : parseNumberList(arg);
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.56";
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.