mapshaper 0.5.82 → 0.5.83

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
@@ -16,6 +16,8 @@
16
16
  get isFiniteNumber () { return isFiniteNumber; },
17
17
  get isNonNegNumber () { return isNonNegNumber; },
18
18
  get isInteger () { return isInteger; },
19
+ get isEven () { return isEven; },
20
+ get isOdd () { return isOdd; },
19
21
  get isString () { return isString; },
20
22
  get isDate () { return isDate; },
21
23
  get isBoolean () { return isBoolean; },
@@ -149,6 +151,14 @@
149
151
  return isNumber(obj) && ((obj | 0) === obj);
150
152
  }
151
153
 
154
+ function isEven(obj) {
155
+ return (obj % 2) === 0;
156
+ }
157
+
158
+ function isOdd(obj) {
159
+ return (obj % 2) === 1;
160
+ }
161
+
152
162
  function isString(obj) {
153
163
  return obj != null && obj.toString === String.prototype.toString;
154
164
  // TODO: replace w/ something better.
@@ -13698,16 +13708,18 @@
13698
13708
  length: 'number', // e.g. arrow length
13699
13709
  rotation: 'number',
13700
13710
  radius: 'number',
13701
- 'arrow-length': 'number',
13702
- 'arrow-direction': 'number',
13703
- 'arrow-head-angle': 'number',
13704
- 'arrow-head-width': 'number',
13705
- 'arrow-stem-width': 'number',
13706
- 'arrow-stem-curve': 'number', // degrees of arc
13707
- 'arrow-stem-taper': 'number',
13708
- 'arrow-stem-length': 'number',
13709
- 'arrow-head-length': 'number',
13710
- 'arrow-min-stem': 'number',
13711
+ radii: null, // string, parsed by function
13712
+ flipped: 'boolean',
13713
+ rotated: 'boolean',
13714
+ direction: 'number',
13715
+ 'head-angle': 'number',
13716
+ 'head-width': 'number',
13717
+ 'head-length': 'number',
13718
+ 'stem-width': 'number',
13719
+ 'stem-curve': 'number', // degrees of arc
13720
+ 'stem-taper': 'number',
13721
+ 'stem-length': 'number',
13722
+ 'min-stem': 'number',
13711
13723
  'arrow-scaling': 'number',
13712
13724
  effect: null // e.g. "fade"
13713
13725
  }, stylePropertyTypes);
@@ -13743,6 +13755,7 @@
13743
13755
  function getSymbolDataAccessor(lyr, opts) {
13744
13756
  var functions = {};
13745
13757
  var properties = [];
13758
+ var fields = lyr.data ? lyr.data.getFields() : [];
13746
13759
 
13747
13760
  Object.keys(opts).forEach(function(optName) {
13748
13761
  var svgName = optName.replace(/_/g, '-');
@@ -13754,6 +13767,8 @@
13754
13767
  properties.push(svgName);
13755
13768
  });
13756
13769
 
13770
+ // TODO: consider applying values of existing fields with names of symbol properties
13771
+
13757
13772
  return function(id) {
13758
13773
  var d = {}, name;
13759
13774
  for (var i=0; i<properties.length; i++) {
@@ -13769,8 +13784,9 @@
13769
13784
  // * ???
13770
13785
  //
13771
13786
  function mightBeExpression(str, fields) {
13787
+ fields = fields || [];
13772
13788
  if (fields.indexOf(str.trim()) > -1) return true;
13773
- return /[(){}.+-/*?:&|=\[]/.test(str);
13789
+ return /[(){}./*?:&|=\[+-]/.test(str);
13774
13790
  }
13775
13791
 
13776
13792
  function getSymbolPropertyAccessor(val, svgName, lyr) {
@@ -13791,6 +13807,7 @@
13791
13807
  // treating the string as a literal value
13792
13808
  literalVal = strVal;
13793
13809
  }
13810
+ // console.log("literalVal:", mightBeExpression(strVal, fields), strVal, fields)
13794
13811
  if (accessor) return accessor;
13795
13812
  if (literalVal !== null) return function(id) {return literalVal;};
13796
13813
  stop('Unexpected value for', svgName + ':', strVal);
@@ -13824,6 +13841,8 @@
13824
13841
  val = isDashArray(strVal) ? strVal : null;
13825
13842
  } else if (type == 'pattern') {
13826
13843
  val = isPattern(strVal) ? strVal : null;
13844
+ } else if (type == 'boolean') {
13845
+ val = parseBoolean(strVal);
13827
13846
  }
13828
13847
  // else {
13829
13848
  // // unknown type -- assume literal value
@@ -13844,10 +13863,17 @@
13844
13863
  return /^( ?[_a-z][-_a-z0-9]*\b)+$/i.test(str);
13845
13864
  }
13846
13865
 
13866
+
13847
13867
  function isSvgNumber(o) {
13848
13868
  return utils.isFiniteNumber(o) || utils.isString(o) && /^-?[.0-9]+$/.test(o);
13849
13869
  }
13850
13870
 
13871
+ function parseBoolean(o) {
13872
+ if (o === true || o === 'true') return true;
13873
+ if (o === false || o === 'false') return false;
13874
+ return null;
13875
+ }
13876
+
13851
13877
  function isSvgMeasure(o) {
13852
13878
  return utils.isFiniteNumber(o) || utils.isString(o) && /^-?[.0-9]+[a-z]*$/.test(o);
13853
13879
  }
@@ -13871,6 +13897,7 @@
13871
13897
  getSymbolPropertyAccessor: getSymbolPropertyAccessor,
13872
13898
  isSvgClassName: isSvgClassName,
13873
13899
  isSvgNumber: isSvgNumber,
13900
+ parseBoolean: parseBoolean,
13874
13901
  isSvgMeasure: isSvgMeasure,
13875
13902
  parseSvgMeasure: parseSvgMeasure,
13876
13903
  isSvgColor: isSvgColor
@@ -14089,6 +14116,9 @@
14089
14116
  o = importLineString(coords[i]);
14090
14117
  o.properties.d = d + o.properties.d + ' Z';
14091
14118
  }
14119
+ if (coords.length > 1) {
14120
+ o.properties['fill-rule'] = 'evenodd'; // support polygons with holes
14121
+ }
14092
14122
  return o;
14093
14123
  }
14094
14124
 
@@ -18005,6 +18035,10 @@ ${svg}
18005
18035
  return chunks;
18006
18036
  }
18007
18037
 
18038
+ function parseNumberList(token) {
18039
+ return token.split(',').map(parseFloat);
18040
+ }
18041
+
18008
18042
  // Split comma-delimited list, trim quotes from entire list and
18009
18043
  // individual members
18010
18044
  function parseStringList(token) {
@@ -18094,6 +18128,7 @@ ${svg}
18094
18128
  var OptionParsingUtils = /*#__PURE__*/Object.freeze({
18095
18129
  __proto__: null,
18096
18130
  splitShellTokens: splitShellTokens,
18131
+ parseNumberList: parseNumberList,
18097
18132
  parseStringList: parseStringList,
18098
18133
  parseColorList: parseColorList,
18099
18134
  cleanArgv: cleanArgv,
@@ -18285,7 +18320,7 @@ ${svg}
18285
18320
  } else if (type == 'strings') {
18286
18321
  val = parseStringList(token);
18287
18322
  } else if (type == 'bbox' || type == 'numbers') {
18288
- val = token.split(',').map(parseFloat);
18323
+ val = parseNumberList(token);
18289
18324
  } else if (type == 'percent') {
18290
18325
  // val = utils.parsePercent(token);
18291
18326
  val = token; // string value is parsed by command function
@@ -20079,51 +20114,73 @@ ${svg}
20079
20114
  describe: 'sides of a polygon or star symbol',
20080
20115
  type: 'number'
20081
20116
  })
20117
+ .option('orientation', {
20118
+ // describe: 'use orientation=b for a rotated or flipped orientation'
20119
+ })
20120
+ .option('flipped', {
20121
+ type: 'flag',
20122
+ describe: 'symbol is vertically flipped'
20123
+ })
20124
+ .option('rotated', {
20125
+ type: 'flag',
20126
+ describe: 'symbol is rotated to a different orientation'
20127
+ })
20082
20128
  .option('rotation', {
20083
20129
  describe: 'rotation of symbol in degrees'
20084
20130
  })
20085
- .option('orientation', {
20086
- describe: 'use orientation=b for a rotated or flipped orientation'
20087
- })
20088
20131
  .option('length', {
20089
20132
  // alias for arrow-length
20090
20133
  })
20091
- .option('star-ratio', {
20092
- describe: 'ratio of major to minor radius of star',
20134
+ .option('point-ratio', {
20135
+ old_alias: 'star-ratio',
20136
+ describe: '(star) ratio of minor to major radius of star',
20093
20137
  type: 'number'
20094
20138
  })
20095
- .option('arrow-length', {
20096
- describe: 'length of arrows in pixels (use with type=arrow)'
20139
+ .option('radii', {
20140
+ describe: '(ring) comma-sep. list of concentric radii, ascending order'
20141
+ })
20142
+ .option('length', {
20143
+ old_alias: 'arrow-length',
20144
+ describe: '(arrow) length of arrow in pixels'
20097
20145
  })
20098
- .option('arrow-direction', {
20099
- describe: 'angle off of vertical (-90 = left-pointing arrow)'
20146
+ .option('direction', {
20147
+ old_alias: 'arrow-direction',
20148
+ describe: '(arrow) angle off vertical (-90 = left-pointing)'
20100
20149
  })
20101
- .option('arrow-head-angle', {
20102
- describe: 'angle of tip of arrow (default is 40 degrees)'
20150
+ .option('head-angle', {
20151
+ old_alias: 'arrow-head-angle',
20152
+ describe: '(arrow) angle of tip of arrow (default is 40 degrees)'
20103
20153
  })
20104
- .option('arrow-head-width', {
20105
- describe: 'size of arrow head from side to side'
20154
+ .option('head-width', {
20155
+ old_alias: 'arrow-head-width',
20156
+ describe: '(arrow) width of arrow head from side to side'
20106
20157
  })
20107
- .option('arrow-head-length', {
20108
- describe: 'length of arrow head (alternative to arrow-head-angle)'
20158
+ .option('head-length', {
20159
+ old_alias: 'arrow-head-width',
20160
+ describe: '(arrow) length of head (alternative to head-angle)'
20109
20161
  })
20110
- .option('arrow-head-shape', {
20162
+ .option('head-shape', {
20111
20163
  // describe: 'options: a b c'
20112
20164
  })
20113
- .option('arrow-stem-width', {
20114
- describe: 'width of stem at its widest point'
20165
+ .option('stem-width', {
20166
+ old_alias: 'arrow-stem-width',
20167
+ describe: '(arrow) width of stem at its widest point'
20115
20168
  })
20116
- .option('arrow-stem-length', {
20117
- describe: 'alternative to arrow-length'
20169
+ .option('stem-length', {
20170
+ old_alias: 'arrow-stem-length',
20171
+ describe: '(arrow) alternative to length'
20118
20172
  })
20119
- .option('arrow-stem-taper', {
20120
- describe: 'factor for tapering the width of the stem'
20173
+ .option('stem-taper', {
20174
+ old_alias: 'arrow-stem-taper',
20175
+ describe: '(arrow) factor for tapering the width of the stem (0-1)'
20121
20176
  })
20122
- .option('arrow-stem-curve', {
20123
- describe: 'curvature in degrees (arrows are straight by default)'
20177
+ .option('stem-curve', {
20178
+ old_alias: 'arrow-stem-curve',
20179
+ describe: '(arrow) curvature in degrees (default is 0)'
20124
20180
  })
20125
- .option('arrow-min-stem', {
20126
- describe: 'min ratio of stem to total length (for small arrows)',
20181
+ .option('min-stem-ratio', {
20182
+ old_alias: 'arrow-min-stem',
20183
+ describe: '(arrow) min ratio of stem to total length',
20127
20184
  type: 'number'
20128
20185
  })
20129
20186
  .option('stroke', {})
@@ -21660,7 +21717,7 @@ ${svg}
21660
21717
  collectionType = 'mixed';
21661
21718
  }
21662
21719
  } else if (currType != t) {
21663
- stop("Unable to import mixed-geometry GeoJSON features");
21720
+ stop("Unable to import mixed-geometry features");
21664
21721
  }
21665
21722
  }
21666
21723
 
@@ -21771,6 +21828,33 @@ ${svg}
21771
21828
  importShp: importShp
21772
21829
  });
21773
21830
 
21831
+ function importGeoJSON(src, optsArg) {
21832
+ var opts = optsArg || {};
21833
+ var supportedGeometries = Object.keys(GeoJSON.pathImporters),
21834
+ srcObj = utils.isString(src) ? JSON.parse(src) : src,
21835
+ importer = new GeoJSONParser(opts),
21836
+ srcCollection, dataset;
21837
+
21838
+ // Convert single feature or geometry into a collection with one member
21839
+ if (srcObj.type == 'Feature') {
21840
+ srcCollection = {
21841
+ type: 'FeatureCollection',
21842
+ features: [srcObj]
21843
+ };
21844
+ } else if (supportedGeometries.includes(srcObj.type)) {
21845
+ srcCollection = {
21846
+ type: 'GeometryCollection',
21847
+ geometries: [srcObj]
21848
+ };
21849
+ } else {
21850
+ srcCollection = srcObj;
21851
+ }
21852
+ (srcCollection.features || srcCollection.geometries || []).forEach(importer.parseObject);
21853
+ dataset = importer.done();
21854
+ importCRS(dataset, srcObj); // TODO: remove this
21855
+ return dataset;
21856
+ }
21857
+
21774
21858
  function GeoJSONParser(opts) {
21775
21859
  var idField = opts.id_field || GeoJSON.ID_FIELD,
21776
21860
  importer = new PathImporter(opts),
@@ -21792,8 +21876,11 @@ ${svg}
21792
21876
  geom = o;
21793
21877
  }
21794
21878
  // TODO: improve so geometry_type option skips features instead of creating null geometries
21795
- importer.startShape(rec);
21796
- if (geom) GeoJSON.importGeometry(geom, importer, opts);
21879
+ if (geom && geom.type == 'GeometryCollection') {
21880
+ GeoJSON.importComplexFeature(importer, geom, rec, opts);
21881
+ } else {
21882
+ GeoJSON.importSimpleFeature(importer, geom, rec, opts);
21883
+ }
21797
21884
  };
21798
21885
 
21799
21886
  this.done = function() {
@@ -21801,50 +21888,58 @@ ${svg}
21801
21888
  };
21802
21889
  }
21803
21890
 
21804
- function importGeoJSON(src, optsArg) {
21805
- var opts = optsArg || {};
21806
- var supportedGeometries = Object.keys(GeoJSON.pathImporters),
21807
- srcObj = utils.isString(src) ? JSON.parse(src) : src,
21808
- importer = new GeoJSONParser(opts),
21809
- srcCollection, dataset;
21810
-
21811
- // Convert single feature or geometry into a collection with one member
21812
- if (srcObj.type == 'Feature') {
21813
- srcCollection = {
21814
- type: 'FeatureCollection',
21815
- features: [srcObj]
21816
- };
21817
- } else if (supportedGeometries.includes(srcObj.type)) {
21818
- srcCollection = {
21819
- type: 'GeometryCollection',
21820
- geometries: [srcObj]
21821
- };
21822
- } else {
21823
- srcCollection = srcObj;
21891
+ GeoJSON.importComplexFeature = function(importer, geom, rec, opts) {
21892
+ var types = divideGeometriesByType(geom.geometries || []);
21893
+ if (types.length === 0) {
21894
+ importer.startShape(rec); // import a feature with null geometry
21895
+ return;
21824
21896
  }
21825
- (srcCollection.features || srcCollection.geometries || []).forEach(importer.parseObject);
21826
- dataset = importer.done();
21827
- importCRS(dataset, srcObj); // TODO: remove this
21828
- return dataset;
21897
+ types.forEach(function(geometries, i) {
21898
+ importer.startShape(copyRecord(rec));
21899
+ geometries.forEach(function(geom) {
21900
+ GeoJSON.importSimpleGeometry(importer, geom, opts);
21901
+ });
21902
+ });
21903
+ };
21904
+
21905
+ function divideGeometriesByType(geometries, index) {
21906
+ index = index || {};
21907
+ geometries.forEach(function(geom) {
21908
+ if (!geom) return;
21909
+ var mtype = GeoJSON.translateGeoJSONType(geom.type);
21910
+ if (mtype) {
21911
+ if (mtype in index === false) {
21912
+ index[mtype] = [];
21913
+ }
21914
+ index[mtype].push(geom);
21915
+ } else if (geom.type == 'GeometryCollection') {
21916
+ divideGeometriesByType(geom.geometries || [], index);
21917
+ }
21918
+ });
21919
+ return Object.values(index);
21829
21920
  }
21830
21921
 
21831
- GeoJSON.importGeometry = function(geom, importer, opts) {
21832
- var type = geom.type;
21833
- if (type in GeoJSON.pathImporters) {
21922
+ GeoJSON.importSimpleFeature = function(importer, geom, rec, opts) {
21923
+ importer.startShape(rec);
21924
+ GeoJSON.importSimpleGeometry(importer, geom, opts);
21925
+ };
21926
+
21927
+ GeoJSON.importSimpleGeometry = function(importer, geom, opts) {
21928
+ var type = geom ? geom.type : null;
21929
+ if (type === null) {
21930
+ // no geometry to import
21931
+ } else if (type in GeoJSON.pathImporters) {
21834
21932
  if (opts.geometry_type && opts.geometry_type != GeoJSON.translateGeoJSONType(type)) {
21835
21933
  // kludge to filter out all but one type of geometry
21836
21934
  return;
21837
21935
  }
21838
21936
  GeoJSON.pathImporters[type](geom.coordinates, importer);
21839
- } else if (type == 'GeometryCollection') {
21840
- geom.geometries.forEach(function(geom) {
21841
- GeoJSON.importGeometry(geom, importer, opts);
21842
- });
21843
21937
  } else {
21844
- verbose("GeoJSON.importGeometry() Unsupported geometry type:", geom.type);
21938
+ verbose("Unsupported geometry type:", geom.type);
21845
21939
  }
21846
21940
  };
21847
21941
 
21942
+
21848
21943
  // Functions for importing geometry coordinates using a PathImporter
21849
21944
  //
21850
21945
  GeoJSON.pathImporters = {
@@ -21883,8 +21978,8 @@ ${svg}
21883
21978
 
21884
21979
  var GeojsonImport = /*#__PURE__*/Object.freeze({
21885
21980
  __proto__: null,
21886
- GeoJSONParser: GeoJSONParser,
21887
21981
  importGeoJSON: importGeoJSON,
21982
+ GeoJSONParser: GeoJSONParser,
21888
21983
  importCRS: importCRS
21889
21984
  });
21890
21985
 
@@ -37968,70 +38063,80 @@ ${svg}
37968
38063
  // return [stem, head];
37969
38064
  // }
37970
38065
 
37971
- function getMinStemRatio(d) {
37972
- return d['arrow-min-stem'] >= 0 ? d['arrow-min-stem'] : 0.4;
37973
- }
37974
38066
 
37975
38067
  function getFilledArrowCoords(d) {
37976
- var totalLen = d['arrow-length'] || d.radius || d.length || d.r || 0,
37977
- direction = d.rotation || d['arrow-direction'] || 0,
37978
- unscaledStemWidth = d['arrow-stem-width'] || 2,
37979
- unscaledHeadWidth = d['arrow-head-width'] || unscaledStemWidth * 3,
37980
- unscaledHeadLen = getHeadLength(unscaledHeadWidth, d),
37981
- stemTaper = d['arrow-stem-taper'] || 0,
37982
- stemCurve = d['arrow-stem-curve'] || 0;
38068
+ var direction = d.rotation || d.direction || 0,
38069
+ stemTaper = d['stem-taper'] || 0,
38070
+ stemCurve = d['stem-curve'] || 0,
38071
+ size = calcArrowSize(d);
37983
38072
 
37984
- var headLen, headWidth, stemLen, stemWidth;
38073
+ if (!size) return null;
37985
38074
 
37986
- var scale = 1;
37987
-
37988
- if (totalLen > 0) {
37989
- scale = getScale(totalLen, unscaledHeadLen, getMinStemRatio(d));
37990
- headWidth = unscaledHeadWidth * scale;
37991
- headLen = unscaledHeadLen * scale;
37992
- stemWidth = unscaledStemWidth * scale;
37993
- stemLen = totalLen - headLen;
38075
+ var headDx = size.headWidth / 2,
38076
+ stemDx = size.stemWidth / 2,
38077
+ baseDx = stemDx * (1 - stemTaper),
38078
+ coords;
37994
38079
 
38080
+ if (!stemCurve || Math.abs(stemCurve) > 90) {
38081
+ coords = calcStraightArrowCoords(size.stemLen, size.headLen, stemDx, headDx, baseDx);
37995
38082
  } else {
37996
- headWidth = unscaledHeadWidth;
37997
- headLen = unscaledHeadLen;
37998
- stemWidth = unscaledStemWidth;
37999
- stemLen = d['arrow-stem-length'] || 0;
38000
- totalLen = headLen + stemLen;
38083
+ if (direction > 0) stemCurve = -stemCurve;
38084
+ coords = getCurvedArrowCoords(size.stemLen, size.headLen, size.stemCurve, stemDx, headDx, baseDx);
38001
38085
  }
38002
38086
 
38003
- if (totalLen > 0 === false) return null;
38087
+ rotateCoords(coords, direction);
38088
+ if (d.flipped) {
38089
+ flipY(coords);
38090
+ }
38091
+ return [coords];
38092
+ }
38004
38093
 
38005
- var coords;
38094
+ function calcStraightArrowCoords(stemLen, headLen, stemDx, headDx, baseDx) {
38095
+ return [[baseDx, 0], [stemDx, stemLen], [headDx, stemLen], [0, stemLen + headLen],
38096
+ [-headDx, stemLen], [-stemDx, stemLen], [-baseDx, 0], [baseDx, 0]];
38097
+ }
38006
38098
 
38007
- var headDx = headWidth / 2,
38008
- stemDx = stemWidth / 2,
38009
- baseDx = stemDx * (1 - stemTaper);
38099
+ function calcArrowSize(d) {
38100
+ var totalLen = d.radius || d.length || d.r || 0,
38101
+ unscaledStemWidth = d['stem-width'] || 2,
38102
+ unscaledHeadWidth = d['head-width'] || unscaledStemWidth * 3,
38103
+ unscaledHeadLen = d['head-length'] || calcHeadLength(unscaledHeadWidth, d),
38104
+ scale = 1,
38105
+ o = {};
38010
38106
 
38011
- if (unscaledHeadWidth < unscaledStemWidth) {
38012
- stop('Arrow head must be at least as wide as the stem.');
38013
- }
38107
+ if (totalLen > 0) {
38108
+ scale = calcScale(totalLen, unscaledHeadLen, d);
38109
+ o.headWidth = unscaledHeadWidth * scale;
38110
+ o.headLen = unscaledHeadLen * scale;
38111
+ o.stemWidth = unscaledStemWidth * scale;
38112
+ o.stemLen = totalLen - o.headLen;
38014
38113
 
38015
- if (!stemCurve || Math.abs(stemCurve) > 90) {
38016
- coords = [[baseDx, 0], [stemDx, stemLen], [headDx, stemLen], [0, stemLen + headLen],
38017
- [-headDx, stemLen], [-stemDx, stemLen], [-baseDx, 0], [baseDx, 0]];
38018
38114
  } else {
38019
- if (direction > 0) stemCurve = -stemCurve;
38020
- coords = getCurvedArrowCoords(stemLen, headLen, stemCurve, stemDx, headDx, baseDx);
38115
+ o.headWidth = unscaledHeadWidth;
38116
+ o.headLen = unscaledHeadLen;
38117
+ o.stemWidth = unscaledStemWidth;
38118
+ o.stemLen = d['stem-length'] || 0;
38021
38119
  }
38022
38120
 
38023
- rotateCoords(coords, direction);
38024
- return [coords];
38121
+ if (unscaledHeadWidth < unscaledStemWidth) {
38122
+ stop('Arrow head must be at least as wide as the stem.');
38123
+ }
38124
+ return o;
38025
38125
  }
38026
38126
 
38027
-
38028
- function getScale(totalLen, headLen, minStemRatio) {
38127
+ function calcScale(totalLen, headLen, d) {
38128
+ var minStemRatio = d['min-stem'] >= 0 ? d['min-stem'] : 0;
38129
+ var stemLen = d['stem-length'] || 0;
38029
38130
  var maxHeadPct = 1 - minStemRatio;
38030
38131
  var headPct = headLen / totalLen;
38132
+ var scale = 1;
38133
+
38031
38134
  if (headPct > maxHeadPct) {
38032
- return maxHeadPct / headPct;
38135
+ scale = maxHeadPct / headPct;
38136
+ } else if (stemLen + headLen > totalLen) {
38137
+ scale = totalLen / (stemLen + headLen);
38033
38138
  }
38034
- return 1;
38139
+ return scale;
38035
38140
  }
38036
38141
 
38037
38142
  // function getStickArrowTip(totalLen, curve) {
@@ -38047,10 +38152,8 @@ ${svg}
38047
38152
  }
38048
38153
 
38049
38154
 
38050
- function getHeadLength(headWidth, d) {
38051
- var headLength = d['arrow-head-length'];
38052
- if (headLength > 0) return headLength;
38053
- var headAngle = d['arrow-head-angle'] || 40;
38155
+ function calcHeadLength(headWidth, d) {
38156
+ var headAngle = d['head-angle'] || 40;
38054
38157
  var headRatio = 1 / Math.tan(Math.PI * headAngle / 180 / 2) / 2; // length-to-width head ratio
38055
38158
  return headWidth * headRatio;
38056
38159
  }
@@ -38109,11 +38212,11 @@ ${svg}
38109
38212
  // sides: e.g. 5-pointed star has 10 sides
38110
38213
  // radius: distance from center to point
38111
38214
  //
38112
- function getPolygonCoords(opts) {
38113
- var radius = opts.radius || opts.length || opts.r;
38215
+ function getPolygonCoords(d) {
38216
+ var radius = d.radius || d.length || d.r;
38114
38217
  if (radius > 0 === false) return null;
38115
- var type = opts.type;
38116
- var sides = +opts.sides || getDefaultSides(type);
38218
+ var type = d.type;
38219
+ var sides = +d.sides || getDefaultSides(type);
38117
38220
  var isStar = type == 'star';
38118
38221
  if (isStar && (sides < 6 || sides % 2 !== 0)) {
38119
38222
  stop(`Invalid number of sides for a star (${sides})`);
@@ -38124,14 +38227,14 @@ ${svg}
38124
38227
  angle = 360 / sides,
38125
38228
  b = isStar ? 1 : 0.5,
38126
38229
  theta, even, len;
38127
- if (opts.orientation == 'b') {
38230
+ if (d.orientation == 'b' || d.flipped || d.rotated) {
38128
38231
  b = 0;
38129
38232
  }
38130
38233
  for (var i=0; i<sides; i++) {
38131
38234
  even = i % 2 == 0;
38132
38235
  len = radius;
38133
38236
  if (isStar && even) {
38134
- len *= (opts.star_ratio || 0.5);
38237
+ len *= (d.star_ratio || 0.5);
38135
38238
  }
38136
38239
  theta = (i + b) * angle % 360;
38137
38240
  coords.push(getPlanarSegmentEndpoint(0, 0, theta, len));
@@ -38155,6 +38258,37 @@ ${svg}
38155
38258
  }[type] || 4;
38156
38259
  }
38157
38260
 
38261
+ // Returns GeoJSON MultiPolygon coords
38262
+ function getRingCoords(d) {
38263
+ var radii = parseRings(d.radii || '2');
38264
+ var coords = [];
38265
+ var solidCenter = utils.isOdd(radii.length);
38266
+ var ring, hole;
38267
+ for (var i=0; i<radii.length; i++) {
38268
+ ring = getPolygonCoords({
38269
+ type: 'circle',
38270
+ radius: radii[i]
38271
+ });
38272
+ if (!solidCenter || i > 0) {
38273
+ i++;
38274
+ hole = ring;
38275
+ ring = getPolygonCoords({
38276
+ type: 'circle',
38277
+ radius: radii[i]
38278
+ });
38279
+ ring.push(hole[0]);
38280
+ }
38281
+ coords.push(ring);
38282
+ }
38283
+ return coords;
38284
+ }
38285
+
38286
+ function parseRings(arg) {
38287
+ var arr = Array.isArray(arg) ? arg : parseNumberList(arg);
38288
+ utils.genericSort(arr, true);
38289
+ return utils.uniq(arr);
38290
+ }
38291
+
38158
38292
  // TODO: refactor to remove duplication in mapshaper-svg-style.js
38159
38293
  cmd.symbols = function(inputLyr, dataset, opts) {
38160
38294
  requireSinglePointLayer(inputLyr);
@@ -38171,9 +38305,13 @@ ${svg}
38171
38305
  if (!shp) return null;
38172
38306
  var d = getSymbolData(i);
38173
38307
  var rec = records[i] || {};
38308
+ var geojsonType = 'Polygon';
38174
38309
  var coords;
38175
38310
  if (d.type == 'arrow') {
38176
38311
  coords = getFilledArrowCoords(d);
38312
+ } else if (d.type == 'ring') {
38313
+ coords = getRingCoords(d);
38314
+ geojsonType = 'MultiPolygon';
38177
38315
  } else {
38178
38316
  coords = getPolygonCoords(d);
38179
38317
  }
@@ -38188,9 +38326,9 @@ ${svg}
38188
38326
  if (polygonMode) {
38189
38327
  scaleAndShiftCoords(coords, metersPerPx, shp[0]);
38190
38328
  if (d.tfill) rec.fill = d.fill;
38191
- return createGeometry(coords);
38329
+ return createGeometry(coords, geojsonType);
38192
38330
  } else {
38193
- rec['svg-symbol'] = makeSvgSymbol(coords, d);
38331
+ rec['svg-symbol'] = makeSvgPolygonSymbol(coords, d, geojsonType);
38194
38332
  }
38195
38333
  });
38196
38334
 
@@ -38221,9 +38359,9 @@ ${svg}
38221
38359
  return importGeoJSON(geojson);
38222
38360
  }
38223
38361
 
38224
- function createGeometry(coords) {
38362
+ function createGeometry(coords, type) {
38225
38363
  return {
38226
- type: 'Polygon',
38364
+ type: type,
38227
38365
  coordinates: coords
38228
38366
  };
38229
38367
  }
@@ -38236,7 +38374,12 @@ ${svg}
38236
38374
  }
38237
38375
 
38238
38376
  // Returns an svg-symbol data object for one symbol
38239
- function makeSvgSymbol(coords, properties) {
38377
+ function makeSvgPolygonSymbol(coords, properties, geojsonType) {
38378
+ if (geojsonType == 'MultiPolygon') {
38379
+ coords = convertMultiPolygonCoords(coords);
38380
+ } else if (geojsonType != 'Polygon') {
38381
+ error('Unsupported type:', geojsonType);
38382
+ }
38240
38383
  roundCoordsForSVG(coords);
38241
38384
  return {
38242
38385
  type: 'polygon',
@@ -38245,9 +38388,14 @@ ${svg}
38245
38388
  };
38246
38389
  }
38247
38390
 
38391
+ function convertMultiPolygonCoords(coords) {
38392
+ return coords.reduce(function(memo, poly) {
38393
+ return memo.concat(poly);
38394
+ }, []);
38395
+ }
38396
+
38248
38397
  var Symbols = /*#__PURE__*/Object.freeze({
38249
- __proto__: null,
38250
- makeSvgSymbol: makeSvgSymbol
38398
+ __proto__: null
38251
38399
  });
38252
38400
 
38253
38401
  cmd.target = function(catalog, opts) {