mapshaper 0.7.28 → 0.7.29
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 +70 -9
- package/package.json +1 -1
- package/www/mapshaper.js +70 -9
package/mapshaper.js
CHANGED
|
@@ -44900,7 +44900,7 @@ ${svg}
|
|
|
44900
44900
|
message('[medial] sample sites: ' + sites.coords.length);
|
|
44901
44901
|
}
|
|
44902
44902
|
profileStart('medial:computeSegments');
|
|
44903
|
-
var medial = computeMedialSegments(sites, coordDistances);
|
|
44903
|
+
var medial = computeMedialSegments(sites, coordDistances, sites.grid);
|
|
44904
44904
|
profileEnd('medial:computeSegments');
|
|
44905
44905
|
if (medial.segments.length === 0) return null;
|
|
44906
44906
|
// Stitch the individual Voronoi edges (2-point segments that meet at shared
|
|
@@ -45165,7 +45165,11 @@ ${svg}
|
|
|
45165
45165
|
// extrapolated as an outward ray). Dropping the touching interior borders and
|
|
45166
45166
|
// the no-feature coastline shrinks the one remaining Delaunay and avoids
|
|
45167
45167
|
// building a redundant medial where the source boundary already partitions.
|
|
45168
|
-
|
|
45168
|
+
var kept = keptSites(sites, grid, coordDistances);
|
|
45169
|
+
// Keep the segment grid with the sites so computeMedialSegments can re-measure
|
|
45170
|
+
// the true source gap when the sample-pair proximity test is too coarse.
|
|
45171
|
+
kept.grid = grid;
|
|
45172
|
+
return kept;
|
|
45169
45173
|
}
|
|
45170
45174
|
|
|
45171
45175
|
// Bucket every boundary segment into a uniform grid so the nearest cross-feature
|
|
@@ -45280,6 +45284,50 @@ ${svg}
|
|
|
45280
45284
|
return best;
|
|
45281
45285
|
}
|
|
45282
45286
|
|
|
45287
|
+
// True when medial vertex c lies in the buffer overlap of features fp and fq:
|
|
45288
|
+
// within fp's radius of an fp-owned source segment AND within fq's radius of an
|
|
45289
|
+
// fq-owned source segment. Measured against the actual source segments via the
|
|
45290
|
+
// grid, so it is correct regardless of how coarsely the banks were sampled --
|
|
45291
|
+
// unlike the sample-pair distance, which overestimates the gap when the nearest
|
|
45292
|
+
// samples on opposite banks are staggered or far from the true closest approach.
|
|
45293
|
+
// Slack on each reach when rescuing a cross-feature edge whose sample endpoints
|
|
45294
|
+
// fell outside the cheap proximity test. It absorbs the discretization of the
|
|
45295
|
+
// medial graph near a pinch point: the connecting Voronoi edge is bounded by the
|
|
45296
|
+
// site spacing (capped at the buffer distance), so a genuinely contested edge can
|
|
45297
|
+
// run up to ~1.5x reach and its medial vertices can land a similar fraction
|
|
45298
|
+
// outside the overlap. 1.3 covers the worst real case observed (~1.18) with
|
|
45299
|
+
// headroom, while spurious edges between sites contested with *other* features
|
|
45300
|
+
// miss by far more (>=1.5 or have no nearby source segment) and stay pruned.
|
|
45301
|
+
var MEDIAL_OVERLAP_SLACK = 1.3;
|
|
45302
|
+
|
|
45303
|
+
function medialVertexInOverlap(ctx, c, fp, fq, rp, rq) {
|
|
45304
|
+
var sp = rp * MEDIAL_OVERLAP_SLACK, sq = rq * MEDIAL_OVERLAP_SLACK;
|
|
45305
|
+
return pointFeatureDistSq(ctx, c[0], c[1], fp) <= sp * sp &&
|
|
45306
|
+
pointFeatureDistSq(ctx, c[0], c[1], fq) <= sq * sq;
|
|
45307
|
+
}
|
|
45308
|
+
|
|
45309
|
+
// Squared distance from (x, y) to the nearest segment owned by feature @feat,
|
|
45310
|
+
// probing the 3x3 grid-cell neighborhood (cell == max reach, so any segment
|
|
45311
|
+
// within a single feature's radius is in the window). Infinity if none.
|
|
45312
|
+
function pointFeatureDistSq(ctx, x, y, feat) {
|
|
45313
|
+
var seg = ctx.seg, grid = ctx.grid;
|
|
45314
|
+
var cx = ctx.colOf(x), cy = ctx.rowOf(y);
|
|
45315
|
+
var best = Infinity;
|
|
45316
|
+
for (var gx = cx - 1; gx <= cx + 1; gx++) {
|
|
45317
|
+
for (var gy = cy - 1; gy <= cy + 1; gy++) {
|
|
45318
|
+
var bucket = grid.get(ctx.cellKey(gx, gy));
|
|
45319
|
+
if (!bucket) continue;
|
|
45320
|
+
for (var b = 0; b < bucket.length; b++) {
|
|
45321
|
+
var s = bucket[b];
|
|
45322
|
+
if (seg.feat[s] !== feat) continue;
|
|
45323
|
+
var d2 = pointSegDistSq2(x, y, seg.x0[s], seg.y0[s], seg.x1[s], seg.y1[s]);
|
|
45324
|
+
if (d2 < best) best = d2;
|
|
45325
|
+
}
|
|
45326
|
+
}
|
|
45327
|
+
}
|
|
45328
|
+
return best;
|
|
45329
|
+
}
|
|
45330
|
+
|
|
45283
45331
|
// Keep only the sites that border a real gap: a different feature within reach
|
|
45284
45332
|
// (finite gap) but farther than the touching threshold. These are the only sites
|
|
45285
45333
|
// that can shape the medial axis. Touching/coincident interior borders (gap ~ 0,
|
|
@@ -45460,7 +45508,7 @@ ${svg}
|
|
|
45460
45508
|
return Math.floor(e / 3);
|
|
45461
45509
|
}
|
|
45462
45510
|
|
|
45463
|
-
function computeMedialSegments(sites, coordDistances) {
|
|
45511
|
+
function computeMedialSegments(sites, coordDistances, ctx) {
|
|
45464
45512
|
var coords = sites.coords;
|
|
45465
45513
|
var owner = sites.owner;
|
|
45466
45514
|
profileStart('medial:delaunay');
|
|
@@ -45485,18 +45533,28 @@ ${svg}
|
|
|
45485
45533
|
var opp = halfedges[e];
|
|
45486
45534
|
var p = triangles[e];
|
|
45487
45535
|
var q = triangles[nextHalfedge(e)];
|
|
45488
|
-
|
|
45536
|
+
var fp = owner[p], fq = owner[q];
|
|
45537
|
+
if (fp === fq) continue;
|
|
45489
45538
|
var dx = coords[p][0] - coords[q][0];
|
|
45490
45539
|
var dy = coords[p][1] - coords[q][1];
|
|
45491
45540
|
var siteDist = Math.sqrt(dx * dx + dy * dy);
|
|
45492
|
-
var
|
|
45493
|
-
|
|
45494
|
-
// never have overlapping buffers, so their bisector is not a contested edge
|
|
45495
|
-
if (siteDist > reach) continue;
|
|
45541
|
+
var rp = coordDistances[fp], rq = coordDistances[fq];
|
|
45542
|
+
var reach = rp + rq;
|
|
45496
45543
|
var t1 = triangleOfEdge(e);
|
|
45497
45544
|
var c1 = verts[t1];
|
|
45498
45545
|
if (!c1) continue; // degenerate (near-collinear) triangle
|
|
45546
|
+
// Sites within the sum of their radii are accepted directly; this is the
|
|
45547
|
+
// common, cheap case. When they are farther apart, the bisector might still
|
|
45548
|
+
// be contested -- the nearest sample pair overestimates the true source gap
|
|
45549
|
+
// where banks are sampled coarsely or staggered. Re-measure the actual gap
|
|
45550
|
+
// at the medial vertex against the source segments (the grid) and rescue the
|
|
45551
|
+
// edge if it really lies in the buffer overlap. Without the rescue the medial
|
|
45552
|
+
// axis fragments at such spots, leaving the equidistant cut wall open so the
|
|
45553
|
+
// overlap face is never subdivided and a whole contested corridor is assigned
|
|
45554
|
+
// to one feature (a feature wrapping a neighbor's enclosed island).
|
|
45555
|
+
var near = siteDist <= reach;
|
|
45499
45556
|
if (opp === -1) {
|
|
45557
|
+
if (!near && !(ctx && medialVertexInOverlap(ctx, c1, fp, fq, rp, rq))) continue;
|
|
45500
45558
|
// Hull edge: the Voronoi edge here is an unbounded ray (the bisector of
|
|
45501
45559
|
// two sites on the convex hull). Emit it as an outward ray from the
|
|
45502
45560
|
// circumcenter so the medial line reaches and crosses the buffer
|
|
@@ -45517,6 +45575,9 @@ ${svg}
|
|
|
45517
45575
|
var t2 = triangleOfEdge(opp);
|
|
45518
45576
|
var c2 = verts[t2];
|
|
45519
45577
|
if (!c2) continue;
|
|
45578
|
+
if (!near && !(ctx &&
|
|
45579
|
+
(medialVertexInOverlap(ctx, c1, fp, fq, rp, rq) ||
|
|
45580
|
+
medialVertexInOverlap(ctx, c2, fp, fq, rp, rq)))) continue;
|
|
45520
45581
|
var sx = c1[0] - c2[0], sy = c1[1] - c2[1];
|
|
45521
45582
|
var segLen = Math.sqrt(sx * sx + sy * sy);
|
|
45522
45583
|
// a real medial edge inside the overlap is short (on the order of the site
|
|
@@ -63697,7 +63758,7 @@ ${svg}
|
|
|
63697
63758
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
63698
63759
|
}
|
|
63699
63760
|
|
|
63700
|
-
var version = "0.7.
|
|
63761
|
+
var version = "0.7.29";
|
|
63701
63762
|
|
|
63702
63763
|
// Parse command line args into commands and run them
|
|
63703
63764
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
package/package.json
CHANGED
package/www/mapshaper.js
CHANGED
|
@@ -44900,7 +44900,7 @@ ${svg}
|
|
|
44900
44900
|
message('[medial] sample sites: ' + sites.coords.length);
|
|
44901
44901
|
}
|
|
44902
44902
|
profileStart('medial:computeSegments');
|
|
44903
|
-
var medial = computeMedialSegments(sites, coordDistances);
|
|
44903
|
+
var medial = computeMedialSegments(sites, coordDistances, sites.grid);
|
|
44904
44904
|
profileEnd('medial:computeSegments');
|
|
44905
44905
|
if (medial.segments.length === 0) return null;
|
|
44906
44906
|
// Stitch the individual Voronoi edges (2-point segments that meet at shared
|
|
@@ -45165,7 +45165,11 @@ ${svg}
|
|
|
45165
45165
|
// extrapolated as an outward ray). Dropping the touching interior borders and
|
|
45166
45166
|
// the no-feature coastline shrinks the one remaining Delaunay and avoids
|
|
45167
45167
|
// building a redundant medial where the source boundary already partitions.
|
|
45168
|
-
|
|
45168
|
+
var kept = keptSites(sites, grid, coordDistances);
|
|
45169
|
+
// Keep the segment grid with the sites so computeMedialSegments can re-measure
|
|
45170
|
+
// the true source gap when the sample-pair proximity test is too coarse.
|
|
45171
|
+
kept.grid = grid;
|
|
45172
|
+
return kept;
|
|
45169
45173
|
}
|
|
45170
45174
|
|
|
45171
45175
|
// Bucket every boundary segment into a uniform grid so the nearest cross-feature
|
|
@@ -45280,6 +45284,50 @@ ${svg}
|
|
|
45280
45284
|
return best;
|
|
45281
45285
|
}
|
|
45282
45286
|
|
|
45287
|
+
// True when medial vertex c lies in the buffer overlap of features fp and fq:
|
|
45288
|
+
// within fp's radius of an fp-owned source segment AND within fq's radius of an
|
|
45289
|
+
// fq-owned source segment. Measured against the actual source segments via the
|
|
45290
|
+
// grid, so it is correct regardless of how coarsely the banks were sampled --
|
|
45291
|
+
// unlike the sample-pair distance, which overestimates the gap when the nearest
|
|
45292
|
+
// samples on opposite banks are staggered or far from the true closest approach.
|
|
45293
|
+
// Slack on each reach when rescuing a cross-feature edge whose sample endpoints
|
|
45294
|
+
// fell outside the cheap proximity test. It absorbs the discretization of the
|
|
45295
|
+
// medial graph near a pinch point: the connecting Voronoi edge is bounded by the
|
|
45296
|
+
// site spacing (capped at the buffer distance), so a genuinely contested edge can
|
|
45297
|
+
// run up to ~1.5x reach and its medial vertices can land a similar fraction
|
|
45298
|
+
// outside the overlap. 1.3 covers the worst real case observed (~1.18) with
|
|
45299
|
+
// headroom, while spurious edges between sites contested with *other* features
|
|
45300
|
+
// miss by far more (>=1.5 or have no nearby source segment) and stay pruned.
|
|
45301
|
+
var MEDIAL_OVERLAP_SLACK = 1.3;
|
|
45302
|
+
|
|
45303
|
+
function medialVertexInOverlap(ctx, c, fp, fq, rp, rq) {
|
|
45304
|
+
var sp = rp * MEDIAL_OVERLAP_SLACK, sq = rq * MEDIAL_OVERLAP_SLACK;
|
|
45305
|
+
return pointFeatureDistSq(ctx, c[0], c[1], fp) <= sp * sp &&
|
|
45306
|
+
pointFeatureDistSq(ctx, c[0], c[1], fq) <= sq * sq;
|
|
45307
|
+
}
|
|
45308
|
+
|
|
45309
|
+
// Squared distance from (x, y) to the nearest segment owned by feature @feat,
|
|
45310
|
+
// probing the 3x3 grid-cell neighborhood (cell == max reach, so any segment
|
|
45311
|
+
// within a single feature's radius is in the window). Infinity if none.
|
|
45312
|
+
function pointFeatureDistSq(ctx, x, y, feat) {
|
|
45313
|
+
var seg = ctx.seg, grid = ctx.grid;
|
|
45314
|
+
var cx = ctx.colOf(x), cy = ctx.rowOf(y);
|
|
45315
|
+
var best = Infinity;
|
|
45316
|
+
for (var gx = cx - 1; gx <= cx + 1; gx++) {
|
|
45317
|
+
for (var gy = cy - 1; gy <= cy + 1; gy++) {
|
|
45318
|
+
var bucket = grid.get(ctx.cellKey(gx, gy));
|
|
45319
|
+
if (!bucket) continue;
|
|
45320
|
+
for (var b = 0; b < bucket.length; b++) {
|
|
45321
|
+
var s = bucket[b];
|
|
45322
|
+
if (seg.feat[s] !== feat) continue;
|
|
45323
|
+
var d2 = pointSegDistSq2(x, y, seg.x0[s], seg.y0[s], seg.x1[s], seg.y1[s]);
|
|
45324
|
+
if (d2 < best) best = d2;
|
|
45325
|
+
}
|
|
45326
|
+
}
|
|
45327
|
+
}
|
|
45328
|
+
return best;
|
|
45329
|
+
}
|
|
45330
|
+
|
|
45283
45331
|
// Keep only the sites that border a real gap: a different feature within reach
|
|
45284
45332
|
// (finite gap) but farther than the touching threshold. These are the only sites
|
|
45285
45333
|
// that can shape the medial axis. Touching/coincident interior borders (gap ~ 0,
|
|
@@ -45460,7 +45508,7 @@ ${svg}
|
|
|
45460
45508
|
return Math.floor(e / 3);
|
|
45461
45509
|
}
|
|
45462
45510
|
|
|
45463
|
-
function computeMedialSegments(sites, coordDistances) {
|
|
45511
|
+
function computeMedialSegments(sites, coordDistances, ctx) {
|
|
45464
45512
|
var coords = sites.coords;
|
|
45465
45513
|
var owner = sites.owner;
|
|
45466
45514
|
profileStart('medial:delaunay');
|
|
@@ -45485,18 +45533,28 @@ ${svg}
|
|
|
45485
45533
|
var opp = halfedges[e];
|
|
45486
45534
|
var p = triangles[e];
|
|
45487
45535
|
var q = triangles[nextHalfedge(e)];
|
|
45488
|
-
|
|
45536
|
+
var fp = owner[p], fq = owner[q];
|
|
45537
|
+
if (fp === fq) continue;
|
|
45489
45538
|
var dx = coords[p][0] - coords[q][0];
|
|
45490
45539
|
var dy = coords[p][1] - coords[q][1];
|
|
45491
45540
|
var siteDist = Math.sqrt(dx * dx + dy * dy);
|
|
45492
|
-
var
|
|
45493
|
-
|
|
45494
|
-
// never have overlapping buffers, so their bisector is not a contested edge
|
|
45495
|
-
if (siteDist > reach) continue;
|
|
45541
|
+
var rp = coordDistances[fp], rq = coordDistances[fq];
|
|
45542
|
+
var reach = rp + rq;
|
|
45496
45543
|
var t1 = triangleOfEdge(e);
|
|
45497
45544
|
var c1 = verts[t1];
|
|
45498
45545
|
if (!c1) continue; // degenerate (near-collinear) triangle
|
|
45546
|
+
// Sites within the sum of their radii are accepted directly; this is the
|
|
45547
|
+
// common, cheap case. When they are farther apart, the bisector might still
|
|
45548
|
+
// be contested -- the nearest sample pair overestimates the true source gap
|
|
45549
|
+
// where banks are sampled coarsely or staggered. Re-measure the actual gap
|
|
45550
|
+
// at the medial vertex against the source segments (the grid) and rescue the
|
|
45551
|
+
// edge if it really lies in the buffer overlap. Without the rescue the medial
|
|
45552
|
+
// axis fragments at such spots, leaving the equidistant cut wall open so the
|
|
45553
|
+
// overlap face is never subdivided and a whole contested corridor is assigned
|
|
45554
|
+
// to one feature (a feature wrapping a neighbor's enclosed island).
|
|
45555
|
+
var near = siteDist <= reach;
|
|
45499
45556
|
if (opp === -1) {
|
|
45557
|
+
if (!near && !(ctx && medialVertexInOverlap(ctx, c1, fp, fq, rp, rq))) continue;
|
|
45500
45558
|
// Hull edge: the Voronoi edge here is an unbounded ray (the bisector of
|
|
45501
45559
|
// two sites on the convex hull). Emit it as an outward ray from the
|
|
45502
45560
|
// circumcenter so the medial line reaches and crosses the buffer
|
|
@@ -45517,6 +45575,9 @@ ${svg}
|
|
|
45517
45575
|
var t2 = triangleOfEdge(opp);
|
|
45518
45576
|
var c2 = verts[t2];
|
|
45519
45577
|
if (!c2) continue;
|
|
45578
|
+
if (!near && !(ctx &&
|
|
45579
|
+
(medialVertexInOverlap(ctx, c1, fp, fq, rp, rq) ||
|
|
45580
|
+
medialVertexInOverlap(ctx, c2, fp, fq, rp, rq)))) continue;
|
|
45520
45581
|
var sx = c1[0] - c2[0], sy = c1[1] - c2[1];
|
|
45521
45582
|
var segLen = Math.sqrt(sx * sx + sy * sy);
|
|
45522
45583
|
// a real medial edge inside the overlap is short (on the order of the site
|
|
@@ -63697,7 +63758,7 @@ ${svg}
|
|
|
63697
63758
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
63698
63759
|
}
|
|
63699
63760
|
|
|
63700
|
-
var version = "0.7.
|
|
63761
|
+
var version = "0.7.29";
|
|
63701
63762
|
|
|
63702
63763
|
// Parse command line args into commands and run them
|
|
63703
63764
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|