mapshaper 0.7.31 → 0.7.32

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
@@ -32938,6 +32938,10 @@ ${svg}
32938
32938
  describe: 'shrinkage-correction (default 1; 0 = none, >1 exaggerates bends)',
32939
32939
  type: 'number'
32940
32940
  })
32941
+ .option('max-bend-angle', {
32942
+ describe: 'max bend between output segments in degrees (default is 8)',
32943
+ type: 'number'
32944
+ })
32941
32945
  .option('planar', {
32942
32946
  // describe: 'smooth decimal degree coords in 2D space (default is spherical)',
32943
32947
  type: 'flag'
@@ -62587,20 +62591,25 @@ ${svg}
62587
62591
  // chord * accumulatedTurn / 8 estimates the bow of a circular arc, and we also
62588
62592
  // cut when it exceeds a fraction of the tolerance. Both tests are O(1) per dense
62589
62593
  // 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
62594
+ var DENSE_STEP_FACTOR = 0.033; // dense sampling step = tolerance * this at the
62595
+ // default bend angle. Must resolve the sharpest
62596
+ // smoothed feature (radius ~ the kernel scale)
62597
+ // finely enough that the angle filter can reach
62598
+ // the bend-angle joins: one dense segment turns
62599
+ // ~ this/0.4 radians there, kept well under the
62600
+ // bend angle so the discrete accumulation barely
62601
+ // overshoots the threshold. For a smaller-than-
62602
+ // default bend angle the step is refined in
62603
+ // proportion so the threshold stays reachable.
62604
+ var DEFAULT_BEND_ANGLE = 8 * Math.PI / 180; // keep a vertex after this much accumulated
62605
+ // turn (max turn between consecutive output
62606
+ // segments); user-overridable via max-bend-angle
62598
62607
  var DEVIATION_FACTOR = 0.1; // sagitta guard: also cut a gentle bend that bows
62599
62608
  // more than tolerance * this from its chord
62600
62609
 
62601
62610
  // Smooth a single arc's coordinates.
62602
62611
  // @xx, @yy: coordinate arrays (may be typed-array subarrays) for one arc.
62603
- // @opts: {tolerance, method, spherical, closed, keepCorners}
62612
+ // @opts: {tolerance, method, spherical, closed, keepCorners, gain, maxBendAngle}
62604
62613
  // Returns {xx: [], yy: []} with the smoothed coordinates. Endpoints of open
62605
62614
  // arcs are preserved exactly (so shared topology nodes stay put); closed arcs
62606
62615
  // are smoothed cyclically and returned closed (first point repeated at the end).
@@ -62615,6 +62624,16 @@ ${svg}
62615
62624
  return g >= 0 ? g : 0;
62616
62625
  }
62617
62626
 
62627
+ // Resolve the output bend-angle threshold from the user option (in degrees) to
62628
+ // radians. It caps the turn between consecutive output segments: a larger angle
62629
+ // keeps fewer vertices (coarser joins), a smaller one keeps more (smoother joins).
62630
+ // Non-positive or missing values fall back to the default.
62631
+ function resolveBendAngle(opts) {
62632
+ var deg = opts.maxBendAngle;
62633
+ if (deg === undefined || deg === null || !(deg > 0)) return DEFAULT_BEND_ANGLE;
62634
+ return deg * Math.PI / 180;
62635
+ }
62636
+
62618
62637
  function smoothArcCoords(xx, yy, opts) {
62619
62638
  var n = xx.length;
62620
62639
  var origX = toArray(xx);
@@ -62627,12 +62646,18 @@ ${svg}
62627
62646
  var closed = !!opts.closed;
62628
62647
  var spherical = !!opts.spherical;
62629
62648
  var keepCorners = !!opts.keepCorners;
62649
+ var bendAngle = resolveBendAngle(opts);
62630
62650
  var ctx = {
62631
62651
  tol: tol,
62632
62652
  method: method,
62633
62653
  spherical: spherical,
62634
62654
  keepCorners: keepCorners,
62635
62655
  gain: resolveGain(opts),
62656
+ bendAngle: bendAngle,
62657
+ // Refine the dense step for a smaller-than-default bend angle so one dense
62658
+ // segment still turns well under the threshold; never coarsen it beyond the
62659
+ // default (the angle filter alone thins the output for larger angles).
62660
+ denseStep: tol * DENSE_STEP_FACTOR * Math.min(1, bendAngle / DEFAULT_BEND_ANGLE),
62636
62661
  radius: tol * WINDOW_RADIUS_FACTOR,
62637
62662
  scale: (method == 'gaussian' ? GAUSSIAN_SIGMA_FACTOR : PAEK_SCALE_FACTOR) * tol
62638
62663
  };
@@ -62864,7 +62889,7 @@ ${svg}
62864
62889
  // "Output resampling" note above). Step 1 evaluates the smoother at a uniform
62865
62890
  // dense step; step 2 makes one forward pass keeping the endpoints plus every
62866
62891
  // 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
62892
+ // the bend-angle threshold, or where the sagitta guard trips. @inputCount bounds the dense
62868
62893
  // sampling (and thus the output). Returns one array per channel, ordered by
62869
62894
  // increasing arc length, including both endpoints.
62870
62895
  function sampleSmoothedCurve(src, a, b, closed, ctx, inputCount) {
@@ -62873,7 +62898,7 @@ ${svg}
62873
62898
  var maxPoints = Math.max(inputCount, MIN_CLOSED_SEGMENTS) * MAX_OUTPUT_FACTOR;
62874
62899
 
62875
62900
  // 1. dense uniform sampling of the smoothed curve
62876
- var nDense = Math.ceil(span / (ctx.tol * DENSE_STEP_FACTOR)) + 1;
62901
+ var nDense = Math.ceil(span / ctx.denseStep) + 1;
62877
62902
  if (nDense < MIN_CLOSED_SEGMENTS + 1) nDense = MIN_CLOSED_SEGMENTS + 1;
62878
62903
  if (nDense > maxPoints) nDense = maxPoints;
62879
62904
  if (nDense < 2) nDense = 2;
@@ -62883,7 +62908,7 @@ ${svg}
62883
62908
  }
62884
62909
 
62885
62910
  // 2. one-pass bend-angle filter
62886
- var theta = BEND_ANGLE;
62911
+ var theta = ctx.bendAngle;
62887
62912
  var epsDev = ctx.tol * DEVIATION_FACTOR;
62888
62913
  var out = [];
62889
62914
  for (var c = 0; c < K; c++) out.push([]);
@@ -63180,6 +63205,10 @@ ${svg}
63180
63205
  if (opts.gain !== undefined && opts.gain !== null && !(opts.gain >= 0)) {
63181
63206
  stop$1('Expected gain to be a number >= 0');
63182
63207
  }
63208
+ if (opts.max_bend_angle !== undefined && opts.max_bend_angle !== null &&
63209
+ !(opts.max_bend_angle > 0)) {
63210
+ stop$1('Expected max-bend-angle to be a number > 0 (degrees)');
63211
+ }
63183
63212
  var implicitlySmoothedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasPaths);
63184
63213
 
63185
63214
  // Smoothing rewrites coordinates, so lock in any pending (non-destructive)
@@ -63210,7 +63239,8 @@ ${svg}
63210
63239
  method: method,
63211
63240
  spherical: spherical,
63212
63241
  keepCorners: !!opts.keep_corners,
63213
- gain: opts.gain
63242
+ gain: opts.gain,
63243
+ maxBendAngle: opts.max_bend_angle
63214
63244
  });
63215
63245
 
63216
63246
  if (implicitlySmoothedNames.length > 0) {
@@ -63237,6 +63267,7 @@ ${svg}
63237
63267
  spherical: opts.spherical,
63238
63268
  keepCorners: opts.keepCorners,
63239
63269
  gain: opts.gain,
63270
+ maxBendAngle: opts.maxBendAngle,
63240
63271
  closed: arcs.arcIsClosed(arcId)
63241
63272
  });
63242
63273
  nn.push(res.xx.length);
@@ -65053,7 +65084,7 @@ ${svg}
65053
65084
  return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
65054
65085
  }
65055
65086
 
65056
- var version = "0.7.31";
65087
+ var version = "0.7.32";
65057
65088
 
65058
65089
  // Parse command line args into commands and run them
65059
65090
  // 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.32",
4
4
  "description": "A tool for editing geospatial data for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
package/www/mapshaper.js CHANGED
@@ -32938,6 +32938,10 @@ ${svg}
32938
32938
  describe: 'shrinkage-correction (default 1; 0 = none, >1 exaggerates bends)',
32939
32939
  type: 'number'
32940
32940
  })
32941
+ .option('max-bend-angle', {
32942
+ describe: 'max bend between output segments in degrees (default is 8)',
32943
+ type: 'number'
32944
+ })
32941
32945
  .option('planar', {
32942
32946
  // describe: 'smooth decimal degree coords in 2D space (default is spherical)',
32943
32947
  type: 'flag'
@@ -62587,20 +62591,25 @@ ${svg}
62587
62591
  // chord * accumulatedTurn / 8 estimates the bow of a circular arc, and we also
62588
62592
  // cut when it exceeds a fraction of the tolerance. Both tests are O(1) per dense
62589
62593
  // 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
62594
+ var DENSE_STEP_FACTOR = 0.033; // dense sampling step = tolerance * this at the
62595
+ // default bend angle. Must resolve the sharpest
62596
+ // smoothed feature (radius ~ the kernel scale)
62597
+ // finely enough that the angle filter can reach
62598
+ // the bend-angle joins: one dense segment turns
62599
+ // ~ this/0.4 radians there, kept well under the
62600
+ // bend angle so the discrete accumulation barely
62601
+ // overshoots the threshold. For a smaller-than-
62602
+ // default bend angle the step is refined in
62603
+ // proportion so the threshold stays reachable.
62604
+ var DEFAULT_BEND_ANGLE = 8 * Math.PI / 180; // keep a vertex after this much accumulated
62605
+ // turn (max turn between consecutive output
62606
+ // segments); user-overridable via max-bend-angle
62598
62607
  var DEVIATION_FACTOR = 0.1; // sagitta guard: also cut a gentle bend that bows
62599
62608
  // more than tolerance * this from its chord
62600
62609
 
62601
62610
  // Smooth a single arc's coordinates.
62602
62611
  // @xx, @yy: coordinate arrays (may be typed-array subarrays) for one arc.
62603
- // @opts: {tolerance, method, spherical, closed, keepCorners}
62612
+ // @opts: {tolerance, method, spherical, closed, keepCorners, gain, maxBendAngle}
62604
62613
  // Returns {xx: [], yy: []} with the smoothed coordinates. Endpoints of open
62605
62614
  // arcs are preserved exactly (so shared topology nodes stay put); closed arcs
62606
62615
  // are smoothed cyclically and returned closed (first point repeated at the end).
@@ -62615,6 +62624,16 @@ ${svg}
62615
62624
  return g >= 0 ? g : 0;
62616
62625
  }
62617
62626
 
62627
+ // Resolve the output bend-angle threshold from the user option (in degrees) to
62628
+ // radians. It caps the turn between consecutive output segments: a larger angle
62629
+ // keeps fewer vertices (coarser joins), a smaller one keeps more (smoother joins).
62630
+ // Non-positive or missing values fall back to the default.
62631
+ function resolveBendAngle(opts) {
62632
+ var deg = opts.maxBendAngle;
62633
+ if (deg === undefined || deg === null || !(deg > 0)) return DEFAULT_BEND_ANGLE;
62634
+ return deg * Math.PI / 180;
62635
+ }
62636
+
62618
62637
  function smoothArcCoords(xx, yy, opts) {
62619
62638
  var n = xx.length;
62620
62639
  var origX = toArray(xx);
@@ -62627,12 +62646,18 @@ ${svg}
62627
62646
  var closed = !!opts.closed;
62628
62647
  var spherical = !!opts.spherical;
62629
62648
  var keepCorners = !!opts.keepCorners;
62649
+ var bendAngle = resolveBendAngle(opts);
62630
62650
  var ctx = {
62631
62651
  tol: tol,
62632
62652
  method: method,
62633
62653
  spherical: spherical,
62634
62654
  keepCorners: keepCorners,
62635
62655
  gain: resolveGain(opts),
62656
+ bendAngle: bendAngle,
62657
+ // Refine the dense step for a smaller-than-default bend angle so one dense
62658
+ // segment still turns well under the threshold; never coarsen it beyond the
62659
+ // default (the angle filter alone thins the output for larger angles).
62660
+ denseStep: tol * DENSE_STEP_FACTOR * Math.min(1, bendAngle / DEFAULT_BEND_ANGLE),
62636
62661
  radius: tol * WINDOW_RADIUS_FACTOR,
62637
62662
  scale: (method == 'gaussian' ? GAUSSIAN_SIGMA_FACTOR : PAEK_SCALE_FACTOR) * tol
62638
62663
  };
@@ -62864,7 +62889,7 @@ ${svg}
62864
62889
  // "Output resampling" note above). Step 1 evaluates the smoother at a uniform
62865
62890
  // dense step; step 2 makes one forward pass keeping the endpoints plus every
62866
62891
  // 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
62892
+ // the bend-angle threshold, or where the sagitta guard trips. @inputCount bounds the dense
62868
62893
  // sampling (and thus the output). Returns one array per channel, ordered by
62869
62894
  // increasing arc length, including both endpoints.
62870
62895
  function sampleSmoothedCurve(src, a, b, closed, ctx, inputCount) {
@@ -62873,7 +62898,7 @@ ${svg}
62873
62898
  var maxPoints = Math.max(inputCount, MIN_CLOSED_SEGMENTS) * MAX_OUTPUT_FACTOR;
62874
62899
 
62875
62900
  // 1. dense uniform sampling of the smoothed curve
62876
- var nDense = Math.ceil(span / (ctx.tol * DENSE_STEP_FACTOR)) + 1;
62901
+ var nDense = Math.ceil(span / ctx.denseStep) + 1;
62877
62902
  if (nDense < MIN_CLOSED_SEGMENTS + 1) nDense = MIN_CLOSED_SEGMENTS + 1;
62878
62903
  if (nDense > maxPoints) nDense = maxPoints;
62879
62904
  if (nDense < 2) nDense = 2;
@@ -62883,7 +62908,7 @@ ${svg}
62883
62908
  }
62884
62909
 
62885
62910
  // 2. one-pass bend-angle filter
62886
- var theta = BEND_ANGLE;
62911
+ var theta = ctx.bendAngle;
62887
62912
  var epsDev = ctx.tol * DEVIATION_FACTOR;
62888
62913
  var out = [];
62889
62914
  for (var c = 0; c < K; c++) out.push([]);
@@ -63180,6 +63205,10 @@ ${svg}
63180
63205
  if (opts.gain !== undefined && opts.gain !== null && !(opts.gain >= 0)) {
63181
63206
  stop$1('Expected gain to be a number >= 0');
63182
63207
  }
63208
+ if (opts.max_bend_angle !== undefined && opts.max_bend_angle !== null &&
63209
+ !(opts.max_bend_angle > 0)) {
63210
+ stop$1('Expected max-bend-angle to be a number > 0 (degrees)');
63211
+ }
63183
63212
  var implicitlySmoothedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasPaths);
63184
63213
 
63185
63214
  // Smoothing rewrites coordinates, so lock in any pending (non-destructive)
@@ -63210,7 +63239,8 @@ ${svg}
63210
63239
  method: method,
63211
63240
  spherical: spherical,
63212
63241
  keepCorners: !!opts.keep_corners,
63213
- gain: opts.gain
63242
+ gain: opts.gain,
63243
+ maxBendAngle: opts.max_bend_angle
63214
63244
  });
63215
63245
 
63216
63246
  if (implicitlySmoothedNames.length > 0) {
@@ -63237,6 +63267,7 @@ ${svg}
63237
63267
  spherical: opts.spherical,
63238
63268
  keepCorners: opts.keepCorners,
63239
63269
  gain: opts.gain,
63270
+ maxBendAngle: opts.maxBendAngle,
63240
63271
  closed: arcs.arcIsClosed(arcId)
63241
63272
  });
63242
63273
  nn.push(res.xx.length);
@@ -65053,7 +65084,7 @@ ${svg}
65053
65084
  return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
65054
65085
  }
65055
65086
 
65056
- var version = "0.7.31";
65087
+ var version = "0.7.32";
65057
65088
 
65058
65089
  // Parse command line args into commands and run them
65059
65090
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.