mapshaper 0.7.35 → 0.7.36
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 +686 -62
- package/package.json +1 -1
- package/www/mapshaper.js +686 -62
package/mapshaper.js
CHANGED
|
@@ -32519,6 +32519,14 @@ ${svg}
|
|
|
32519
32519
|
type: 'number',
|
|
32520
32520
|
describe: 'Visvalingam angle-weight coefficient (default 0.7); higher removes spiky detail more eagerly'
|
|
32521
32521
|
})
|
|
32522
|
+
.option('roundness', {
|
|
32523
|
+
type: 'number',
|
|
32524
|
+
describe: 'protect rounded loops: min enclosed-area / loop-perimeter as a fraction of the distance (default 0.2); higher removes more, 0 disables'
|
|
32525
|
+
})
|
|
32526
|
+
.option('min-area', {
|
|
32527
|
+
type: 'number',
|
|
32528
|
+
describe: 'drop a closed ring (island/hole) when the filter leaves less than this fraction of its original area (default 0.6); 0 disables'
|
|
32529
|
+
})
|
|
32522
32530
|
.option('planar', {
|
|
32523
32531
|
describe: 'treat decimal degree coords as planar x,y (default is spherical)',
|
|
32524
32532
|
type: 'flag'
|
|
@@ -33052,6 +33060,13 @@ ${svg}
|
|
|
33052
33060
|
describe: 'shrinkage-correction (default 1; 0 = none, >1 exaggerates bends)',
|
|
33053
33061
|
type: 'number'
|
|
33054
33062
|
})
|
|
33063
|
+
.option('strength', {
|
|
33064
|
+
// undocumented: multiplier on the smoothing kernel scale relative to the
|
|
33065
|
+
// distance (default 1; >1 smooths more strongly with larger divergence from
|
|
33066
|
+
// the original, <1 more gently). Scales only the low-pass kernel -- corner
|
|
33067
|
+
// detection, prefiltering and island dropping stay keyed to the raw distance.
|
|
33068
|
+
type: 'number'
|
|
33069
|
+
})
|
|
33055
33070
|
.option('max-bend-angle', {
|
|
33056
33071
|
describe: 'max bend between output segments in degrees (default is 8)',
|
|
33057
33072
|
type: 'number'
|
|
@@ -33061,10 +33076,15 @@ ${svg}
|
|
|
33061
33076
|
type: 'flag'
|
|
33062
33077
|
})
|
|
33063
33078
|
.option('corner-bias', {
|
|
33064
|
-
//
|
|
33065
|
-
//
|
|
33066
|
-
//
|
|
33067
|
-
//
|
|
33079
|
+
// Sensitivity of corner detection (default 0 = neutral). Positive keeps
|
|
33080
|
+
// more corners, negative fewer; +/- values of equal size are inverses.
|
|
33081
|
+
// Under the hood it scales only the distance-proportional detection
|
|
33082
|
+
// parameters (not angles), detecting corners as if the distance were
|
|
33083
|
+
// divided by k, where k = bias+1 for bias >= 0 and 1/(1-bias) for bias < 0;
|
|
33084
|
+
// the smoothing kernel keeps using the real distance. So corner-bias=-1
|
|
33085
|
+
// finds the corners a 2x distance would, corner-bias=1 those of a 0.5x
|
|
33086
|
+
// distance. Use no-corners to turn corner preservation off entirely.
|
|
33087
|
+
describe: 'corner-detection sensitivity (default 0; + is more sensitive, - is less)',
|
|
33068
33088
|
type: 'number'
|
|
33069
33089
|
})
|
|
33070
33090
|
.option('prefilter-gate', {
|
|
@@ -33072,6 +33092,19 @@ ${svg}
|
|
|
33072
33092
|
// intricate detail (default 4; higher removes less)
|
|
33073
33093
|
type: 'number'
|
|
33074
33094
|
})
|
|
33095
|
+
.option('prefilter-roundness', {
|
|
33096
|
+
// undocumented: prefilter roundness protection -- min enclosed-area /
|
|
33097
|
+
// loop-perimeter (as a fraction of the smoothing distance) for a loop
|
|
33098
|
+
// (e.g. an island) to be protected from the prefilter (default 0.2;
|
|
33099
|
+
// higher removes more, 0 disables and lets rounded islands collapse)
|
|
33100
|
+
type: 'number'
|
|
33101
|
+
})
|
|
33102
|
+
.option('prefilter-min-area', {
|
|
33103
|
+
// undocumented: drop a closed ring (island/hole) when the prefilter would
|
|
33104
|
+
// leave less than this fraction of its original area (default 0.6), rather
|
|
33105
|
+
// than smoothing a distorted remnant; 0 disables the drop
|
|
33106
|
+
type: 'number'
|
|
33107
|
+
})
|
|
33075
33108
|
.option('planar', {
|
|
33076
33109
|
// describe: 'smooth decimal degree coords in 2D space (default is spherical)',
|
|
33077
33110
|
type: 'flag'
|
|
@@ -58334,18 +58367,55 @@ ${svg}
|
|
|
58334
58367
|
// threshold) -- a jetty, fjord or crinkle. Otherwise restore the run's
|
|
58335
58368
|
// original vertices, so gentle stretches keep full detail for -smooth.
|
|
58336
58369
|
//
|
|
58370
|
+
// A ROUNDNESS gate protects substantial, rounded loops from both the merge pass
|
|
58371
|
+
// and the commit. Tortuosity (path length / chord) alone cannot tell a thin
|
|
58372
|
+
// needle from a round bulge -- both can be highly tortuous, and a closing chord
|
|
58373
|
+
// of length ~0 makes tortuosity infinite for either. Worst case: a closed ring
|
|
58374
|
+
// stores its start and end vertex at the same coordinate, so the seam chord
|
|
58375
|
+
// (vertex 0 -> vertex n-1) has length 0 and infinite tortuosity, which used to
|
|
58376
|
+
// make the merge pass splice out the entire ring -- destroying every island
|
|
58377
|
+
// whose perimeter fit inside the merge window, regardless of size or roundness.
|
|
58378
|
+
// The gate distinguishes them by the enclosed area: for a candidate section
|
|
58379
|
+
// closed by its chord, protect it when area / loop-perimeter >= roundness * D
|
|
58380
|
+
// (the isoperimetric area-to-perimeter ratio, biased by the detail distance). A
|
|
58381
|
+
// thin needle encloses ~0 area (never protected -> still cut); a round island
|
|
58382
|
+
// encloses a large area (protected). Because area/perimeter equals radius/2 for
|
|
58383
|
+
// a circle, the default gate protects circular features at or above the detail
|
|
58384
|
+
// resolution and preferentially removes smaller or less-round ones.
|
|
58385
|
+
//
|
|
58337
58386
|
// @xx, @yy coordinate arrays for one arc (may be typed-array subarrays).
|
|
58338
|
-
// @opts: {distance, tortuosity, spherical, weighting}
|
|
58387
|
+
// @opts: {distance, tortuosity, spherical, weighting, roundness}
|
|
58339
58388
|
// distance detail size threshold in ground units (meters when spherical): the
|
|
58340
58389
|
// longest chord the filter is allowed to create.
|
|
58341
58390
|
// tortuosity min original-length / chord ratio for a run to be cut (default 2).
|
|
58342
58391
|
// spherical measure area/length on the sphere (lng/lat -> geocentric x,y,z).
|
|
58343
58392
|
// weighting Visvalingam angle-weight coefficient (default 0.7), matching
|
|
58344
58393
|
// -simplify's weighted_visvalingam.
|
|
58394
|
+
// roundness min enclosed-area / loop-perimeter (as a fraction of the detail
|
|
58395
|
+
// distance) for a loop to be protected from removal (default 0.2);
|
|
58396
|
+
// higher removes more, 0 disables the protection.
|
|
58345
58397
|
// Returns {xx: [], yy: []}. Arc endpoints are always preserved, so shared
|
|
58346
58398
|
// topology nodes stay put and the operation is topology-safe like -simplify.
|
|
58347
58399
|
var DEFAULT_WEIGHTING = 0.7;
|
|
58348
58400
|
var DEFAULT_TORTUOSITY = 4;
|
|
58401
|
+
// Protect a candidate loop from collapse when its enclosed-area / loop-perimeter
|
|
58402
|
+
// exceeds this fraction of the detail distance D. For a circle area/perimeter =
|
|
58403
|
+
// radius/2, so 0.2 protects circular features of diameter >= ~D and drops finer
|
|
58404
|
+
// or less-round detail; a thin needle (area ~ 0) is never protected.
|
|
58405
|
+
var DEFAULT_ROUNDNESS = 0.2;
|
|
58406
|
+
// A closed ring (island, lake or hole) is dropped entirely when the filter would
|
|
58407
|
+
// leave less than this fraction of its original enclosed area. The roundness gate
|
|
58408
|
+
// stops a substantial ring from being merged away wholesale, but it is evaluated
|
|
58409
|
+
// per candidate span, so cutRun can still slice off convoluted sub-spans of the
|
|
58410
|
+
// perimeter and shrink a near-scale island to a small, distorted remnant. Once
|
|
58411
|
+
// most of the area is gone the remnant no longer faithfully represents the island
|
|
58412
|
+
// and a clean drop is better than a mangled sliver -- so if the survivors enclose
|
|
58413
|
+
// less than this share of the original area, collapse the ring to its degenerate
|
|
58414
|
+
// seam (like a fully sub-scale ring) and let the pipeline discard it. Only closed
|
|
58415
|
+
// rings are affected; open arcs and shared boundaries are never dropped, so the
|
|
58416
|
+
// operation stays topology-safe. Set to 0 to disable. The default 0.6 keeps a
|
|
58417
|
+
// ring only while the filter removes at most ~40% of its area.
|
|
58418
|
+
var DEFAULT_MIN_RING_AREA = 0.6;
|
|
58349
58419
|
// How far (in detail-distances of arc length) the survivor-merge pass looks ahead
|
|
58350
58420
|
// for a chord that closes a convoluted excursion. Bounds the pass to O(n) and
|
|
58351
58421
|
// caps how long a thin spike it can slice in one merge.
|
|
@@ -58359,7 +58429,7 @@ ${svg}
|
|
|
58359
58429
|
|
|
58360
58430
|
function collapseArcDetail(xx, yy, opts) {
|
|
58361
58431
|
var n = xx.length;
|
|
58362
|
-
var outX = [], outY = [];
|
|
58432
|
+
var outX = [], outY = [], outIdx = [];
|
|
58363
58433
|
var D = opts.distance;
|
|
58364
58434
|
if (n < 3 || !(D > 0)) {
|
|
58365
58435
|
for (var p = 0; p < n; p++) { outX.push(xx[p]); outY.push(yy[p]); }
|
|
@@ -58368,6 +58438,12 @@ ${svg}
|
|
|
58368
58438
|
var spherical = !!opts.spherical;
|
|
58369
58439
|
var k = opts.weighting >= 0 ? opts.weighting : DEFAULT_WEIGHTING;
|
|
58370
58440
|
var T = opts.tortuosity > 0 ? opts.tortuosity : DEFAULT_TORTUOSITY;
|
|
58441
|
+
// roundness >= 0; 0 disables the roundness protection (legacy behavior).
|
|
58442
|
+
var R = opts.roundness >= 0 ? opts.roundness : DEFAULT_ROUNDNESS;
|
|
58443
|
+
// minRingArea >= 0; 0 disables the drop-shredded-ring gate.
|
|
58444
|
+
var minRingArea = opts.minRingArea >= 0 ? opts.minRingArea : DEFAULT_MIN_RING_AREA;
|
|
58445
|
+
// A closed ring stores its start and end vertex at the same coordinate.
|
|
58446
|
+
var isRing = n >= 4 && xx[0] === xx[n - 1] && yy[0] === yy[n - 1];
|
|
58371
58447
|
var Dsq = D * D;
|
|
58372
58448
|
|
|
58373
58449
|
// Metric coordinates: geocentric x,y,z on a sphere for lng/lat input, plain
|
|
@@ -58445,6 +58521,52 @@ ${svg}
|
|
|
58445
58521
|
var cumLen = new Float64Array(n);
|
|
58446
58522
|
for (var q = 1; q < n; q++) cumLen[q] = cumLen[q - 1] + dist(q - 1, q);
|
|
58447
58523
|
|
|
58524
|
+
// Cumulative cross products of successive position vectors (in a frame
|
|
58525
|
+
// translated to vertex 0, to keep magnitudes small and the subtraction
|
|
58526
|
+
// precise) give the enclosed area of any section i..j closed by its chord in
|
|
58527
|
+
// O(1). The vector-area form (0.5 * |sum of edge cross products + closing
|
|
58528
|
+
// term|) is origin-independent for a closed loop, so it works unchanged for
|
|
58529
|
+
// the geocentric 3D coords used in spherical mode and the plain 2D coords
|
|
58530
|
+
// otherwise. Only used by the roundness gate below.
|
|
58531
|
+
var ox = mx[0], oy = my[0], oz = spherical ? mz[0] : 0;
|
|
58532
|
+
var cumCz = new Float64Array(n), cumCx, cumCy;
|
|
58533
|
+
if (spherical) { cumCx = new Float64Array(n); cumCy = new Float64Array(n); }
|
|
58534
|
+
for (var t = 1; t < n; t++) {
|
|
58535
|
+
var ax = mx[t - 1] - ox, ay = my[t - 1] - oy, bx = mx[t] - ox, by = my[t] - oy;
|
|
58536
|
+
if (spherical) {
|
|
58537
|
+
var az = mz[t - 1] - oz, bz = mz[t] - oz;
|
|
58538
|
+
cumCx[t] = cumCx[t - 1] + (ay * bz - az * by);
|
|
58539
|
+
cumCy[t] = cumCy[t - 1] + (az * bx - ax * bz);
|
|
58540
|
+
cumCz[t] = cumCz[t - 1] + (ax * by - ay * bx);
|
|
58541
|
+
} else {
|
|
58542
|
+
cumCz[t] = cumCz[t - 1] + (ax * by - ay * bx);
|
|
58543
|
+
}
|
|
58544
|
+
}
|
|
58545
|
+
|
|
58546
|
+
// Area enclosed by the section vertices i..j closed by the chord j->i.
|
|
58547
|
+
function sectionArea(i, j) {
|
|
58548
|
+
var aix = mx[i] - ox, aiy = my[i] - oy, ajx = mx[j] - ox, ajy = my[j] - oy;
|
|
58549
|
+
if (spherical) {
|
|
58550
|
+
var aiz = mz[i] - oz, ajz = mz[j] - oz;
|
|
58551
|
+
var vx = (cumCx[j] - cumCx[i]) + (ajy * aiz - ajz * aiy);
|
|
58552
|
+
var vy = (cumCy[j] - cumCy[i]) + (ajz * aix - ajx * aiz);
|
|
58553
|
+
var vz = (cumCz[j] - cumCz[i]) + (ajx * aiy - ajy * aix);
|
|
58554
|
+
return 0.5 * Math.sqrt(vx * vx + vy * vy + vz * vz);
|
|
58555
|
+
}
|
|
58556
|
+
return 0.5 * Math.abs((cumCz[j] - cumCz[i]) + (ajx * aiy - ajy * aix));
|
|
58557
|
+
}
|
|
58558
|
+
|
|
58559
|
+
// A candidate section (i..j) is protected from collapse when the loop it forms
|
|
58560
|
+
// with its closing chord is a substantial, rounded feature: enclosed area per
|
|
58561
|
+
// unit loop-perimeter reaches the roundness fraction of the detail distance.
|
|
58562
|
+
// A thin needle encloses ~0 area (area/perimeter ~ 0) and is never protected;
|
|
58563
|
+
// a round bulge or island encloses enough area to clear the gate.
|
|
58564
|
+
function isProtected(i, j) {
|
|
58565
|
+
if (!(R > 0)) return false;
|
|
58566
|
+
var perim = (cumLen[j] - cumLen[i]) + dist(i, j);
|
|
58567
|
+
return perim > 0 && sectionArea(i, j) / perim >= R * D;
|
|
58568
|
+
}
|
|
58569
|
+
|
|
58448
58570
|
function cutRun(a, e) {
|
|
58449
58571
|
// Emit the kept vertices in (a, e]; a has already been emitted. Walk from a
|
|
58450
58572
|
// and, at each step, find the span [i, j] (chord <= D) with the highest
|
|
@@ -58461,7 +58583,8 @@ ${svg}
|
|
|
58461
58583
|
var c = dist(i, j);
|
|
58462
58584
|
if (c > D) continue; // never create a chord longer than the detail scale
|
|
58463
58585
|
var tort = c > 0 ? (cumLen[j] - cumLen[i]) / c : Infinity;
|
|
58464
|
-
|
|
58586
|
+
// roundness gate: keep substantial, rounded bulges at full detail
|
|
58587
|
+
if (tort > bestTort && !isProtected(i, j)) {
|
|
58465
58588
|
bestTort = tort;
|
|
58466
58589
|
bestJ = j;
|
|
58467
58590
|
}
|
|
@@ -58469,10 +58592,12 @@ ${svg}
|
|
|
58469
58592
|
if (bestJ >= 0) {
|
|
58470
58593
|
outX.push(xx[bestJ]);
|
|
58471
58594
|
outY.push(yy[bestJ]);
|
|
58595
|
+
outIdx.push(bestJ);
|
|
58472
58596
|
i = bestJ;
|
|
58473
58597
|
} else {
|
|
58474
58598
|
outX.push(xx[i + 1]);
|
|
58475
58599
|
outY.push(yy[i + 1]);
|
|
58600
|
+
outIdx.push(i + 1);
|
|
58476
58601
|
i++;
|
|
58477
58602
|
}
|
|
58478
58603
|
}
|
|
@@ -58502,7 +58627,9 @@ ${svg}
|
|
|
58502
58627
|
if (chordSq(s, u) <= mergeChordSq) {
|
|
58503
58628
|
var ud = dist(s, u);
|
|
58504
58629
|
var utort = ud > 0 ? (cumLen[u] - cumLen[s]) / ud : Infinity;
|
|
58505
|
-
|
|
58630
|
+
// roundness gate: a rounded loop (e.g. a closed ring closing on its own
|
|
58631
|
+
// zero-length seam) is a real feature, not a needle -- never merge it.
|
|
58632
|
+
if (utort > mergeTort && !isProtected(s, u)) {
|
|
58506
58633
|
mergeTort = utort;
|
|
58507
58634
|
mergeJ = u;
|
|
58508
58635
|
}
|
|
@@ -58521,13 +58648,49 @@ ${svg}
|
|
|
58521
58648
|
|
|
58522
58649
|
outX.push(xx[0]);
|
|
58523
58650
|
outY.push(yy[0]);
|
|
58651
|
+
outIdx.push(0);
|
|
58524
58652
|
var a = 0;
|
|
58525
58653
|
while (a !== n - 1) {
|
|
58526
58654
|
var e = next[a];
|
|
58527
58655
|
cutRun(a, e);
|
|
58528
58656
|
a = e;
|
|
58529
58657
|
}
|
|
58658
|
+
|
|
58659
|
+
// Drop a closed ring that the filter has shredded to a small remnant: if the
|
|
58660
|
+
// survivors enclose less than minRingArea of the original ring's area, collapse
|
|
58661
|
+
// it to its degenerate seam so the pipeline discards it (a clean drop rather
|
|
58662
|
+
// than a distorted sliver). Uses the survivors' metric coordinates for the
|
|
58663
|
+
// filtered area and the whole-ring section area for the original.
|
|
58664
|
+
if (isRing && minRingArea > 0) {
|
|
58665
|
+
var origArea = sectionArea(0, n - 1);
|
|
58666
|
+
if (origArea > 0 && ringAreaByIdx(outIdx) < minRingArea * origArea) {
|
|
58667
|
+
return {xx: [xx[0], xx[n - 1]], yy: [yy[0], yy[n - 1]]};
|
|
58668
|
+
}
|
|
58669
|
+
}
|
|
58530
58670
|
return {xx: outX, yy: outY};
|
|
58671
|
+
|
|
58672
|
+
// Enclosed area of the closed ring formed by the survivor vertices (given as
|
|
58673
|
+
// indices into the original arc), measured in the same metric space as
|
|
58674
|
+
// sectionArea via the origin-independent vector-area form.
|
|
58675
|
+
function ringAreaByIdx(idx) {
|
|
58676
|
+
var m = idx.length;
|
|
58677
|
+
if (m < 4) return 0;
|
|
58678
|
+
var o0x = mx[idx[0]], o0y = my[idx[0]], o0z = spherical ? mz[idx[0]] : 0;
|
|
58679
|
+
var vx = 0, vy = 0, vz = 0;
|
|
58680
|
+
for (var t = 1; t < m; t++) {
|
|
58681
|
+
var p = idx[t - 1], q = idx[t];
|
|
58682
|
+
var ax = mx[p] - o0x, ay = my[p] - o0y, bx = mx[q] - o0x, by = my[q] - o0y;
|
|
58683
|
+
if (spherical) {
|
|
58684
|
+
var az = mz[p] - o0z, bz = mz[q] - o0z;
|
|
58685
|
+
vx += ay * bz - az * by;
|
|
58686
|
+
vy += az * bx - ax * bz;
|
|
58687
|
+
vz += ax * by - ay * bx;
|
|
58688
|
+
} else {
|
|
58689
|
+
vz += ax * by - ay * bx;
|
|
58690
|
+
}
|
|
58691
|
+
}
|
|
58692
|
+
return spherical ? 0.5 * Math.sqrt(vx * vx + vy * vy + vz * vz) : 0.5 * Math.abs(vz);
|
|
58693
|
+
}
|
|
58531
58694
|
}
|
|
58532
58695
|
|
|
58533
58696
|
// Optional preprocessing step before -smooth: collapse intricate sub-scale
|
|
@@ -58558,6 +58721,8 @@ ${svg}
|
|
|
58558
58721
|
distance: distance,
|
|
58559
58722
|
tortuosity: opts.tortuosity,
|
|
58560
58723
|
weighting: opts.weighting,
|
|
58724
|
+
roundness: opts.roundness,
|
|
58725
|
+
minRingArea: opts.min_area,
|
|
58561
58726
|
spherical: spherical
|
|
58562
58727
|
});
|
|
58563
58728
|
var removed = before - arcs.getPointCount();
|
|
@@ -58581,6 +58746,8 @@ ${svg}
|
|
|
58581
58746
|
distance: opts.distance,
|
|
58582
58747
|
tortuosity: opts.tortuosity,
|
|
58583
58748
|
weighting: opts.weighting,
|
|
58749
|
+
roundness: opts.roundness,
|
|
58750
|
+
minRingArea: opts.minRingArea,
|
|
58584
58751
|
spherical: opts.spherical
|
|
58585
58752
|
});
|
|
58586
58753
|
nn.push(res.xx.length);
|
|
@@ -63423,7 +63590,7 @@ ${svg}
|
|
|
63423
63590
|
});
|
|
63424
63591
|
|
|
63425
63592
|
// Structural-corner detection for -smooth's corner preservation (on by default;
|
|
63426
|
-
// disabled with no-corners
|
|
63593
|
+
// disabled with no-corners).
|
|
63427
63594
|
//
|
|
63428
63595
|
// Many boundaries alternate between natural, freely-curving stretches (coast,
|
|
63429
63596
|
// river centerline) and artificial straight-line segments (state/county
|
|
@@ -63457,21 +63624,112 @@ ${svg}
|
|
|
63457
63624
|
var INNER_WINDOW_FACTOR = 0.4; // concentration probe window = tangentWindow * this
|
|
63458
63625
|
var CORNER_CONCENTRATION = 0.6; // min ratio of inner-window turn to full-window turn
|
|
63459
63626
|
var MIN_RUN_LEN_FACTOR = 1.0; // a structural run must be at least tol * this long
|
|
63460
|
-
|
|
63461
|
-
|
|
63462
|
-
//
|
|
63463
|
-
//
|
|
63464
|
-
//
|
|
63627
|
+
// ...and bend no tighter than radius tol * this. This is the curvature gate for
|
|
63628
|
+
// "structural" (straight or slowly-curving, e.g. a surveyed border or graticule
|
|
63629
|
+
// arc). It must be well above 1: at factor 1 a minimal run may turn a full
|
|
63630
|
+
// radian (~57 deg) over its own length, so ordinary coastal arcs qualify and
|
|
63631
|
+
// their end bends get pinned as spurious corners (radius ~1-1.6*tol). At 3 a
|
|
63632
|
+
// minimal run turns <= ~19 deg, excluding natural coastal curvature while still
|
|
63633
|
+
// admitting genuinely straight borders (radius ~infinite) and graticule arcs
|
|
63634
|
+
// (radius >> tol).
|
|
63635
|
+
var MIN_RUN_RADIUS_FACTOR = 3.0;
|
|
63636
|
+
|
|
63637
|
+
// Straightness gate used to decide corner *retention* (whether a detected corner
|
|
63638
|
+
// borders a run worth pinning), as distinct from isStructuralRun's per-vertex
|
|
63639
|
+
// gate that decides whether a span is copied verbatim. A run is "straight at the
|
|
63640
|
+
// smoothing scale" if every vertex stays within a thin corridor around the
|
|
63641
|
+
// straight chord joining the run's endpoints: max perpendicular deviation <=
|
|
63642
|
+
// STRAIGHT_DEV_FACTOR * chord length. Because this measures deviation from the
|
|
63643
|
+
// chord rather than summing raw per-segment turning, it is robust to
|
|
63644
|
+
// sub-tolerance digitizing wiggle: a finely ragged but geometrically straight
|
|
63645
|
+
// border (huge total per-vertex turning, tiny deviation) qualifies, so its
|
|
63646
|
+
// bounding corners are kept -- while isStructuralRun would (correctly, for its
|
|
63647
|
+
// own purpose) reject it as too wiggly to copy verbatim. The ratio behaves like
|
|
63648
|
+
// a minimum-radius-over-length gate: a run bending with radius R over length L
|
|
63649
|
+
// deviates from its chord by ~L/(8R), so the threshold corresponds to
|
|
63650
|
+
// R >~ L/(8*STRAIGHT_DEV_FACTOR) -- longer runs must be proportionally straighter
|
|
63651
|
+
// to count, which matches intuition (a 28 km stretch bending at radius 4 km is
|
|
63652
|
+
// obviously not straight). Genuinely curving coastline bows far from its chord
|
|
63653
|
+
// and is still rejected, so spurious corners inside wiggly stretches keep getting
|
|
63654
|
+
// culled.
|
|
63655
|
+
var STRAIGHT_DEV_FACTOR = 0.03;
|
|
63656
|
+
|
|
63657
|
+
// Angle coupling for corner retention: how much sharper the corner must turn than
|
|
63658
|
+
// the run it borders already curves. A run that passes the chord test may still
|
|
63659
|
+
// bend gently within the STRAIGHT_DEV_FACTOR corridor -- for a circular arc the
|
|
63660
|
+
// chord-deviation ratio is ~ (the run's total turn)/8, so the base 0.03 admits a
|
|
63661
|
+
// run that curves ~14 deg over its length. Pinning a *gentle* bend at the end of
|
|
63662
|
+
// such a run is unsafe: the "corner" is barely sharper than the run's own
|
|
63663
|
+
// curving, so it is really a point on a smooth bend, not a junction. (This is the
|
|
63664
|
+
// failure mode on coarsely sampled / already-simplified coastlines, where a
|
|
63665
|
+
// gently curving stretch is sampled as a few long segments that read as a
|
|
63666
|
+
// borderline-straight run with soft bends at each end.) So the straightness limit
|
|
63667
|
+
// for retention is tightened for gentle corners: a corner is pinnable only if its
|
|
63668
|
+
// turn is at least PIN_TURN_RATIO times the run's own bend, i.e.
|
|
63669
|
+
// turn >= PIN_TURN_RATIO * (8 * dev) <=> dev <= turn / (8 * PIN_TURN_RATIO).
|
|
63670
|
+
// retentionDevLimit() returns the smaller of STRAIGHT_DEV_FACTOR and
|
|
63671
|
+
// turn/(8*PIN_TURN_RATIO), so the coupling only bites for gentle corners (below
|
|
63672
|
+
// ~2*STRAIGHT_DEV_FACTOR*PIN_TURN_RATIO ~ 69 deg); sharp corners (surveyed-border
|
|
63673
|
+
// right angles, spits, hairpins) keep the full base tolerance, unchanged.
|
|
63674
|
+
var PIN_TURN_RATIO = 5;
|
|
63675
|
+
|
|
63676
|
+
// Minimum length (in tol units) a straight run must have to justify *pinning* a
|
|
63677
|
+
// bordering corner. This is deliberately larger than MIN_RUN_LEN_FACTOR (the
|
|
63678
|
+
// floor for calling a span "structural" at all): pinning a corner is a stronger
|
|
63679
|
+
// commitment than copying a clean span, so it demands stronger evidence that the
|
|
63680
|
+
// run is a deliberate straight feature rather than incidental collinearity.
|
|
63681
|
+
//
|
|
63682
|
+
// The failure mode this guards against appears on sparse / already-simplified
|
|
63683
|
+
// data, where a gently curving coastline is sampled as a few long segments. A
|
|
63684
|
+
// short near-collinear stretch only ~1*tol long (often just 1-2 segments) then
|
|
63685
|
+
// passes the chord-deviation test -- with so few interior points there is almost
|
|
63686
|
+
// nothing to deviate -- and gets pinned, kinking an otherwise smooth curve. At
|
|
63687
|
+
// the smoothing scale such a stretch is indistinguishable from a coarsely
|
|
63688
|
+
// sampled bend, so it should not anchor a corner. Requiring the run to be
|
|
63689
|
+
// clearly longer than the smoothing distance (factor 2) drops these stubs while
|
|
63690
|
+
// keeping genuine straight borders (which run many times the distance) and even
|
|
63691
|
+
// coarsely sampled but truly long straight segments (e.g. a 2-3*tol contour
|
|
63692
|
+
// edge). Scales with corner-bias (via ctol), so a positive bias restores the
|
|
63693
|
+
// old 1*tol behaviour for users who want shorter runs pinned.
|
|
63694
|
+
var MIN_PIN_RUN_LEN_FACTOR = 2.0;
|
|
63695
|
+
|
|
63696
|
+
// Convert the user-facing corner-bias (0 = neutral) into the positive multiplier
|
|
63697
|
+
// k applied to corner-detection resolution (ctol = tol / k). The mapping is
|
|
63698
|
+
// symmetric about zero -- k(+b) * k(-b) = 1 -- and smooth there (both branches
|
|
63699
|
+
// have slope 1 at b = 0), so opposite biases of equal magnitude are exact
|
|
63700
|
+
// inverses. A positive bias makes detection finer (k > 1, ctol < tol: more, more
|
|
63701
|
+
// finely supported corners); a negative bias makes it coarser (k < 1, ctol > tol:
|
|
63702
|
+
// fewer corners), each as if the smoothing distance were tol/k. Examples: +1
|
|
63703
|
+
// doubles the resolution (k=2, "as if distance were halved"), -1 halves it
|
|
63704
|
+
// (k=1/2, "as if doubled"); +2 -> k=3, -2 -> k=1/3.
|
|
63705
|
+
function cornerBiasScale(cornerBias) {
|
|
63706
|
+
var b = cornerBias || 0;
|
|
63707
|
+
return b >= 0 ? b + 1 : 1 / (1 - b);
|
|
63708
|
+
}
|
|
63709
|
+
|
|
63710
|
+
// @cornerBias (optional, default 0 = neutral) scales only the distance-
|
|
63711
|
+
// proportional corner parameters, by dividing the tolerance they key off
|
|
63712
|
+
// (ctol = tol / k, k = cornerBiasScale(bias)). The dimensionless thresholds are
|
|
63713
|
+
// left untouched: the corner angle, the concentration ratio, and -- downstream,
|
|
63714
|
+
// inside isStraightRun / retentionDevLimit -- STRAIGHT_DEV_FACTOR and
|
|
63715
|
+
// PIN_TURN_RATIO. So corner-bias detects (and retains) corners exactly as if the
|
|
63716
|
+
// smoothing distance were tol/k, while the smoothing kernel keeps using the real
|
|
63717
|
+
// distance. In particular `-smooth corner-bias=-1 1km` gives the same corner
|
|
63718
|
+
// results as `-smooth 2km` (a negative bias finds fewer, only well-supported
|
|
63719
|
+
// corners; a positive bias finds more), but smooths at 1km. All lengths below are
|
|
63720
|
+
// derived from ctol; only cornerAngle and concentration (both dimensionless) stay
|
|
63721
|
+
// fixed.
|
|
63465
63722
|
function getCornerParams(tol, cornerBias) {
|
|
63466
|
-
var
|
|
63723
|
+
var ctol = tol / cornerBiasScale(cornerBias);
|
|
63467
63724
|
return {
|
|
63468
63725
|
tol: tol,
|
|
63469
63726
|
cornerAngle: CORNER_ANGLE,
|
|
63470
|
-
tangentWindow: TANGENT_WINDOW_FACTOR *
|
|
63471
|
-
innerWindow: INNER_WINDOW_FACTOR * TANGENT_WINDOW_FACTOR *
|
|
63727
|
+
tangentWindow: TANGENT_WINDOW_FACTOR * ctol,
|
|
63728
|
+
innerWindow: INNER_WINDOW_FACTOR * TANGENT_WINDOW_FACTOR * ctol,
|
|
63472
63729
|
concentration: CORNER_CONCENTRATION,
|
|
63473
|
-
minRunLen: MIN_RUN_LEN_FACTOR *
|
|
63474
|
-
|
|
63730
|
+
minRunLen: MIN_RUN_LEN_FACTOR * ctol,
|
|
63731
|
+
minPinRunLen: MIN_PIN_RUN_LEN_FACTOR * ctol,
|
|
63732
|
+
maxTurnRate: 1 / (MIN_RUN_RADIUS_FACTOR * ctol) // radians of turning per ground unit
|
|
63475
63733
|
};
|
|
63476
63734
|
}
|
|
63477
63735
|
|
|
@@ -63523,6 +63781,122 @@ ${svg}
|
|
|
63523
63781
|
return totalTurn / len <= params.maxTurnRate;
|
|
63524
63782
|
}
|
|
63525
63783
|
|
|
63784
|
+
// Cyclic form of isStructuralRun for a closed ring: the span runs forward from
|
|
63785
|
+
// ring vertex @a to ring vertex @b over the m = n-1 unique vertices, wrapping
|
|
63786
|
+
// when b <= a (a == b means the whole ring). Length and turning are measured
|
|
63787
|
+
// cyclically. Used to decide whether a detected ring corner borders a genuine
|
|
63788
|
+
// straight/low-curvature run before it is pinned (see smoothArcCoords).
|
|
63789
|
+
function isStructuralRingSpan(t, channels, n, a, b, params) {
|
|
63790
|
+
var m = n - 1;
|
|
63791
|
+
if (m < 2) return false;
|
|
63792
|
+
var L = t[n - 1];
|
|
63793
|
+
var len = b > a ? t[b] - t[a] : (L - t[a]) + t[b];
|
|
63794
|
+
if (!(len >= params.minRunLen)) return false;
|
|
63795
|
+
var K = channels.length;
|
|
63796
|
+
var totalTurn = 0;
|
|
63797
|
+
var i = a;
|
|
63798
|
+
while (true) {
|
|
63799
|
+
i = (i + 1) % m;
|
|
63800
|
+
if (i === b) break;
|
|
63801
|
+
totalTurn += ringVertexTurn(channels, K, m, i);
|
|
63802
|
+
if (totalTurn / len > params.maxTurnRate) return false;
|
|
63803
|
+
}
|
|
63804
|
+
return totalTurn / len <= params.maxTurnRate;
|
|
63805
|
+
}
|
|
63806
|
+
|
|
63807
|
+
// Is span [a, b] (inclusive vertex indices, a < b, open frame) "straight at the
|
|
63808
|
+
// smoothing scale": clearly longer than the smoothing distance (>= minPinRunLen,
|
|
63809
|
+
// see MIN_PIN_RUN_LEN_FACTOR) AND confined to a thin corridor around its endpoint
|
|
63810
|
+
// chord (see STRAIGHT_DEV_FACTOR)? Used to decide whether a detected corner
|
|
63811
|
+
// borders a straight run worth pinning. Unlike isStructuralRun -- which sums raw
|
|
63812
|
+
// per-segment turning and is therefore defeated by sub-tolerance digitizing
|
|
63813
|
+
// noise -- this measures perpendicular deviation from the chord, so a finely
|
|
63814
|
+
// ragged but geometrically straight border still qualifies. The length floor is
|
|
63815
|
+
// the pinning-specific minPinRunLen (not minRunLen): a run only ~1*tol long has
|
|
63816
|
+
// too few interior points for the chord test to distinguish a true straight
|
|
63817
|
+
// border from a coarsely sampled bend, so it must not anchor a corner. @devLimit
|
|
63818
|
+
// overrides the corridor half-width (default STRAIGHT_DEV_FACTOR); retention
|
|
63819
|
+
// passes a per-corner value tightened for gentle bends (see retentionDevLimit).
|
|
63820
|
+
function isStraightRun(t, channels, a, b, params, devLimit) {
|
|
63821
|
+
var lim = devLimit === undefined ? STRAIGHT_DEV_FACTOR : devLimit;
|
|
63822
|
+
var len = t[b] - t[a];
|
|
63823
|
+
if (!(len >= params.minPinRunLen)) return false;
|
|
63824
|
+
var K = channels.length;
|
|
63825
|
+
var A = getPt(channels, K, a);
|
|
63826
|
+
var AB = subv(getPt(channels, K, b), A, K);
|
|
63827
|
+
var abDot = dot(AB, AB, K);
|
|
63828
|
+
if (!(abDot > 0)) return false;
|
|
63829
|
+
var limit2 = lim * lim * abDot;
|
|
63830
|
+
for (var i = a + 1; i < b; i++) {
|
|
63831
|
+
if (perpDistSq(channels, K, i, A, AB, abDot) > limit2) return false;
|
|
63832
|
+
}
|
|
63833
|
+
return true;
|
|
63834
|
+
}
|
|
63835
|
+
|
|
63836
|
+
// Cyclic form of isStraightRun for a closed ring: the span runs forward from ring
|
|
63837
|
+
// vertex @a to ring vertex @b over the m = n-1 unique vertices, wrapping when
|
|
63838
|
+
// b <= a. A whole-ring span (a == b, the single-corner case) has no meaningful
|
|
63839
|
+
// chord, so it falls back to the turning-rate test (a large low-curvature ring
|
|
63840
|
+
// keeps its one corner). Used by the closed-ring corner cull (see
|
|
63841
|
+
// filterRingCornersByStructure in mapshaper-smooth-algos).
|
|
63842
|
+
function isStraightRingSpan(t, channels, n, a, b, params, devLimit) {
|
|
63843
|
+
var lim = devLimit === undefined ? STRAIGHT_DEV_FACTOR : devLimit;
|
|
63844
|
+
var m = n - 1;
|
|
63845
|
+
if (m < 2) return false;
|
|
63846
|
+
if (a === b) return isStructuralRingSpan(t, channels, n, a, b, params);
|
|
63847
|
+
var L = t[n - 1];
|
|
63848
|
+
var len = b > a ? t[b] - t[a] : (L - t[a]) + t[b];
|
|
63849
|
+
if (!(len >= params.minPinRunLen)) return false;
|
|
63850
|
+
var K = channels.length;
|
|
63851
|
+
var A = getPt(channels, K, a);
|
|
63852
|
+
var AB = subv(getPt(channels, K, b), A, K);
|
|
63853
|
+
var abDot = dot(AB, AB, K);
|
|
63854
|
+
if (!(abDot > 0)) return false;
|
|
63855
|
+
var limit2 = lim * lim * abDot;
|
|
63856
|
+
var i = a;
|
|
63857
|
+
while (true) {
|
|
63858
|
+
i = (i + 1) % m;
|
|
63859
|
+
if (i === b) break;
|
|
63860
|
+
if (perpDistSq(channels, K, i, A, AB, abDot) > limit2) return false;
|
|
63861
|
+
}
|
|
63862
|
+
return true;
|
|
63863
|
+
}
|
|
63864
|
+
|
|
63865
|
+
// Straightness limit for pinning a corner whose windowed turn is @turnRad (see
|
|
63866
|
+
// PIN_TURN_RATIO): min(STRAIGHT_DEV_FACTOR, turnRad / (8 * PIN_TURN_RATIO)).
|
|
63867
|
+
function retentionDevLimit(turnRad) {
|
|
63868
|
+
var lim = turnRad / (8 * PIN_TURN_RATIO);
|
|
63869
|
+
return lim < STRAIGHT_DEV_FACTOR ? lim : STRAIGHT_DEV_FACTOR;
|
|
63870
|
+
}
|
|
63871
|
+
|
|
63872
|
+
// Windowed turn (radians) at vertex @i, over params.tangentWindow each side --
|
|
63873
|
+
// the same measure findInteriorCorners uses to flag the corner. @cyclic selects
|
|
63874
|
+
// the open or ring frame.
|
|
63875
|
+
function cornerTurn(t, channels, n, i, cyclic, params) {
|
|
63876
|
+
var K = channels.length;
|
|
63877
|
+
var L = t[n - 1];
|
|
63878
|
+
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);
|
|
63881
|
+
}
|
|
63882
|
+
|
|
63883
|
+
// Does the open span [a, b] justify pinning the corner at vertex @corner: is it a
|
|
63884
|
+
// straight run (isStraightRun) whose straightness is enough for the corner's turn
|
|
63885
|
+
// angle (retentionDevLimit)? A gentle bend needs a straighter run than a sharp
|
|
63886
|
+
// one. Used by refineBounds.
|
|
63887
|
+
function bordersStraightRun(t, channels, n, corner, a, b, params) {
|
|
63888
|
+
var lim = retentionDevLimit(cornerTurn(t, channels, n, corner, false, params));
|
|
63889
|
+
return isStraightRun(t, channels, a, b, params, lim);
|
|
63890
|
+
}
|
|
63891
|
+
|
|
63892
|
+
// Ring analogue of bordersStraightRun, for the closed-ring corner cull
|
|
63893
|
+
// (filterRingCornersByStructure). @corner is a ring vertex; the span runs from
|
|
63894
|
+
// ring vertex @a to @b (cyclic when b <= a).
|
|
63895
|
+
function bordersStraightRingSpan(t, channels, n, corner, a, b, params) {
|
|
63896
|
+
var lim = retentionDevLimit(cornerTurn(t, channels, n, corner, true, params));
|
|
63897
|
+
return isStraightRingSpan(t, channels, n, a, b, params, lim);
|
|
63898
|
+
}
|
|
63899
|
+
|
|
63526
63900
|
// --- internals ---
|
|
63527
63901
|
|
|
63528
63902
|
function ringSegLengths(t, m) {
|
|
@@ -63551,6 +63925,14 @@ ${svg}
|
|
|
63551
63925
|
return angleBetween(subv(pi, pp, K), subv(pn, pi, K), K);
|
|
63552
63926
|
}
|
|
63553
63927
|
|
|
63928
|
+
// Local turn at ring vertex i using cyclic neighbours over m unique vertices.
|
|
63929
|
+
function ringVertexTurn(channels, K, m, i) {
|
|
63930
|
+
var pi = getPt(channels, K, i);
|
|
63931
|
+
var pp = getPt(channels, K, (i - 1 + m) % m);
|
|
63932
|
+
var pn = getPt(channels, K, (i + 1) % m);
|
|
63933
|
+
return angleBetween(subv(pi, pp, K), subv(pn, pi, K), K);
|
|
63934
|
+
}
|
|
63935
|
+
|
|
63554
63936
|
// Walk from vertex i in direction dir (+1/-1) until accumulated arc length
|
|
63555
63937
|
// reaches W (or a boundary, for open arcs), returning the reached vertex index.
|
|
63556
63938
|
function reach(t, segLen, n, m, L, i, dir, W, cyclic) {
|
|
@@ -63595,6 +63977,25 @@ ${svg}
|
|
|
63595
63977
|
return o;
|
|
63596
63978
|
}
|
|
63597
63979
|
|
|
63980
|
+
function dot(a, b, K) {
|
|
63981
|
+
var d = 0;
|
|
63982
|
+
for (var c = 0; c < K; c++) d += a[c] * b[c];
|
|
63983
|
+
return d;
|
|
63984
|
+
}
|
|
63985
|
+
|
|
63986
|
+
// Squared perpendicular distance of vertex @i from the line through point @A
|
|
63987
|
+
// with direction @AB (abDot = AB.AB). = |AP|^2 - (AP.AB)^2 / |AB|^2.
|
|
63988
|
+
function perpDistSq(channels, K, i, A, AB, abDot) {
|
|
63989
|
+
var apAp = 0, apAb = 0, d;
|
|
63990
|
+
for (var c = 0; c < K; c++) {
|
|
63991
|
+
d = channels[c][i] - A[c];
|
|
63992
|
+
apAp += d * d;
|
|
63993
|
+
apAb += d * AB[c];
|
|
63994
|
+
}
|
|
63995
|
+
var perp = apAp - apAb * apAb / abDot;
|
|
63996
|
+
return perp > 0 ? perp : 0;
|
|
63997
|
+
}
|
|
63998
|
+
|
|
63598
63999
|
function angleBetween(u, v, K) {
|
|
63599
64000
|
var d = 0, nu = 0, nv = 0;
|
|
63600
64001
|
for (var c = 0; c < K; c++) {
|
|
@@ -63613,13 +64014,15 @@ ${svg}
|
|
|
63613
64014
|
// Scale-aware line smoothing primitives, shared by the -smooth command.
|
|
63614
64015
|
//
|
|
63615
64016
|
// The smoother treats a path as coordinate signals parameterized by arc length s
|
|
63616
|
-
// and applies a length-scaled low-pass filter. The user-facing distance is
|
|
63617
|
-
//
|
|
63618
|
-
//
|
|
63619
|
-
//
|
|
63620
|
-
//
|
|
63621
|
-
//
|
|
63622
|
-
//
|
|
64017
|
+
// and applies a length-scaled low-pass filter. The user-facing distance is
|
|
64018
|
+
// calibrated so that it approximates the maximum displacement of the smoothed
|
|
64019
|
+
// line from the original at high-displacement features (e.g. acute bends): finer
|
|
64020
|
+
// detail is removed and larger features pass through progressively less changed.
|
|
64021
|
+
// In frequency terms the kernel's half-amplitude (-6 dB) wavelength sits at
|
|
64022
|
+
// roughly KERNEL_STRENGTH * distance, so detail a few times finer than the
|
|
64023
|
+
// distance is strongly attenuated. The distance is not a strict deviation
|
|
64024
|
+
// bound -- a tall, narrow sub-resolution spike can still be displaced by an
|
|
64025
|
+
// amount comparable to its own amplitude (inherent to convolution smoothers).
|
|
63623
64026
|
//
|
|
63624
64027
|
// The filter is a local second-degree polynomial fit whose quadratic term
|
|
63625
64028
|
// corrects the inward shrinkage that plain weighted averaging causes on curved
|
|
@@ -63639,21 +64042,44 @@ ${svg}
|
|
|
63639
64042
|
// -simplify treats spherical coordinates. The kernel scale stays in true ground
|
|
63640
64043
|
// distance because arc length is measured with great-circle distance.
|
|
63641
64044
|
//
|
|
63642
|
-
//
|
|
63643
|
-
//
|
|
63644
|
-
//
|
|
63645
|
-
// the
|
|
63646
|
-
//
|
|
63647
|
-
//
|
|
63648
|
-
//
|
|
63649
|
-
//
|
|
64045
|
+
// KERNEL_FROM_DISTANCE maps the user distance onto the internal reference scale
|
|
64046
|
+
// (tol) that keys corner detection, output sampling and densification. The kernel
|
|
64047
|
+
// itself is then widened by KERNEL_STRENGTH (below), so the distance approximates
|
|
64048
|
+
// the maximum displacement at sharp features and the -6 dB wavelength sits near
|
|
64049
|
+
// KERNEL_STRENGTH * distance. The remaining calibration constants are expressed
|
|
64050
|
+
// relative to the internal scale and map it onto kernel widths and output
|
|
64051
|
+
// sampling; they are collected here so the mapping can be retuned in one place.
|
|
64052
|
+
// See docs/reference.md.
|
|
63650
64053
|
var KERNEL_FROM_DISTANCE = 1.2; // internal kernel scale = distance * this
|
|
64054
|
+
// Base smoothing strength baked into the default: the low-pass kernel scale is
|
|
64055
|
+
// this * tol (before the user's `strength` multiplier and the ring cap). It is
|
|
64056
|
+
// calibrated so the distance parameter approximates the maximum displacement of
|
|
64057
|
+
// the smoothed line from the original at high-displacement features (e.g. acute
|
|
64058
|
+
// bends) -- a markedly stronger, more intuitive effect than the raw -6 dB
|
|
64059
|
+
// mapping (which displaced the line far less than the distance). ONLY the kernel
|
|
64060
|
+
// scale is affected; tol -- and therefore corner detection, output sampling, the
|
|
64061
|
+
// prefilter and island dropping -- stays keyed to the raw distance.
|
|
64062
|
+
var KERNEL_STRENGTH = 5;
|
|
63651
64063
|
var GAUSSIAN_SIGMA_FACTOR = 0.4; // gaussian sigma = internal scale * this
|
|
63652
64064
|
var PAEK_SCALE_FACTOR = 0.4; // exponential kernel scale d = internal scale * this
|
|
63653
64065
|
var WINDOW_RADIUS_FACTOR = 1.2; // window half-length = internal scale * this
|
|
63654
64066
|
var SOURCE_SPACING_FACTOR = 0.25; // densify source to <= tolerance * this before smoothing
|
|
63655
64067
|
var MAX_OUTPUT_FACTOR = 8; // cap output (and source) vertices at inputCount * this
|
|
63656
64068
|
var MIN_CLOSED_SEGMENTS = 16; // floor on segments for closed rings (so they resolve)
|
|
64069
|
+
// A closed ring cannot be smoothed at a resolution coarser than the ring itself:
|
|
64070
|
+
// once the kernel window (radius = internal scale * WINDOW_RADIUS_FACTOR)
|
|
64071
|
+
// reaches half the ring's perimeter, every output point averages over the whole
|
|
64072
|
+
// loop and the ring degenerates toward a point (a circle once re-inflated). So
|
|
64073
|
+
// for a closed ring the internal scale is capped just below that threshold,
|
|
64074
|
+
// which is factor 1/(2*WINDOW_RADIUS_FACTOR) ~ 0.42. Up to the threshold the
|
|
64075
|
+
// ring keeps its shape (elongated stays elongated, and detail is rounded as much
|
|
64076
|
+
// as the ring's own size allows); the enclosed area it loses to curve-shortening
|
|
64077
|
+
// on the way is restored afterward by restoreRingArea() (a similarity rescale
|
|
64078
|
+
// about the centroid), so a small island is rounded at close to the full
|
|
64079
|
+
// requested scale without shrinking. The cap only binds when the requested
|
|
64080
|
+
// distance nears the ring's own size; large rings (perimeter >> distance) smooth
|
|
64081
|
+
// gently, lose negligible area and are effectively unaffected by either step.
|
|
64082
|
+
var MAX_RING_SCALE_FACTOR = 0.42;
|
|
63657
64083
|
|
|
63658
64084
|
// Output resampling. The smoothed curve is a continuous function of arc length;
|
|
63659
64085
|
// we sample it densely at a uniform step and then thin that dense polyline with a
|
|
@@ -63716,12 +64142,29 @@ ${svg}
|
|
|
63716
64142
|
return deg * Math.PI / 180;
|
|
63717
64143
|
}
|
|
63718
64144
|
|
|
63719
|
-
// Resolve the
|
|
63720
|
-
//
|
|
63721
|
-
//
|
|
64145
|
+
// Resolve the corner-detection bias (default 0 = neutral). This is the raw
|
|
64146
|
+
// user-facing value; getCornerParams / cornerBiasScale convert it into the
|
|
64147
|
+
// multiplier on detection resolution. A positive bias keeps more corners, a
|
|
64148
|
+
// negative bias fewer. Missing/null falls back to neutral.
|
|
63722
64149
|
function resolveCornerBias(opts) {
|
|
63723
64150
|
var b = opts.cornerBias;
|
|
63724
|
-
return b
|
|
64151
|
+
return (b === undefined || b === null) ? 0 : b;
|
|
64152
|
+
}
|
|
64153
|
+
|
|
64154
|
+
// Resolve the smoothing-strength multiplier (default 1). It scales only the
|
|
64155
|
+
// low-pass kernel (window radius and sigma) relative to the distance, so a value
|
|
64156
|
+
// > 1 smooths more strongly (wider kernel, larger divergence from the original)
|
|
64157
|
+
// and < 1 more gently. Everything else keyed to the distance -- corner detection,
|
|
64158
|
+
// output sampling, the prefilter and island dropping -- is left unchanged.
|
|
64159
|
+
// Non-positive or missing values fall back to 1.
|
|
64160
|
+
//
|
|
64161
|
+
// By design, curve exaggeration (gain > 1) scales WITH strength: gain multiplies
|
|
64162
|
+
// the quadratic curvature correction (a0 - mean) in smoothPoint, and that term is
|
|
64163
|
+
// measured over the strength-scaled kernel window, so a wider kernel amplifies a
|
|
64164
|
+
// given gain. This coupling is intentional -- do not normalize it out.
|
|
64165
|
+
function resolveStrength(opts) {
|
|
64166
|
+
var s = opts.strength;
|
|
64167
|
+
return s > 0 ? s : 1;
|
|
63725
64168
|
}
|
|
63726
64169
|
|
|
63727
64170
|
function smoothArcCoords(xx, yy, opts) {
|
|
@@ -63737,6 +64180,29 @@ ${svg}
|
|
|
63737
64180
|
var spherical = !!opts.spherical;
|
|
63738
64181
|
var keepCorners = !!opts.keepCorners;
|
|
63739
64182
|
var bendAngle = resolveBendAngle(opts);
|
|
64183
|
+
|
|
64184
|
+
// Cumulative arc length in ground units (meters for spherical data), so the
|
|
64185
|
+
// kernel scale stays in true distance regardless of coordinate representation.
|
|
64186
|
+
var t = arcLengths(origX, origY, n, spherical);
|
|
64187
|
+
if (!(t[n - 1] > 0)) {
|
|
64188
|
+
return {xx: origX, yy: origY}; // degenerate (coincident points)
|
|
64189
|
+
}
|
|
64190
|
+
// The low-pass kernel scale is the raw distance scale (tol) times the baked-in
|
|
64191
|
+
// KERNEL_STRENGTH calibration and the user's `strength` multiplier (default 1).
|
|
64192
|
+
// Only the kernel (radius, sigma) uses this scale; tol -- which drives corner
|
|
64193
|
+
// detection, output sampling and densification -- stays keyed to the raw
|
|
64194
|
+
// distance, so those effects are unaffected by either strength factor.
|
|
64195
|
+
var kernelScale = tol * KERNEL_STRENGTH * resolveStrength(opts);
|
|
64196
|
+
// A closed ring smaller than the smoothing resolution would collapse toward its
|
|
64197
|
+
// centroid, so cap both scales at a fraction of the ring's perimeter (see
|
|
64198
|
+
// MAX_RING_SCALE_FACTOR). This only binds when the requested distance (or the
|
|
64199
|
+
// boosted kernel) approaches the ring's own size; otherwise it is a no-op. The
|
|
64200
|
+
// cap on kernelScale also stops a large `strength` from collapsing a ring.
|
|
64201
|
+
if (closed) {
|
|
64202
|
+
var ringCap = MAX_RING_SCALE_FACTOR * t[n - 1];
|
|
64203
|
+
tol = Math.min(tol, ringCap);
|
|
64204
|
+
kernelScale = Math.min(kernelScale, ringCap);
|
|
64205
|
+
}
|
|
63740
64206
|
var ctx = {
|
|
63741
64207
|
tol: tol,
|
|
63742
64208
|
method: method,
|
|
@@ -63749,21 +64215,25 @@ ${svg}
|
|
|
63749
64215
|
// segment still turns well under the threshold; never coarsen it beyond the
|
|
63750
64216
|
// default (the angle filter alone thins the output for larger angles).
|
|
63751
64217
|
denseStep: tol * DENSE_STEP_FACTOR * Math.min(1, bendAngle / DEFAULT_BEND_ANGLE),
|
|
63752
|
-
radius:
|
|
63753
|
-
scale: (method == 'gaussian' ? GAUSSIAN_SIGMA_FACTOR : PAEK_SCALE_FACTOR) *
|
|
64218
|
+
radius: kernelScale * WINDOW_RADIUS_FACTOR,
|
|
64219
|
+
scale: (method == 'gaussian' ? GAUSSIAN_SIGMA_FACTOR : PAEK_SCALE_FACTOR) * kernelScale
|
|
63754
64220
|
};
|
|
63755
|
-
|
|
63756
|
-
// Cumulative arc length in ground units (meters for spherical data), so the
|
|
63757
|
-
// kernel scale stays in true distance regardless of coordinate representation.
|
|
63758
|
-
var t = arcLengths(origX, origY, n, spherical);
|
|
63759
|
-
if (!(t[n - 1] > 0)) {
|
|
63760
|
-
return {xx: origX, yy: origY}; // degenerate (coincident points)
|
|
63761
|
-
}
|
|
63762
64221
|
var channels = spherical ? lngLatToXYZChannels(origX, origY, n) : [origX, origY];
|
|
63763
64222
|
|
|
63764
64223
|
if (closed) {
|
|
64224
|
+
var ringParams = getCornerParams(tol, ctx.cornerBias);
|
|
63765
64225
|
var corners = keepCorners ?
|
|
63766
|
-
findInteriorCorners(t, channels, n, true,
|
|
64226
|
+
findInteriorCorners(t, channels, n, true, ringParams) : [];
|
|
64227
|
+
// findInteriorCorners flags localized bends by angle alone; it does not check
|
|
64228
|
+
// whether a candidate borders a structural (long, low-curvature) run. Keep
|
|
64229
|
+
// only the corners that do -- a natural ring with no straight segments has
|
|
64230
|
+
// none and must smooth cyclically. Otherwise the ring would be rotated to
|
|
64231
|
+
// corners[0] and smoothed as an open path with that vertex pinned as a
|
|
64232
|
+
// spurious cusp (whose location shifts with the tolerance-scaled detection
|
|
64233
|
+
// window), even though refineBounds later drops every interior breakpoint.
|
|
64234
|
+
if (corners.length > 0) {
|
|
64235
|
+
corners = filterRingCornersByStructure(t, channels, n, corners, ringParams);
|
|
64236
|
+
}
|
|
63767
64237
|
if (corners.length === 0) {
|
|
63768
64238
|
return smoothClosedCyclic(t, channels, n, ctx);
|
|
63769
64239
|
}
|
|
@@ -63785,9 +64255,18 @@ ${svg}
|
|
|
63785
64255
|
}
|
|
63786
64256
|
|
|
63787
64257
|
// Smooth an open path partitioned at @interiorBreaks (sorted interior vertex
|
|
63788
|
-
// indices).
|
|
63789
|
-
//
|
|
63790
|
-
// its
|
|
64258
|
+
// indices). Corner retention and verbatim-copy are two separate decisions:
|
|
64259
|
+
// - A breakpoint is kept only if it borders a straight run that is straight
|
|
64260
|
+
// enough for its turn angle (bordersStraightRun -- deviation from the endpoint
|
|
64261
|
+
// chord, robust to sub-tolerance wiggle, and tightened for gentle bends so a
|
|
64262
|
+
// soft bend on a borderline-straight run is not pinned); otherwise
|
|
64263
|
+
// refineBounds drops it.
|
|
64264
|
+
// - A kept span is copied verbatim only if it is clean per-vertex
|
|
64265
|
+
// (isStructuralRun); otherwise it is smoothed with its endpoints pinned. So a
|
|
64266
|
+
// straight-but-noisy border is smoothed into a clean straight line between
|
|
64267
|
+
// its pinned corners, rather than curving into its neighbours.
|
|
64268
|
+
// Every breakpoint (and the two arc endpoints) keeps its exact original position;
|
|
64269
|
+
// shared breakpoint vertices are emitted once.
|
|
63791
64270
|
function smoothOpenSpans(origX, origY, t, channels, n, interiorBreaks, ctx) {
|
|
63792
64271
|
var bounds = [0].concat(interiorBreaks);
|
|
63793
64272
|
bounds.push(n - 1);
|
|
@@ -63807,17 +64286,28 @@ ${svg}
|
|
|
63807
64286
|
return {xx: xx, yy: yy};
|
|
63808
64287
|
}
|
|
63809
64288
|
|
|
63810
|
-
// Drop interior breakpoints that don't border any
|
|
63811
|
-
// inside a wiggly stretch
|
|
63812
|
-
//
|
|
63813
|
-
//
|
|
64289
|
+
// Drop interior breakpoints that don't border any pinnable straight run (e.g.
|
|
64290
|
+
// spikes inside a wiggly stretch, or -- crucially on sparse/simplified data --
|
|
64291
|
+
// points sampled along a gentle curve), merging their spans, until the partition
|
|
64292
|
+
// is stable. Merging can turn two short pieces back into one straight run, so the
|
|
64293
|
+
// test is repeated each pass. A breakpoint is kept only if an adjacent span is
|
|
64294
|
+
// straight at the smoothing scale AND straight enough for the breakpoint's own
|
|
64295
|
+
// turn angle (bordersStraightRun): deviation from the endpoint chord, tightened
|
|
64296
|
+
// for gentle corners so a soft bend on a borderline-straight run is not pinned.
|
|
64297
|
+
// The older per-vertex turning gate (isStructuralRun) is deliberately NOT used
|
|
64298
|
+
// for retention -- it admits any run bending no tighter than radius
|
|
64299
|
+
// MIN_RUN_RADIUS_FACTOR*tol, i.e. gentle curves, which on coarsely-sampled data
|
|
64300
|
+
// produces spurious corners along smooth bends. (isStructuralRun still governs
|
|
64301
|
+
// verbatim-copy of a kept span; see smoothOpenSpans.) The corner for both
|
|
64302
|
+
// adjacent spans is the breakpoint itself, so its turn angle gates each side.
|
|
63814
64303
|
function refineBounds(t, channels, bounds, params) {
|
|
64304
|
+
var n = channels[0].length;
|
|
63815
64305
|
var changed = true;
|
|
63816
64306
|
while (changed && bounds.length > 2) {
|
|
63817
64307
|
changed = false;
|
|
63818
64308
|
for (var i = 1; i < bounds.length - 1; i++) {
|
|
63819
|
-
var leftStruct =
|
|
63820
|
-
var rightStruct =
|
|
64309
|
+
var leftStruct = bordersStraightRun(t, channels, n, bounds[i], bounds[i - 1], bounds[i], params);
|
|
64310
|
+
var rightStruct = bordersStraightRun(t, channels, n, bounds[i], bounds[i], bounds[i + 1], params);
|
|
63821
64311
|
if (!leftStruct && !rightStruct) {
|
|
63822
64312
|
bounds.splice(i, 1);
|
|
63823
64313
|
changed = true;
|
|
@@ -63828,6 +64318,39 @@ ${svg}
|
|
|
63828
64318
|
return bounds;
|
|
63829
64319
|
}
|
|
63830
64320
|
|
|
64321
|
+
// Drop closed-ring corners that don't border a run worth pinning on either side,
|
|
64322
|
+
// merging their (cyclic) spans, until the set is stable -- the cyclic analogue
|
|
64323
|
+
// of refineBounds, applied before the ring is rotated/pinned. A single corner
|
|
64324
|
+
// is tested against the whole-ring span. Uses the same angle-coupled
|
|
64325
|
+
// chord-straightness criterion as refineBounds (see bordersStraightRingSpan).
|
|
64326
|
+
// Returns the surviving corners (a subset of @corners, order preserved); an empty
|
|
64327
|
+
// result means the ring has no qualifying corner and should smooth cyclically.
|
|
64328
|
+
function filterRingCornersByStructure(t, channels, n, corners, params) {
|
|
64329
|
+
var list = corners.slice();
|
|
64330
|
+
var changed = true;
|
|
64331
|
+
while (changed && list.length > 0) {
|
|
64332
|
+
changed = false;
|
|
64333
|
+
for (var i = 0; i < list.length; i++) {
|
|
64334
|
+
var cur = list[i];
|
|
64335
|
+
var leftStruct, rightStruct;
|
|
64336
|
+
if (list.length === 1) {
|
|
64337
|
+
leftStruct = rightStruct = bordersStraightRingSpan(t, channels, n, cur, cur, cur, params);
|
|
64338
|
+
} else {
|
|
64339
|
+
var prev = list[(i - 1 + list.length) % list.length];
|
|
64340
|
+
var next = list[(i + 1) % list.length];
|
|
64341
|
+
leftStruct = bordersStraightRingSpan(t, channels, n, cur, prev, cur, params);
|
|
64342
|
+
rightStruct = bordersStraightRingSpan(t, channels, n, cur, cur, next, params);
|
|
64343
|
+
}
|
|
64344
|
+
if (!leftStruct && !rightStruct) {
|
|
64345
|
+
list.splice(i, 1);
|
|
64346
|
+
changed = true;
|
|
64347
|
+
break;
|
|
64348
|
+
}
|
|
64349
|
+
}
|
|
64350
|
+
}
|
|
64351
|
+
return list;
|
|
64352
|
+
}
|
|
64353
|
+
|
|
63831
64354
|
// Smooth a single open span [lo, hi] (inclusive) and pin both ends to their
|
|
63832
64355
|
// original coordinates. Reuses the whole-arc smoothing pipeline on the sub-arc.
|
|
63833
64356
|
function smoothSpanOpen(origX, origY, t, channels, lo, hi, ctx) {
|
|
@@ -63859,6 +64382,10 @@ ${svg}
|
|
|
63859
64382
|
var dense = densifyChannels(t, channels, maxSpacing);
|
|
63860
64383
|
var src = buildSource(dense.t, dense.channels, true, ctx.radius, L);
|
|
63861
64384
|
var sm = sampleSmoothedCurve(src, 0, L, true, ctx, n);
|
|
64385
|
+
// Smoothing shrinks a closed loop (curve-shortening); restore its original
|
|
64386
|
+
// enclosed area so small rings can be rounded at the full scale without
|
|
64387
|
+
// shrinking. A no-op for large rings (they lose negligible area).
|
|
64388
|
+
restoreRingArea(sm, channels, n, ctx.spherical);
|
|
63862
64389
|
var out = ctx.spherical ? xyzChannelsToLngLat(sm) : {xx: sm[0], yy: sm[1]};
|
|
63863
64390
|
// force an exactly closed ring (the periodic endpoints are equal up to fp)
|
|
63864
64391
|
out.xx[out.xx.length - 1] = out.xx[0];
|
|
@@ -63866,6 +64393,98 @@ ${svg}
|
|
|
63866
64393
|
return out;
|
|
63867
64394
|
}
|
|
63868
64395
|
|
|
64396
|
+
// Rescale a smoothed closed ring about its centroid so it re-encloses the
|
|
64397
|
+
// original ring's area. Because it is a uniform similarity transform, the
|
|
64398
|
+
// smoothed *shape* is unchanged -- only its size -- so the rounding introduced by
|
|
64399
|
+
// smoothing is preserved while the curve-shortening shrinkage is undone.
|
|
64400
|
+
// @sm are the smoothed smoothing channels (plain arrays; [x,y] planar or unit-
|
|
64401
|
+
// sphere [X,Y,Z] spherical, last point == first). @orig are the original channels
|
|
64402
|
+
// (length @n, closed). Silent no-op if either area is non-positive.
|
|
64403
|
+
function restoreRingArea(sm, orig, n, spherical) {
|
|
64404
|
+
var origArea = ringChannelArea(orig, n, spherical);
|
|
64405
|
+
var m = sm[0].length;
|
|
64406
|
+
var smArea = ringChannelArea(sm, m, spherical);
|
|
64407
|
+
if (!(origArea > 0) || !(smArea > 0)) return;
|
|
64408
|
+
var f = Math.sqrt(origArea / smArea);
|
|
64409
|
+
if (spherical) {
|
|
64410
|
+
scaleRingSpherical(sm, m, f);
|
|
64411
|
+
} else {
|
|
64412
|
+
scaleRingPlanar(sm, m, f);
|
|
64413
|
+
}
|
|
64414
|
+
}
|
|
64415
|
+
|
|
64416
|
+
// Enclosed-area proxy of a closed ring (@count points, last == first). Planar:
|
|
64417
|
+
// the shoelace area on (x,y). Spherical: the shoelace area of the ring projected
|
|
64418
|
+
// into the tangent plane at its centroid direction. Only ratios of two such
|
|
64419
|
+
// areas are used, so the (unit-sphere) scale is irrelevant, and the tangent-plane
|
|
64420
|
+
// error is second order in the ring's size -- negligible for the small rings
|
|
64421
|
+
// where this is needed.
|
|
64422
|
+
function ringChannelArea(ch, count, spherical) {
|
|
64423
|
+
if (!spherical) {
|
|
64424
|
+
var x = ch[0], y = ch[1], a = 0;
|
|
64425
|
+
for (var i = 0; i < count - 1; i++) a += x[i] * y[i + 1] - x[i + 1] * y[i];
|
|
64426
|
+
return Math.abs(a / 2);
|
|
64427
|
+
}
|
|
64428
|
+
var basis = tangentBasis(ringCentroidDir(ch, count - 1));
|
|
64429
|
+
var ex = basis.ex, ey = basis.ey;
|
|
64430
|
+
var X = ch[0], Y = ch[1], Z = ch[2], area = 0, px, py, qx, qy;
|
|
64431
|
+
for (var j = 0; j < count - 1; j++) {
|
|
64432
|
+
px = X[j] * ex[0] + Y[j] * ex[1] + Z[j] * ex[2];
|
|
64433
|
+
py = X[j] * ey[0] + Y[j] * ey[1] + Z[j] * ey[2];
|
|
64434
|
+
qx = X[j + 1] * ex[0] + Y[j + 1] * ex[1] + Z[j + 1] * ex[2];
|
|
64435
|
+
qy = X[j + 1] * ey[0] + Y[j + 1] * ey[1] + Z[j + 1] * ey[2];
|
|
64436
|
+
area += px * qy - qx * py;
|
|
64437
|
+
}
|
|
64438
|
+
return Math.abs(area / 2);
|
|
64439
|
+
}
|
|
64440
|
+
|
|
64441
|
+
function scaleRingPlanar(sm, count, f) {
|
|
64442
|
+
var x = sm[0], y = sm[1], cx = 0, cy = 0, i;
|
|
64443
|
+
for (i = 0; i < count - 1; i++) { cx += x[i]; cy += y[i]; }
|
|
64444
|
+
cx /= (count - 1); cy /= (count - 1);
|
|
64445
|
+
for (i = 0; i < count; i++) {
|
|
64446
|
+
x[i] = cx + (x[i] - cx) * f;
|
|
64447
|
+
y[i] = cy + (y[i] - cy) * f;
|
|
64448
|
+
}
|
|
64449
|
+
}
|
|
64450
|
+
|
|
64451
|
+
// Scale each unit-sphere point's angular offset from the centroid direction by
|
|
64452
|
+
// ~f (keeping the radial component, then renormalizing), which scales the
|
|
64453
|
+
// enclosed area by ~f^2 for the small caps where this runs.
|
|
64454
|
+
function scaleRingSpherical(sm, count, f) {
|
|
64455
|
+
var X = sm[0], Y = sm[1], Z = sm[2];
|
|
64456
|
+
var c = ringCentroidDir(sm, count - 1);
|
|
64457
|
+
for (var i = 0; i < count; i++) {
|
|
64458
|
+
var dot = X[i] * c[0] + Y[i] * c[1] + Z[i] * c[2];
|
|
64459
|
+
var tx = X[i] - dot * c[0], ty = Y[i] - dot * c[1], tz = Z[i] - dot * c[2];
|
|
64460
|
+
var vx = dot * c[0] + f * tx, vy = dot * c[1] + f * ty, vz = dot * c[2] + f * tz;
|
|
64461
|
+
var nrm = Math.sqrt(vx * vx + vy * vy + vz * vz) || 1;
|
|
64462
|
+
X[i] = vx / nrm; Y[i] = vy / nrm; Z[i] = vz / nrm;
|
|
64463
|
+
}
|
|
64464
|
+
}
|
|
64465
|
+
|
|
64466
|
+
function ringCentroidDir(ch, m) {
|
|
64467
|
+
var X = ch[0], Y = ch[1], Z = ch[2], cx = 0, cy = 0, cz = 0;
|
|
64468
|
+
for (var i = 0; i < m; i++) { cx += X[i]; cy += Y[i]; cz += Z[i]; }
|
|
64469
|
+
var nrm = Math.sqrt(cx * cx + cy * cy + cz * cz) || 1;
|
|
64470
|
+
return [cx / nrm, cy / nrm, cz / nrm];
|
|
64471
|
+
}
|
|
64472
|
+
|
|
64473
|
+
// Orthonormal tangent basis (ex, ey) at unit direction c on the sphere.
|
|
64474
|
+
function tangentBasis(c) {
|
|
64475
|
+
// pick the world axis least aligned with c to avoid a degenerate cross product
|
|
64476
|
+
var ax = Math.abs(c[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0];
|
|
64477
|
+
var ex = cross(c, ax);
|
|
64478
|
+
var en = Math.sqrt(ex[0] * ex[0] + ex[1] * ex[1] + ex[2] * ex[2]) || 1;
|
|
64479
|
+
ex = [ex[0] / en, ex[1] / en, ex[2] / en];
|
|
64480
|
+
var ey = cross(c, ex);
|
|
64481
|
+
return {c: c, ex: ex, ey: ey};
|
|
64482
|
+
}
|
|
64483
|
+
|
|
64484
|
+
function cross(a, b) {
|
|
64485
|
+
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
64486
|
+
}
|
|
64487
|
+
|
|
63869
64488
|
function copySpan(origX, origY, lo, hi) {
|
|
63870
64489
|
var xx = [], yy = [];
|
|
63871
64490
|
for (var i = lo; i <= hi; i++) {
|
|
@@ -64305,11 +64924,12 @@ ${svg}
|
|
|
64305
64924
|
stop$1('Expected prefilter-gate to be a number > 0');
|
|
64306
64925
|
}
|
|
64307
64926
|
if (opts.corner_bias !== undefined && opts.corner_bias !== null &&
|
|
64308
|
-
|
|
64309
|
-
stop$1('Expected corner-bias to be a number
|
|
64927
|
+
typeof opts.corner_bias != 'number') {
|
|
64928
|
+
stop$1('Expected corner-bias to be a number');
|
|
64310
64929
|
}
|
|
64311
|
-
// Corner preservation is on by default; no-corners
|
|
64312
|
-
|
|
64930
|
+
// Corner preservation is on by default; no-corners turns it off. (corner-bias
|
|
64931
|
+
// only tunes sensitivity: 0 is neutral, not off.)
|
|
64932
|
+
var keepCorners = !opts.no_corners;
|
|
64313
64933
|
var implicitlySmoothedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasPaths);
|
|
64314
64934
|
|
|
64315
64935
|
// Smoothing rewrites coordinates, so lock in any pending (non-destructive)
|
|
@@ -64328,6 +64948,8 @@ ${svg}
|
|
|
64328
64948
|
filterDetailPaths(arcs, {
|
|
64329
64949
|
distance: tolerance,
|
|
64330
64950
|
tortuosity: opts.prefilter_gate,
|
|
64951
|
+
roundness: opts.prefilter_roundness,
|
|
64952
|
+
minRingArea: opts.prefilter_min_area,
|
|
64331
64953
|
spherical: spherical
|
|
64332
64954
|
});
|
|
64333
64955
|
var removed = before - arcs.getPointCount();
|
|
@@ -64343,6 +64965,7 @@ ${svg}
|
|
|
64343
64965
|
keepCorners: keepCorners,
|
|
64344
64966
|
cornerBias: opts.corner_bias,
|
|
64345
64967
|
gain: opts.gain,
|
|
64968
|
+
strength: opts.strength,
|
|
64346
64969
|
maxBendAngle: opts.max_bend_angle
|
|
64347
64970
|
});
|
|
64348
64971
|
|
|
@@ -64371,6 +64994,7 @@ ${svg}
|
|
|
64371
64994
|
keepCorners: opts.keepCorners,
|
|
64372
64995
|
cornerBias: opts.cornerBias,
|
|
64373
64996
|
gain: opts.gain,
|
|
64997
|
+
strength: opts.strength,
|
|
64374
64998
|
maxBendAngle: opts.maxBendAngle,
|
|
64375
64999
|
closed: arcs.arcIsClosed(arcId)
|
|
64376
65000
|
});
|
|
@@ -66188,7 +66812,7 @@ ${svg}
|
|
|
66188
66812
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
66189
66813
|
}
|
|
66190
66814
|
|
|
66191
|
-
var version = "0.7.
|
|
66815
|
+
var version = "0.7.36";
|
|
66192
66816
|
|
|
66193
66817
|
// Parse command line args into commands and run them
|
|
66194
66818
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|