mapshaper 0.7.31 → 0.7.33

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
@@ -32930,14 +32930,30 @@ ${svg}
32930
32930
  .option('method', {
32931
32931
  // hidden option (set by the paek/gaussian flags)
32932
32932
  })
32933
- .option('keep-corners', {
32934
- describe: 'preserve sharp corners where straight-line segments meet',
32935
- type: 'flag'
32936
- })
32937
32933
  .option('gain', {
32938
32934
  describe: 'shrinkage-correction (default 1; 0 = none, >1 exaggerates bends)',
32939
32935
  type: 'number'
32940
32936
  })
32937
+ .option('max-bend-angle', {
32938
+ describe: 'max bend between output segments in degrees (default is 8)',
32939
+ type: 'number'
32940
+ })
32941
+ .option('no-corners', {
32942
+ describe: 'round sharp corners instead of preserving them (on by default)',
32943
+ type: 'flag'
32944
+ })
32945
+ .option('corner-bias', {
32946
+ // undocumented: corner-preservation sensitivity (default 1). Its inverse
32947
+ // scales the min structural-run length, so corner-bias=0.5 doubles it (fewer
32948
+ // corners kept) and corner-bias=2 halves it (more corners kept).
32949
+ // corner-bias=0 turns corner preservation off entirely.
32950
+ type: 'number'
32951
+ })
32952
+ .option('prefilter-gate', {
32953
+ // undocumented: prefilter sinuosity (path/chord) threshold for cutting
32954
+ // intricate detail (default 4; higher removes less)
32955
+ type: 'number'
32956
+ })
32941
32957
  .option('planar', {
32942
32958
  // describe: 'smooth decimal degree coords in 2D space (default is spherical)',
32943
32959
  type: 'flag'
@@ -57264,7 +57280,7 @@ ${svg}
57264
57280
  // Returns {xx: [], yy: []}. Arc endpoints are always preserved, so shared
57265
57281
  // topology nodes stay put and the operation is topology-safe like -simplify.
57266
57282
  var DEFAULT_WEIGHTING = 0.7;
57267
- var DEFAULT_TORTUOSITY = 2;
57283
+ var DEFAULT_TORTUOSITY = 4;
57268
57284
  // How far (in detail-distances of arc length) the survivor-merge pass looks ahead
57269
57285
  // for a chord that closes a convoluted excursion. Bounds the pass to O(n) and
57270
57286
  // caps how long a thin spike it can slice in one merge.
@@ -62341,7 +62357,8 @@ ${svg}
62341
62357
  useSphericalSimplify: useSphericalSimplify
62342
62358
  });
62343
62359
 
62344
- // Structural-corner detection for -smooth's "keep-corners" option.
62360
+ // Structural-corner detection for -smooth's corner preservation (on by default;
62361
+ // disabled with no-corners or corner-bias=0).
62345
62362
  //
62346
62363
  // Many boundaries alternate between natural, freely-curving stretches (coast,
62347
62364
  // river centerline) and artificial straight-line segments (state/county
@@ -62377,14 +62394,18 @@ ${svg}
62377
62394
  var MIN_RUN_LEN_FACTOR = 1.0; // a structural run must be at least tol * this long
62378
62395
  var MIN_RUN_RADIUS_FACTOR = 1.0; // and bend no tighter than radius tol * this
62379
62396
 
62380
- function getCornerParams(tol) {
62397
+ // @cornerBias (optional, default 1) divides the min structural-run length, so a
62398
+ // value < 1 lengthens the run a corner must border to be preserved (fewer, only
62399
+ // well-supported corners), and a value > 1 shortens it (more corners kept).
62400
+ function getCornerParams(tol, cornerBias) {
62401
+ var bias = cornerBias > 0 ? cornerBias : 1;
62381
62402
  return {
62382
62403
  tol: tol,
62383
62404
  cornerAngle: CORNER_ANGLE,
62384
62405
  tangentWindow: TANGENT_WINDOW_FACTOR * tol,
62385
62406
  innerWindow: INNER_WINDOW_FACTOR * TANGENT_WINDOW_FACTOR * tol,
62386
62407
  concentration: CORNER_CONCENTRATION,
62387
- minRunLen: MIN_RUN_LEN_FACTOR * tol,
62408
+ minRunLen: MIN_RUN_LEN_FACTOR * tol / bias,
62388
62409
  maxTurnRate: 1 / (MIN_RUN_RADIUS_FACTOR * tol) // radians of turning per ground unit
62389
62410
  };
62390
62411
  }
@@ -62587,20 +62608,25 @@ ${svg}
62587
62608
  // chord * accumulatedTurn / 8 estimates the bow of a circular arc, and we also
62588
62609
  // cut when it exceeds a fraction of the tolerance. Both tests are O(1) per dense
62589
62610
  // vertex, so the pass stays O(n).
62590
- var DENSE_STEP_FACTOR = 0.033; // dense sampling step = tolerance * this. Must
62591
- // resolve the sharpest smoothed feature (radius
62592
- // ~ the kernel scale) finely enough that the
62593
- // angle filter can reach BEND_ANGLE joins: one
62594
- // dense segment turns ~ this/0.4 radians there,
62595
- // kept well under BEND_ANGLE so the discrete
62596
- // accumulation barely overshoots the threshold.
62597
- var BEND_ANGLE = 8 * Math.PI / 180; // keep a vertex after this much accumulated turn
62611
+ var DENSE_STEP_FACTOR = 0.033; // dense sampling step = tolerance * this at the
62612
+ // default bend angle. Must resolve the sharpest
62613
+ // smoothed feature (radius ~ the kernel scale)
62614
+ // finely enough that the angle filter can reach
62615
+ // the bend-angle joins: one dense segment turns
62616
+ // ~ this/0.4 radians there, kept well under the
62617
+ // bend angle so the discrete accumulation barely
62618
+ // overshoots the threshold. For a smaller-than-
62619
+ // default bend angle the step is refined in
62620
+ // proportion so the threshold stays reachable.
62621
+ var DEFAULT_BEND_ANGLE = 8 * Math.PI / 180; // keep a vertex after this much accumulated
62622
+ // turn (max turn between consecutive output
62623
+ // segments); user-overridable via max-bend-angle
62598
62624
  var DEVIATION_FACTOR = 0.1; // sagitta guard: also cut a gentle bend that bows
62599
62625
  // more than tolerance * this from its chord
62600
62626
 
62601
62627
  // Smooth a single arc's coordinates.
62602
62628
  // @xx, @yy: coordinate arrays (may be typed-array subarrays) for one arc.
62603
- // @opts: {tolerance, method, spherical, closed, keepCorners}
62629
+ // @opts: {tolerance, method, spherical, closed, keepCorners, gain, maxBendAngle}
62604
62630
  // Returns {xx: [], yy: []} with the smoothed coordinates. Endpoints of open
62605
62631
  // arcs are preserved exactly (so shared topology nodes stay put); closed arcs
62606
62632
  // are smoothed cyclically and returned closed (first point repeated at the end).
@@ -62615,6 +62641,24 @@ ${svg}
62615
62641
  return g >= 0 ? g : 0;
62616
62642
  }
62617
62643
 
62644
+ // Resolve the output bend-angle threshold from the user option (in degrees) to
62645
+ // radians. It caps the turn between consecutive output segments: a larger angle
62646
+ // keeps fewer vertices (coarser joins), a smaller one keeps more (smoother joins).
62647
+ // Non-positive or missing values fall back to the default.
62648
+ function resolveBendAngle(opts) {
62649
+ var deg = opts.maxBendAngle;
62650
+ if (deg === undefined || deg === null || !(deg > 0)) return DEFAULT_BEND_ANGLE;
62651
+ return deg * Math.PI / 180;
62652
+ }
62653
+
62654
+ // Resolve the keep-corners run-length bias (default 1). Its inverse scales the
62655
+ // min structural-run length, so a value < 1 protects only longer straight runs
62656
+ // (fewer corners kept) and a value > 1 protects shorter runs (more corners kept).
62657
+ function resolveCornerBias(opts) {
62658
+ var b = opts.cornerBias;
62659
+ return b > 0 ? b : 1;
62660
+ }
62661
+
62618
62662
  function smoothArcCoords(xx, yy, opts) {
62619
62663
  var n = xx.length;
62620
62664
  var origX = toArray(xx);
@@ -62627,12 +62671,19 @@ ${svg}
62627
62671
  var closed = !!opts.closed;
62628
62672
  var spherical = !!opts.spherical;
62629
62673
  var keepCorners = !!opts.keepCorners;
62674
+ var bendAngle = resolveBendAngle(opts);
62630
62675
  var ctx = {
62631
62676
  tol: tol,
62632
62677
  method: method,
62633
62678
  spherical: spherical,
62634
62679
  keepCorners: keepCorners,
62680
+ cornerBias: resolveCornerBias(opts),
62635
62681
  gain: resolveGain(opts),
62682
+ bendAngle: bendAngle,
62683
+ // Refine the dense step for a smaller-than-default bend angle so one dense
62684
+ // segment still turns well under the threshold; never coarsen it beyond the
62685
+ // default (the angle filter alone thins the output for larger angles).
62686
+ denseStep: tol * DENSE_STEP_FACTOR * Math.min(1, bendAngle / DEFAULT_BEND_ANGLE),
62636
62687
  radius: tol * WINDOW_RADIUS_FACTOR,
62637
62688
  scale: (method == 'gaussian' ? GAUSSIAN_SIGMA_FACTOR : PAEK_SCALE_FACTOR) * tol
62638
62689
  };
@@ -62647,7 +62698,7 @@ ${svg}
62647
62698
 
62648
62699
  if (closed) {
62649
62700
  var corners = keepCorners ?
62650
- findInteriorCorners(t, channels, n, true, getCornerParams(tol)) : [];
62701
+ findInteriorCorners(t, channels, n, true, getCornerParams(tol, ctx.cornerBias)) : [];
62651
62702
  if (corners.length === 0) {
62652
62703
  return smoothClosedCyclic(t, channels, n, ctx);
62653
62704
  }
@@ -62664,7 +62715,7 @@ ${svg}
62664
62715
  }
62665
62716
 
62666
62717
  var openBreaks = keepCorners ?
62667
- findInteriorCorners(t, channels, n, false, getCornerParams(tol)) : [];
62718
+ findInteriorCorners(t, channels, n, false, getCornerParams(tol, ctx.cornerBias)) : [];
62668
62719
  return smoothOpenSpans(origX, origY, t, channels, n, openBreaks, ctx);
62669
62720
  }
62670
62721
 
@@ -62676,9 +62727,9 @@ ${svg}
62676
62727
  var bounds = [0].concat(interiorBreaks);
62677
62728
  bounds.push(n - 1);
62678
62729
  if (ctx.keepCorners && bounds.length > 2) {
62679
- bounds = refineBounds(t, channels, bounds, getCornerParams(ctx.tol));
62730
+ bounds = refineBounds(t, channels, bounds, getCornerParams(ctx.tol, ctx.cornerBias));
62680
62731
  }
62681
- var params = ctx.keepCorners ? getCornerParams(ctx.tol) : null;
62732
+ var params = ctx.keepCorners ? getCornerParams(ctx.tol, ctx.cornerBias) : null;
62682
62733
  var xx = [], yy = [];
62683
62734
  for (var s = 0; s < bounds.length - 1; s++) {
62684
62735
  var lo = bounds[s], hi = bounds[s + 1];
@@ -62864,7 +62915,7 @@ ${svg}
62864
62915
  // "Output resampling" note above). Step 1 evaluates the smoother at a uniform
62865
62916
  // dense step; step 2 makes one forward pass keeping the endpoints plus every
62866
62917
  // interior vertex where the accumulated turn since the last kept vertex reaches
62867
- // BEND_ANGLE, or where the sagitta guard trips. @inputCount bounds the dense
62918
+ // the bend-angle threshold, or where the sagitta guard trips. @inputCount bounds the dense
62868
62919
  // sampling (and thus the output). Returns one array per channel, ordered by
62869
62920
  // increasing arc length, including both endpoints.
62870
62921
  function sampleSmoothedCurve(src, a, b, closed, ctx, inputCount) {
@@ -62873,7 +62924,7 @@ ${svg}
62873
62924
  var maxPoints = Math.max(inputCount, MIN_CLOSED_SEGMENTS) * MAX_OUTPUT_FACTOR;
62874
62925
 
62875
62926
  // 1. dense uniform sampling of the smoothed curve
62876
- var nDense = Math.ceil(span / (ctx.tol * DENSE_STEP_FACTOR)) + 1;
62927
+ var nDense = Math.ceil(span / ctx.denseStep) + 1;
62877
62928
  if (nDense < MIN_CLOSED_SEGMENTS + 1) nDense = MIN_CLOSED_SEGMENTS + 1;
62878
62929
  if (nDense > maxPoints) nDense = maxPoints;
62879
62930
  if (nDense < 2) nDense = 2;
@@ -62883,7 +62934,7 @@ ${svg}
62883
62934
  }
62884
62935
 
62885
62936
  // 2. one-pass bend-angle filter
62886
- var theta = BEND_ANGLE;
62937
+ var theta = ctx.bendAngle;
62887
62938
  var epsDev = ctx.tol * DEVIATION_FACTOR;
62888
62939
  var out = [];
62889
62940
  for (var c = 0; c < K; c++) out.push([]);
@@ -63180,6 +63231,20 @@ ${svg}
63180
63231
  if (opts.gain !== undefined && opts.gain !== null && !(opts.gain >= 0)) {
63181
63232
  stop$1('Expected gain to be a number >= 0');
63182
63233
  }
63234
+ if (opts.max_bend_angle !== undefined && opts.max_bend_angle !== null &&
63235
+ !(opts.max_bend_angle > 0)) {
63236
+ stop$1('Expected max-bend-angle to be a number > 0 (degrees)');
63237
+ }
63238
+ if (opts.prefilter_gate !== undefined && opts.prefilter_gate !== null &&
63239
+ !(opts.prefilter_gate > 0)) {
63240
+ stop$1('Expected prefilter-gate to be a number > 0');
63241
+ }
63242
+ if (opts.corner_bias !== undefined && opts.corner_bias !== null &&
63243
+ !(opts.corner_bias >= 0)) {
63244
+ stop$1('Expected corner-bias to be a number >= 0');
63245
+ }
63246
+ // Corner preservation is on by default; no-corners or corner-bias=0 turns it off.
63247
+ var keepCorners = !opts.no_corners && opts.corner_bias !== 0;
63183
63248
  var implicitlySmoothedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasPaths);
63184
63249
 
63185
63250
  // Smoothing rewrites coordinates, so lock in any pending (non-destructive)
@@ -63197,6 +63262,7 @@ ${svg}
63197
63262
  var before = arcs.getPointCount();
63198
63263
  filterDetailPaths(arcs, {
63199
63264
  distance: tolerance,
63265
+ tortuosity: opts.prefilter_gate,
63200
63266
  spherical: spherical
63201
63267
  });
63202
63268
  var removed = before - arcs.getPointCount();
@@ -63209,8 +63275,10 @@ ${svg}
63209
63275
  tolerance: tolerance,
63210
63276
  method: method,
63211
63277
  spherical: spherical,
63212
- keepCorners: !!opts.keep_corners,
63213
- gain: opts.gain
63278
+ keepCorners: keepCorners,
63279
+ cornerBias: opts.corner_bias,
63280
+ gain: opts.gain,
63281
+ maxBendAngle: opts.max_bend_angle
63214
63282
  });
63215
63283
 
63216
63284
  if (implicitlySmoothedNames.length > 0) {
@@ -63236,7 +63304,9 @@ ${svg}
63236
63304
  method: opts.method,
63237
63305
  spherical: opts.spherical,
63238
63306
  keepCorners: opts.keepCorners,
63307
+ cornerBias: opts.cornerBias,
63239
63308
  gain: opts.gain,
63309
+ maxBendAngle: opts.maxBendAngle,
63240
63310
  closed: arcs.arcIsClosed(arcId)
63241
63311
  });
63242
63312
  nn.push(res.xx.length);
@@ -65053,7 +65123,7 @@ ${svg}
65053
65123
  return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
65054
65124
  }
65055
65125
 
65056
- var version = "0.7.31";
65126
+ var version = "0.7.33";
65057
65127
 
65058
65128
  // Parse command line args into commands and run them
65059
65129
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.7.31",
3
+ "version": "0.7.33",
4
4
  "description": "A tool for editing geospatial data for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
package/www/mapshaper.js CHANGED
@@ -32930,14 +32930,30 @@ ${svg}
32930
32930
  .option('method', {
32931
32931
  // hidden option (set by the paek/gaussian flags)
32932
32932
  })
32933
- .option('keep-corners', {
32934
- describe: 'preserve sharp corners where straight-line segments meet',
32935
- type: 'flag'
32936
- })
32937
32933
  .option('gain', {
32938
32934
  describe: 'shrinkage-correction (default 1; 0 = none, >1 exaggerates bends)',
32939
32935
  type: 'number'
32940
32936
  })
32937
+ .option('max-bend-angle', {
32938
+ describe: 'max bend between output segments in degrees (default is 8)',
32939
+ type: 'number'
32940
+ })
32941
+ .option('no-corners', {
32942
+ describe: 'round sharp corners instead of preserving them (on by default)',
32943
+ type: 'flag'
32944
+ })
32945
+ .option('corner-bias', {
32946
+ // undocumented: corner-preservation sensitivity (default 1). Its inverse
32947
+ // scales the min structural-run length, so corner-bias=0.5 doubles it (fewer
32948
+ // corners kept) and corner-bias=2 halves it (more corners kept).
32949
+ // corner-bias=0 turns corner preservation off entirely.
32950
+ type: 'number'
32951
+ })
32952
+ .option('prefilter-gate', {
32953
+ // undocumented: prefilter sinuosity (path/chord) threshold for cutting
32954
+ // intricate detail (default 4; higher removes less)
32955
+ type: 'number'
32956
+ })
32941
32957
  .option('planar', {
32942
32958
  // describe: 'smooth decimal degree coords in 2D space (default is spherical)',
32943
32959
  type: 'flag'
@@ -57264,7 +57280,7 @@ ${svg}
57264
57280
  // Returns {xx: [], yy: []}. Arc endpoints are always preserved, so shared
57265
57281
  // topology nodes stay put and the operation is topology-safe like -simplify.
57266
57282
  var DEFAULT_WEIGHTING = 0.7;
57267
- var DEFAULT_TORTUOSITY = 2;
57283
+ var DEFAULT_TORTUOSITY = 4;
57268
57284
  // How far (in detail-distances of arc length) the survivor-merge pass looks ahead
57269
57285
  // for a chord that closes a convoluted excursion. Bounds the pass to O(n) and
57270
57286
  // caps how long a thin spike it can slice in one merge.
@@ -62341,7 +62357,8 @@ ${svg}
62341
62357
  useSphericalSimplify: useSphericalSimplify
62342
62358
  });
62343
62359
 
62344
- // Structural-corner detection for -smooth's "keep-corners" option.
62360
+ // Structural-corner detection for -smooth's corner preservation (on by default;
62361
+ // disabled with no-corners or corner-bias=0).
62345
62362
  //
62346
62363
  // Many boundaries alternate between natural, freely-curving stretches (coast,
62347
62364
  // river centerline) and artificial straight-line segments (state/county
@@ -62377,14 +62394,18 @@ ${svg}
62377
62394
  var MIN_RUN_LEN_FACTOR = 1.0; // a structural run must be at least tol * this long
62378
62395
  var MIN_RUN_RADIUS_FACTOR = 1.0; // and bend no tighter than radius tol * this
62379
62396
 
62380
- function getCornerParams(tol) {
62397
+ // @cornerBias (optional, default 1) divides the min structural-run length, so a
62398
+ // value < 1 lengthens the run a corner must border to be preserved (fewer, only
62399
+ // well-supported corners), and a value > 1 shortens it (more corners kept).
62400
+ function getCornerParams(tol, cornerBias) {
62401
+ var bias = cornerBias > 0 ? cornerBias : 1;
62381
62402
  return {
62382
62403
  tol: tol,
62383
62404
  cornerAngle: CORNER_ANGLE,
62384
62405
  tangentWindow: TANGENT_WINDOW_FACTOR * tol,
62385
62406
  innerWindow: INNER_WINDOW_FACTOR * TANGENT_WINDOW_FACTOR * tol,
62386
62407
  concentration: CORNER_CONCENTRATION,
62387
- minRunLen: MIN_RUN_LEN_FACTOR * tol,
62408
+ minRunLen: MIN_RUN_LEN_FACTOR * tol / bias,
62388
62409
  maxTurnRate: 1 / (MIN_RUN_RADIUS_FACTOR * tol) // radians of turning per ground unit
62389
62410
  };
62390
62411
  }
@@ -62587,20 +62608,25 @@ ${svg}
62587
62608
  // chord * accumulatedTurn / 8 estimates the bow of a circular arc, and we also
62588
62609
  // cut when it exceeds a fraction of the tolerance. Both tests are O(1) per dense
62589
62610
  // vertex, so the pass stays O(n).
62590
- var DENSE_STEP_FACTOR = 0.033; // dense sampling step = tolerance * this. Must
62591
- // resolve the sharpest smoothed feature (radius
62592
- // ~ the kernel scale) finely enough that the
62593
- // angle filter can reach BEND_ANGLE joins: one
62594
- // dense segment turns ~ this/0.4 radians there,
62595
- // kept well under BEND_ANGLE so the discrete
62596
- // accumulation barely overshoots the threshold.
62597
- var BEND_ANGLE = 8 * Math.PI / 180; // keep a vertex after this much accumulated turn
62611
+ var DENSE_STEP_FACTOR = 0.033; // dense sampling step = tolerance * this at the
62612
+ // default bend angle. Must resolve the sharpest
62613
+ // smoothed feature (radius ~ the kernel scale)
62614
+ // finely enough that the angle filter can reach
62615
+ // the bend-angle joins: one dense segment turns
62616
+ // ~ this/0.4 radians there, kept well under the
62617
+ // bend angle so the discrete accumulation barely
62618
+ // overshoots the threshold. For a smaller-than-
62619
+ // default bend angle the step is refined in
62620
+ // proportion so the threshold stays reachable.
62621
+ var DEFAULT_BEND_ANGLE = 8 * Math.PI / 180; // keep a vertex after this much accumulated
62622
+ // turn (max turn between consecutive output
62623
+ // segments); user-overridable via max-bend-angle
62598
62624
  var DEVIATION_FACTOR = 0.1; // sagitta guard: also cut a gentle bend that bows
62599
62625
  // more than tolerance * this from its chord
62600
62626
 
62601
62627
  // Smooth a single arc's coordinates.
62602
62628
  // @xx, @yy: coordinate arrays (may be typed-array subarrays) for one arc.
62603
- // @opts: {tolerance, method, spherical, closed, keepCorners}
62629
+ // @opts: {tolerance, method, spherical, closed, keepCorners, gain, maxBendAngle}
62604
62630
  // Returns {xx: [], yy: []} with the smoothed coordinates. Endpoints of open
62605
62631
  // arcs are preserved exactly (so shared topology nodes stay put); closed arcs
62606
62632
  // are smoothed cyclically and returned closed (first point repeated at the end).
@@ -62615,6 +62641,24 @@ ${svg}
62615
62641
  return g >= 0 ? g : 0;
62616
62642
  }
62617
62643
 
62644
+ // Resolve the output bend-angle threshold from the user option (in degrees) to
62645
+ // radians. It caps the turn between consecutive output segments: a larger angle
62646
+ // keeps fewer vertices (coarser joins), a smaller one keeps more (smoother joins).
62647
+ // Non-positive or missing values fall back to the default.
62648
+ function resolveBendAngle(opts) {
62649
+ var deg = opts.maxBendAngle;
62650
+ if (deg === undefined || deg === null || !(deg > 0)) return DEFAULT_BEND_ANGLE;
62651
+ return deg * Math.PI / 180;
62652
+ }
62653
+
62654
+ // Resolve the keep-corners run-length bias (default 1). Its inverse scales the
62655
+ // min structural-run length, so a value < 1 protects only longer straight runs
62656
+ // (fewer corners kept) and a value > 1 protects shorter runs (more corners kept).
62657
+ function resolveCornerBias(opts) {
62658
+ var b = opts.cornerBias;
62659
+ return b > 0 ? b : 1;
62660
+ }
62661
+
62618
62662
  function smoothArcCoords(xx, yy, opts) {
62619
62663
  var n = xx.length;
62620
62664
  var origX = toArray(xx);
@@ -62627,12 +62671,19 @@ ${svg}
62627
62671
  var closed = !!opts.closed;
62628
62672
  var spherical = !!opts.spherical;
62629
62673
  var keepCorners = !!opts.keepCorners;
62674
+ var bendAngle = resolveBendAngle(opts);
62630
62675
  var ctx = {
62631
62676
  tol: tol,
62632
62677
  method: method,
62633
62678
  spherical: spherical,
62634
62679
  keepCorners: keepCorners,
62680
+ cornerBias: resolveCornerBias(opts),
62635
62681
  gain: resolveGain(opts),
62682
+ bendAngle: bendAngle,
62683
+ // Refine the dense step for a smaller-than-default bend angle so one dense
62684
+ // segment still turns well under the threshold; never coarsen it beyond the
62685
+ // default (the angle filter alone thins the output for larger angles).
62686
+ denseStep: tol * DENSE_STEP_FACTOR * Math.min(1, bendAngle / DEFAULT_BEND_ANGLE),
62636
62687
  radius: tol * WINDOW_RADIUS_FACTOR,
62637
62688
  scale: (method == 'gaussian' ? GAUSSIAN_SIGMA_FACTOR : PAEK_SCALE_FACTOR) * tol
62638
62689
  };
@@ -62647,7 +62698,7 @@ ${svg}
62647
62698
 
62648
62699
  if (closed) {
62649
62700
  var corners = keepCorners ?
62650
- findInteriorCorners(t, channels, n, true, getCornerParams(tol)) : [];
62701
+ findInteriorCorners(t, channels, n, true, getCornerParams(tol, ctx.cornerBias)) : [];
62651
62702
  if (corners.length === 0) {
62652
62703
  return smoothClosedCyclic(t, channels, n, ctx);
62653
62704
  }
@@ -62664,7 +62715,7 @@ ${svg}
62664
62715
  }
62665
62716
 
62666
62717
  var openBreaks = keepCorners ?
62667
- findInteriorCorners(t, channels, n, false, getCornerParams(tol)) : [];
62718
+ findInteriorCorners(t, channels, n, false, getCornerParams(tol, ctx.cornerBias)) : [];
62668
62719
  return smoothOpenSpans(origX, origY, t, channels, n, openBreaks, ctx);
62669
62720
  }
62670
62721
 
@@ -62676,9 +62727,9 @@ ${svg}
62676
62727
  var bounds = [0].concat(interiorBreaks);
62677
62728
  bounds.push(n - 1);
62678
62729
  if (ctx.keepCorners && bounds.length > 2) {
62679
- bounds = refineBounds(t, channels, bounds, getCornerParams(ctx.tol));
62730
+ bounds = refineBounds(t, channels, bounds, getCornerParams(ctx.tol, ctx.cornerBias));
62680
62731
  }
62681
- var params = ctx.keepCorners ? getCornerParams(ctx.tol) : null;
62732
+ var params = ctx.keepCorners ? getCornerParams(ctx.tol, ctx.cornerBias) : null;
62682
62733
  var xx = [], yy = [];
62683
62734
  for (var s = 0; s < bounds.length - 1; s++) {
62684
62735
  var lo = bounds[s], hi = bounds[s + 1];
@@ -62864,7 +62915,7 @@ ${svg}
62864
62915
  // "Output resampling" note above). Step 1 evaluates the smoother at a uniform
62865
62916
  // dense step; step 2 makes one forward pass keeping the endpoints plus every
62866
62917
  // interior vertex where the accumulated turn since the last kept vertex reaches
62867
- // BEND_ANGLE, or where the sagitta guard trips. @inputCount bounds the dense
62918
+ // the bend-angle threshold, or where the sagitta guard trips. @inputCount bounds the dense
62868
62919
  // sampling (and thus the output). Returns one array per channel, ordered by
62869
62920
  // increasing arc length, including both endpoints.
62870
62921
  function sampleSmoothedCurve(src, a, b, closed, ctx, inputCount) {
@@ -62873,7 +62924,7 @@ ${svg}
62873
62924
  var maxPoints = Math.max(inputCount, MIN_CLOSED_SEGMENTS) * MAX_OUTPUT_FACTOR;
62874
62925
 
62875
62926
  // 1. dense uniform sampling of the smoothed curve
62876
- var nDense = Math.ceil(span / (ctx.tol * DENSE_STEP_FACTOR)) + 1;
62927
+ var nDense = Math.ceil(span / ctx.denseStep) + 1;
62877
62928
  if (nDense < MIN_CLOSED_SEGMENTS + 1) nDense = MIN_CLOSED_SEGMENTS + 1;
62878
62929
  if (nDense > maxPoints) nDense = maxPoints;
62879
62930
  if (nDense < 2) nDense = 2;
@@ -62883,7 +62934,7 @@ ${svg}
62883
62934
  }
62884
62935
 
62885
62936
  // 2. one-pass bend-angle filter
62886
- var theta = BEND_ANGLE;
62937
+ var theta = ctx.bendAngle;
62887
62938
  var epsDev = ctx.tol * DEVIATION_FACTOR;
62888
62939
  var out = [];
62889
62940
  for (var c = 0; c < K; c++) out.push([]);
@@ -63180,6 +63231,20 @@ ${svg}
63180
63231
  if (opts.gain !== undefined && opts.gain !== null && !(opts.gain >= 0)) {
63181
63232
  stop$1('Expected gain to be a number >= 0');
63182
63233
  }
63234
+ if (opts.max_bend_angle !== undefined && opts.max_bend_angle !== null &&
63235
+ !(opts.max_bend_angle > 0)) {
63236
+ stop$1('Expected max-bend-angle to be a number > 0 (degrees)');
63237
+ }
63238
+ if (opts.prefilter_gate !== undefined && opts.prefilter_gate !== null &&
63239
+ !(opts.prefilter_gate > 0)) {
63240
+ stop$1('Expected prefilter-gate to be a number > 0');
63241
+ }
63242
+ if (opts.corner_bias !== undefined && opts.corner_bias !== null &&
63243
+ !(opts.corner_bias >= 0)) {
63244
+ stop$1('Expected corner-bias to be a number >= 0');
63245
+ }
63246
+ // Corner preservation is on by default; no-corners or corner-bias=0 turns it off.
63247
+ var keepCorners = !opts.no_corners && opts.corner_bias !== 0;
63183
63248
  var implicitlySmoothedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasPaths);
63184
63249
 
63185
63250
  // Smoothing rewrites coordinates, so lock in any pending (non-destructive)
@@ -63197,6 +63262,7 @@ ${svg}
63197
63262
  var before = arcs.getPointCount();
63198
63263
  filterDetailPaths(arcs, {
63199
63264
  distance: tolerance,
63265
+ tortuosity: opts.prefilter_gate,
63200
63266
  spherical: spherical
63201
63267
  });
63202
63268
  var removed = before - arcs.getPointCount();
@@ -63209,8 +63275,10 @@ ${svg}
63209
63275
  tolerance: tolerance,
63210
63276
  method: method,
63211
63277
  spherical: spherical,
63212
- keepCorners: !!opts.keep_corners,
63213
- gain: opts.gain
63278
+ keepCorners: keepCorners,
63279
+ cornerBias: opts.corner_bias,
63280
+ gain: opts.gain,
63281
+ maxBendAngle: opts.max_bend_angle
63214
63282
  });
63215
63283
 
63216
63284
  if (implicitlySmoothedNames.length > 0) {
@@ -63236,7 +63304,9 @@ ${svg}
63236
63304
  method: opts.method,
63237
63305
  spherical: opts.spherical,
63238
63306
  keepCorners: opts.keepCorners,
63307
+ cornerBias: opts.cornerBias,
63239
63308
  gain: opts.gain,
63309
+ maxBendAngle: opts.maxBendAngle,
63240
63310
  closed: arcs.arcIsClosed(arcId)
63241
63311
  });
63242
63312
  nn.push(res.xx.length);
@@ -65053,7 +65123,7 @@ ${svg}
65053
65123
  return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
65054
65124
  }
65055
65125
 
65056
- var version = "0.7.31";
65126
+ var version = "0.7.33";
65057
65127
 
65058
65128
  // Parse command line args into commands and run them
65059
65129
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.