mapshaper 0.7.36 → 0.7.37

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
@@ -63744,7 +63744,6 @@ ${svg}
63744
63744
  var L = t[n - 1];
63745
63745
  var m = cyclic ? n - 1 : n;
63746
63746
  if (cyclic && m < 3) return [];
63747
- var segLen = cyclic ? ringSegLengths(t, m) : null;
63748
63747
  var W = params.tangentWindow;
63749
63748
  var Wi = params.innerWindow;
63750
63749
  var turns = new Float64Array(m);
@@ -63752,8 +63751,8 @@ ${svg}
63752
63751
  var lo = cyclic ? 0 : 1;
63753
63752
  var hi = cyclic ? m : n - 1; // exclusive
63754
63753
  for (var i = lo; i < hi; i++) {
63755
- turns[i] = windowedTurn(t, channels, K, n, L, m, segLen, i, W, cyclic);
63756
- inner[i] = windowedTurn(t, channels, K, n, L, m, segLen, i, Wi, cyclic);
63754
+ turns[i] = windowedTurn(t, channels, K, n, L, m, i, W, cyclic);
63755
+ inner[i] = windowedTurn(t, channels, K, n, L, m, i, Wi, cyclic);
63757
63756
  }
63758
63757
  // candidates above the angle threshold, that are concentrated (a localized
63759
63758
  // turn, not gradual bending -- see isConcentratedTurn), and that are the
@@ -63876,8 +63875,7 @@ ${svg}
63876
63875
  var K = channels.length;
63877
63876
  var L = t[n - 1];
63878
63877
  var m = cyclic ? n - 1 : n;
63879
- var segLen = cyclic ? ringSegLengths(t, m) : null;
63880
- return windowedTurn(t, channels, K, n, L, m, segLen, i, params.tangentWindow, cyclic);
63878
+ return windowedTurn(t, channels, K, n, L, m, i, params.tangentWindow, cyclic);
63881
63879
  }
63882
63880
 
63883
63881
  // Does the open span [a, b] justify pinning the corner at vertex @corner: is it a
@@ -63899,18 +63897,12 @@ ${svg}
63899
63897
 
63900
63898
  // --- internals ---
63901
63899
 
63902
- function ringSegLengths(t, m) {
63903
- var segLen = new Float64Array(m);
63904
- for (var i = 0; i < m; i++) segLen[i] = t[i + 1] - t[i];
63905
- return segLen;
63906
- }
63907
-
63908
63900
  // Turn angle at vertex i between the incoming and outgoing directions, each
63909
63901
  // estimated over an arc-length window W (so the measure is scale-aware and not
63910
63902
  // dominated by a single short segment).
63911
- function windowedTurn(t, channels, K, n, L, m, segLen, i, W, cyclic) {
63912
- var back = reach(t, segLen, n, m, L, i, -1, W, cyclic);
63913
- var fwd = reach(t, segLen, n, m, L, i, 1, W, cyclic);
63903
+ function windowedTurn(t, channels, K, n, L, m, i, W, cyclic) {
63904
+ var back = reach(t, n, m, L, i, -1, W, cyclic);
63905
+ var fwd = reach(t, n, m, L, i, 1, W, cyclic);
63914
63906
  var pi = getPt(channels, K, i);
63915
63907
  var pb = getPt(channels, K, back);
63916
63908
  var pf = getPt(channels, K, fwd);
@@ -63935,13 +63927,21 @@ ${svg}
63935
63927
 
63936
63928
  // Walk from vertex i in direction dir (+1/-1) until accumulated arc length
63937
63929
  // reaches W (or a boundary, for open arcs), returning the reached vertex index.
63938
- function reach(t, segLen, n, m, L, i, dir, W, cyclic) {
63930
+ // Segment lengths are read straight from the cumulative-length array t (t has
63931
+ // n = m+1 entries with t[m] = L, so t[j+1]-t[j] is valid for every ring vertex
63932
+ // j in 0..m-1, including the closing segment); this avoids allocating a per-ring
63933
+ // segment-length array on every call (cornerTurn is hit ~twice per corner).
63934
+ function reach(t, n, m, L, i, dir, W, cyclic) {
63939
63935
  var j = i, acc = 0;
63940
63936
  while (acc < W) {
63941
63937
  if (cyclic) {
63942
- var k = dir > 0 ? (j + 1) % m : (j - 1 + m) % m;
63943
- acc += dir > 0 ? segLen[j] : segLen[k];
63944
- j = k;
63938
+ if (dir > 0) {
63939
+ acc += t[j + 1] - t[j]; // vertex j -> j+1 (t[m] == L handles the seam)
63940
+ j = j + 1 === m ? 0 : j + 1;
63941
+ } else {
63942
+ acc += j > 0 ? t[j] - t[j - 1] : L - t[m - 1]; // vertex j -> j-1
63943
+ j = (j - 1 + m) % m;
63944
+ }
63945
63945
  if (j === i) break; // wrapped the whole ring
63946
63946
  } else {
63947
63947
  var nk = j + dir;
@@ -63953,14 +63953,39 @@ ${svg}
63953
63953
  return j;
63954
63954
  }
63955
63955
 
63956
+ // Non-maximum suppression: is vertex j the sharpest turn within an arc-length
63957
+ // window W (ties broken toward the lower index)? Only the vertices within W of j
63958
+ // are examined -- walking outward from j in each direction and stopping once the
63959
+ // arc-length gap reaches W -- rather than scanning the whole arc for every
63960
+ // candidate, which was O(vertices^2) when many vertices are candidates (e.g. a
63961
+ // large ring smoothed below its vertex spacing). Cumulative arc length is
63962
+ // monotone, so the outward gap only grows and the early break is safe; a vertex
63963
+ // within the cyclic window is reached going forward or backward (or both on a
63964
+ // tiny ring, which is harmless for an all-or-nothing test).
63956
63965
  function isLocalMaxTurn(t, turns, j, W, L, m, lo, hi, cyclic) {
63957
- for (var k = lo; k < hi; k++) {
63958
- if (k === j) continue;
63959
- var d = Math.abs(t[k] - t[j]);
63960
- if (cyclic && d > L - d) d = L - d;
63961
- if (d < W && (turns[k] > turns[j] || (turns[k] === turns[j] && k < j))) {
63962
- return false;
63966
+ var k, d;
63967
+ if (cyclic) {
63968
+ for (k = (j + 1) % m; k !== j; k = (k + 1) % m) {
63969
+ d = t[k] - t[j];
63970
+ if (d < 0) d += L; // forward arc distance
63971
+ if (d >= W) break;
63972
+ if (turns[k] > turns[j] || (turns[k] === turns[j] && k < j)) return false;
63973
+ }
63974
+ for (k = (j - 1 + m) % m; k !== j; k = (k - 1 + m) % m) {
63975
+ d = t[j] - t[k];
63976
+ if (d < 0) d += L; // backward arc distance
63977
+ if (d >= W) break;
63978
+ if (turns[k] > turns[j] || (turns[k] === turns[j] && k < j)) return false;
63963
63979
  }
63980
+ return true;
63981
+ }
63982
+ for (k = j + 1; k < hi; k++) {
63983
+ if (t[k] - t[j] >= W) break;
63984
+ if (turns[k] > turns[j] || (turns[k] === turns[j] && k < j)) return false;
63985
+ }
63986
+ for (k = j - 1; k >= lo; k--) {
63987
+ if (t[j] - t[k] >= W) break;
63988
+ if (turns[k] > turns[j] || (turns[k] === turns[j] && k < j)) return false;
63964
63989
  }
63965
63990
  return true;
63966
63991
  }
@@ -64311,7 +64336,14 @@ ${svg}
64311
64336
  if (!leftStruct && !rightStruct) {
64312
64337
  bounds.splice(i, 1);
64313
64338
  changed = true;
64314
- break;
64339
+ // Removing bounds[i] only changes the spans of its two neighbours; the
64340
+ // breakpoints before them stay stable, so resume the scan just before the
64341
+ // removal (rechecking the left neighbour) instead of restarting from the
64342
+ // start. Restarting made this O(breaks^2) -- a hang on a large ring where
64343
+ // the smoothing distance is finer than the vertex spacing and nearly
64344
+ // every vertex reads as a breakpoint. The removal sequence is identical to
64345
+ // a from-scratch rescan (the stable prefix is never revisited).
64346
+ i = i < 2 ? 0 : i - 2;
64315
64347
  }
64316
64348
  }
64317
64349
  }
@@ -64344,7 +64376,15 @@ ${svg}
64344
64376
  if (!leftStruct && !rightStruct) {
64345
64377
  list.splice(i, 1);
64346
64378
  changed = true;
64347
- break;
64379
+ // As in refineBounds, only the removed corner's neighbours change, so
64380
+ // resume just before the removal (i-- via i-2) rather than restarting the
64381
+ // scan -- restarting is what made this O(corners^2) and hung on large
64382
+ // rings smoothed below their vertex spacing (~half the vertices read as
64383
+ // corners, nearly all culled). Cross-seam effects (the wrap between the
64384
+ // last and first corner) are picked up by the outer while(changed) pass.
64385
+ // The stable prefix is never revisited, so the surviving set matches a
64386
+ // from-scratch rescan exactly.
64387
+ i = i < 2 ? -1 : i - 2;
64348
64388
  }
64349
64389
  }
64350
64390
  }
@@ -66812,7 +66852,7 @@ ${svg}
66812
66852
  return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
66813
66853
  }
66814
66854
 
66815
- var version = "0.7.36";
66855
+ var version = "0.7.37";
66816
66856
 
66817
66857
  // Parse command line args into commands and run them
66818
66858
  // 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.36",
3
+ "version": "0.7.37",
4
4
  "description": "A tool for editing geospatial data for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
package/www/mapshaper.js CHANGED
@@ -63744,7 +63744,6 @@ ${svg}
63744
63744
  var L = t[n - 1];
63745
63745
  var m = cyclic ? n - 1 : n;
63746
63746
  if (cyclic && m < 3) return [];
63747
- var segLen = cyclic ? ringSegLengths(t, m) : null;
63748
63747
  var W = params.tangentWindow;
63749
63748
  var Wi = params.innerWindow;
63750
63749
  var turns = new Float64Array(m);
@@ -63752,8 +63751,8 @@ ${svg}
63752
63751
  var lo = cyclic ? 0 : 1;
63753
63752
  var hi = cyclic ? m : n - 1; // exclusive
63754
63753
  for (var i = lo; i < hi; i++) {
63755
- turns[i] = windowedTurn(t, channels, K, n, L, m, segLen, i, W, cyclic);
63756
- inner[i] = windowedTurn(t, channels, K, n, L, m, segLen, i, Wi, cyclic);
63754
+ turns[i] = windowedTurn(t, channels, K, n, L, m, i, W, cyclic);
63755
+ inner[i] = windowedTurn(t, channels, K, n, L, m, i, Wi, cyclic);
63757
63756
  }
63758
63757
  // candidates above the angle threshold, that are concentrated (a localized
63759
63758
  // turn, not gradual bending -- see isConcentratedTurn), and that are the
@@ -63876,8 +63875,7 @@ ${svg}
63876
63875
  var K = channels.length;
63877
63876
  var L = t[n - 1];
63878
63877
  var m = cyclic ? n - 1 : n;
63879
- var segLen = cyclic ? ringSegLengths(t, m) : null;
63880
- return windowedTurn(t, channels, K, n, L, m, segLen, i, params.tangentWindow, cyclic);
63878
+ return windowedTurn(t, channels, K, n, L, m, i, params.tangentWindow, cyclic);
63881
63879
  }
63882
63880
 
63883
63881
  // Does the open span [a, b] justify pinning the corner at vertex @corner: is it a
@@ -63899,18 +63897,12 @@ ${svg}
63899
63897
 
63900
63898
  // --- internals ---
63901
63899
 
63902
- function ringSegLengths(t, m) {
63903
- var segLen = new Float64Array(m);
63904
- for (var i = 0; i < m; i++) segLen[i] = t[i + 1] - t[i];
63905
- return segLen;
63906
- }
63907
-
63908
63900
  // Turn angle at vertex i between the incoming and outgoing directions, each
63909
63901
  // estimated over an arc-length window W (so the measure is scale-aware and not
63910
63902
  // dominated by a single short segment).
63911
- function windowedTurn(t, channels, K, n, L, m, segLen, i, W, cyclic) {
63912
- var back = reach(t, segLen, n, m, L, i, -1, W, cyclic);
63913
- var fwd = reach(t, segLen, n, m, L, i, 1, W, cyclic);
63903
+ function windowedTurn(t, channels, K, n, L, m, i, W, cyclic) {
63904
+ var back = reach(t, n, m, L, i, -1, W, cyclic);
63905
+ var fwd = reach(t, n, m, L, i, 1, W, cyclic);
63914
63906
  var pi = getPt(channels, K, i);
63915
63907
  var pb = getPt(channels, K, back);
63916
63908
  var pf = getPt(channels, K, fwd);
@@ -63935,13 +63927,21 @@ ${svg}
63935
63927
 
63936
63928
  // Walk from vertex i in direction dir (+1/-1) until accumulated arc length
63937
63929
  // reaches W (or a boundary, for open arcs), returning the reached vertex index.
63938
- function reach(t, segLen, n, m, L, i, dir, W, cyclic) {
63930
+ // Segment lengths are read straight from the cumulative-length array t (t has
63931
+ // n = m+1 entries with t[m] = L, so t[j+1]-t[j] is valid for every ring vertex
63932
+ // j in 0..m-1, including the closing segment); this avoids allocating a per-ring
63933
+ // segment-length array on every call (cornerTurn is hit ~twice per corner).
63934
+ function reach(t, n, m, L, i, dir, W, cyclic) {
63939
63935
  var j = i, acc = 0;
63940
63936
  while (acc < W) {
63941
63937
  if (cyclic) {
63942
- var k = dir > 0 ? (j + 1) % m : (j - 1 + m) % m;
63943
- acc += dir > 0 ? segLen[j] : segLen[k];
63944
- j = k;
63938
+ if (dir > 0) {
63939
+ acc += t[j + 1] - t[j]; // vertex j -> j+1 (t[m] == L handles the seam)
63940
+ j = j + 1 === m ? 0 : j + 1;
63941
+ } else {
63942
+ acc += j > 0 ? t[j] - t[j - 1] : L - t[m - 1]; // vertex j -> j-1
63943
+ j = (j - 1 + m) % m;
63944
+ }
63945
63945
  if (j === i) break; // wrapped the whole ring
63946
63946
  } else {
63947
63947
  var nk = j + dir;
@@ -63953,14 +63953,39 @@ ${svg}
63953
63953
  return j;
63954
63954
  }
63955
63955
 
63956
+ // Non-maximum suppression: is vertex j the sharpest turn within an arc-length
63957
+ // window W (ties broken toward the lower index)? Only the vertices within W of j
63958
+ // are examined -- walking outward from j in each direction and stopping once the
63959
+ // arc-length gap reaches W -- rather than scanning the whole arc for every
63960
+ // candidate, which was O(vertices^2) when many vertices are candidates (e.g. a
63961
+ // large ring smoothed below its vertex spacing). Cumulative arc length is
63962
+ // monotone, so the outward gap only grows and the early break is safe; a vertex
63963
+ // within the cyclic window is reached going forward or backward (or both on a
63964
+ // tiny ring, which is harmless for an all-or-nothing test).
63956
63965
  function isLocalMaxTurn(t, turns, j, W, L, m, lo, hi, cyclic) {
63957
- for (var k = lo; k < hi; k++) {
63958
- if (k === j) continue;
63959
- var d = Math.abs(t[k] - t[j]);
63960
- if (cyclic && d > L - d) d = L - d;
63961
- if (d < W && (turns[k] > turns[j] || (turns[k] === turns[j] && k < j))) {
63962
- return false;
63966
+ var k, d;
63967
+ if (cyclic) {
63968
+ for (k = (j + 1) % m; k !== j; k = (k + 1) % m) {
63969
+ d = t[k] - t[j];
63970
+ if (d < 0) d += L; // forward arc distance
63971
+ if (d >= W) break;
63972
+ if (turns[k] > turns[j] || (turns[k] === turns[j] && k < j)) return false;
63973
+ }
63974
+ for (k = (j - 1 + m) % m; k !== j; k = (k - 1 + m) % m) {
63975
+ d = t[j] - t[k];
63976
+ if (d < 0) d += L; // backward arc distance
63977
+ if (d >= W) break;
63978
+ if (turns[k] > turns[j] || (turns[k] === turns[j] && k < j)) return false;
63963
63979
  }
63980
+ return true;
63981
+ }
63982
+ for (k = j + 1; k < hi; k++) {
63983
+ if (t[k] - t[j] >= W) break;
63984
+ if (turns[k] > turns[j] || (turns[k] === turns[j] && k < j)) return false;
63985
+ }
63986
+ for (k = j - 1; k >= lo; k--) {
63987
+ if (t[j] - t[k] >= W) break;
63988
+ if (turns[k] > turns[j] || (turns[k] === turns[j] && k < j)) return false;
63964
63989
  }
63965
63990
  return true;
63966
63991
  }
@@ -64311,7 +64336,14 @@ ${svg}
64311
64336
  if (!leftStruct && !rightStruct) {
64312
64337
  bounds.splice(i, 1);
64313
64338
  changed = true;
64314
- break;
64339
+ // Removing bounds[i] only changes the spans of its two neighbours; the
64340
+ // breakpoints before them stay stable, so resume the scan just before the
64341
+ // removal (rechecking the left neighbour) instead of restarting from the
64342
+ // start. Restarting made this O(breaks^2) -- a hang on a large ring where
64343
+ // the smoothing distance is finer than the vertex spacing and nearly
64344
+ // every vertex reads as a breakpoint. The removal sequence is identical to
64345
+ // a from-scratch rescan (the stable prefix is never revisited).
64346
+ i = i < 2 ? 0 : i - 2;
64315
64347
  }
64316
64348
  }
64317
64349
  }
@@ -64344,7 +64376,15 @@ ${svg}
64344
64376
  if (!leftStruct && !rightStruct) {
64345
64377
  list.splice(i, 1);
64346
64378
  changed = true;
64347
- break;
64379
+ // As in refineBounds, only the removed corner's neighbours change, so
64380
+ // resume just before the removal (i-- via i-2) rather than restarting the
64381
+ // scan -- restarting is what made this O(corners^2) and hung on large
64382
+ // rings smoothed below their vertex spacing (~half the vertices read as
64383
+ // corners, nearly all culled). Cross-seam effects (the wrap between the
64384
+ // last and first corner) are picked up by the outer while(changed) pass.
64385
+ // The stable prefix is never revisited, so the surviving set matches a
64386
+ // from-scratch rescan exactly.
64387
+ i = i < 2 ? -1 : i - 2;
64348
64388
  }
64349
64389
  }
64350
64390
  }
@@ -66812,7 +66852,7 @@ ${svg}
66812
66852
  return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
66813
66853
  }
66814
66854
 
66815
- var version = "0.7.36";
66855
+ var version = "0.7.37";
66816
66856
 
66817
66857
  // Parse command line args into commands and run them
66818
66858
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.