mapshaper 0.7.51 → 0.7.52
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 +252 -1
- package/package.json +2 -1
- package/www/mapshaper-gui.js +387 -108
- package/www/mapshaper.js +252 -1
- package/www/page.css +5 -0
package/mapshaper.js
CHANGED
|
@@ -75897,7 +75897,7 @@ ${svg}
|
|
|
75897
75897
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
75898
75898
|
}
|
|
75899
75899
|
|
|
75900
|
-
var version = "0.7.
|
|
75900
|
+
var version = "0.7.52";
|
|
75901
75901
|
|
|
75902
75902
|
// Parse command line args into commands and run them
|
|
75903
75903
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -76559,6 +76559,256 @@ ${svg}
|
|
|
76559
76559
|
return false;
|
|
76560
76560
|
}
|
|
76561
76561
|
|
|
76562
|
+
// Utilities for dividing a path at one or two points ("snipping").
|
|
76563
|
+
//
|
|
76564
|
+
// The new arcs are appended to the end of the collection; the original arc is
|
|
76565
|
+
// left in place (it may still be referenced by other shapes). Callers are
|
|
76566
|
+
// responsible for updating the shapes that should use the new arcs.
|
|
76567
|
+
|
|
76568
|
+
// Divide an arc's vertices into slices at one or two cut locations.
|
|
76569
|
+
// n: number of vertices in the arc
|
|
76570
|
+
// cuts: one or two cut objects, ordered from the start of the arc:
|
|
76571
|
+
// {offset: i, point: [x, y]} cuts the segment between vertices i and i + 1
|
|
76572
|
+
// {offset: i, point: null} cuts at vertex i (which is duplicated, so that
|
|
76573
|
+
// both adjacent slices contain it)
|
|
76574
|
+
// Returns slice descriptors {start, end, prefix, suffix}, where start and end
|
|
76575
|
+
// are an inclusive vertex range (empty if start > end) and prefix/suffix are
|
|
76576
|
+
// points inserted at a cut location.
|
|
76577
|
+
function getArcSlices(n, cuts) {
|
|
76578
|
+
var slices = [];
|
|
76579
|
+
var start = 0;
|
|
76580
|
+
var prefix = null;
|
|
76581
|
+
cuts.forEach(function(cut) {
|
|
76582
|
+
var i = cut.offset;
|
|
76583
|
+
if (cut.point) {
|
|
76584
|
+
if (i < 0 || i > n - 2) error('Out-of-range arc cut offset:', i);
|
|
76585
|
+
slices.push({start: start, end: i, prefix: prefix, suffix: cut.point});
|
|
76586
|
+
prefix = cut.point;
|
|
76587
|
+
start = i + 1;
|
|
76588
|
+
} else {
|
|
76589
|
+
if (i < 1 || i > n - 2) error('Out-of-range arc cut offset:', i);
|
|
76590
|
+
slices.push({start: start, end: i, prefix: prefix, suffix: null});
|
|
76591
|
+
prefix = null;
|
|
76592
|
+
start = i;
|
|
76593
|
+
}
|
|
76594
|
+
});
|
|
76595
|
+
slices.push({start: start, end: n - 1, prefix: prefix, suffix: null});
|
|
76596
|
+
return slices;
|
|
76597
|
+
}
|
|
76598
|
+
|
|
76599
|
+
function getArcSliceLength(slice) {
|
|
76600
|
+
var n = slice.end >= slice.start ? slice.end - slice.start + 1 : 0;
|
|
76601
|
+
if (slice.prefix) n++;
|
|
76602
|
+
if (slice.suffix) n++;
|
|
76603
|
+
return n;
|
|
76604
|
+
}
|
|
76605
|
+
|
|
76606
|
+
// Append the slices of a divided arc to an ArcCollection.
|
|
76607
|
+
// Returns the ids of the new arcs, ordered from the start of the original arc.
|
|
76608
|
+
function splitArcAtCuts(arcs, arcId, cuts) {
|
|
76609
|
+
var data = arcs.getVertexData();
|
|
76610
|
+
var zlimit = arcs.getRetainedInterval();
|
|
76611
|
+
var arcStart = data.ii[arcId];
|
|
76612
|
+
var arcCount = data.nn.length;
|
|
76613
|
+
var slices = getArcSlices(data.nn[arcId], cuts);
|
|
76614
|
+
var pointCount = data.xx.length;
|
|
76615
|
+
var added = slices.reduce(function(memo, slice) {
|
|
76616
|
+
return memo + getArcSliceLength(slice);
|
|
76617
|
+
}, 0);
|
|
76618
|
+
var xx2 = new Float64Array(pointCount + added);
|
|
76619
|
+
var yy2 = new Float64Array(pointCount + added);
|
|
76620
|
+
var zz2 = arcs.isFlat() ? null : new Float64Array(pointCount + added);
|
|
76621
|
+
var nn2 = new Uint32Array(arcCount + slices.length);
|
|
76622
|
+
var i = pointCount;
|
|
76623
|
+
var ids = [];
|
|
76624
|
+
|
|
76625
|
+
xx2.set(data.xx);
|
|
76626
|
+
yy2.set(data.yy);
|
|
76627
|
+
nn2.set(data.nn);
|
|
76628
|
+
if (zz2) zz2.set(data.zz);
|
|
76629
|
+
|
|
76630
|
+
slices.forEach(function(slice, sliceId) {
|
|
76631
|
+
var first = i;
|
|
76632
|
+
var j;
|
|
76633
|
+
if (slice.prefix) {
|
|
76634
|
+
xx2[i] = slice.prefix[0];
|
|
76635
|
+
yy2[i] = slice.prefix[1];
|
|
76636
|
+
i++;
|
|
76637
|
+
}
|
|
76638
|
+
for (j = slice.start; j <= slice.end; j++) {
|
|
76639
|
+
xx2[i] = data.xx[arcStart + j];
|
|
76640
|
+
yy2[i] = data.yy[arcStart + j];
|
|
76641
|
+
if (zz2) zz2[i] = data.zz[arcStart + j];
|
|
76642
|
+
i++;
|
|
76643
|
+
}
|
|
76644
|
+
if (slice.suffix) {
|
|
76645
|
+
xx2[i] = slice.suffix[0];
|
|
76646
|
+
yy2[i] = slice.suffix[1];
|
|
76647
|
+
i++;
|
|
76648
|
+
}
|
|
76649
|
+
if (zz2) {
|
|
76650
|
+
// arc endpoints must never be removed by simplification
|
|
76651
|
+
zz2[first] = Infinity;
|
|
76652
|
+
zz2[i - 1] = Infinity;
|
|
76653
|
+
}
|
|
76654
|
+
nn2[arcCount + sliceId] = i - first;
|
|
76655
|
+
ids.push(arcCount + sliceId);
|
|
76656
|
+
});
|
|
76657
|
+
|
|
76658
|
+
arcs.updateVertexData(nn2, xx2, yy2, zz2);
|
|
76659
|
+
arcs.setRetainedInterval(zlimit);
|
|
76660
|
+
return ids;
|
|
76661
|
+
}
|
|
76662
|
+
|
|
76663
|
+
// A cut identifies one location along a path (an array of arc ids):
|
|
76664
|
+
// seq: index into the path of the arc containing the cut
|
|
76665
|
+
// offset: vertex index within that arc, in traversal orientation
|
|
76666
|
+
// point: coords of the cut, or null to cut at vertex <offset>
|
|
76667
|
+
// displayPoint: coords of the cut in the GUI's display CRS, or null
|
|
76668
|
+
// t: position along the segment (0-1), used to order two cuts
|
|
76669
|
+
// that land on the same segment
|
|
76670
|
+
|
|
76671
|
+
// Work out how to divide a path at one cut (open path) or two cuts (ring),
|
|
76672
|
+
// without mutating anything.
|
|
76673
|
+
//
|
|
76674
|
+
// path: array of arc ids
|
|
76675
|
+
// cutsArg: one cut (open path) or two cuts (ring), in any order
|
|
76676
|
+
// arcLen: function(arcId) returning the number of vertices in an arc
|
|
76677
|
+
// baseId: arcs.size(), used to predict the ids of arcs that will be appended
|
|
76678
|
+
// closed: whether the path is a ring
|
|
76679
|
+
//
|
|
76680
|
+
// Returns {splits, a, b}, where splits are arc divisions to perform in order
|
|
76681
|
+
// and a and b are the two resulting paths, or null if the cuts are degenerate
|
|
76682
|
+
// (at the endpoint of an open path, or two cuts in the same place on a ring).
|
|
76683
|
+
//
|
|
76684
|
+
function planPathDivision(path, cutsArg, arcLen, baseId, closed) {
|
|
76685
|
+
var cuts = sortCuts(cutsArg);
|
|
76686
|
+
var boundaryCuts = []; // cuts that fall between two arcs
|
|
76687
|
+
var interiorCuts = {}; // cuts that fall inside an arc, by path index
|
|
76688
|
+
var expanded = []; // path with divided arcs replaced by their pieces
|
|
76689
|
+
var positions = []; // position of each cut within the expanded path
|
|
76690
|
+
var splits = [];
|
|
76691
|
+
var nextArcId = baseId;
|
|
76692
|
+
var a, b, q, q1, q2;
|
|
76693
|
+
|
|
76694
|
+
if (cuts.length != (closed ? 2 : 1)) return null;
|
|
76695
|
+
|
|
76696
|
+
cuts.forEach(function(cut, cutId) {
|
|
76697
|
+
var n = arcLen(absArcId(path[cut.seq]));
|
|
76698
|
+
var seq;
|
|
76699
|
+
if (!cut.point && (cut.offset === 0 || cut.offset === n - 1)) {
|
|
76700
|
+
// a cut at an arc endpoint doesn't divide the arc, it divides the
|
|
76701
|
+
// sequence of arcs
|
|
76702
|
+
seq = cut.offset === 0 ? cut.seq : cut.seq + 1;
|
|
76703
|
+
if (closed && seq === path.length) seq = 0; // a ring's seam is one place
|
|
76704
|
+
boundaryCuts.push({cutId: cutId, seq: seq});
|
|
76705
|
+
} else {
|
|
76706
|
+
if (!interiorCuts[cut.seq]) interiorCuts[cut.seq] = [];
|
|
76707
|
+
interiorCuts[cut.seq].push({cutId: cutId, cut: cut, arcLen: n});
|
|
76708
|
+
}
|
|
76709
|
+
});
|
|
76710
|
+
|
|
76711
|
+
for (var k=0; k<=path.length; k++) {
|
|
76712
|
+
boundaryCuts.forEach(function(item) {
|
|
76713
|
+
if (item.seq === k) positions[item.cutId] = expanded.length;
|
|
76714
|
+
});
|
|
76715
|
+
if (k === path.length) break;
|
|
76716
|
+
if (!interiorCuts[k]) {
|
|
76717
|
+
expanded.push(path[k]);
|
|
76718
|
+
continue;
|
|
76719
|
+
}
|
|
76720
|
+
addDividedArc(interiorCuts[k], path[k]);
|
|
76721
|
+
}
|
|
76722
|
+
|
|
76723
|
+
if (!closed) {
|
|
76724
|
+
q = positions[0];
|
|
76725
|
+
if (q === 0 || q === expanded.length) return null; // cut at a path endpoint
|
|
76726
|
+
a = expanded.slice(0, q);
|
|
76727
|
+
b = expanded.slice(q);
|
|
76728
|
+
} else {
|
|
76729
|
+
q1 = Math.min(positions[0], positions[1]);
|
|
76730
|
+
q2 = Math.max(positions[0], positions[1]);
|
|
76731
|
+
if (q1 === q2) return null; // both cuts in the same place
|
|
76732
|
+
a = expanded.slice(q1, q2);
|
|
76733
|
+
b = expanded.slice(q2).concat(expanded.slice(0, q1));
|
|
76734
|
+
}
|
|
76735
|
+
return {splits: splits, a: a, b: b};
|
|
76736
|
+
|
|
76737
|
+
function addDividedArc(group, arcId) {
|
|
76738
|
+
var fwd = arcId >= 0;
|
|
76739
|
+
var n = group[0].arcLen;
|
|
76740
|
+
// convert traversal offsets to forward-orientation offsets
|
|
76741
|
+
var fwdCuts = group.map(function(item) {
|
|
76742
|
+
return {
|
|
76743
|
+
offset: fwd ? item.cut.offset :
|
|
76744
|
+
n - (item.cut.point ? 2 : 1) - item.cut.offset,
|
|
76745
|
+
point: item.cut.point || null,
|
|
76746
|
+
displayPoint: item.cut.displayPoint || null
|
|
76747
|
+
};
|
|
76748
|
+
});
|
|
76749
|
+
// a reversed arc is traversed from its last vertex, so traversal order is
|
|
76750
|
+
// the reverse of forward order
|
|
76751
|
+
var pieces = getSplitArcIds(nextArcId, group.length, fwd);
|
|
76752
|
+
splits.push({
|
|
76753
|
+
arcId: absArcId(arcId),
|
|
76754
|
+
cuts: fwd ? fwdCuts : fwdCuts.concat().reverse()
|
|
76755
|
+
});
|
|
76756
|
+
nextArcId += group.length + 1;
|
|
76757
|
+
pieces.forEach(function(id, i) {
|
|
76758
|
+
if (i > 0) positions[group[i - 1].cutId] = expanded.length;
|
|
76759
|
+
expanded.push(id);
|
|
76760
|
+
});
|
|
76761
|
+
}
|
|
76762
|
+
}
|
|
76763
|
+
|
|
76764
|
+
// Ids of the pieces of a divided arc, in traversal order
|
|
76765
|
+
function getSplitArcIds(baseId, cutCount, fwd) {
|
|
76766
|
+
var ids = [];
|
|
76767
|
+
for (var i=0; i<=cutCount; i++) {
|
|
76768
|
+
ids.push(fwd ? baseId + i : ~(baseId + cutCount - i));
|
|
76769
|
+
}
|
|
76770
|
+
return ids;
|
|
76771
|
+
}
|
|
76772
|
+
|
|
76773
|
+
function sortCuts(cuts) {
|
|
76774
|
+
return cuts.concat().sort(function(a, b) {
|
|
76775
|
+
return a.seq - b.seq || getCutOffset(a) - getCutOffset(b);
|
|
76776
|
+
});
|
|
76777
|
+
}
|
|
76778
|
+
|
|
76779
|
+
// A sortable offset within one arc. A cut on the segment following a vertex
|
|
76780
|
+
// comes after the vertex itself; two cuts on the same segment are ordered by
|
|
76781
|
+
// their position along it.
|
|
76782
|
+
function getCutOffset(cut) {
|
|
76783
|
+
if (!cut.point) return cut.offset;
|
|
76784
|
+
return cut.offset + 0.5 + (cut.t > 0 ? Math.min(cut.t, 1) : 0) * 0.4;
|
|
76785
|
+
}
|
|
76786
|
+
|
|
76787
|
+
function getPlanarPathLength(ids, arcs) {
|
|
76788
|
+
var iter = arcs.getShapeIter(ids);
|
|
76789
|
+
var len = 0;
|
|
76790
|
+
var x, y;
|
|
76791
|
+
if (iter.hasNext()) {
|
|
76792
|
+
x = iter.x;
|
|
76793
|
+
y = iter.y;
|
|
76794
|
+
}
|
|
76795
|
+
while (iter.hasNext()) {
|
|
76796
|
+
len += distance2D$1(x, y, iter.x, iter.y);
|
|
76797
|
+
x = iter.x;
|
|
76798
|
+
y = iter.y;
|
|
76799
|
+
}
|
|
76800
|
+
return len;
|
|
76801
|
+
}
|
|
76802
|
+
|
|
76803
|
+
var ArcSplit = /*#__PURE__*/Object.freeze({
|
|
76804
|
+
__proto__: null,
|
|
76805
|
+
getArcSliceLength: getArcSliceLength,
|
|
76806
|
+
getArcSlices: getArcSlices,
|
|
76807
|
+
getPlanarPathLength: getPlanarPathLength,
|
|
76808
|
+
planPathDivision: planPathDivision,
|
|
76809
|
+
splitArcAtCuts: splitArcAtCuts
|
|
76810
|
+
});
|
|
76811
|
+
|
|
76562
76812
|
var BOX_BLUR_PASSES = 3;
|
|
76563
76813
|
|
|
76564
76814
|
function blurRasterGrid(raster, optsArg, crs) {
|
|
@@ -77735,6 +77985,7 @@ ${svg}
|
|
|
77735
77985
|
AnchorPoints,
|
|
77736
77986
|
ArcClassifier,
|
|
77737
77987
|
ArcDissolve,
|
|
77988
|
+
ArcSplit,
|
|
77738
77989
|
ArcUtils,
|
|
77739
77990
|
Bbox2Clipping,
|
|
77740
77991
|
BinArray$1,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mapshaper",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.52",
|
|
4
4
|
"description": "A tool for editing geospatial data for mapping and GIS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"shapefile",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"postpublish": "./release_web_ui; ./release_github_version",
|
|
33
33
|
"dev": "rollup --config --watch",
|
|
34
34
|
"test:browser": "playwright test --fully-parallel --workers=6",
|
|
35
|
+
"install:browsers": "./install-playwright-browsers",
|
|
35
36
|
"benchmark:raster": "MAPSHAPER_RUN_BENCHMARKS=1 playwright test browser-tests/raster-resampling-benchmark.spec.mjs --project=chromium --workers=1"
|
|
36
37
|
},
|
|
37
38
|
"main": "./mapshaper.js",
|
package/www/mapshaper-gui.js
CHANGED
|
@@ -8933,6 +8933,44 @@
|
|
|
8933
8933
|
}
|
|
8934
8934
|
|
|
8935
8935
|
|
|
8936
|
+
// Divide an arc at one or two cut locations, appending the pieces to the end of
|
|
8937
|
+
// the arc collection (the original arc is left in place).
|
|
8938
|
+
// cuts: cut descriptors ordered from the start of the arc, each containing an
|
|
8939
|
+
// arc-relative vertex offset plus source and display coordinates of the cut
|
|
8940
|
+
// location (both null when cutting at an existing vertex).
|
|
8941
|
+
// Returns the ids of the new arcs.
|
|
8942
|
+
function splitArc(lyr, arcId, cuts) {
|
|
8943
|
+
var ids = internal.splitArcAtCuts(lyr.gui.source.dataset.arcs, arcId,
|
|
8944
|
+
cuts.map(function(cut) {
|
|
8945
|
+
return {offset: cut.offset, point: cut.point || null};
|
|
8946
|
+
}));
|
|
8947
|
+
if (isProjectedLayer(lyr)) {
|
|
8948
|
+
internal.splitArcAtCuts(lyr.gui.displayArcs, arcId,
|
|
8949
|
+
cuts.map(function(cut) {
|
|
8950
|
+
return {offset: cut.offset, point: cut.displayPoint || null};
|
|
8951
|
+
}));
|
|
8952
|
+
}
|
|
8953
|
+
return ids;
|
|
8954
|
+
}
|
|
8955
|
+
|
|
8956
|
+
function deleteLastArcs(lyr, count) {
|
|
8957
|
+
for (var i=0; i<count; i++) {
|
|
8958
|
+
internal.deleteLastArc(lyr.gui.source.dataset.arcs);
|
|
8959
|
+
if (isProjectedLayer(lyr)) {
|
|
8960
|
+
internal.deleteLastArc(lyr.gui.displayArcs);
|
|
8961
|
+
}
|
|
8962
|
+
}
|
|
8963
|
+
}
|
|
8964
|
+
|
|
8965
|
+
// Add a path feature to the end of a layer.
|
|
8966
|
+
// shp: an array of parts; rec: an attribute record, or null for an empty one
|
|
8967
|
+
function appendFeature(lyr, shp, rec) {
|
|
8968
|
+
lyr.shapes.push(shp);
|
|
8969
|
+
if (lyr.data) {
|
|
8970
|
+
lyr.data.getRecords().push(rec || getEmptyDataRecord(lyr.data));
|
|
8971
|
+
}
|
|
8972
|
+
}
|
|
8973
|
+
|
|
8936
8974
|
function deleteLastPath(lyr) {
|
|
8937
8975
|
var arcId = lyr.gui.displayArcs.size() - 1;
|
|
8938
8976
|
if (lyr.data) {
|
|
@@ -12523,6 +12561,60 @@
|
|
|
12523
12561
|
}
|
|
12524
12562
|
}
|
|
12525
12563
|
|
|
12564
|
+
// Divide one path of one feature at one location (open path) or two locations
|
|
12565
|
+
// (ring). See internal.planPathDivision() for the cut format.
|
|
12566
|
+
//
|
|
12567
|
+
// The shorter of the two resulting paths is split off into a new feature at the
|
|
12568
|
+
// end of the layer, with a copy of the original feature's attributes; the
|
|
12569
|
+
// longer one stays in place. For a multipart feature this leaves the longer
|
|
12570
|
+
// path connected to the rest of the feature.
|
|
12571
|
+
//
|
|
12572
|
+
// Returns a descriptor for undoing the edit, or null if the cuts are degenerate.
|
|
12573
|
+
//
|
|
12574
|
+
function snipPath(lyr, fid, partId, cuts) {
|
|
12575
|
+
var arcs = lyr.gui.source.dataset.arcs;
|
|
12576
|
+
var shp = lyr.shapes[fid];
|
|
12577
|
+
var path = shp[partId];
|
|
12578
|
+
var closed = geom.pathIsClosed(path, arcs);
|
|
12579
|
+
var prevShape = shp.map(function(part) { return part.concat(); });
|
|
12580
|
+
var arcCount = arcs.size();
|
|
12581
|
+
var plan = internal.planPathDivision(path, cuts, function(arcId) {
|
|
12582
|
+
return arcs.getVertexData().nn[arcId];
|
|
12583
|
+
}, arcCount, closed);
|
|
12584
|
+
var newShape, keep, remove, rec;
|
|
12585
|
+
if (!plan) return null;
|
|
12586
|
+
plan.splits.forEach(function(split) {
|
|
12587
|
+
splitArc(lyr, split.arcId, split.cuts);
|
|
12588
|
+
});
|
|
12589
|
+
if (internal.getPlanarPathLength(plan.a, arcs) >=
|
|
12590
|
+
internal.getPlanarPathLength(plan.b, arcs)) {
|
|
12591
|
+
keep = plan.a;
|
|
12592
|
+
remove = plan.b;
|
|
12593
|
+
} else {
|
|
12594
|
+
keep = plan.b;
|
|
12595
|
+
remove = plan.a;
|
|
12596
|
+
}
|
|
12597
|
+
newShape = shp.concat();
|
|
12598
|
+
newShape[partId] = keep;
|
|
12599
|
+
lyr.shapes[fid] = newShape;
|
|
12600
|
+
rec = lyr.data ? internal.copyRecord(lyr.data.getRecordAt(fid)) : null;
|
|
12601
|
+
appendFeature(lyr, [remove], rec);
|
|
12602
|
+
return {
|
|
12603
|
+
fid: fid,
|
|
12604
|
+
partId: partId,
|
|
12605
|
+
cuts: cuts,
|
|
12606
|
+
prevShape: prevShape,
|
|
12607
|
+
newFeatureId: lyr.shapes.length - 1,
|
|
12608
|
+
appendedArcCount: arcs.size() - arcCount
|
|
12609
|
+
};
|
|
12610
|
+
}
|
|
12611
|
+
|
|
12612
|
+
function undoSnip(lyr, result) {
|
|
12613
|
+
deleteFeature(lyr, result.newFeatureId);
|
|
12614
|
+
lyr.shapes[result.fid] = result.prevShape;
|
|
12615
|
+
deleteLastArcs(lyr, result.appendedArcCount);
|
|
12616
|
+
}
|
|
12617
|
+
|
|
12526
12618
|
var copyRecord = internal.copyRecord;
|
|
12527
12619
|
|
|
12528
12620
|
function isUndoEvt(e) {
|
|
@@ -12693,6 +12785,22 @@
|
|
|
12693
12785
|
addHistoryState(undo, redo);
|
|
12694
12786
|
});
|
|
12695
12787
|
|
|
12788
|
+
gui.on('snip', function(e) {
|
|
12789
|
+
var target = e.target;
|
|
12790
|
+
var result = e.result;
|
|
12791
|
+
var undo = function() {
|
|
12792
|
+
undoSnip(target, result);
|
|
12793
|
+
gui.model.updated({arc_count: true});
|
|
12794
|
+
};
|
|
12795
|
+
var redo = function() {
|
|
12796
|
+
// re-snipping restores the same arc ids, because the arcs added by the
|
|
12797
|
+
// previous snip were removed from the end of the collection
|
|
12798
|
+
result = snipPath(target, result.fid, result.partId, result.cuts);
|
|
12799
|
+
gui.model.updated({arc_count: true});
|
|
12800
|
+
};
|
|
12801
|
+
addHistoryState(undo, redo);
|
|
12802
|
+
});
|
|
12803
|
+
|
|
12696
12804
|
gui.on('point_add', function(e) {
|
|
12697
12805
|
var redo = function() {
|
|
12698
12806
|
appendNewPoint(e.data.target, e.p);
|
|
@@ -13174,7 +13282,7 @@
|
|
|
13174
13282
|
empty: ['edit_polygons', 'edit_lines', 'edit_points', 'box', 'ruler'],
|
|
13175
13283
|
polygons: ['info', 'selection', 'box', 'polygon_style', 'edit_polygons', 'ruler'],
|
|
13176
13284
|
rectangles: ['info', 'selection', 'box', 'polygon_style', 'rectangles', 'edit_polygons', 'ruler'],
|
|
13177
|
-
lines: ['info', 'selection', 'box', 'line_style', 'edit_lines', '
|
|
13285
|
+
lines: ['info', 'selection', 'box', 'line_style', 'edit_lines', 'snip_lines', 'ruler'],
|
|
13178
13286
|
table: ['info', 'selection'],
|
|
13179
13287
|
raster: ['ruler', 'box'],
|
|
13180
13288
|
labels: ['info', 'selection', 'box', 'point_style', 'labels', 'edit_points', 'ruler'],
|
|
@@ -13278,7 +13386,7 @@
|
|
|
13278
13386
|
};
|
|
13279
13387
|
|
|
13280
13388
|
this.modeSupportsUndo = function(mode) {
|
|
13281
|
-
return ['data', 'label_style', 'point_style', 'line_style', 'polygon_style', 'labels', 'edit_points', 'edit_lines', 'edit_polygons', 'vertices', 'rectangles'].includes(mode);
|
|
13389
|
+
return ['data', 'label_style', 'point_style', 'line_style', 'polygon_style', 'labels', 'edit_points', 'edit_lines', 'edit_polygons', 'snip_lines', 'vertices', 'rectangles'].includes(mode);
|
|
13282
13390
|
};
|
|
13283
13391
|
|
|
13284
13392
|
this.getMode = getInteractionMode;
|
|
@@ -16807,7 +16915,8 @@
|
|
|
16807
16915
|
test = polygonVertexTest;
|
|
16808
16916
|
} else if (
|
|
16809
16917
|
interactionMode == 'vertices' ||
|
|
16810
|
-
interactionMode == 'edit_lines'
|
|
16918
|
+
interactionMode == 'edit_lines' ||
|
|
16919
|
+
interactionMode == 'snip_lines') {
|
|
16811
16920
|
test = vertexTest;
|
|
16812
16921
|
} else if (geoType == 'polyline') {
|
|
16813
16922
|
test = polylineTest;
|
|
@@ -17332,6 +17441,22 @@
|
|
|
17332
17441
|
triggerChangeEvent();
|
|
17333
17442
|
};
|
|
17334
17443
|
|
|
17444
|
+
// Mark the location of a snip that has been chosen but not yet applied
|
|
17445
|
+
// (the first of the two cuts needed to divide a ring).
|
|
17446
|
+
self.setPendingSnip = function(p) {
|
|
17447
|
+
var p2 = storedData.snip_coordinates;
|
|
17448
|
+
if (!active || !p) return;
|
|
17449
|
+
if (p2 && p2[0] == p[0] && p2[1] == p[1]) return;
|
|
17450
|
+
storedData.snip_coordinates = p;
|
|
17451
|
+
triggerChangeEvent();
|
|
17452
|
+
};
|
|
17453
|
+
|
|
17454
|
+
self.clearPendingSnip = function() {
|
|
17455
|
+
if (!storedData.snip_coordinates) return;
|
|
17456
|
+
delete storedData.snip_coordinates;
|
|
17457
|
+
triggerChangeEvent();
|
|
17458
|
+
};
|
|
17459
|
+
|
|
17335
17460
|
self.clearSelection = function() {
|
|
17336
17461
|
updateSelectionState(null);
|
|
17337
17462
|
};
|
|
@@ -17584,6 +17709,11 @@
|
|
|
17584
17709
|
(type == 'hover' || type == 'dblclick')) {
|
|
17585
17710
|
return true; // special case -- using hover for line drawing animation
|
|
17586
17711
|
}
|
|
17712
|
+
if (mode == 'snip_lines' && (type == 'hover' || type == 'click')) {
|
|
17713
|
+
// the snip tool tracks vertices under the pointer, so it needs hover
|
|
17714
|
+
// events as soon as a feature is hit, not one event later
|
|
17715
|
+
return true;
|
|
17716
|
+
}
|
|
17587
17717
|
|
|
17588
17718
|
// ignore pointer events when no features are being hit
|
|
17589
17719
|
// (don't block pan and other navigation when events aren't being used for editing)
|
|
@@ -17601,7 +17731,8 @@
|
|
|
17601
17731
|
}
|
|
17602
17732
|
|
|
17603
17733
|
function possiblyStopPropagation(e) {
|
|
17604
|
-
|
|
17734
|
+
var mode = interactionMode();
|
|
17735
|
+
if (mode == 'edit_lines' || mode == 'edit_polygons' || mode == 'snip_lines') {
|
|
17605
17736
|
// handled conditionally in the control
|
|
17606
17737
|
return;
|
|
17607
17738
|
}
|
|
@@ -19189,6 +19320,7 @@
|
|
|
19189
19320
|
// hoverFill = "rgba(255, 120, 255, 0.12)",
|
|
19190
19321
|
hoverFill = "rgba(0, 0, 0, 0.08)",
|
|
19191
19322
|
grey = "#888",
|
|
19323
|
+
orange = "#f28100",
|
|
19192
19324
|
violet = "#cc6acc",
|
|
19193
19325
|
black = 'black',
|
|
19194
19326
|
violetFill = "rgba(249, 120, 249, 0.25)",
|
|
@@ -19420,20 +19552,30 @@
|
|
|
19420
19552
|
|
|
19421
19553
|
// style for vertex edit mode
|
|
19422
19554
|
function getLineEditingStyle(o) {
|
|
19555
|
+
var isVertex = o.hit_type == 'vertex' || o.hit_type == 'disabled';
|
|
19423
19556
|
return {
|
|
19424
19557
|
ids: o.ids,
|
|
19425
19558
|
overlay: true,
|
|
19426
19559
|
strokeColor: black,
|
|
19427
19560
|
strokeWidth: 1.2,
|
|
19428
19561
|
vertices: true,
|
|
19429
|
-
vertex_overlay_color: o.hit_type
|
|
19430
|
-
vertex_overlay_scale:
|
|
19562
|
+
vertex_overlay_color: getVertexOverlayColor(o.hit_type),
|
|
19563
|
+
vertex_overlay_scale: isVertex ? 2.5 : 2,
|
|
19431
19564
|
vertex_overlay: o.hit_coordinates || null,
|
|
19565
|
+
pending_snip: o.snip_coordinates || null,
|
|
19566
|
+
pending_snip_color: orange,
|
|
19432
19567
|
selected_points: o.selected_points || null,
|
|
19433
19568
|
fillColor: null
|
|
19434
19569
|
};
|
|
19435
19570
|
}
|
|
19436
19571
|
|
|
19572
|
+
function getVertexOverlayColor(hitType) {
|
|
19573
|
+
if (hitType == 'vertex') return violet;
|
|
19574
|
+
// a muted dot marks a vertex that the current tool can not act on
|
|
19575
|
+
if (hitType == 'disabled') return grey;
|
|
19576
|
+
return black;
|
|
19577
|
+
}
|
|
19578
|
+
|
|
19437
19579
|
function getSelectedFeatureStyle(lyr, o, opts) {
|
|
19438
19580
|
var isPinned = o.pinned;
|
|
19439
19581
|
var inSelection = o.ids.indexOf(o.id) > -1;
|
|
@@ -20480,147 +20622,275 @@
|
|
|
20480
20622
|
}
|
|
20481
20623
|
}
|
|
20482
20624
|
|
|
20483
|
-
|
|
20484
|
-
// find the feature part and arc containing the vertex
|
|
20485
|
-
// divide the one feature into two features
|
|
20486
|
-
// divide the arc into two new arcs
|
|
20487
|
-
var arc1, arc2;
|
|
20488
|
-
// divide the feature into two parts
|
|
20489
|
-
}
|
|
20490
|
-
|
|
20491
|
-
function mergeLinesAtVertex(lyr) {
|
|
20492
|
-
|
|
20493
|
-
}
|
|
20494
|
-
|
|
20495
|
-
// TODO: support snipping rings (by snipping in two places)
|
|
20496
|
-
|
|
20497
|
-
|
|
20498
|
-
// pixel distance threshold for hovering near a vertex or segment midpoint
|
|
20625
|
+
// pixel distance threshold for hovering near a vertex or a segment
|
|
20499
20626
|
var HOVER_THRESHOLD = 10;
|
|
20500
20627
|
|
|
20501
20628
|
function initSnipTool(gui, ext, hit) {
|
|
20502
|
-
var
|
|
20503
|
-
var
|
|
20504
|
-
var
|
|
20629
|
+
var hoverInfo = null;
|
|
20630
|
+
var pendingCut = null; // first of the two cuts needed to divide a ring
|
|
20631
|
+
var sessionCount = 0;
|
|
20632
|
+
var alert;
|
|
20633
|
+
|
|
20634
|
+
function active() {
|
|
20635
|
+
return gui.interaction.getMode() == 'snip_lines' && !!hit.getHitTarget();
|
|
20636
|
+
}
|
|
20505
20637
|
|
|
20506
20638
|
gui.on('interaction_mode_change', function(e) {
|
|
20507
|
-
if (
|
|
20639
|
+
if (e.mode == 'snip_lines') {
|
|
20640
|
+
turnOn();
|
|
20641
|
+
} else {
|
|
20508
20642
|
turnOff();
|
|
20509
20643
|
}
|
|
20510
|
-
|
|
20644
|
+
updateCursor();
|
|
20511
20645
|
}, null, 10); // higher priority than hit control, so turnOff() has correct hit target
|
|
20512
20646
|
|
|
20647
|
+
gui.on('undo_redo_pre', function() {
|
|
20648
|
+
clearPendingCut();
|
|
20649
|
+
hoverInfo = null;
|
|
20650
|
+
});
|
|
20651
|
+
|
|
20652
|
+
// A pending cut describes a location in the target layer's current geometry,
|
|
20653
|
+
// and choosing one does not change the model -- so any model update means
|
|
20654
|
+
// something else has happened and the cut is stale.
|
|
20655
|
+
gui.model.on('update', function() {
|
|
20656
|
+
clearPendingCut();
|
|
20657
|
+
hoverInfo = null;
|
|
20658
|
+
});
|
|
20659
|
+
|
|
20660
|
+
function turnOn() {
|
|
20661
|
+
hoverInfo = null;
|
|
20662
|
+
if (sessionCount === 0) {
|
|
20663
|
+
showInstructions();
|
|
20664
|
+
}
|
|
20665
|
+
sessionCount++;
|
|
20666
|
+
}
|
|
20667
|
+
|
|
20668
|
+
function turnOff() {
|
|
20669
|
+
clearPendingCut();
|
|
20670
|
+
clearHoverVertex();
|
|
20671
|
+
hideInstructions();
|
|
20672
|
+
}
|
|
20673
|
+
|
|
20674
|
+
function showInstructions() {
|
|
20675
|
+
var msg = 'Instructions: click a line to snip it apart. Snipping a ring ' +
|
|
20676
|
+
'takes two clicks -- the ring divides at the second one.';
|
|
20677
|
+
alert = showPopupAlert(msg, null, {non_blocking: true, max_width: '350px'});
|
|
20678
|
+
}
|
|
20679
|
+
|
|
20680
|
+
function hideInstructions() {
|
|
20681
|
+
if (!alert) return;
|
|
20682
|
+
alert.close('fade');
|
|
20683
|
+
alert = null;
|
|
20684
|
+
}
|
|
20685
|
+
|
|
20686
|
+
function clearHoverVertex() {
|
|
20687
|
+
hit.clearHoverVertex();
|
|
20688
|
+
hoverInfo = null;
|
|
20689
|
+
}
|
|
20690
|
+
|
|
20691
|
+
function clearPendingCut() {
|
|
20692
|
+
if (!pendingCut) return;
|
|
20693
|
+
pendingCut = null;
|
|
20694
|
+
hit.clearPendingSnip();
|
|
20695
|
+
}
|
|
20696
|
+
|
|
20697
|
+
function setPendingCut(info) {
|
|
20698
|
+
pendingCut = info;
|
|
20699
|
+
hit.setPendingSnip(info.markerPoint);
|
|
20700
|
+
}
|
|
20701
|
+
|
|
20702
|
+
// The pending cut is described in terms of the target layer's current arcs
|
|
20703
|
+
// and feature ids, so it has to be discarded if either could have changed.
|
|
20704
|
+
function pendingCutIsValid() {
|
|
20705
|
+
return !!pendingCut && pendingCut.target === hit.getHitTarget();
|
|
20706
|
+
}
|
|
20513
20707
|
|
|
20514
|
-
// hover event highlights the nearest point in close proximity to the pointer
|
|
20515
|
-
// ... or the closest point along the segment (for adding a new vertex)
|
|
20516
20708
|
hit.on('hover', function(e) {
|
|
20517
20709
|
if (!active()) return;
|
|
20518
|
-
|
|
20519
|
-
|
|
20520
|
-
|
|
20521
|
-
|
|
20522
|
-
|
|
20523
|
-
|
|
20524
|
-
|
|
20525
|
-
hit.setHoverVertex(hoverVertexInfo.displayPoint, hoverVertexInfo.type);
|
|
20710
|
+
if (pendingCut && !pendingCutIsValid()) {
|
|
20711
|
+
clearPendingCut();
|
|
20712
|
+
}
|
|
20713
|
+
hoverInfo = e.id >= 0 ? findSnipTarget(e) : null;
|
|
20714
|
+
if (hoverInfo) {
|
|
20715
|
+
hit.setHoverVertex(hoverInfo.markerPoint,
|
|
20716
|
+
hoverInfo.disabled ? 'disabled' : hoverInfo.type);
|
|
20526
20717
|
} else {
|
|
20527
|
-
clearHoverVertex();
|
|
20718
|
+
hit.clearHoverVertex();
|
|
20528
20719
|
}
|
|
20529
|
-
|
|
20530
|
-
|
|
20720
|
+
if (pendingCut) {
|
|
20721
|
+
hit.setPendingSnip(pendingCut.markerPoint);
|
|
20722
|
+
}
|
|
20723
|
+
updateCursor();
|
|
20531
20724
|
}, null, 100);
|
|
20532
20725
|
|
|
20533
20726
|
hit.on('click', function(e) {
|
|
20534
|
-
|
|
20535
|
-
var
|
|
20536
|
-
if (
|
|
20537
|
-
|
|
20727
|
+
var info = hoverInfo;
|
|
20728
|
+
var cuts;
|
|
20729
|
+
if (!active() || !info || info.disabled) return;
|
|
20730
|
+
// a snip invalidates the hover data (feature ids and arcs both change), so
|
|
20731
|
+
// clear it -- this also makes the second click of a double-click a no-op
|
|
20732
|
+
hoverInfo = null;
|
|
20733
|
+
if (!info.ring) {
|
|
20734
|
+
clearPendingCut();
|
|
20735
|
+
snip(info, [info.cut]);
|
|
20538
20736
|
return;
|
|
20539
20737
|
}
|
|
20540
|
-
|
|
20541
|
-
|
|
20542
|
-
|
|
20543
|
-
|
|
20738
|
+
if (!pendingCutIsValid() || pendingCut.fid !== info.fid ||
|
|
20739
|
+
pendingCut.partId !== info.partId) {
|
|
20740
|
+
// first cut on this ring: remember it, don't change any geometry
|
|
20741
|
+
setPendingCut(info);
|
|
20742
|
+
return;
|
|
20743
|
+
}
|
|
20744
|
+
if (cutsAreAdjacent(pendingCut, info)) {
|
|
20745
|
+
clearPendingCut(); // clicking the pending cut again cancels it
|
|
20746
|
+
return;
|
|
20544
20747
|
}
|
|
20748
|
+
cuts = [pendingCut.cut, info.cut];
|
|
20749
|
+
clearPendingCut();
|
|
20750
|
+
snip(info, cuts);
|
|
20751
|
+
});
|
|
20545
20752
|
|
|
20546
|
-
|
|
20753
|
+
gui.keyboard.on('keydown', function(e) {
|
|
20754
|
+
if (!active() || !pendingCut || e.keyName != 'esc') return;
|
|
20755
|
+
e.stopPropagation();
|
|
20756
|
+
e.originalEvent.preventDefault();
|
|
20757
|
+
clearPendingCut();
|
|
20758
|
+
}, null, 10);
|
|
20547
20759
|
|
|
20548
|
-
|
|
20760
|
+
function snip(info, cuts) {
|
|
20761
|
+
var target = hit.getHitTarget();
|
|
20762
|
+
var result = snipPath(target, info.fid, info.partId, cuts);
|
|
20763
|
+
if (!result) return;
|
|
20764
|
+
gui.dispatchEvent('snip', {target: target, result: result});
|
|
20765
|
+
hit.clearHoverVertex();
|
|
20766
|
+
hideInstructions();
|
|
20767
|
+
gui.model.updated({arc_count: true}); // update display arcs and redraw
|
|
20768
|
+
}
|
|
20549
20769
|
|
|
20550
|
-
|
|
20770
|
+
// Test if two cuts are close enough on screen to be treated as the same place
|
|
20771
|
+
function cutsAreAdjacent(a, b) {
|
|
20772
|
+
var dist = geom.distance2D(a.markerPoint[0], a.markerPoint[1],
|
|
20773
|
+
b.markerPoint[0], b.markerPoint[1]);
|
|
20774
|
+
return dist / ext.getPixelSize() < HOVER_THRESHOLD;
|
|
20775
|
+
}
|
|
20551
20776
|
|
|
20552
|
-
|
|
20553
|
-
|
|
20554
|
-
|
|
20555
|
-
|
|
20556
|
-
var target = hit.getHitTarget();
|
|
20557
|
-
var shp = target.shapes[e.id];
|
|
20558
|
-
var p = ext.pixCoordsToMapCoords(e.x, e.y);
|
|
20559
|
-
var ids = internal.findNearestVertices(p, shp, target.gui.displayArcs);
|
|
20560
|
-
var p2 = target.gui.displayArcs.getVertex2(ids[0]);
|
|
20561
|
-
var dist = geom.distance2D(p[0], p[1], p2[0], p2[1]);
|
|
20562
|
-
var pixelDist = dist / ext.getPixelSize();
|
|
20563
|
-
if (pixelDist > HOVER_THRESHOLD) {
|
|
20564
|
-
return null;
|
|
20565
|
-
}
|
|
20566
|
-
var point = getVertexCoords(target, ids[0]); // data coordinates
|
|
20567
|
-
var displayPoint = target.gui.displayArcs.getVertex2(ids[0]);
|
|
20568
|
-
return {target, ids, point, displayPoint, type: 'vertex'};
|
|
20777
|
+
function updateCursor() {
|
|
20778
|
+
var el = gui.container.findChild('.map-layers');
|
|
20779
|
+
el.classed('snip-tool', active());
|
|
20780
|
+
el.classed('no-snip', !!(hoverInfo && hoverInfo.disabled));
|
|
20569
20781
|
}
|
|
20570
20782
|
|
|
20571
|
-
|
|
20783
|
+
// Find the snip location under the pointer: the nearest vertex within the
|
|
20784
|
+
// hover threshold, or failing that the nearest point on a segment.
|
|
20785
|
+
// Returns null if the pointer is not close enough to the hit feature.
|
|
20786
|
+
function findSnipTarget(e) {
|
|
20572
20787
|
var target = hit.getHitTarget();
|
|
20573
|
-
|
|
20574
|
-
|
|
20575
|
-
var
|
|
20576
|
-
var
|
|
20577
|
-
var
|
|
20578
|
-
|
|
20579
|
-
|
|
20580
|
-
|
|
20581
|
-
|
|
20582
|
-
|
|
20583
|
-
|
|
20584
|
-
|
|
20585
|
-
|
|
20586
|
-
|
|
20587
|
-
|
|
20588
|
-
|
|
20589
|
-
|
|
20590
|
-
|
|
20591
|
-
|
|
20592
|
-
|
|
20593
|
-
|
|
20788
|
+
var shp = target && target.shapes[e.id];
|
|
20789
|
+
var arcs = target && target.gui.displayArcs;
|
|
20790
|
+
var bestVertex = null;
|
|
20791
|
+
var bestSegment = null;
|
|
20792
|
+
var p, threshold, data;
|
|
20793
|
+
if (!shp || !arcs) return null;
|
|
20794
|
+
p = ext.pixCoordsToMapCoords(e.x, e.y);
|
|
20795
|
+
threshold = HOVER_THRESHOLD * ext.getPixelSize();
|
|
20796
|
+
data = arcs.getVertexData();
|
|
20797
|
+
|
|
20798
|
+
shp.forEach(function(path, partId) {
|
|
20799
|
+
path.forEach(function(arcId, seq) {
|
|
20800
|
+
var absId = internal.absArcId(arcId);
|
|
20801
|
+
var fwd = arcId >= 0;
|
|
20802
|
+
var start = data.ii[absId];
|
|
20803
|
+
var n = data.nn[absId];
|
|
20804
|
+
var i, v1, v2, dist, p2;
|
|
20805
|
+
if (arcIsOutOfRange(data.bb, absId, p, threshold)) return;
|
|
20806
|
+
for (i=0; i<n; i++) {
|
|
20807
|
+
v1 = fwd ? start + i : start + n - 1 - i;
|
|
20808
|
+
dist = geom.distance2D(p[0], p[1], data.xx[v1], data.yy[v1]);
|
|
20809
|
+
if (dist < threshold && (!bestVertex || dist < bestVertex.dist)) {
|
|
20810
|
+
bestVertex = {partId: partId, seq: seq, offset: i, dist: dist, vid: v1};
|
|
20811
|
+
}
|
|
20812
|
+
if (i === n - 1) break;
|
|
20813
|
+
v2 = fwd ? v1 + 1 : v1 - 1;
|
|
20814
|
+
p2 = internal.findClosestPointOnSeg(p[0], p[1], data.xx[v1],
|
|
20815
|
+
data.yy[v1], data.xx[v2], data.yy[v2], 0);
|
|
20816
|
+
dist = geom.distance2D(p[0], p[1], p2[0], p2[1]);
|
|
20817
|
+
if (dist < threshold && (!bestSegment || dist < bestSegment.dist)) {
|
|
20818
|
+
bestSegment = {
|
|
20819
|
+
partId: partId, seq: seq, offset: i, dist: dist, displayPoint: p2,
|
|
20820
|
+
t: getSegmentPosition(data.xx[v1], data.yy[v1], data.xx[v2],
|
|
20821
|
+
data.yy[v2], p2)
|
|
20822
|
+
};
|
|
20823
|
+
}
|
|
20824
|
+
}
|
|
20825
|
+
});
|
|
20594
20826
|
});
|
|
20595
20827
|
|
|
20596
|
-
if (
|
|
20597
|
-
|
|
20598
|
-
|
|
20599
|
-
closest.point = translateDisplayPoint(target, closest.displayPoint);
|
|
20600
|
-
closest.type = 'interpolated';
|
|
20601
|
-
closest.target = target;
|
|
20602
|
-
return closest;
|
|
20828
|
+
if (bestVertex) return getVertexTarget(target, e.id, bestVertex);
|
|
20829
|
+
if (bestSegment) return getSegmentTarget(target, e.id, bestSegment);
|
|
20830
|
+
return null;
|
|
20603
20831
|
}
|
|
20604
20832
|
|
|
20605
|
-
function
|
|
20606
|
-
var
|
|
20607
|
-
return
|
|
20608
|
-
|
|
20833
|
+
function getVertexTarget(target, fid, info) {
|
|
20834
|
+
var displayPoint = target.gui.displayArcs.getVertex2(info.vid);
|
|
20835
|
+
return {
|
|
20836
|
+
target: target,
|
|
20837
|
+
fid: fid,
|
|
20838
|
+
partId: info.partId,
|
|
20839
|
+
type: 'vertex',
|
|
20840
|
+
ring: pathIsRing(target, fid, info.partId),
|
|
20841
|
+
disabled: !pathIsRing(target, fid, info.partId) &&
|
|
20842
|
+
vertexIsPathEndpoint(target, fid, info),
|
|
20843
|
+
markerPoint: displayPoint,
|
|
20844
|
+
cut: {seq: info.seq, offset: info.offset, point: null, displayPoint: null, t: 0}
|
|
20845
|
+
};
|
|
20609
20846
|
}
|
|
20610
20847
|
|
|
20611
|
-
function
|
|
20612
|
-
|
|
20613
|
-
|
|
20848
|
+
function getSegmentTarget(target, fid, info) {
|
|
20849
|
+
return {
|
|
20850
|
+
target: target,
|
|
20851
|
+
fid: fid,
|
|
20852
|
+
partId: info.partId,
|
|
20853
|
+
type: 'interpolated',
|
|
20854
|
+
ring: pathIsRing(target, fid, info.partId),
|
|
20855
|
+
disabled: false,
|
|
20856
|
+
markerPoint: info.displayPoint,
|
|
20857
|
+
cut: {
|
|
20858
|
+
seq: info.seq,
|
|
20859
|
+
offset: info.offset,
|
|
20860
|
+
point: translateDisplayPoint(target, info.displayPoint),
|
|
20861
|
+
displayPoint: info.displayPoint,
|
|
20862
|
+
t: info.t
|
|
20863
|
+
}
|
|
20864
|
+
};
|
|
20614
20865
|
}
|
|
20615
20866
|
|
|
20616
|
-
function
|
|
20617
|
-
return
|
|
20867
|
+
function pathIsRing(target, fid, partId) {
|
|
20868
|
+
return geom.pathIsClosed(target.shapes[fid][partId], target.gui.displayArcs);
|
|
20618
20869
|
}
|
|
20619
20870
|
|
|
20620
|
-
|
|
20621
|
-
|
|
20871
|
+
// Test if a vertex is one of the two endpoints of an open path (as opposed to
|
|
20872
|
+
// an interior node between two of the path's arcs, which is snippable).
|
|
20873
|
+
function vertexIsPathEndpoint(target, fid, info) {
|
|
20874
|
+
var path = target.shapes[fid][info.partId];
|
|
20875
|
+
var data = target.gui.displayArcs.getVertexData();
|
|
20876
|
+
var lastId = internal.absArcId(path[path.length - 1]);
|
|
20877
|
+
if (info.seq === 0 && info.offset === 0) return true;
|
|
20878
|
+
return info.seq === path.length - 1 && info.offset === data.nn[lastId] - 1;
|
|
20622
20879
|
}
|
|
20880
|
+
}
|
|
20623
20881
|
|
|
20882
|
+
// Test if an arc's bounding box is too far from a point to contain a hit
|
|
20883
|
+
function arcIsOutOfRange(bb, arcId, p, dist) {
|
|
20884
|
+
var i = arcId * 4;
|
|
20885
|
+
return p[0] + dist < bb[i] || p[0] - dist > bb[i + 2] ||
|
|
20886
|
+
p[1] + dist < bb[i + 1] || p[1] - dist > bb[i + 3];
|
|
20887
|
+
}
|
|
20888
|
+
|
|
20889
|
+
// Position of point p along the segment from (x1, y1) to (x2, y2), as a
|
|
20890
|
+
// fraction of the segment's length
|
|
20891
|
+
function getSegmentPosition(x1, y1, x2, y2, p) {
|
|
20892
|
+
var len = geom.distance2D(x1, y1, x2, y2);
|
|
20893
|
+
return len > 0 ? geom.distance2D(x1, y1, p[0], p[1]) / len : 0;
|
|
20624
20894
|
}
|
|
20625
20895
|
|
|
20626
20896
|
function initInteractiveEditing(gui, ext, hit) {
|
|
@@ -21822,6 +22092,15 @@
|
|
|
21822
22092
|
_ctx.fill();
|
|
21823
22093
|
_ctx.closePath();
|
|
21824
22094
|
}
|
|
22095
|
+
|
|
22096
|
+
if (style.pending_snip) {
|
|
22097
|
+
_ctx.beginPath();
|
|
22098
|
+
_ctx.fillStyle = style.pending_snip_color || 'black';
|
|
22099
|
+
p = style.pending_snip;
|
|
22100
|
+
drawCircle(p[0] * t.mx + t.bx, p[1] * t.my + t.by, radius * 2.5, _ctx);
|
|
22101
|
+
_ctx.fill();
|
|
22102
|
+
_ctx.closePath();
|
|
22103
|
+
}
|
|
21825
22104
|
};
|
|
21826
22105
|
|
|
21827
22106
|
// Optimized to draw paths in same-style batches (faster Canvas drawing)
|
package/www/mapshaper.js
CHANGED
|
@@ -75897,7 +75897,7 @@ ${svg}
|
|
|
75897
75897
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
75898
75898
|
}
|
|
75899
75899
|
|
|
75900
|
-
var version = "0.7.
|
|
75900
|
+
var version = "0.7.52";
|
|
75901
75901
|
|
|
75902
75902
|
// Parse command line args into commands and run them
|
|
75903
75903
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -76559,6 +76559,256 @@ ${svg}
|
|
|
76559
76559
|
return false;
|
|
76560
76560
|
}
|
|
76561
76561
|
|
|
76562
|
+
// Utilities for dividing a path at one or two points ("snipping").
|
|
76563
|
+
//
|
|
76564
|
+
// The new arcs are appended to the end of the collection; the original arc is
|
|
76565
|
+
// left in place (it may still be referenced by other shapes). Callers are
|
|
76566
|
+
// responsible for updating the shapes that should use the new arcs.
|
|
76567
|
+
|
|
76568
|
+
// Divide an arc's vertices into slices at one or two cut locations.
|
|
76569
|
+
// n: number of vertices in the arc
|
|
76570
|
+
// cuts: one or two cut objects, ordered from the start of the arc:
|
|
76571
|
+
// {offset: i, point: [x, y]} cuts the segment between vertices i and i + 1
|
|
76572
|
+
// {offset: i, point: null} cuts at vertex i (which is duplicated, so that
|
|
76573
|
+
// both adjacent slices contain it)
|
|
76574
|
+
// Returns slice descriptors {start, end, prefix, suffix}, where start and end
|
|
76575
|
+
// are an inclusive vertex range (empty if start > end) and prefix/suffix are
|
|
76576
|
+
// points inserted at a cut location.
|
|
76577
|
+
function getArcSlices(n, cuts) {
|
|
76578
|
+
var slices = [];
|
|
76579
|
+
var start = 0;
|
|
76580
|
+
var prefix = null;
|
|
76581
|
+
cuts.forEach(function(cut) {
|
|
76582
|
+
var i = cut.offset;
|
|
76583
|
+
if (cut.point) {
|
|
76584
|
+
if (i < 0 || i > n - 2) error('Out-of-range arc cut offset:', i);
|
|
76585
|
+
slices.push({start: start, end: i, prefix: prefix, suffix: cut.point});
|
|
76586
|
+
prefix = cut.point;
|
|
76587
|
+
start = i + 1;
|
|
76588
|
+
} else {
|
|
76589
|
+
if (i < 1 || i > n - 2) error('Out-of-range arc cut offset:', i);
|
|
76590
|
+
slices.push({start: start, end: i, prefix: prefix, suffix: null});
|
|
76591
|
+
prefix = null;
|
|
76592
|
+
start = i;
|
|
76593
|
+
}
|
|
76594
|
+
});
|
|
76595
|
+
slices.push({start: start, end: n - 1, prefix: prefix, suffix: null});
|
|
76596
|
+
return slices;
|
|
76597
|
+
}
|
|
76598
|
+
|
|
76599
|
+
function getArcSliceLength(slice) {
|
|
76600
|
+
var n = slice.end >= slice.start ? slice.end - slice.start + 1 : 0;
|
|
76601
|
+
if (slice.prefix) n++;
|
|
76602
|
+
if (slice.suffix) n++;
|
|
76603
|
+
return n;
|
|
76604
|
+
}
|
|
76605
|
+
|
|
76606
|
+
// Append the slices of a divided arc to an ArcCollection.
|
|
76607
|
+
// Returns the ids of the new arcs, ordered from the start of the original arc.
|
|
76608
|
+
function splitArcAtCuts(arcs, arcId, cuts) {
|
|
76609
|
+
var data = arcs.getVertexData();
|
|
76610
|
+
var zlimit = arcs.getRetainedInterval();
|
|
76611
|
+
var arcStart = data.ii[arcId];
|
|
76612
|
+
var arcCount = data.nn.length;
|
|
76613
|
+
var slices = getArcSlices(data.nn[arcId], cuts);
|
|
76614
|
+
var pointCount = data.xx.length;
|
|
76615
|
+
var added = slices.reduce(function(memo, slice) {
|
|
76616
|
+
return memo + getArcSliceLength(slice);
|
|
76617
|
+
}, 0);
|
|
76618
|
+
var xx2 = new Float64Array(pointCount + added);
|
|
76619
|
+
var yy2 = new Float64Array(pointCount + added);
|
|
76620
|
+
var zz2 = arcs.isFlat() ? null : new Float64Array(pointCount + added);
|
|
76621
|
+
var nn2 = new Uint32Array(arcCount + slices.length);
|
|
76622
|
+
var i = pointCount;
|
|
76623
|
+
var ids = [];
|
|
76624
|
+
|
|
76625
|
+
xx2.set(data.xx);
|
|
76626
|
+
yy2.set(data.yy);
|
|
76627
|
+
nn2.set(data.nn);
|
|
76628
|
+
if (zz2) zz2.set(data.zz);
|
|
76629
|
+
|
|
76630
|
+
slices.forEach(function(slice, sliceId) {
|
|
76631
|
+
var first = i;
|
|
76632
|
+
var j;
|
|
76633
|
+
if (slice.prefix) {
|
|
76634
|
+
xx2[i] = slice.prefix[0];
|
|
76635
|
+
yy2[i] = slice.prefix[1];
|
|
76636
|
+
i++;
|
|
76637
|
+
}
|
|
76638
|
+
for (j = slice.start; j <= slice.end; j++) {
|
|
76639
|
+
xx2[i] = data.xx[arcStart + j];
|
|
76640
|
+
yy2[i] = data.yy[arcStart + j];
|
|
76641
|
+
if (zz2) zz2[i] = data.zz[arcStart + j];
|
|
76642
|
+
i++;
|
|
76643
|
+
}
|
|
76644
|
+
if (slice.suffix) {
|
|
76645
|
+
xx2[i] = slice.suffix[0];
|
|
76646
|
+
yy2[i] = slice.suffix[1];
|
|
76647
|
+
i++;
|
|
76648
|
+
}
|
|
76649
|
+
if (zz2) {
|
|
76650
|
+
// arc endpoints must never be removed by simplification
|
|
76651
|
+
zz2[first] = Infinity;
|
|
76652
|
+
zz2[i - 1] = Infinity;
|
|
76653
|
+
}
|
|
76654
|
+
nn2[arcCount + sliceId] = i - first;
|
|
76655
|
+
ids.push(arcCount + sliceId);
|
|
76656
|
+
});
|
|
76657
|
+
|
|
76658
|
+
arcs.updateVertexData(nn2, xx2, yy2, zz2);
|
|
76659
|
+
arcs.setRetainedInterval(zlimit);
|
|
76660
|
+
return ids;
|
|
76661
|
+
}
|
|
76662
|
+
|
|
76663
|
+
// A cut identifies one location along a path (an array of arc ids):
|
|
76664
|
+
// seq: index into the path of the arc containing the cut
|
|
76665
|
+
// offset: vertex index within that arc, in traversal orientation
|
|
76666
|
+
// point: coords of the cut, or null to cut at vertex <offset>
|
|
76667
|
+
// displayPoint: coords of the cut in the GUI's display CRS, or null
|
|
76668
|
+
// t: position along the segment (0-1), used to order two cuts
|
|
76669
|
+
// that land on the same segment
|
|
76670
|
+
|
|
76671
|
+
// Work out how to divide a path at one cut (open path) or two cuts (ring),
|
|
76672
|
+
// without mutating anything.
|
|
76673
|
+
//
|
|
76674
|
+
// path: array of arc ids
|
|
76675
|
+
// cutsArg: one cut (open path) or two cuts (ring), in any order
|
|
76676
|
+
// arcLen: function(arcId) returning the number of vertices in an arc
|
|
76677
|
+
// baseId: arcs.size(), used to predict the ids of arcs that will be appended
|
|
76678
|
+
// closed: whether the path is a ring
|
|
76679
|
+
//
|
|
76680
|
+
// Returns {splits, a, b}, where splits are arc divisions to perform in order
|
|
76681
|
+
// and a and b are the two resulting paths, or null if the cuts are degenerate
|
|
76682
|
+
// (at the endpoint of an open path, or two cuts in the same place on a ring).
|
|
76683
|
+
//
|
|
76684
|
+
function planPathDivision(path, cutsArg, arcLen, baseId, closed) {
|
|
76685
|
+
var cuts = sortCuts(cutsArg);
|
|
76686
|
+
var boundaryCuts = []; // cuts that fall between two arcs
|
|
76687
|
+
var interiorCuts = {}; // cuts that fall inside an arc, by path index
|
|
76688
|
+
var expanded = []; // path with divided arcs replaced by their pieces
|
|
76689
|
+
var positions = []; // position of each cut within the expanded path
|
|
76690
|
+
var splits = [];
|
|
76691
|
+
var nextArcId = baseId;
|
|
76692
|
+
var a, b, q, q1, q2;
|
|
76693
|
+
|
|
76694
|
+
if (cuts.length != (closed ? 2 : 1)) return null;
|
|
76695
|
+
|
|
76696
|
+
cuts.forEach(function(cut, cutId) {
|
|
76697
|
+
var n = arcLen(absArcId(path[cut.seq]));
|
|
76698
|
+
var seq;
|
|
76699
|
+
if (!cut.point && (cut.offset === 0 || cut.offset === n - 1)) {
|
|
76700
|
+
// a cut at an arc endpoint doesn't divide the arc, it divides the
|
|
76701
|
+
// sequence of arcs
|
|
76702
|
+
seq = cut.offset === 0 ? cut.seq : cut.seq + 1;
|
|
76703
|
+
if (closed && seq === path.length) seq = 0; // a ring's seam is one place
|
|
76704
|
+
boundaryCuts.push({cutId: cutId, seq: seq});
|
|
76705
|
+
} else {
|
|
76706
|
+
if (!interiorCuts[cut.seq]) interiorCuts[cut.seq] = [];
|
|
76707
|
+
interiorCuts[cut.seq].push({cutId: cutId, cut: cut, arcLen: n});
|
|
76708
|
+
}
|
|
76709
|
+
});
|
|
76710
|
+
|
|
76711
|
+
for (var k=0; k<=path.length; k++) {
|
|
76712
|
+
boundaryCuts.forEach(function(item) {
|
|
76713
|
+
if (item.seq === k) positions[item.cutId] = expanded.length;
|
|
76714
|
+
});
|
|
76715
|
+
if (k === path.length) break;
|
|
76716
|
+
if (!interiorCuts[k]) {
|
|
76717
|
+
expanded.push(path[k]);
|
|
76718
|
+
continue;
|
|
76719
|
+
}
|
|
76720
|
+
addDividedArc(interiorCuts[k], path[k]);
|
|
76721
|
+
}
|
|
76722
|
+
|
|
76723
|
+
if (!closed) {
|
|
76724
|
+
q = positions[0];
|
|
76725
|
+
if (q === 0 || q === expanded.length) return null; // cut at a path endpoint
|
|
76726
|
+
a = expanded.slice(0, q);
|
|
76727
|
+
b = expanded.slice(q);
|
|
76728
|
+
} else {
|
|
76729
|
+
q1 = Math.min(positions[0], positions[1]);
|
|
76730
|
+
q2 = Math.max(positions[0], positions[1]);
|
|
76731
|
+
if (q1 === q2) return null; // both cuts in the same place
|
|
76732
|
+
a = expanded.slice(q1, q2);
|
|
76733
|
+
b = expanded.slice(q2).concat(expanded.slice(0, q1));
|
|
76734
|
+
}
|
|
76735
|
+
return {splits: splits, a: a, b: b};
|
|
76736
|
+
|
|
76737
|
+
function addDividedArc(group, arcId) {
|
|
76738
|
+
var fwd = arcId >= 0;
|
|
76739
|
+
var n = group[0].arcLen;
|
|
76740
|
+
// convert traversal offsets to forward-orientation offsets
|
|
76741
|
+
var fwdCuts = group.map(function(item) {
|
|
76742
|
+
return {
|
|
76743
|
+
offset: fwd ? item.cut.offset :
|
|
76744
|
+
n - (item.cut.point ? 2 : 1) - item.cut.offset,
|
|
76745
|
+
point: item.cut.point || null,
|
|
76746
|
+
displayPoint: item.cut.displayPoint || null
|
|
76747
|
+
};
|
|
76748
|
+
});
|
|
76749
|
+
// a reversed arc is traversed from its last vertex, so traversal order is
|
|
76750
|
+
// the reverse of forward order
|
|
76751
|
+
var pieces = getSplitArcIds(nextArcId, group.length, fwd);
|
|
76752
|
+
splits.push({
|
|
76753
|
+
arcId: absArcId(arcId),
|
|
76754
|
+
cuts: fwd ? fwdCuts : fwdCuts.concat().reverse()
|
|
76755
|
+
});
|
|
76756
|
+
nextArcId += group.length + 1;
|
|
76757
|
+
pieces.forEach(function(id, i) {
|
|
76758
|
+
if (i > 0) positions[group[i - 1].cutId] = expanded.length;
|
|
76759
|
+
expanded.push(id);
|
|
76760
|
+
});
|
|
76761
|
+
}
|
|
76762
|
+
}
|
|
76763
|
+
|
|
76764
|
+
// Ids of the pieces of a divided arc, in traversal order
|
|
76765
|
+
function getSplitArcIds(baseId, cutCount, fwd) {
|
|
76766
|
+
var ids = [];
|
|
76767
|
+
for (var i=0; i<=cutCount; i++) {
|
|
76768
|
+
ids.push(fwd ? baseId + i : ~(baseId + cutCount - i));
|
|
76769
|
+
}
|
|
76770
|
+
return ids;
|
|
76771
|
+
}
|
|
76772
|
+
|
|
76773
|
+
function sortCuts(cuts) {
|
|
76774
|
+
return cuts.concat().sort(function(a, b) {
|
|
76775
|
+
return a.seq - b.seq || getCutOffset(a) - getCutOffset(b);
|
|
76776
|
+
});
|
|
76777
|
+
}
|
|
76778
|
+
|
|
76779
|
+
// A sortable offset within one arc. A cut on the segment following a vertex
|
|
76780
|
+
// comes after the vertex itself; two cuts on the same segment are ordered by
|
|
76781
|
+
// their position along it.
|
|
76782
|
+
function getCutOffset(cut) {
|
|
76783
|
+
if (!cut.point) return cut.offset;
|
|
76784
|
+
return cut.offset + 0.5 + (cut.t > 0 ? Math.min(cut.t, 1) : 0) * 0.4;
|
|
76785
|
+
}
|
|
76786
|
+
|
|
76787
|
+
function getPlanarPathLength(ids, arcs) {
|
|
76788
|
+
var iter = arcs.getShapeIter(ids);
|
|
76789
|
+
var len = 0;
|
|
76790
|
+
var x, y;
|
|
76791
|
+
if (iter.hasNext()) {
|
|
76792
|
+
x = iter.x;
|
|
76793
|
+
y = iter.y;
|
|
76794
|
+
}
|
|
76795
|
+
while (iter.hasNext()) {
|
|
76796
|
+
len += distance2D$1(x, y, iter.x, iter.y);
|
|
76797
|
+
x = iter.x;
|
|
76798
|
+
y = iter.y;
|
|
76799
|
+
}
|
|
76800
|
+
return len;
|
|
76801
|
+
}
|
|
76802
|
+
|
|
76803
|
+
var ArcSplit = /*#__PURE__*/Object.freeze({
|
|
76804
|
+
__proto__: null,
|
|
76805
|
+
getArcSliceLength: getArcSliceLength,
|
|
76806
|
+
getArcSlices: getArcSlices,
|
|
76807
|
+
getPlanarPathLength: getPlanarPathLength,
|
|
76808
|
+
planPathDivision: planPathDivision,
|
|
76809
|
+
splitArcAtCuts: splitArcAtCuts
|
|
76810
|
+
});
|
|
76811
|
+
|
|
76562
76812
|
var BOX_BLUR_PASSES = 3;
|
|
76563
76813
|
|
|
76564
76814
|
function blurRasterGrid(raster, optsArg, crs) {
|
|
@@ -77735,6 +77985,7 @@ ${svg}
|
|
|
77735
77985
|
AnchorPoints,
|
|
77736
77986
|
ArcClassifier,
|
|
77737
77987
|
ArcDissolve,
|
|
77988
|
+
ArcSplit,
|
|
77738
77989
|
ArcUtils,
|
|
77739
77990
|
Bbox2Clipping,
|
|
77740
77991
|
BinArray$1,
|
package/www/page.css
CHANGED
|
@@ -1972,10 +1972,15 @@ div.basemap-style-btn.active {
|
|
|
1972
1972
|
|
|
1973
1973
|
.map-layers.add-points,
|
|
1974
1974
|
.map-layers.draw-tool,
|
|
1975
|
+
.map-layers.snip-tool,
|
|
1975
1976
|
.map-layers.ruler-tool {
|
|
1976
1977
|
cursor: crosshair;
|
|
1977
1978
|
}
|
|
1978
1979
|
|
|
1980
|
+
.map-layers.snip-tool.no-snip {
|
|
1981
|
+
cursor: default;
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1979
1984
|
.map-layers.ruler-dragging {
|
|
1980
1985
|
cursor: grabbing;
|
|
1981
1986
|
}
|