mapshaper 0.5.84 → 0.5.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.5.83";
3
+ var VERSION = "0.5.87";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -4895,6 +4895,11 @@
4895
4895
  };
4896
4896
  }
4897
4897
 
4898
+ function MultiShapeIter(arcs) {
4899
+ var iter = new ShapeIter(arcs);
4900
+
4901
+ }
4902
+
4898
4903
  // Iterate along a path made up of one or more arcs.
4899
4904
  //
4900
4905
  function ShapeIter(arcs) {
@@ -4945,6 +4950,7 @@
4945
4950
  PointIter: PointIter,
4946
4951
  ArcIter: ArcIter,
4947
4952
  FilteredArcIter: FilteredArcIter,
4953
+ MultiShapeIter: MultiShapeIter,
4948
4954
  ShapeIter: ShapeIter
4949
4955
  });
4950
4956
 
@@ -8222,10 +8228,21 @@
8222
8228
  return +n.toPrecision(d);
8223
8229
  }
8224
8230
 
8231
+
8225
8232
  function roundToDigits(n, d) {
8226
8233
  return +n.toFixed(d); // string conversion makes this slow
8227
8234
  }
8228
8235
 
8236
+ // Used in mapshaper-expression-utils.js
8237
+ // TODO: choose between this and the above function
8238
+ function roundToDigits2(n, d) {
8239
+ var k = 1;
8240
+ if (!n && n !== 0) return n; // don't coerce null to 0
8241
+ d = d | 0;
8242
+ while (d-- > 0) k *= 10;
8243
+ return Math.round(n * k) / k;
8244
+ }
8245
+
8229
8246
  function roundToTenths(n) {
8230
8247
  return (Math.round(n * 10)) / 10;
8231
8248
  }
@@ -8300,6 +8317,7 @@
8300
8317
  __proto__: null,
8301
8318
  roundToSignificantDigits: roundToSignificantDigits,
8302
8319
  roundToDigits: roundToDigits,
8320
+ roundToDigits2: roundToDigits2,
8303
8321
  roundToTenths: roundToTenths,
8304
8322
  getRoundingFunction: getRoundingFunction,
8305
8323
  getBoundsPrecisionForDisplay: getBoundsPrecisionForDisplay,
@@ -8938,15 +8956,15 @@
8938
8956
  });
8939
8957
  }
8940
8958
 
8941
- function addUtils(env) {
8959
+ function cleanExpression(exp) {
8960
+ // workaround for problem in GNU Make v4: end-of-line backslashes inside
8961
+ // quoted strings are left in the string (other shell environments remove them)
8962
+ return exp.replace(/\\\n/g, ' ');
8963
+ }
8964
+
8965
+ function addFeatureExpressionUtils(env) {
8942
8966
  Object.assign(env, {
8943
- round: function(val, dig) {
8944
- var k = 1;
8945
- if (!val && val !== 0) return val; // don't coerce null to 0
8946
- dig = dig | 0;
8947
- while(dig-- > 0) k *= 10;
8948
- return Math.round(val * k) / k;
8949
- },
8967
+ round: roundToDigits2,
8950
8968
  int_median: interpolated_median,
8951
8969
  sprintf: utils.format,
8952
8970
  blend: blend
@@ -9325,6 +9343,39 @@
9325
9343
  };
9326
9344
  }
9327
9345
 
9346
+ // Returns an object representing a layer in a JS expression
9347
+ function getLayerProxy(lyr, arcs) {
9348
+ var obj = {};
9349
+ var records = lyr.data ? lyr.data.getRecords() : null;
9350
+ var getters = {
9351
+ name: lyr.name,
9352
+ data: records,
9353
+ type: lyr.geometry_type
9354
+ };
9355
+ addGetters(obj, getters);
9356
+ addBBoxGetter(obj, lyr, arcs);
9357
+ obj.empty = function() {
9358
+ return getFeatureCount(lyr) === 0;
9359
+ };
9360
+ obj.size = function() {
9361
+ return getFeatureCount(lyr);
9362
+ };
9363
+ return obj;
9364
+ }
9365
+
9366
+ function addLayerGetters(ctx, lyr, arcs) {
9367
+ var layerProxy;
9368
+ addGetters(ctx, {
9369
+ layer_name: lyr.name || '', // consider removing this
9370
+ layer: function() {
9371
+ // init on first access (to avoid overhead if not used)
9372
+ if (!layerProxy) layerProxy = getLayerProxy(lyr, arcs);
9373
+ return layerProxy;
9374
+ }
9375
+ });
9376
+ return ctx;
9377
+ }
9378
+
9328
9379
  function addBBoxGetter(obj, lyr, arcs) {
9329
9380
  var bbox;
9330
9381
  addGetters(obj, {
@@ -9354,32 +9405,6 @@
9354
9405
  return bbox;
9355
9406
  }
9356
9407
 
9357
- // Returns an object representing a layer in a JS expression
9358
- function getLayerProxy(lyr, arcs) {
9359
- var obj = {};
9360
- var records = lyr.data ? lyr.data.getRecords() : null;
9361
- var getters = {
9362
- name: lyr.name,
9363
- data: records
9364
- };
9365
- addGetters(obj, getters);
9366
- addBBoxGetter(obj, lyr, arcs);
9367
- return obj;
9368
- }
9369
-
9370
- function addLayerGetters(ctx, lyr, arcs) {
9371
- var layerProxy;
9372
- addGetters(ctx, {
9373
- layer_name: lyr.name || '', // consider removing this
9374
- layer: function() {
9375
- // init on first access (to avoid overhead if not used)
9376
- if (!layerProxy) layerProxy = getLayerProxy(lyr, arcs);
9377
- return layerProxy;
9378
- }
9379
- });
9380
- return ctx;
9381
- }
9382
-
9383
9408
  // Returns a function to return a feature proxy by id
9384
9409
  // (the proxy appears as "this" or "$" in a feature expression)
9385
9410
  function initFeatureProxy(lyr, arcs, optsArg) {
@@ -9576,11 +9601,6 @@
9576
9601
  return compileFeatureExpression(exp, lyr, arcs, opts);
9577
9602
  }
9578
9603
 
9579
- function cleanExpression(exp) {
9580
- // workaround for problem in GNU Make v4: end-of-line backslashes inside
9581
- // quoted strings are left in the string (other shell environments remove them)
9582
- return exp.replace(/\\\n/g, ' ');
9583
- }
9584
9604
 
9585
9605
  function compileFeaturePairFilterExpression(exp, lyr, arcs) {
9586
9606
  var func = compileFeaturePairExpression(exp, lyr, arcs);
@@ -9716,14 +9736,19 @@
9716
9736
 
9717
9737
  function compileExpressionToFunction(exp, opts) {
9718
9738
  // $$ added to avoid duplication with data field variables (an error condition)
9719
- var functionBody = "with($$env){with($$record){ " + (opts.returns ? 'return ' : '') +
9720
- exp + "}}";
9721
- var func;
9739
+ var functionBody, func;
9740
+ if (opts.returns) {
9741
+ // functionBody = 'return ' + functionBody;
9742
+ functionBody = 'var $$retn = ' + exp + '; return $$retn;';
9743
+ } else {
9744
+ functionBody = exp;
9745
+ }
9746
+ functionBody = 'with($$env){with($$record){ ' + functionBody + '}}';
9722
9747
  try {
9723
- func = new Function("$$record,$$env", functionBody);
9748
+ func = new Function('$$record,$$env', functionBody);
9724
9749
  } catch(e) {
9725
9750
  // if (opts.quiet) throw e;
9726
- stop(e.name, "in expression [" + exp + "]");
9751
+ stop(e.name, 'in expression [' + exp + ']');
9727
9752
  }
9728
9753
  return func;
9729
9754
  }
@@ -9766,7 +9791,7 @@
9766
9791
  var ctx = {};
9767
9792
  var fields = lyr.data ? lyr.data.getFields() : [];
9768
9793
  opts = opts || {};
9769
- addUtils(env); // mix in round(), sprintf(), etc.
9794
+ addFeatureExpressionUtils(env); // mix in round(), sprintf(), etc.
9770
9795
  if (fields.length > 0) {
9771
9796
  // default to null values, so assignments to missing data properties
9772
9797
  // are applied to the data record, not the global object
@@ -9821,7 +9846,6 @@
9821
9846
  var Expressions = /*#__PURE__*/Object.freeze({
9822
9847
  __proto__: null,
9823
9848
  compileValueExpression: compileValueExpression,
9824
- cleanExpression: cleanExpression,
9825
9849
  compileFeaturePairFilterExpression: compileFeaturePairFilterExpression,
9826
9850
  compileFeaturePairExpression: compileFeaturePairExpression,
9827
9851
  compileFeatureExpression: compileFeatureExpression,
@@ -13712,6 +13736,9 @@
13712
13736
  flipped: 'boolean',
13713
13737
  rotated: 'boolean',
13714
13738
  direction: 'number',
13739
+ sides: 'number', // polygons and stars
13740
+ points: 'number', // polygons and stars
13741
+ anchor: null, // arrows; takes start, middle, end
13715
13742
  'head-angle': 'number',
13716
13743
  'head-width': 'number',
13717
13744
  'head-length': 'number',
@@ -18561,6 +18588,13 @@ ${svg}
18561
18588
  return this;
18562
18589
  };
18563
18590
 
18591
+ this.options = function(o) {
18592
+ Object.keys(o).forEach(function(key) {
18593
+ this.option(key, o[key]);
18594
+ }, this);
18595
+ return this;
18596
+ };
18597
+
18564
18598
  this.done = function() {
18565
18599
  return _command;
18566
18600
  };
@@ -19524,14 +19558,6 @@ ${svg}
19524
19558
  .option('target', targetOpt)
19525
19559
  .option('no-replace', noReplaceOpt);
19526
19560
 
19527
- parser.command('ignore')
19528
- // .describe('stop processing if a condition is met')
19529
- .option('empty', {
19530
- describe: 'ignore empty files',
19531
- type: 'flag'
19532
- })
19533
- .option('target', targetOpt);
19534
-
19535
19561
  parser.command('include')
19536
19562
  .describe('import JS data and functions for use in JS expressions')
19537
19563
  .option('file', {
@@ -20111,16 +20137,17 @@ ${svg}
20111
20137
  type: 'distance'
20112
20138
  })
20113
20139
  .option('sides', {
20114
- describe: 'sides of a polygon or star symbol',
20140
+ describe: 'number of sides of a polygon symbol',
20115
20141
  type: 'number'
20116
20142
  })
20117
20143
  .option('orientation', {
20144
+ // TODO: removed (replaced by flipped and rotated)
20118
20145
  // describe: 'use orientation=b for a rotated or flipped orientation'
20119
20146
  })
20120
- .option('flipped', {
20121
- type: 'flag',
20122
- describe: 'symbol is vertically flipped'
20123
- })
20147
+ // .option('flipped', {
20148
+ // type: 'flag',
20149
+ // describe: 'symbol is vertically flipped'
20150
+ // })
20124
20151
  .option('rotated', {
20125
20152
  type: 'flag',
20126
20153
  describe: 'symbol is rotated to a different orientation'
@@ -20128,8 +20155,8 @@ ${svg}
20128
20155
  .option('rotation', {
20129
20156
  describe: 'rotation of symbol in degrees'
20130
20157
  })
20131
- .option('length', {
20132
- // alias for arrow-length
20158
+ .option('points', {
20159
+ describe: '(star) number of points'
20133
20160
  })
20134
20161
  .option('point-ratio', {
20135
20162
  old_alias: 'star-ratio',
@@ -20183,6 +20210,9 @@ ${svg}
20183
20210
  describe: '(arrow) min ratio of stem to total length',
20184
20211
  type: 'number'
20185
20212
  })
20213
+ .option('anchor', {
20214
+ describe: '(arrow) takes one of: start, middle, end (default is start)'
20215
+ })
20186
20216
  .option('stroke', {})
20187
20217
  .option('stroke-width', {})
20188
20218
  .option('fill', {
@@ -20490,6 +20520,49 @@ ${svg}
20490
20520
  })
20491
20521
  .option('target', targetOpt);
20492
20522
 
20523
+ parser.section('Control flow commands');
20524
+
20525
+ var ifOpts = {
20526
+ expression: {
20527
+ DEFAULT: true,
20528
+ describe: 'JS expression targeting a single layer'
20529
+ },
20530
+ empty: {
20531
+ describe: 'run if layer is empty',
20532
+ type: 'flag'
20533
+ },
20534
+ 'not-empty': {
20535
+ describe: 'run if layer is not empty',
20536
+ type: 'flag'
20537
+ },
20538
+ layer: {
20539
+ describe: 'name or id of layer to test (default is current target)'
20540
+ },
20541
+ target: targetOpt
20542
+ };
20543
+
20544
+ parser.command('if')
20545
+ .describe('run the following commands if a condition is met')
20546
+ .options(ifOpts);
20547
+
20548
+ parser.command('elif')
20549
+ .describe('test an alternate condition; used after -if')
20550
+ .options(ifOpts);
20551
+
20552
+ parser.command('else')
20553
+ .describe('run commands if all preceding -if/-elif conditions are false');
20554
+
20555
+ parser.command('endif')
20556
+ .describe('mark the end of an -if sequence');
20557
+
20558
+ parser.command('ignore')
20559
+ // .describe('stop processing if a condition is met')
20560
+ .option('empty', {
20561
+ describe: 'ignore empty files',
20562
+ type: 'flag'
20563
+ })
20564
+ .option('target', targetOpt);
20565
+
20493
20566
  parser.section('Informational commands');
20494
20567
 
20495
20568
  parser.command('calc')
@@ -34162,6 +34235,141 @@ ${svg}
34162
34235
  };
34163
34236
  }
34164
34237
 
34238
+ function resetControlFlow() {
34239
+ setStateVar('control', null);
34240
+ }
34241
+
34242
+ function inControlBlock() {
34243
+ var state = getState();
34244
+ return !!state.inControlBlock;
34245
+ }
34246
+
34247
+ function enterActiveBranch() {
34248
+ var state = getState();
34249
+ state.inControlBlock = true;
34250
+ state.active = true;
34251
+ state.complete = true;
34252
+ }
34253
+
34254
+ function enterInactiveBranch() {
34255
+ var state = getState();
34256
+ state.inControlBlock = true;
34257
+ state.active = false;
34258
+ }
34259
+
34260
+ function blockWasActive() {
34261
+ return !!getState().complete;
34262
+ }
34263
+
34264
+ function inActiveBranch() {
34265
+ return !!getState().active;
34266
+ }
34267
+
34268
+ function getState() {
34269
+ var o = getStateVar('control') || setStateVar('control', {}) || getStateVar('control');
34270
+ return o;
34271
+ }
34272
+
34273
+ function compileLayerExpression(expr, lyr, dataset, opts) {
34274
+ var proxy = getLayerProxy(lyr, dataset.arcs, opts);
34275
+ var exprOpts = Object.assign({returns: true}, opts);
34276
+ var func = compileExpressionToFunction(expr, exprOpts);
34277
+ var ctx = proxy;
34278
+ return function() {
34279
+ try {
34280
+ return func.call(proxy, {}, ctx);
34281
+ } catch(e) {
34282
+ // if (opts.quiet) throw e;
34283
+ stop(e.name, "in expression [" + expr + "]:", e.message);
34284
+ }
34285
+ };
34286
+ }
34287
+
34288
+ function skipCommand(cmdName) {
34289
+ // allow all control commands to run
34290
+ if (isControlFlowCommand(cmdName)) return false;
34291
+ return inControlBlock() && !inActiveBranch();
34292
+ }
34293
+
34294
+ cmd.if = function(catalog, opts) {
34295
+ if (inControlBlock()) {
34296
+ stop('Nested -if commands are not supported.');
34297
+ }
34298
+ evaluateIf(catalog, opts);
34299
+ };
34300
+
34301
+ cmd.elif = function(catalog, opts) {
34302
+ if (!inControlBlock()) {
34303
+ stop('-elif command must be preceded by an -if command.');
34304
+ }
34305
+ evaluateIf(catalog, opts);
34306
+ };
34307
+
34308
+ cmd.else = function() {
34309
+ if (!inControlBlock()) {
34310
+ stop('-else command must be preceded by an -if command.');
34311
+ }
34312
+ if (blockWasActive()) {
34313
+ enterInactiveBranch();
34314
+ } else {
34315
+ enterActiveBranch();
34316
+ }
34317
+ };
34318
+
34319
+ cmd.endif = function() {
34320
+ if (!inControlBlock()) {
34321
+ stop('-endif command must be preceded by an -if command.');
34322
+ }
34323
+ resetControlFlow();
34324
+ };
34325
+
34326
+ function isControlFlowCommand(cmd) {
34327
+ return ['if','elif','else','endif'].includes(cmd);
34328
+ }
34329
+
34330
+ function testLayer(catalog, opts) {
34331
+ var targ = getTargetLayer(catalog, opts);
34332
+ if (opts.expression) {
34333
+ return compileLayerExpression(opts.expression, targ.layer, targ.dataset, opts)();
34334
+ }
34335
+ if (opts.empty) {
34336
+ return layerIsEmpty(targ.layer);
34337
+ }
34338
+ if (opts.not_empty) {
34339
+ return !layerIsEmpty(targ.layer);
34340
+ }
34341
+ return true;
34342
+ }
34343
+
34344
+ function evaluateIf(catalog, opts) {
34345
+ if (!blockWasActive() && testLayer(catalog, opts)) {
34346
+ enterActiveBranch();
34347
+ } else {
34348
+ enterInactiveBranch();
34349
+ }
34350
+ }
34351
+
34352
+ // layerId: optional layer identifier
34353
+ //
34354
+ function getTargetLayer(catalog, opts) {
34355
+ var layerId = opts.layer || opts.target;
34356
+ var targets = catalog.findCommandTargets(layerId);
34357
+ if (targets.length === 0) {
34358
+ if (layerId) {
34359
+ stop('Layer not found:', layerId);
34360
+ } else {
34361
+ stop('Missing a target layer.');
34362
+ }
34363
+ }
34364
+ if (targets.length > 1 || targets[0].layers.length > 1) {
34365
+ stop('Command requires a single target layer.');
34366
+ }
34367
+ return {
34368
+ layer: targets[0].layers[0],
34369
+ dataset: targets[0].dataset
34370
+ };
34371
+ }
34372
+
34165
34373
  cmd.ignore = function(targetLayer, dataset, opts) {
34166
34374
  if (opts.empty && layerIsEmpty(targetLayer)) {
34167
34375
  interrupt('Layer is empty, stopping processing');
@@ -34708,6 +34916,28 @@ ${svg}
34708
34916
  return findVertexIds(p2.x, p2.y, arcs);
34709
34917
  }
34710
34918
 
34919
+ // Given a location @p (e.g. corresponding to the mouse pointer location),
34920
+ // find the midpoint of two vertices on @shp suitable for inserting a new vertex,
34921
+ // but only if:
34922
+ // 1. point @p is closer to the midpoint than either adjacent vertex
34923
+ // 2. the segment containing @p is longer than a minimum distance in pixels.
34924
+ //
34925
+ function findInsertionPoint(p, shp, arcs, pixelSize) {
34926
+ var p2 = findNearestVertex(p[0], p[1], shp, arcs);
34927
+
34928
+ }
34929
+
34930
+ function snapVerticesToPoint(ids, p, arcs, final) {
34931
+ ids.forEach(function(idx) {
34932
+ setVertexCoords(p[0], p[1], idx, arcs);
34933
+ });
34934
+ if (final) {
34935
+ // kludge to get dataset to recalculate internal bounding boxes
34936
+ arcs.transformPoints(function() {});
34937
+ }
34938
+ }
34939
+
34940
+
34711
34941
  // p: point to snap
34712
34942
  // ids: ids of nearby vertices, possibly including an arc endpoint
34713
34943
  function snapPointToArcEndpoint(p, ids, arcs) {
@@ -34794,6 +35024,8 @@ ${svg}
34794
35024
  var VertexUtils = /*#__PURE__*/Object.freeze({
34795
35025
  __proto__: null,
34796
35026
  findNearestVertices: findNearestVertices,
35027
+ findInsertionPoint: findInsertionPoint,
35028
+ snapVerticesToPoint: snapVerticesToPoint,
34797
35029
  snapPointToArcEndpoint: snapPointToArcEndpoint,
34798
35030
  findVertexIds: findVertexIds,
34799
35031
  getVertexCoords: getVertexCoords,
@@ -38075,40 +38307,14 @@ ${svg}
38075
38307
  // return [a[0] + b[0], a[1] + b[1]];
38076
38308
  // }
38077
38309
 
38078
- function getFilledArrowCoords(d) {
38079
- var direction = d.rotation || d.direction || 0,
38080
- stemTaper = d['stem-taper'] || 0,
38081
- stemCurve = d['stem-curve'] || 0,
38082
- size = calcArrowSize(d);
38083
-
38084
- if (!size) return null;
38085
-
38086
- var headDx = size.headWidth / 2,
38087
- stemDx = size.stemWidth / 2,
38088
- baseDx = stemDx * (1 - stemTaper),
38089
- coords;
38090
-
38091
- if (!stemCurve || Math.abs(stemCurve) > 90) {
38092
- coords = calcStraightArrowCoords(size.stemLen, size.headLen, stemDx, headDx, baseDx);
38093
- } else {
38094
- if (direction > 0) stemCurve = -stemCurve;
38095
- coords = getCurvedArrowCoords(size.stemLen, size.headLen, stemCurve, stemDx, headDx, baseDx);
38096
- }
38097
-
38098
- rotateCoords(coords, direction);
38099
- if (d.flipped) {
38100
- flipY(coords);
38101
- }
38102
- return [coords];
38103
- }
38104
-
38105
38310
  function calcStraightArrowCoords(stemLen, headLen, stemDx, headDx, baseDx) {
38106
38311
  return [[baseDx, 0], [stemDx, stemLen], [headDx, stemLen], [0, stemLen + headLen],
38107
38312
  [-headDx, stemLen], [-stemDx, stemLen], [-baseDx, 0], [baseDx, 0]];
38108
38313
  }
38109
38314
 
38110
38315
  function calcArrowSize(d) {
38111
- var totalLen = d.radius || d.length || d.r || 0,
38316
+ // don't display arrows with negative length
38317
+ var totalLen = Math.max(d.radius || d.length || d.r || 0, 0),
38112
38318
  scale = 1,
38113
38319
  o = initArrowSize(d); // calc several parameters
38114
38320
 
@@ -38168,21 +38374,55 @@ ${svg}
38168
38374
  return 1 / Math.tan(Math.PI * headAngle / 180 / 2) / 2;
38169
38375
  }
38170
38376
 
38171
- function getCurvedArrowCoords(stemLen, headLen, curvature, stemDx, headDx, baseDx) {
38377
+ function getFilledArrowCoords(d) {
38378
+ var direction = d.rotation || d.direction || 0,
38379
+ stemTaper = d['stem-taper'] || 0,
38380
+ curvature = d['stem-curve'] || 0,
38381
+ size = calcArrowSize(d);
38382
+ if (!size) return null;
38383
+ var stemLen = size.stemLen,
38384
+ headLen = size.headLen,
38385
+ headDx = size.headWidth / 2,
38386
+ stemDx = size.stemWidth / 2,
38387
+ baseDx = stemDx * (1 - stemTaper),
38388
+ head, stem, coords, dx, dy;
38389
+
38390
+ if (curvature) {
38391
+ if (direction > 0) curvature = -curvature;
38392
+ var theta = Math.abs(curvature) / 180 * Math.PI;
38393
+ var sign = curvature > 0 ? 1 : -1;
38394
+ var ax = baseDx * Math.cos(theta); // rotate arrow base
38395
+ var ay = baseDx * Math.sin(theta) * -sign;
38396
+ dx = stemLen * Math.sin(theta / 2) * sign;
38397
+ dy = stemLen * Math.cos(theta / 2);
38398
+ var leftStem = getCurvedStemCoords(-ax, -ay, -stemDx + dx, dy, theta);
38399
+ var rightStem = getCurvedStemCoords(ax, ay, stemDx + dx, dy, theta);
38400
+ stem = leftStem.concat(rightStem.reverse());
38401
+
38402
+ } else {
38403
+ dx = 0;
38404
+ dy = stemLen;
38405
+ stem = [[-baseDx, 0], [baseDx, 0], [baseDx, 0]];
38406
+ }
38407
+
38172
38408
  // coordinates go counter clockwise, starting from the leftmost head coordinate
38173
- var theta = Math.abs(curvature) / 180 * Math.PI;
38174
- var sign = curvature > 0 ? 1 : -1;
38175
- var dx = stemLen * Math.sin(theta / 2) * sign;
38176
- var dy = stemLen * Math.cos(theta / 2);
38177
- var head = [[stemDx + dx, dy], [headDx + dx, dy],
38178
- [dx, headLen + dy], [-headDx + dx, dy], [-stemDx + dx, dy]];
38179
- var ax = baseDx * Math.cos(theta); // rotate arrow base
38180
- var ay = baseDx * Math.sin(theta) * -sign;
38181
- var leftStem = getCurvedStemCoords(-ax, -ay, -stemDx + dx, dy, theta);
38182
- var rightStem = getCurvedStemCoords(ax, ay, stemDx + dx, dy, theta);
38183
- var stem = leftStem.concat(rightStem.reverse());
38184
- // stem.pop();
38185
- return stem.concat(head);
38409
+ head = [[stemDx + dx, dy], [headDx + dx, dy],
38410
+ [dx, headLen + dy], [-headDx + dx, dy], [-stemDx + dx, dy]];
38411
+
38412
+ coords = stem.concat(head);
38413
+ if (d.anchor == 'end') {
38414
+ scaleAndShiftCoords(coords, 1, [-dx, -dy - headLen]);
38415
+ } else if (d.anchor == 'middle') {
38416
+ // shift midpoint away from the head a bit for a more balanced placement
38417
+ // scaleAndShiftCoords(coords, 1, [-dx/2, (-dy - headLen)/2]);
38418
+ scaleAndShiftCoords(coords, 1, [-dx * 0.5, -dy * 0.5 - headLen * 0.25]);
38419
+ }
38420
+
38421
+ rotateCoords(coords, direction);
38422
+ if (d.flipped) {
38423
+ flipY(coords);
38424
+ }
38425
+ return [coords];
38186
38426
  }
38187
38427
 
38188
38428
  // ax, ay: point on the base
@@ -38219,43 +38459,31 @@ ${svg}
38219
38459
  return coords;
38220
38460
  }
38221
38461
 
38222
- // sides: e.g. 5-pointed star has 10 sides
38223
- // radius: distance from center to point
38224
- //
38225
38462
  function getPolygonCoords(d) {
38226
- var radius = d.radius || d.length || d.r;
38463
+ var radius = d.radius || d.length || d.r,
38464
+ sides = +d.sides || getSidesByType(d.type),
38465
+ rotated = sides % 2 == 1,
38466
+ coords = [],
38467
+ angle, b;
38468
+
38227
38469
  if (radius > 0 === false) return null;
38228
- var type = d.type;
38229
- var sides = +d.sides || getDefaultSides(type);
38230
- var isStar = type == 'star';
38231
- if (isStar && (sides < 6 || sides % 2 !== 0)) {
38232
- stop(`Invalid number of sides for a star (${sides})`);
38233
- } else if (sides >= 3 === false) {
38470
+ if (sides >= 3 === false) {
38234
38471
  stop(`Invalid number of sides (${sides})`);
38235
38472
  }
38236
- var coords = [],
38237
- angle = 360 / sides,
38238
- b = isStar ? 1 : 0.5,
38239
- theta, even, len;
38240
38473
  if (d.orientation == 'b' || d.flipped || d.rotated) {
38241
- b = 0;
38474
+ rotated = !rotated;
38242
38475
  }
38476
+ b = rotated ? 0 : 0.5;
38243
38477
  for (var i=0; i<sides; i++) {
38244
- even = i % 2 == 0;
38245
- len = radius;
38246
- if (isStar && even) {
38247
- len *= (d.star_ratio || 0.5);
38248
- }
38249
- theta = (i + b) * angle % 360;
38250
- coords.push(getPlanarSegmentEndpoint(0, 0, theta, len));
38478
+ angle = (i + b) / sides * 360;
38479
+ coords.push(getPlanarSegmentEndpoint(0, 0, angle, radius));
38251
38480
  }
38252
38481
  coords.push(coords[0].concat());
38253
38482
  return [coords];
38254
38483
  }
38255
38484
 
38256
- function getDefaultSides(type) {
38485
+ function getSidesByType(type) {
38257
38486
  return {
38258
- star: 10,
38259
38487
  circle: 72,
38260
38488
  triangle: 3,
38261
38489
  square: 4,
@@ -38268,6 +38496,52 @@ ${svg}
38268
38496
  }[type] || 4;
38269
38497
  }
38270
38498
 
38499
+ function getStarCoords(d) {
38500
+ var radius = d.radius || d.length || d.r,
38501
+ points = d.points || d.sides && d.sides / 2 || 5,
38502
+ sides = points * 2,
38503
+ minorRadius = getMinorRadius(points) * radius,
38504
+ b = d.orientation == 'b' || d.flipped || d.rotated ? 0 : 1,
38505
+ coords = [],
38506
+ angle, len;
38507
+
38508
+ if (radius > 0 === false) return null;
38509
+ if (points < 5) {
38510
+ stop(`Invalid number of points for a star (${points})`);
38511
+ }
38512
+ for (var i=0; i<sides; i++) {
38513
+ len = i % 2 == 0 ? minorRadius : radius;
38514
+ angle = (i + b) / sides * 360;
38515
+ coords.push(getPlanarSegmentEndpoint(0, 0, angle, len));
38516
+ }
38517
+ coords.push(coords[0].concat());
38518
+ return [coords];
38519
+ }
38520
+
38521
+ function getMinorRadius(points) {
38522
+ var innerAngle = 360 / points;
38523
+ var pointAngle = getDefaultPointAngle(points);
38524
+ var thetaA = Math.PI / 180 * innerAngle / 2;
38525
+ var thetaB = Math.PI / 180 * pointAngle / 2;
38526
+ var a = Math.tan(thetaB) / (Math.tan(thetaB) + Math.tan(thetaA));
38527
+ var c = a / Math.cos(thetaA);
38528
+ return c;
38529
+ }
38530
+
38531
+ function getDefaultPointAngle(points) {
38532
+ var minSkip = 1;
38533
+ var maxSkip = Math.ceil(points / 2) - 2;
38534
+ var skip = Math.floor((maxSkip + minSkip) / 2);
38535
+ return getPointAngle(points, skip);
38536
+ }
38537
+
38538
+ // skip: number of adjacent points to skip when drawing a segment
38539
+ function getPointAngle(points, skip) {
38540
+ var unitAngle = 360 / points;
38541
+ var centerAngle = unitAngle * (skip + 1);
38542
+ return 180 - centerAngle;
38543
+ }
38544
+
38271
38545
  // Returns GeoJSON MultiPolygon coords
38272
38546
  function getRingCoords(d) {
38273
38547
  var radii = parseRings(d.radii || '2');
@@ -38322,6 +38596,8 @@ ${svg}
38322
38596
  } else if (d.type == 'ring') {
38323
38597
  coords = getRingCoords(d);
38324
38598
  geojsonType = 'MultiPolygon';
38599
+ } else if (d.type == 'star') {
38600
+ coords = getStarCoords(d);
38325
38601
  } else {
38326
38602
  coords = getPolygonCoords(d);
38327
38603
  }
@@ -38816,8 +39092,13 @@ ${svg}
38816
39092
  targets,
38817
39093
  targetDataset,
38818
39094
  targetLayers,
39095
+ target,
38819
39096
  arcs;
38820
39097
 
39098
+ if (skipCommand(name)) {
39099
+ return done(null);
39100
+ }
39101
+
38821
39102
  try { // catch errors from synchronous functions
38822
39103
 
38823
39104
  T$1.start();
@@ -38984,6 +39265,14 @@ ${svg}
38984
39265
  outputLayers = targetDataset.layers; // kludge to allow layer naming below
38985
39266
  }
38986
39267
 
39268
+ } else if (name == 'if' || name == 'elif') {
39269
+ // target = findSingleTargetLayer(opts.layer, targets[0], catalog);
39270
+ // cmd[name](target.layer, target.dataset, opts);
39271
+ cmd[name](catalog, opts);
39272
+
39273
+ } else if (name == 'else' || name == 'endif') {
39274
+ cmd[name]();
39275
+
38987
39276
  } else if (name == 'ignore') {
38988
39277
  applyCommandToEachLayer(cmd.ignore, targetLayers, targetDataset, opts);
38989
39278
 
@@ -39216,6 +39505,7 @@ ${svg}
39216
39505
  });
39217
39506
  }
39218
39507
 
39508
+
39219
39509
  // Apply a command to an array of target layers
39220
39510
  function applyCommandToEachLayer(func, targetLayers) {
39221
39511
  var args = utils.toArray(arguments).slice(2);
@@ -39474,6 +39764,8 @@ ${svg}
39474
39764
  }
39475
39765
 
39476
39766
  function runParsedCommands2(commands, catalog, cb) {
39767
+ // resetting closes any unterminated -if blocks from a previous command sequence
39768
+ resetControlFlow();
39477
39769
  utils.reduceAsync(commands, catalog, nextCommand, cb);
39478
39770
 
39479
39771
  function nextCommand(catalog, cmd, next) {