mapshaper 0.6.111 → 0.6.112
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 +792 -487
- package/package.json +1 -1
- package/www/mapshaper-gui.js +55 -12
- package/www/mapshaper.js +792 -487
package/mapshaper.js
CHANGED
|
@@ -1211,8 +1211,13 @@
|
|
|
1211
1211
|
if (len >= 2) {
|
|
1212
1212
|
first = str.charAt(0);
|
|
1213
1213
|
last = str.charAt(len-1);
|
|
1214
|
-
if (first == '"' && last == '"' && !str.includes('","') ||
|
|
1215
|
-
|
|
1214
|
+
// if (first == '"' && last == '"' && !str.includes('","') ||
|
|
1215
|
+
// first == "'" && last == "'" && !str.includes("','")) {
|
|
1216
|
+
// don't strip if there are unescaped quotes
|
|
1217
|
+
// e.g. expressions that start and end with quotes
|
|
1218
|
+
// e.g. comma-separated list of quoted values
|
|
1219
|
+
if (first == '"' && last == '"' && !/[^\\]"./.test(str) ||
|
|
1220
|
+
first == "'" && last == "'" && !/[^\\]'./.test(str)) {
|
|
1216
1221
|
str = str.substr(1, len-2);
|
|
1217
1222
|
// remove string escapes
|
|
1218
1223
|
str = str.replace(first == '"' ? /\\(?=")/g : /\\(?=')/g, '');
|
|
@@ -1347,6 +1352,18 @@
|
|
|
1347
1352
|
}
|
|
1348
1353
|
}
|
|
1349
1354
|
|
|
1355
|
+
function time(slug) {
|
|
1356
|
+
if (useDebug()) {
|
|
1357
|
+
console.time(slug);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
function timeEnd(slug) {
|
|
1362
|
+
if (useDebug()) {
|
|
1363
|
+
console.timeEnd(slug);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1350
1367
|
function printError(err) {
|
|
1351
1368
|
var msg;
|
|
1352
1369
|
if (!LOGGING) return;
|
|
@@ -1465,6 +1482,8 @@
|
|
|
1465
1482
|
setLoggingForCLI: setLoggingForCLI,
|
|
1466
1483
|
setLoggingFunctions: setLoggingFunctions,
|
|
1467
1484
|
stop: stop,
|
|
1485
|
+
time: time,
|
|
1486
|
+
timeEnd: timeEnd,
|
|
1468
1487
|
truncateString: truncateString,
|
|
1469
1488
|
useDebug: useDebug,
|
|
1470
1489
|
useVerbose: useVerbose,
|
|
@@ -3888,10 +3907,12 @@
|
|
|
3888
3907
|
for (var i=0; i<n; i++) {
|
|
3889
3908
|
retn = cb(parts[i], i, parts, shpId);
|
|
3890
3909
|
if (retn === null) {
|
|
3891
|
-
nulls++;
|
|
3892
3910
|
parts[i] = null;
|
|
3893
3911
|
} else if (utils.isArray(retn)) {
|
|
3894
|
-
parts[i] = retn;
|
|
3912
|
+
parts[i] = retn.length > 0 ? retn : null;
|
|
3913
|
+
}
|
|
3914
|
+
if (parts[i] === null) {
|
|
3915
|
+
nulls++;
|
|
3895
3916
|
}
|
|
3896
3917
|
}
|
|
3897
3918
|
if (nulls == n) {
|
|
@@ -7539,7 +7560,15 @@
|
|
|
7539
7560
|
}
|
|
7540
7561
|
|
|
7541
7562
|
function mergeOutputLayerIntoDataset(lyr, dataset, dataset2, opts) {
|
|
7542
|
-
|
|
7563
|
+
var output = mergeOutputLayersIntoDataset(lyr, dataset, dataset2, opts);
|
|
7564
|
+
if (output.length != 1) {
|
|
7565
|
+
error('Expected 1 output layer, received:', output.length);
|
|
7566
|
+
}
|
|
7567
|
+
return output[0];
|
|
7568
|
+
}
|
|
7569
|
+
|
|
7570
|
+
function mergeOutputLayersIntoDataset(lyr, dataset, dataset2, opts) {
|
|
7571
|
+
if (!dataset2 || dataset2.layers.length === 0) {
|
|
7543
7572
|
error('Invalid source dataset');
|
|
7544
7573
|
}
|
|
7545
7574
|
if (dataset.layers.includes(lyr) === false) {
|
|
@@ -7548,24 +7577,21 @@
|
|
|
7548
7577
|
// this command returns merged layers instead of adding them to target dataset
|
|
7549
7578
|
var outputLayers = mergeDatasetsIntoDataset(dataset, [dataset2]);
|
|
7550
7579
|
var lyr2 = outputLayers[0];
|
|
7551
|
-
|
|
7552
7580
|
// TODO: find a more reliable way of knowing when to copy data
|
|
7553
7581
|
var copyData = !lyr2.data && lyr.data && getFeatureCount(lyr2) == lyr.data.size();
|
|
7554
|
-
|
|
7555
7582
|
if (copyData) {
|
|
7556
7583
|
lyr2.data = opts.no_replace ? lyr.data.clone() : lyr.data;
|
|
7557
7584
|
}
|
|
7558
|
-
|
|
7559
|
-
|
|
7585
|
+
lyr2.name = opts.name || lyr.name;
|
|
7586
|
+
if (!opts.no_replace) {
|
|
7587
|
+
outputLayers[0] = Object.assign(lyr, {data: null, shapes: null}, lyr2);
|
|
7560
7588
|
if (layerHasPaths(lyr)) {
|
|
7561
7589
|
// Remove unused arcs from replaced layer
|
|
7562
7590
|
// TODO: consider using clean insead of this
|
|
7563
7591
|
dissolveArcs(dataset);
|
|
7564
7592
|
}
|
|
7565
7593
|
}
|
|
7566
|
-
|
|
7567
|
-
lyr2.name = opts.name || lyr2.name;
|
|
7568
|
-
return lyr2;
|
|
7594
|
+
return outputLayers;
|
|
7569
7595
|
}
|
|
7570
7596
|
|
|
7571
7597
|
// Transform the points in a dataset in-place; don't clean up corrupted shapes
|
|
@@ -7593,6 +7619,7 @@
|
|
|
7593
7619
|
getDatasetBounds: getDatasetBounds,
|
|
7594
7620
|
mergeDatasetInfo: mergeDatasetInfo,
|
|
7595
7621
|
mergeOutputLayerIntoDataset: mergeOutputLayerIntoDataset,
|
|
7622
|
+
mergeOutputLayersIntoDataset: mergeOutputLayersIntoDataset,
|
|
7596
7623
|
pruneArcs: pruneArcs,
|
|
7597
7624
|
replaceLayerContents: replaceLayerContents,
|
|
7598
7625
|
replaceLayers: replaceLayers,
|
|
@@ -11548,7 +11575,7 @@
|
|
|
11548
11575
|
shapes: lyr.shapes || null,
|
|
11549
11576
|
data: data,
|
|
11550
11577
|
menu_order: lyr.menu_order || null,
|
|
11551
|
-
pinned: lyr.pinned || false,
|
|
11578
|
+
pinned: lyr.pinned || opts.show_all || false,
|
|
11552
11579
|
active: !!(lyr.active || lyr == opts.active_layer) // lyr.active: deprecated
|
|
11553
11580
|
};
|
|
11554
11581
|
}
|
|
@@ -16774,7 +16801,6 @@
|
|
|
16774
16801
|
|
|
16775
16802
|
function cleanPath(path, arcs) {
|
|
16776
16803
|
var nulls = 0;
|
|
16777
|
-
// console.log("[cleanPath()]", path, path?.[0])
|
|
16778
16804
|
for (var i=0, n=path.length; i<n; i++) {
|
|
16779
16805
|
if (arcs.arcIsDegenerate(path[i])) {
|
|
16780
16806
|
nulls++;
|
|
@@ -24723,6 +24749,10 @@ ${svg}
|
|
|
24723
24749
|
type: 'flag',
|
|
24724
24750
|
describe: '[CSV] export numbers with decimal commas not points'
|
|
24725
24751
|
})
|
|
24752
|
+
.option('show-all', {
|
|
24753
|
+
type: 'flag',
|
|
24754
|
+
describe: '[Snapshot] show all layers in Web UI'
|
|
24755
|
+
})
|
|
24726
24756
|
.option('final', {
|
|
24727
24757
|
type: 'flag' // for testing
|
|
24728
24758
|
})
|
|
@@ -24771,24 +24801,54 @@ ${svg}
|
|
|
24771
24801
|
// describe: 'number of vertices to use when buffering points',
|
|
24772
24802
|
type: 'integer'
|
|
24773
24803
|
})
|
|
24804
|
+
.option('arc-quality', {
|
|
24805
|
+
// segments per quarter-circle in joins and caps
|
|
24806
|
+
type: 'integer'
|
|
24807
|
+
})
|
|
24808
|
+
.option('slice-length', {
|
|
24809
|
+
// max path segments per buffer section
|
|
24810
|
+
type: 'integer'
|
|
24811
|
+
})
|
|
24774
24812
|
.option('backtrack', {
|
|
24813
|
+
// ...
|
|
24775
24814
|
type: 'integer'
|
|
24776
24815
|
})
|
|
24816
|
+
.option('cap-style', {
|
|
24817
|
+
describe: 'flat or round (default is round)'
|
|
24818
|
+
})
|
|
24777
24819
|
.option('type', {
|
|
24778
24820
|
// left, right, outer, inner (default is full buffer)
|
|
24779
24821
|
})
|
|
24780
24822
|
.option('planar', {
|
|
24781
24823
|
type: 'flag'
|
|
24782
24824
|
})
|
|
24783
|
-
.option('v2', {
|
|
24825
|
+
.option('v2', {
|
|
24826
|
+
type: 'flag'
|
|
24827
|
+
})
|
|
24828
|
+
.option('v3', {
|
|
24829
|
+
type: 'flag'
|
|
24830
|
+
})
|
|
24831
|
+
.option('debug-offset', {
|
|
24832
|
+
type: 'flag'
|
|
24833
|
+
})
|
|
24834
|
+
.option('debug-winding', {
|
|
24784
24835
|
type: 'flag'
|
|
24785
24836
|
})
|
|
24786
|
-
.option('debug-
|
|
24837
|
+
.option('debug-points', {
|
|
24787
24838
|
type: 'flag'
|
|
24788
24839
|
})
|
|
24840
|
+
// .option('debug-division', {
|
|
24841
|
+
// type: 'flag'
|
|
24842
|
+
// })
|
|
24789
24843
|
.option('debug-mosaic', {
|
|
24790
24844
|
type: 'flag'
|
|
24791
24845
|
})
|
|
24846
|
+
.option('left', {
|
|
24847
|
+
type: 'flag'
|
|
24848
|
+
})
|
|
24849
|
+
.option('right', {
|
|
24850
|
+
type: 'flag'
|
|
24851
|
+
})
|
|
24792
24852
|
.option('no-cleanup', {
|
|
24793
24853
|
type: 'flag'
|
|
24794
24854
|
})
|
|
@@ -30925,14 +30985,112 @@ ${svg}
|
|
|
30925
30985
|
return dataset;
|
|
30926
30986
|
}
|
|
30927
30987
|
|
|
30988
|
+
function getPolygonRewinder(nodes) {
|
|
30989
|
+
var splitter = getSelfIntersectionSplitter(nodes);
|
|
30990
|
+
return rewindPolygon;
|
|
30991
|
+
|
|
30992
|
+
function rewindPolygon(shp) {
|
|
30993
|
+
var shp2 = [];
|
|
30994
|
+
forEachShapePart(shp, function(ids) {
|
|
30995
|
+
var rings = splitter(ids);
|
|
30996
|
+
var path;
|
|
30997
|
+
for (var i=0; i<rings.length; i++) {
|
|
30998
|
+
path = rings[i];
|
|
30999
|
+
if (geom.getPlanarPathArea(path, nodes.arcs) < 0) {
|
|
31000
|
+
path = reversePath(path);
|
|
31001
|
+
}
|
|
31002
|
+
shp2.push(path);
|
|
31003
|
+
}
|
|
31004
|
+
});
|
|
31005
|
+
return shp2.length > 0 ? shp2 : null;
|
|
31006
|
+
}
|
|
31007
|
+
}
|
|
31008
|
+
|
|
31009
|
+
function rewindPolygonParts(lyr, nodes) {
|
|
31010
|
+
var rewind = getPolygonRewinder(nodes);
|
|
31011
|
+
lyr.shapes = lyr.shapes.map(function(shp) {
|
|
31012
|
+
return rewind(shp);
|
|
31013
|
+
});
|
|
31014
|
+
}
|
|
31015
|
+
|
|
31016
|
+
// TODO: Need to rethink polygon repair: these function can cause problems
|
|
31017
|
+
// when part of a self-intersecting polygon is removed
|
|
31018
|
+
//
|
|
31019
|
+
function repairPolygonGeometry(layers, dataset, opts) {
|
|
31020
|
+
var nodes = addIntersectionCuts(dataset);
|
|
31021
|
+
layers.forEach(function(lyr) {
|
|
31022
|
+
repairSelfIntersections(lyr, nodes);
|
|
31023
|
+
});
|
|
31024
|
+
return layers;
|
|
31025
|
+
}
|
|
31026
|
+
|
|
31027
|
+
// Remove any small shapes formed by twists in each ring
|
|
31028
|
+
// // OOPS, NO // Retain only the part with largest area
|
|
31029
|
+
// // this causes problems when a cut-off hole has a matching ring in another polygon
|
|
31030
|
+
// TODO: consider cases where cut-off parts should be retained
|
|
31031
|
+
//
|
|
31032
|
+
function repairSelfIntersections(lyr, nodes) {
|
|
31033
|
+
var splitter = getSelfIntersectionSplitter(nodes);
|
|
31034
|
+
|
|
31035
|
+
lyr.shapes = lyr.shapes.map(function(shp, i) {
|
|
31036
|
+
return cleanPolygon(shp);
|
|
31037
|
+
});
|
|
31038
|
+
|
|
31039
|
+
function cleanPolygon(shp) {
|
|
31040
|
+
var cleanedPolygon = [];
|
|
31041
|
+
forEachShapePart(shp, function(ids) {
|
|
31042
|
+
// TODO: consider returning null if path can't be split
|
|
31043
|
+
var splitIds = splitter(ids);
|
|
31044
|
+
if (splitIds.length === 0) {
|
|
31045
|
+
error("[cleanPolygon()] Defective path:", ids);
|
|
31046
|
+
} else if (splitIds.length == 1) {
|
|
31047
|
+
cleanedPolygon.push(splitIds[0]);
|
|
31048
|
+
} else {
|
|
31049
|
+
var shapeArea = geom.getPlanarPathArea(ids, nodes.arcs),
|
|
31050
|
+
sign = shapeArea > 0 ? 1 : -1,
|
|
31051
|
+
mainRing;
|
|
31052
|
+
|
|
31053
|
+
splitIds.reduce(function(max, ringIds, i) {
|
|
31054
|
+
var pathArea = geom.getPlanarPathArea(ringIds, nodes.arcs) * sign;
|
|
31055
|
+
if (pathArea > max) {
|
|
31056
|
+
mainRing = ringIds;
|
|
31057
|
+
max = pathArea;
|
|
31058
|
+
}
|
|
31059
|
+
return max;
|
|
31060
|
+
}, 0);
|
|
31061
|
+
|
|
31062
|
+
if (mainRing) {
|
|
31063
|
+
cleanedPolygon.push(mainRing);
|
|
31064
|
+
}
|
|
31065
|
+
}
|
|
31066
|
+
});
|
|
31067
|
+
return cleanedPolygon.length > 0 ? cleanedPolygon : null;
|
|
31068
|
+
}
|
|
31069
|
+
}
|
|
31070
|
+
|
|
31071
|
+
var PolygonRepair = /*#__PURE__*/Object.freeze({
|
|
31072
|
+
__proto__: null,
|
|
31073
|
+
getPolygonRewinder: getPolygonRewinder,
|
|
31074
|
+
repairPolygonGeometry: repairPolygonGeometry,
|
|
31075
|
+
repairSelfIntersections: repairSelfIntersections,
|
|
31076
|
+
rewindPolygonParts: rewindPolygonParts
|
|
31077
|
+
});
|
|
31078
|
+
|
|
30928
31079
|
function dissolveBufferDataset(dataset, optsArg) {
|
|
30929
31080
|
var opts = optsArg || {};
|
|
30930
31081
|
var lyr = dataset.layers[0];
|
|
30931
31082
|
var tmp;
|
|
30932
|
-
|
|
30933
|
-
|
|
30934
|
-
return debugBufferDivision(lyr, nodes);
|
|
31083
|
+
if (opts.debug_offset) {
|
|
31084
|
+
return; // raw offset path
|
|
30935
31085
|
}
|
|
31086
|
+
var nodes = addIntersectionCuts(dataset, {rebuild_topology: true});
|
|
31087
|
+
if (opts.debug_winding) {
|
|
31088
|
+
rewindPolygonParts(lyr, nodes);
|
|
31089
|
+
// debugRingsAndHoles(lyr, nodes);
|
|
31090
|
+
return;
|
|
31091
|
+
}
|
|
31092
|
+
rewindPolygonParts(lyr, nodes);
|
|
31093
|
+
|
|
30936
31094
|
var mosaicIndex = new MosaicIndex(lyr, nodes, {flat: false, no_holes: false});
|
|
30937
31095
|
if (opts.debug_mosaic) {
|
|
30938
31096
|
tmp = composeMosaicLayer(lyr, mosaicIndex.mosaic);
|
|
@@ -30955,49 +31113,6 @@ ${svg}
|
|
|
30955
31113
|
}
|
|
30956
31114
|
}
|
|
30957
31115
|
|
|
30958
|
-
function debugBufferDivision(lyr, nodes) {
|
|
30959
|
-
var divide = getHoleDivider(nodes);
|
|
30960
|
-
var shapes2 = [];
|
|
30961
|
-
var records = [];
|
|
30962
|
-
lyr.shapes.forEach(divideShape);
|
|
30963
|
-
lyr.shapes = shapes2;
|
|
30964
|
-
lyr.data = new DataTable(records);
|
|
30965
|
-
return lyr;
|
|
30966
|
-
|
|
30967
|
-
function divideShape(shp) {
|
|
30968
|
-
var cw = [], ccw = [];
|
|
30969
|
-
divide(shp, cw, ccw);
|
|
30970
|
-
cw.forEach(function(ring) {
|
|
30971
|
-
shapes2.push([ring]);
|
|
30972
|
-
records.push({type: 'ring'});
|
|
30973
|
-
});
|
|
30974
|
-
ccw.forEach(function(hole) {
|
|
30975
|
-
shapes2.push([reversePath(hole)]);
|
|
30976
|
-
records.push({type: 'hole'});
|
|
30977
|
-
});
|
|
30978
|
-
}
|
|
30979
|
-
}
|
|
30980
|
-
|
|
30981
|
-
// n = number of segments used to approximate a circle
|
|
30982
|
-
// Returns tolerance as a percent of circle radius
|
|
30983
|
-
function getBufferToleranceFromCircleSegments(n) {
|
|
30984
|
-
return 1 - Math.cos(Math.PI / n);
|
|
30985
|
-
}
|
|
30986
|
-
|
|
30987
|
-
function getArcDegreesFromTolerancePct(pct) {
|
|
30988
|
-
return 360 * Math.acos(1 - pct) / Math.PI;
|
|
30989
|
-
}
|
|
30990
|
-
|
|
30991
|
-
// n = number of segments used to approximate a circle
|
|
30992
|
-
// Returns tolerance as a percent of circle radius
|
|
30993
|
-
function getBufferToleranceFromCircleSegments2(n) {
|
|
30994
|
-
return 1 / Math.cos(Math.PI / n) - 1;
|
|
30995
|
-
}
|
|
30996
|
-
|
|
30997
|
-
function getArcDegreesFromTolerancePct2(pct) {
|
|
30998
|
-
return 360 * Math.acos(1 / (pct + 1)) / Math.PI;
|
|
30999
|
-
}
|
|
31000
|
-
|
|
31001
31116
|
// return constant distance in meters, or return null if unparsable
|
|
31002
31117
|
function parseConstantBufferDistance(str, crs) {
|
|
31003
31118
|
var parsed = parseMeasure2(str);
|
|
@@ -31005,16 +31120,6 @@ ${svg}
|
|
|
31005
31120
|
return convertDistanceParam(str, crs) || null;
|
|
31006
31121
|
}
|
|
31007
31122
|
|
|
31008
|
-
function getBufferToleranceFunction(dataset, opts) {
|
|
31009
|
-
var crs = getDatasetCRS(dataset);
|
|
31010
|
-
var constTol = opts.tolerance ? parseConstantBufferDistance(opts.tolerance, crs) : 0;
|
|
31011
|
-
var pctOfRadius = 1/100;
|
|
31012
|
-
return function(meterDist) {
|
|
31013
|
-
if (constTol) return constTol;
|
|
31014
|
-
return constTol ? constTol : meterDist * pctOfRadius;
|
|
31015
|
-
};
|
|
31016
|
-
}
|
|
31017
|
-
|
|
31018
31123
|
function getBufferDistanceFunction(lyr, dataset, opts) {
|
|
31019
31124
|
if (!opts.radius) {
|
|
31020
31125
|
stop('Missing expected radius parameter');
|
|
@@ -31033,17 +31138,579 @@ ${svg}
|
|
|
31033
31138
|
};
|
|
31034
31139
|
}
|
|
31035
31140
|
|
|
31036
|
-
|
|
31037
|
-
|
|
31038
|
-
|
|
31039
|
-
|
|
31040
|
-
|
|
31041
|
-
|
|
31042
|
-
|
|
31043
|
-
|
|
31044
|
-
|
|
31045
|
-
|
|
31046
|
-
|
|
31141
|
+
function BufferBuilder(opts) {
|
|
31142
|
+
var self = {};
|
|
31143
|
+
var buffer, path, insideFlags;
|
|
31144
|
+
var points = [];
|
|
31145
|
+
var backtrackSteps = opts.backtrack >= 0 ? opts.backtrack : 100;
|
|
31146
|
+
var backtrackStopIdx = 0; // lowest vertex id in backtrack pass
|
|
31147
|
+
|
|
31148
|
+
init();
|
|
31149
|
+
|
|
31150
|
+
function init() {
|
|
31151
|
+
buffer = [];
|
|
31152
|
+
path = [];
|
|
31153
|
+
insideFlags = [];
|
|
31154
|
+
backtrackStopIdx = 0;
|
|
31155
|
+
}
|
|
31156
|
+
|
|
31157
|
+
self.size = function() {
|
|
31158
|
+
return path.length + buffer.length;
|
|
31159
|
+
};
|
|
31160
|
+
|
|
31161
|
+
self.getDebugPoints = function() {
|
|
31162
|
+
var tmp = points;
|
|
31163
|
+
points = [];
|
|
31164
|
+
return tmp;
|
|
31165
|
+
};
|
|
31166
|
+
|
|
31167
|
+
function makeDebugPoints() {
|
|
31168
|
+
points = points.concat(buffer.map((p, i) => {
|
|
31169
|
+
return {
|
|
31170
|
+
type: 'Feature',
|
|
31171
|
+
geometry: {
|
|
31172
|
+
type: 'Point',
|
|
31173
|
+
coordinates: p
|
|
31174
|
+
},
|
|
31175
|
+
properties: {
|
|
31176
|
+
id: i,
|
|
31177
|
+
inside: insideFlags[i],
|
|
31178
|
+
fill: insideFlags[i] ? 'magenta' : 'dodgerblue'
|
|
31179
|
+
}
|
|
31180
|
+
};
|
|
31181
|
+
}));
|
|
31182
|
+
}
|
|
31183
|
+
|
|
31184
|
+
self.addPathVertex = function(p) {
|
|
31185
|
+
path.push(p);
|
|
31186
|
+
};
|
|
31187
|
+
|
|
31188
|
+
self.addBufferVertices = function(arr) {
|
|
31189
|
+
for (var i=0; i<arr.length; i++) {
|
|
31190
|
+
self.addBufferVertex(arr[i]);
|
|
31191
|
+
}
|
|
31192
|
+
};
|
|
31193
|
+
|
|
31194
|
+
self.addBufferVertex = function(p, isLeftTurn) {
|
|
31195
|
+
var bufferLen = insideFlags.length;
|
|
31196
|
+
var prevIdx = bufferLen - 1;
|
|
31197
|
+
if (isLeftTurn) {
|
|
31198
|
+
// first extruded point after left turn
|
|
31199
|
+
// assume that the point is inside the buffer
|
|
31200
|
+
insideFlags.push(true);
|
|
31201
|
+
buffer.push(p);
|
|
31202
|
+
return;
|
|
31203
|
+
}
|
|
31204
|
+
if (prevIdx == -1) {
|
|
31205
|
+
// first point in buffer
|
|
31206
|
+
buffer.push(p);
|
|
31207
|
+
insideFlags.push(false);
|
|
31208
|
+
return;
|
|
31209
|
+
}
|
|
31210
|
+
var prevP = buffer[prevIdx];
|
|
31211
|
+
var prevFlag = insideFlags[prevIdx];
|
|
31212
|
+
if (pointsAreSame(prevP, p)) {
|
|
31213
|
+
// console.log('SAME POINT')
|
|
31214
|
+
return;
|
|
31215
|
+
}
|
|
31216
|
+
// if no segment cross is detected below, inside/outside stays the same
|
|
31217
|
+
var newFlag = prevFlag;
|
|
31218
|
+
var hit, a, b, flagA, flagB;
|
|
31219
|
+
for (var i=0, idx = bufferLen - 3; i < backtrackSteps && idx >= backtrackStopIdx; i++, idx--) {
|
|
31220
|
+
a = buffer[idx];
|
|
31221
|
+
b = buffer[idx + 1];
|
|
31222
|
+
// TODO: consider using a geodetic intersection function for lat-long datasets
|
|
31223
|
+
// TODO: consider case of an endpoint hit
|
|
31224
|
+
// TODO: consider case of collinear hit
|
|
31225
|
+
hit = bufferIntersection$2(a[0], a[1], b[0], b[1], prevP[0], prevP[1], p[0], p[1]);
|
|
31226
|
+
if (!hit) {
|
|
31227
|
+
// continue scanning backward for an intersection
|
|
31228
|
+
continue;
|
|
31229
|
+
}
|
|
31230
|
+
// console.log("hit")
|
|
31231
|
+
flagA = insideFlags[idx];
|
|
31232
|
+
flagB = insideFlags[idx + 1];
|
|
31233
|
+
if (flagA && flagB) {
|
|
31234
|
+
// newest segment collides with an interior segment - do not rewind
|
|
31235
|
+
continue;
|
|
31236
|
+
}
|
|
31237
|
+
if (prevFlag === false) {
|
|
31238
|
+
// if segment intersects an outside segment from outside the buffer,
|
|
31239
|
+
// we are likely closing a loop,
|
|
31240
|
+
// so we stop backtracking and reset the backtrack limit to avoid
|
|
31241
|
+
// removing the loop
|
|
31242
|
+
backtrackStopIdx = i + 1;
|
|
31243
|
+
break;
|
|
31244
|
+
}
|
|
31245
|
+
|
|
31246
|
+
// newest segment collides with an exterior segment
|
|
31247
|
+
// * assume we're going from inside to outside
|
|
31248
|
+
// * and can therefore remove an interior loop
|
|
31249
|
+
// TODO: improve (this assumption does not hold when closing an oxbow type loop)
|
|
31250
|
+
while (buffer.length > idx + 1) {
|
|
31251
|
+
buffer.pop();
|
|
31252
|
+
insideFlags.pop();
|
|
31253
|
+
}
|
|
31254
|
+
buffer.push(hit);
|
|
31255
|
+
insideFlags.push(false);
|
|
31256
|
+
newFlag = false;
|
|
31257
|
+
// console.log("going out")
|
|
31258
|
+
break;
|
|
31259
|
+
}
|
|
31260
|
+
|
|
31261
|
+
buffer.push(p);
|
|
31262
|
+
insideFlags.push(newFlag);
|
|
31263
|
+
};
|
|
31264
|
+
|
|
31265
|
+
self.done = function() {
|
|
31266
|
+
if (opts.debug_points) {
|
|
31267
|
+
makeDebugPoints();
|
|
31268
|
+
}
|
|
31269
|
+
var ring = path.reverse().concat(buffer);
|
|
31270
|
+
if (ring.length < 3) {
|
|
31271
|
+
error('Defective buffer ring:', ring);
|
|
31272
|
+
}
|
|
31273
|
+
ring.push(ring[0]);
|
|
31274
|
+
init();
|
|
31275
|
+
return ring;
|
|
31276
|
+
};
|
|
31277
|
+
|
|
31278
|
+
return self;
|
|
31279
|
+
}
|
|
31280
|
+
|
|
31281
|
+
function pointsAreSame(a, b) {
|
|
31282
|
+
return a[0] === b[0] && a[1] === b[1];
|
|
31283
|
+
}
|
|
31284
|
+
|
|
31285
|
+
// Exclude segments with non-intersecting bounding boxes before
|
|
31286
|
+
// calling intersection function
|
|
31287
|
+
// Possibly slightly faster than direct call... not worth it?
|
|
31288
|
+
function bufferIntersection$2(ax, ay, bx, by, cx, cy, dx, dy) {
|
|
31289
|
+
if (ax < cx && ax < dx && bx < cx && bx < dx ||
|
|
31290
|
+
ax > cx && ax > dx && bx > cx && bx > dx ||
|
|
31291
|
+
ay < cy && ay < dy && by < cy && by < dy ||
|
|
31292
|
+
ay > cy && ay > dy && by > cy && by > dy) return null;
|
|
31293
|
+
return segmentIntersection(ax, ay, bx, by, cx, cy, dx, dy);
|
|
31294
|
+
}
|
|
31295
|
+
|
|
31296
|
+
// Returns a function for generating GeoJSON MultiPolygon geometries
|
|
31297
|
+
function getPolylineBufferMaker$2(arcs, geod, getBearing, opts) {
|
|
31298
|
+
// var sliceLen = opts.slice_length || Infinity;
|
|
31299
|
+
var segsPerQuadrant = opts.arc_quality >= 2 ? opts.arc_quality : 12;
|
|
31300
|
+
var capStyle = opts.cap_style || 'round'; // expect 'round' or 'flat'
|
|
31301
|
+
var pathIter = new ShapeIter(arcs);
|
|
31302
|
+
var builder = new BufferBuilder(opts);
|
|
31303
|
+
|
|
31304
|
+
return function makeBufferGeoJSON(shape, distance) {
|
|
31305
|
+
var rings = [];
|
|
31306
|
+
(shape || []).forEach(function(path, i) {
|
|
31307
|
+
var pathRings = makeSinglePathRings(path, distance);
|
|
31308
|
+
rings = rings.concat(pathRings);
|
|
31309
|
+
});
|
|
31310
|
+
if (rings.length === 0) return null;
|
|
31311
|
+
var feat = {
|
|
31312
|
+
type: 'Feature',
|
|
31313
|
+
properties: null,
|
|
31314
|
+
geometry: {
|
|
31315
|
+
type: 'MultiPolygon',
|
|
31316
|
+
coordinates: rings.map(ring => [ring]) // to Polygon format
|
|
31317
|
+
}
|
|
31318
|
+
};
|
|
31319
|
+
if (opts.debug_points) {
|
|
31320
|
+
return [feat].concat(builder.getDebugPoints());
|
|
31321
|
+
}
|
|
31322
|
+
return feat;
|
|
31323
|
+
};
|
|
31324
|
+
|
|
31325
|
+
// each path may be converted into multiple buffer rings, which later
|
|
31326
|
+
// need to be dissolved
|
|
31327
|
+
function makeSinglePathRings(pathArcs, dist) {
|
|
31328
|
+
var revPathArcs;
|
|
31329
|
+
var rings = [];
|
|
31330
|
+
if (!opts.right || opts.left) {
|
|
31331
|
+
rings = rings.concat(makeLeftBufferRings(pathArcs, dist));
|
|
31332
|
+
}
|
|
31333
|
+
if (!opts.left || opts.right) {
|
|
31334
|
+
revPathArcs = reversePath(pathArcs.concat());
|
|
31335
|
+
rings = rings.concat(makeLeftBufferRings(revPathArcs, dist));
|
|
31336
|
+
}
|
|
31337
|
+
return rings;
|
|
31338
|
+
}
|
|
31339
|
+
|
|
31340
|
+
function makeLeftBufferRings(path, dist) {
|
|
31341
|
+
var rings = [];
|
|
31342
|
+
var x0, y0, x1, y1, x2, y2; // path traversal coords
|
|
31343
|
+
var p1, p2; // extruded points
|
|
31344
|
+
var bearing1, bearing2, prevBearing, joinAngle;
|
|
31345
|
+
var firstBearing;
|
|
31346
|
+
var i = 0;
|
|
31347
|
+
pathIter.init(path);
|
|
31348
|
+
|
|
31349
|
+
if (pathIter.hasNext()) {
|
|
31350
|
+
x0 = x2 = pathIter.x;
|
|
31351
|
+
y0 = y2 = pathIter.y;
|
|
31352
|
+
i++;
|
|
31353
|
+
}
|
|
31354
|
+
|
|
31355
|
+
while (pathIter.hasNext()) {
|
|
31356
|
+
// TODO: use a tolerance
|
|
31357
|
+
if (pathIter.x === x2 && pathIter.y === y2) {
|
|
31358
|
+
debug("skipping a duplicate point");
|
|
31359
|
+
continue;
|
|
31360
|
+
}
|
|
31361
|
+
x1 = x2;
|
|
31362
|
+
y1 = y2;
|
|
31363
|
+
x2 = pathIter.x;
|
|
31364
|
+
y2 = pathIter.y;
|
|
31365
|
+
|
|
31366
|
+
// calculate bearing at both segment points
|
|
31367
|
+
// TODO: no need to calculate twice with planar coordinates
|
|
31368
|
+
prevBearing = bearing2;
|
|
31369
|
+
bearing1 = getBearing(x1, y1, x2, y2);
|
|
31370
|
+
bearing2 = getBearing(x2, y2, x1, y1) - 180;
|
|
31371
|
+
p1 = geod(x1, y1, bearing1 - 90, dist);
|
|
31372
|
+
p2 = geod(x2, y2, bearing2 - 90, dist);
|
|
31373
|
+
|
|
31374
|
+
if (i == 1) {
|
|
31375
|
+
firstBearing = bearing1;
|
|
31376
|
+
builder.addPathVertex([x1, y1]);
|
|
31377
|
+
} else {
|
|
31378
|
+
joinAngle = getJoinAngle(prevBearing, bearing1);
|
|
31379
|
+
}
|
|
31380
|
+
|
|
31381
|
+
builder.addPathVertex([x2, y2]);
|
|
31382
|
+
|
|
31383
|
+
if (i > 1 && joinAngle > 0) {
|
|
31384
|
+
builder.addBufferVertices(makeRoundJoin(x1, y1, prevBearing - 90, joinAngle, dist));
|
|
31385
|
+
}
|
|
31386
|
+
|
|
31387
|
+
builder.addBufferVertex(p1, joinAngle < 0);
|
|
31388
|
+
builder.addBufferVertex(p2);
|
|
31389
|
+
|
|
31390
|
+
// TODO: restore slicing?
|
|
31391
|
+
// if (center.length - 1 >= sliceLen) {
|
|
31392
|
+
// builder.done(rings);
|
|
31393
|
+
// }
|
|
31394
|
+
|
|
31395
|
+
i++;
|
|
31396
|
+
}
|
|
31397
|
+
|
|
31398
|
+
if (x2 == x0 && y2 == y0) { // closed path
|
|
31399
|
+
// add join to finish closed path
|
|
31400
|
+
// TODO - figure out which bearing to use
|
|
31401
|
+
joinAngle = getJoinAngle(bearing2, firstBearing);
|
|
31402
|
+
if (joinAngle > 0) {
|
|
31403
|
+
builder.addBufferVertices(makeFinalJoin(x2, y2, bearing1 - 90, joinAngle, dist));
|
|
31404
|
+
}
|
|
31405
|
+
} else if (capStyle == 'round') { // open path
|
|
31406
|
+
// add a cap to finish open path
|
|
31407
|
+
builder.addBufferVertices(makeRoundCap(x2, y2, bearing2 - 90, dist));
|
|
31408
|
+
}
|
|
31409
|
+
|
|
31410
|
+
rings.push(builder.done());
|
|
31411
|
+
return rings;
|
|
31412
|
+
}
|
|
31413
|
+
|
|
31414
|
+
// function extendArray(arr, arr2) {
|
|
31415
|
+
// arr2.reverse();
|
|
31416
|
+
// while(arr2.length > 0) arr.push(arr2.pop());
|
|
31417
|
+
// }
|
|
31418
|
+
|
|
31419
|
+
function makeFinalJoin(x, y, direction, angle, dist) {
|
|
31420
|
+
var points = makeRoundJoin(x, y, direction, angle, dist);
|
|
31421
|
+
points.push(geod(x, y, direction + angle, dist));
|
|
31422
|
+
return points;
|
|
31423
|
+
}
|
|
31424
|
+
|
|
31425
|
+
function makeRoundCap(x, y, startDir, dist) {
|
|
31426
|
+
var points = makeRoundJoin(x, y, startDir, 180, dist);
|
|
31427
|
+
points.push(geod(x, y, startDir + 180, dist)); // add final vertex
|
|
31428
|
+
return points;
|
|
31429
|
+
}
|
|
31430
|
+
|
|
31431
|
+
// get interior vertices of an interpolated CW arc
|
|
31432
|
+
function makeRoundJoin(cx, cy, startBearing, arcAngle, dist) {
|
|
31433
|
+
var points = [];
|
|
31434
|
+
var increment = 90 / segsPerQuadrant;
|
|
31435
|
+
var angle = increment;
|
|
31436
|
+
while (angle < arcAngle) {
|
|
31437
|
+
points.push(geod(cx, cy, startBearing + angle, dist));
|
|
31438
|
+
angle += increment;
|
|
31439
|
+
}
|
|
31440
|
+
return points;
|
|
31441
|
+
}
|
|
31442
|
+
|
|
31443
|
+
// get angle between two extruded segments in degrees
|
|
31444
|
+
// positive angle means join in convex (range: 0-180 degrees)
|
|
31445
|
+
// negative angle means join is concave (range: -180-0 degrees)
|
|
31446
|
+
function getJoinAngle(direction1, direction2) {
|
|
31447
|
+
var delta = direction2 - direction1;
|
|
31448
|
+
if (delta > 180) {
|
|
31449
|
+
delta -= 360;
|
|
31450
|
+
}
|
|
31451
|
+
if (delta < -180) {
|
|
31452
|
+
delta += 360;
|
|
31453
|
+
}
|
|
31454
|
+
return delta;
|
|
31455
|
+
}
|
|
31456
|
+
|
|
31457
|
+
}
|
|
31458
|
+
|
|
31459
|
+
// Returns a function for generating GeoJSON MultiPolygon geometries
|
|
31460
|
+
function getPolylineBufferMaker$1(arcs, geod, getBearing, opts) {
|
|
31461
|
+
var sliceLen = opts.slice_length || Infinity;
|
|
31462
|
+
var backtrackSteps = opts.backtrack >= 0 ? opts.backtrack : 100;
|
|
31463
|
+
var segsPerQuadrant = opts.arc_quality >= 2 ? opts.arc_quality : 12;
|
|
31464
|
+
var capStyle = opts.cap_style || 'round'; // expect 'round' or 'flat'
|
|
31465
|
+
var pathIter = new ShapeIter(arcs);
|
|
31466
|
+
var left, center, rings;
|
|
31467
|
+
|
|
31468
|
+
return function makeBufferGeoJSON(shape, distance) {
|
|
31469
|
+
var rings = [];
|
|
31470
|
+
(shape || []).forEach(function(path, i) {
|
|
31471
|
+
var pathRings = makeSinglePathRings(path, distance);
|
|
31472
|
+
rings = rings.concat(pathRings);
|
|
31473
|
+
});
|
|
31474
|
+
if (rings.length === 0) return null;
|
|
31475
|
+
return {
|
|
31476
|
+
type: 'MultiPolygon',
|
|
31477
|
+
coordinates: rings.map(ring => [ring]) // to Polygon format
|
|
31478
|
+
};
|
|
31479
|
+
};
|
|
31480
|
+
|
|
31481
|
+
// each path may be converted into multiple buffer rings, which later
|
|
31482
|
+
// need to be dissolved
|
|
31483
|
+
function makeSinglePathRings(pathArcs, dist) {
|
|
31484
|
+
var revPathArcs;
|
|
31485
|
+
var rings = [];
|
|
31486
|
+
if (!opts.right || opts.left) {
|
|
31487
|
+
rings = rings.concat(makeLeftBufferRings(pathArcs, dist));
|
|
31488
|
+
}
|
|
31489
|
+
if (!opts.left || opts.right) {
|
|
31490
|
+
revPathArcs = reversePath(pathArcs.concat());
|
|
31491
|
+
rings = rings.concat(makeLeftBufferRings(revPathArcs, dist));
|
|
31492
|
+
}
|
|
31493
|
+
return rings;
|
|
31494
|
+
}
|
|
31495
|
+
|
|
31496
|
+
function makeLeftBufferRings(path, dist) {
|
|
31497
|
+
left = [];
|
|
31498
|
+
center = [];
|
|
31499
|
+
rings = [];
|
|
31500
|
+
var x0, y0, x1, y1, x2, y2; // path traversal coords
|
|
31501
|
+
var p1, p2; // extruded points
|
|
31502
|
+
var prevP;
|
|
31503
|
+
var bearing1, bearing2, prevBearing, joinAngle;
|
|
31504
|
+
var firstBearing;
|
|
31505
|
+
var i = 0;
|
|
31506
|
+
pathIter.init(path);
|
|
31507
|
+
|
|
31508
|
+
if (pathIter.hasNext()) {
|
|
31509
|
+
x0 = x2 = pathIter.x;
|
|
31510
|
+
y0 = y2 = pathIter.y;
|
|
31511
|
+
i++;
|
|
31512
|
+
}
|
|
31513
|
+
|
|
31514
|
+
while (pathIter.hasNext()) {
|
|
31515
|
+
// TODO: use a tolerance
|
|
31516
|
+
if (pathIter.x === x2 && pathIter.y === y2) {
|
|
31517
|
+
debug("skipping a duplicate point");
|
|
31518
|
+
continue;
|
|
31519
|
+
}
|
|
31520
|
+
x1 = x2;
|
|
31521
|
+
y1 = y2;
|
|
31522
|
+
x2 = pathIter.x;
|
|
31523
|
+
y2 = pathIter.y;
|
|
31524
|
+
|
|
31525
|
+
// calculate bearing at both segment points
|
|
31526
|
+
// TODO: no need to calculate twice with planar coordinates
|
|
31527
|
+
prevBearing = bearing2;
|
|
31528
|
+
bearing1 = getBearing(x1, y1, x2, y2);
|
|
31529
|
+
bearing2 = getBearing(x2, y2, x1, y1) - 180;
|
|
31530
|
+
// extrude current segment to the left
|
|
31531
|
+
prevP = p2;
|
|
31532
|
+
p1 = geod(x1, y1, bearing1 - 90, dist);
|
|
31533
|
+
p2 = geod(x2, y2, bearing2 - 90, dist);
|
|
31534
|
+
|
|
31535
|
+
if (i == 1) {
|
|
31536
|
+
firstBearing = bearing1;
|
|
31537
|
+
} else {
|
|
31538
|
+
joinAngle = getJoinAngle(prevBearing, bearing1);
|
|
31539
|
+
}
|
|
31540
|
+
|
|
31541
|
+
// extend center coords (i.e. original path)
|
|
31542
|
+
if (center.length == 0) {
|
|
31543
|
+
center.push([x1, y1]);
|
|
31544
|
+
}
|
|
31545
|
+
center.push([x2, y2]);
|
|
31546
|
+
|
|
31547
|
+
// if (veryCloseToPrevPoint(left, p1[0], p1[1])) {
|
|
31548
|
+
// console.log("VERY CLOSE")
|
|
31549
|
+
// // skip first point
|
|
31550
|
+
// addBufferVertex(p2);
|
|
31551
|
+
// }
|
|
31552
|
+
|
|
31553
|
+
if (i > 1 && joinAngle > 0) {
|
|
31554
|
+
if (prevP) {
|
|
31555
|
+
// start join from last extruded vertex of previous buffer slice
|
|
31556
|
+
addBufferVertex(prevP);
|
|
31557
|
+
}
|
|
31558
|
+
makeRoundJoin(x1, y1, prevBearing - 90, joinAngle, dist).forEach(addBufferVertex);
|
|
31559
|
+
addBufferVertex(p1);
|
|
31560
|
+
addBufferVertex(p2);
|
|
31561
|
+
// left.push(p1, p2)
|
|
31562
|
+
} else {
|
|
31563
|
+
// left.push(p1, p2)
|
|
31564
|
+
addBufferVertex(p1);
|
|
31565
|
+
addBufferVertex(p2);
|
|
31566
|
+
}
|
|
31567
|
+
|
|
31568
|
+
// TODO: finish
|
|
31569
|
+
if (center.length - 1 >= sliceLen) {
|
|
31570
|
+
finishRing();
|
|
31571
|
+
}
|
|
31572
|
+
i++;
|
|
31573
|
+
}
|
|
31574
|
+
|
|
31575
|
+
if (center.length > 1) {
|
|
31576
|
+
finishRing();
|
|
31577
|
+
}
|
|
31578
|
+
|
|
31579
|
+
if (x2 == x0 && y2 == y0) { // closed path
|
|
31580
|
+
// add join to finish closed path
|
|
31581
|
+
// TODO - figure out which bearing to use
|
|
31582
|
+
joinAngle = getJoinAngle(bearing2, firstBearing);
|
|
31583
|
+
if (joinAngle > 0) {
|
|
31584
|
+
appendJoinToRing(rings[rings.length-1], x2, y2, bearing1 - 90, joinAngle, dist);
|
|
31585
|
+
}
|
|
31586
|
+
} else { // open path
|
|
31587
|
+
// add a cap to finish open path
|
|
31588
|
+
// left.push.apply(left, makeCap(x2, y2, bearing, dist));
|
|
31589
|
+
appendCapToRing(rings[rings.length-1], x2, y2, bearing2, dist);
|
|
31590
|
+
}
|
|
31591
|
+
|
|
31592
|
+
return rings;
|
|
31593
|
+
}
|
|
31594
|
+
|
|
31595
|
+
function finishRing() {
|
|
31596
|
+
if (center.length < 2) {
|
|
31597
|
+
debug('defective path, skipping');
|
|
31598
|
+
return;
|
|
31599
|
+
}
|
|
31600
|
+
var ring = center.reverse().concat(left);
|
|
31601
|
+
ring.push(ring[0]);
|
|
31602
|
+
|
|
31603
|
+
// Start next partial (if there is one)
|
|
31604
|
+
left = [];
|
|
31605
|
+
center = [];
|
|
31606
|
+
rings.push(ring);
|
|
31607
|
+
}
|
|
31608
|
+
|
|
31609
|
+
// function extendArray(arr, arr2) {
|
|
31610
|
+
// arr2.reverse();
|
|
31611
|
+
// while(arr2.length > 0) arr.push(arr2.pop());
|
|
31612
|
+
// }
|
|
31613
|
+
|
|
31614
|
+
function appendJoinToRing(ring, x, y, direction, angle, dist) {
|
|
31615
|
+
var p1 = ring.pop();
|
|
31616
|
+
var coords = makeRoundJoin(x, y, direction, angle, dist);
|
|
31617
|
+
ring.push.apply(ring, coords);
|
|
31618
|
+
ring.push(geod(x, y, direction + angle, dist));
|
|
31619
|
+
ring.push(p1);
|
|
31620
|
+
}
|
|
31621
|
+
|
|
31622
|
+
function appendCapToRing(ring, x, y, direction, dist) {
|
|
31623
|
+
if (capStyle == 'flat' || ring.length < 4) {
|
|
31624
|
+
return;
|
|
31625
|
+
}
|
|
31626
|
+
var p1 = ring.pop();
|
|
31627
|
+
var coords = makeRoundCap(x, y, direction - 90, dist);
|
|
31628
|
+
ring.push.apply(ring, coords);
|
|
31629
|
+
ring.push(p1);
|
|
31630
|
+
}
|
|
31631
|
+
|
|
31632
|
+
function makeRoundCap(x, y, startDir, dist) {
|
|
31633
|
+
var points = makeRoundJoin(x, y, startDir, 180, dist);
|
|
31634
|
+
points.push(geod(x, y, startDir + 180, dist)); // add final vertex
|
|
31635
|
+
return points;
|
|
31636
|
+
}
|
|
31637
|
+
|
|
31638
|
+
// get interior vertices of an interpolated CW arc
|
|
31639
|
+
function makeRoundJoin(cx, cy, startBearing, arcAngle, dist) {
|
|
31640
|
+
var points = [];
|
|
31641
|
+
var increment = 90 / segsPerQuadrant;
|
|
31642
|
+
var angle = increment;
|
|
31643
|
+
while (angle < arcAngle) {
|
|
31644
|
+
points.push(geod(cx, cy, startBearing + angle, dist));
|
|
31645
|
+
angle += increment;
|
|
31646
|
+
}
|
|
31647
|
+
return points;
|
|
31648
|
+
}
|
|
31649
|
+
|
|
31650
|
+
// get angle between two extruded segments in degrees
|
|
31651
|
+
// positive angle means join in convex (range: 0-180 degrees)
|
|
31652
|
+
// negative angle means join is concave (range: -180-0 degrees)
|
|
31653
|
+
function getJoinAngle(direction1, direction2) {
|
|
31654
|
+
var delta = direction2 - direction1;
|
|
31655
|
+
if (delta > 180) {
|
|
31656
|
+
delta -= 360;
|
|
31657
|
+
}
|
|
31658
|
+
if (delta < -180) {
|
|
31659
|
+
delta += 360;
|
|
31660
|
+
}
|
|
31661
|
+
return delta;
|
|
31662
|
+
}
|
|
31663
|
+
|
|
31664
|
+
|
|
31665
|
+
function addBufferVertex(d) {
|
|
31666
|
+
var arr = left;
|
|
31667
|
+
var a, b, c, hit;
|
|
31668
|
+
// c is the start point of the segment formed by appending point d to the polyline.
|
|
31669
|
+
c = arr[arr.length - 1];
|
|
31670
|
+
for (var i=0, idx = arr.length - 3; i < backtrackSteps && idx >= 0; i++, idx--) {
|
|
31671
|
+
a = arr[idx];
|
|
31672
|
+
b = arr[idx + 1];
|
|
31673
|
+
// TODO: consider using a geodetic intersection function for lat-long datasets
|
|
31674
|
+
hit = bufferIntersection$1(a[0], a[1], b[0], b[1], c[0], c[1], d[0], d[1]);
|
|
31675
|
+
// TODO: disregard hits caused by oxbows
|
|
31676
|
+
if (hit) {
|
|
31677
|
+
// TODO: handle collinear segments
|
|
31678
|
+
// if (hit.length != 2) console.log('COLLINEAR', hit)
|
|
31679
|
+
// segments intersect -- replace two internal segment endpoints with xx point
|
|
31680
|
+
while (arr.length > idx + 1) arr.pop();
|
|
31681
|
+
appendPoint(arr, hit);
|
|
31682
|
+
c = hit; // update starting point of the newly added segment
|
|
31683
|
+
}
|
|
31684
|
+
}
|
|
31685
|
+
appendPoint(arr, d);
|
|
31686
|
+
}
|
|
31687
|
+
|
|
31688
|
+
}
|
|
31689
|
+
|
|
31690
|
+
// Test if two points are within a snapping tolerance
|
|
31691
|
+
// TODO: calculate the tolerance more sensibly
|
|
31692
|
+
function veryClose(x1, y1, x2, y2, tol) {
|
|
31693
|
+
var dist = geom.distance2D(x1, y1, x2, y2);
|
|
31694
|
+
return dist < tol;
|
|
31695
|
+
}
|
|
31696
|
+
|
|
31697
|
+
function appendPoint(arr, p) {
|
|
31698
|
+
var prev = arr[arr.length - 1];
|
|
31699
|
+
if (!prev || !veryClose(prev[0], prev[1], p[0], p[1], 1e-10)) {
|
|
31700
|
+
arr.push(p);
|
|
31701
|
+
}
|
|
31702
|
+
}
|
|
31703
|
+
|
|
31704
|
+
// Exclude segments with non-intersecting bounding boxes before
|
|
31705
|
+
// calling intersection function
|
|
31706
|
+
// Possibly slightly faster than direct call... not worth it?
|
|
31707
|
+
function bufferIntersection$1(ax, ay, bx, by, cx, cy, dx, dy) {
|
|
31708
|
+
if (ax < cx && ax < dx && bx < cx && bx < dx ||
|
|
31709
|
+
ax > cx && ax > dx && bx > cx && bx > dx ||
|
|
31710
|
+
ay < cy && ay < dy && by < cy && by < dy ||
|
|
31711
|
+
ay > cy && ay > dy && by > cy && by > dy) return null;
|
|
31712
|
+
return geom.segmentIntersection(ax, ay, bx, by, cx, cy, dx, dy);
|
|
31713
|
+
}
|
|
31047
31714
|
|
|
31048
31715
|
// Returns a function for generating GeoJSON geometries (MultiLineString or MultiPolygon)
|
|
31049
31716
|
function getPolylineBufferMaker(arcs, geod, getBearing, opts) {
|
|
@@ -31237,28 +31904,6 @@ ${svg}
|
|
|
31237
31904
|
};
|
|
31238
31905
|
}
|
|
31239
31906
|
|
|
31240
|
-
function addBufferVertex(arr, d, maxBacktrack) {
|
|
31241
|
-
var a, b, c, hit;
|
|
31242
|
-
for (var i=0, idx = arr.length - 3; i<maxBacktrack && idx >= 0; i++, idx--) {
|
|
31243
|
-
c = arr[arr.length - 1];
|
|
31244
|
-
a = arr[idx];
|
|
31245
|
-
b = arr[idx + 1];
|
|
31246
|
-
// TODO: consider using a geodetic intersection function for lat-long datasets
|
|
31247
|
-
hit = bufferIntersection(a[0], a[1], b[0], b[1], c[0], c[1], d[0], d[1]);
|
|
31248
|
-
if (hit) {
|
|
31249
|
-
// TODO: handle collinear segments
|
|
31250
|
-
if (hit.length != 2) ;
|
|
31251
|
-
// segments intersect -- replace two internal segment endpoints with xx point
|
|
31252
|
-
while (arr.length > idx + 1) arr.pop();
|
|
31253
|
-
// TODO: check proximity of hit to several points
|
|
31254
|
-
arr.push(hit);
|
|
31255
|
-
}
|
|
31256
|
-
}
|
|
31257
|
-
|
|
31258
|
-
// TODO: check proximity to previous point
|
|
31259
|
-
arr.push(d); // add new point
|
|
31260
|
-
}
|
|
31261
|
-
|
|
31262
31907
|
// Exclude segments with non-intersecting bounding boxes before
|
|
31263
31908
|
// calling intersection function
|
|
31264
31909
|
// Possibly slightly faster than direct call... not worth it?
|
|
@@ -31270,302 +31915,6 @@ ${svg}
|
|
|
31270
31915
|
return geom.segmentIntersection(ax, ay, bx, by, cx, cy, dx, dy);
|
|
31271
31916
|
}
|
|
31272
31917
|
|
|
31273
|
-
var PathBuffer = /*#__PURE__*/Object.freeze({
|
|
31274
|
-
__proto__: null,
|
|
31275
|
-
addBufferVertex: addBufferVertex,
|
|
31276
|
-
bufferIntersection: bufferIntersection,
|
|
31277
|
-
getPolylineBufferMaker: getPolylineBufferMaker
|
|
31278
|
-
});
|
|
31279
|
-
|
|
31280
|
-
function getPolylineBufferMaker2(arcs, geod, getBearing, opts) {
|
|
31281
|
-
var makeLeftBuffer = getPathBufferMaker2(arcs, geod, getBearing, opts);
|
|
31282
|
-
var geomType = opts.geometry_type;
|
|
31283
|
-
|
|
31284
|
-
function polygonCoords(ring) {
|
|
31285
|
-
return [ring];
|
|
31286
|
-
}
|
|
31287
|
-
|
|
31288
|
-
function needLeftBuffer(path, arcs) {
|
|
31289
|
-
if (geomType == 'polyline') {
|
|
31290
|
-
return opts.type != 'right';
|
|
31291
|
-
}
|
|
31292
|
-
// assume polygon type
|
|
31293
|
-
if (opts.type == 'outer') {
|
|
31294
|
-
return geom.getPathWinding(path, arcs) == 1;
|
|
31295
|
-
}
|
|
31296
|
-
if (opts.type == 'inner') {
|
|
31297
|
-
return geom.getPathWinding(path, arcs) == -1;
|
|
31298
|
-
}
|
|
31299
|
-
return true;
|
|
31300
|
-
}
|
|
31301
|
-
|
|
31302
|
-
function needRightBuffer() {
|
|
31303
|
-
return geomType == 'polyline' && opts.type != 'left';
|
|
31304
|
-
}
|
|
31305
|
-
|
|
31306
|
-
function makeBufferParts(pathArcs, dist) {
|
|
31307
|
-
var leftPartials, rightPartials, parts, revPathArcs;
|
|
31308
|
-
|
|
31309
|
-
if (needLeftBuffer(pathArcs, arcs)) {
|
|
31310
|
-
leftPartials = makeLeftBuffer(pathArcs, dist);
|
|
31311
|
-
}
|
|
31312
|
-
if (needRightBuffer()) {
|
|
31313
|
-
revPathArcs = reversePath(pathArcs.concat());
|
|
31314
|
-
rightPartials = makeLeftBuffer(revPathArcs, dist);
|
|
31315
|
-
}
|
|
31316
|
-
parts = (leftPartials || []).concat(rightPartials || []);
|
|
31317
|
-
return parts.map(polygonCoords);
|
|
31318
|
-
}
|
|
31319
|
-
|
|
31320
|
-
// Returns a GeoJSON Geometry (MultiLineString or MultiPolygon) or null
|
|
31321
|
-
return function(shape, dist) {
|
|
31322
|
-
var geom = {
|
|
31323
|
-
type: 'MultiPolygon',
|
|
31324
|
-
coordinates: []
|
|
31325
|
-
};
|
|
31326
|
-
for (var i=0; i<shape.length; i++) {
|
|
31327
|
-
geom.coordinates = geom.coordinates.concat(makeBufferParts(shape[i], dist));
|
|
31328
|
-
}
|
|
31329
|
-
return geom.coordinates.length == 0 ? null : geom;
|
|
31330
|
-
};
|
|
31331
|
-
}
|
|
31332
|
-
|
|
31333
|
-
function getPathBufferMaker2(arcs, geod, getBearing, opts) {
|
|
31334
|
-
var backtrackSteps = opts.backtrack >= 0 ? opts.backtrack : 50;
|
|
31335
|
-
var pathIter = new ShapeIter(arcs);
|
|
31336
|
-
// var capStyle = opts.cap_style || 'round'; // expect 'round' or 'flat'
|
|
31337
|
-
var partials, left, center;
|
|
31338
|
-
var bounds;
|
|
31339
|
-
// TODO: implement other join styles than round
|
|
31340
|
-
|
|
31341
|
-
// function updateTolerance(dist) {
|
|
31342
|
-
|
|
31343
|
-
// }
|
|
31344
|
-
|
|
31345
|
-
function addRoundJoin(x, y, startDir, angle, dist) {
|
|
31346
|
-
var increment = 10;
|
|
31347
|
-
var endDir = startDir + angle;
|
|
31348
|
-
var dir = startDir + increment;
|
|
31349
|
-
while (dir < endDir) {
|
|
31350
|
-
addBufferVertex(geod(x, y, dir, dist));
|
|
31351
|
-
dir += increment;
|
|
31352
|
-
}
|
|
31353
|
-
}
|
|
31354
|
-
|
|
31355
|
-
// function addRoundJoin2(arr, x, y, startDir, angle, dist) {
|
|
31356
|
-
// var increment = 10;
|
|
31357
|
-
// var endDir = startDir + angle;
|
|
31358
|
-
// var dir = startDir + increment;
|
|
31359
|
-
// while (dir < endDir) {
|
|
31360
|
-
// addBufferVertex(arr, geod(x, y, dir, dist));
|
|
31361
|
-
// dir += increment;
|
|
31362
|
-
// }
|
|
31363
|
-
// }
|
|
31364
|
-
|
|
31365
|
-
// Test if two points are within a snapping tolerance
|
|
31366
|
-
// TODO: calculate the tolerance more sensibly
|
|
31367
|
-
function veryClose(x1, y1, x2, y2, tol) {
|
|
31368
|
-
var dist = geom.distance2D(x1, y1, x2, y2);
|
|
31369
|
-
return dist < tol;
|
|
31370
|
-
}
|
|
31371
|
-
|
|
31372
|
-
function veryCloseToPrevPoint(arr, x, y) {
|
|
31373
|
-
var prev = arr[arr.length - 1];
|
|
31374
|
-
return veryClose(prev[0], prev[1], x, y, 0.000001);
|
|
31375
|
-
}
|
|
31376
|
-
|
|
31377
|
-
function appendPoint(arr, p) {
|
|
31378
|
-
var prev = arr[arr.length - 1];
|
|
31379
|
-
if (!veryClose(prev[0], prev[1], p[0], p[1], 1e-10)) {
|
|
31380
|
-
arr.push(p);
|
|
31381
|
-
}
|
|
31382
|
-
}
|
|
31383
|
-
|
|
31384
|
-
// function makeCap(x, y, direction, dist) {
|
|
31385
|
-
// if (capStyle == 'flat') {
|
|
31386
|
-
// return [[x, y]];
|
|
31387
|
-
// }
|
|
31388
|
-
// return makeRoundCap(x, y, direction, dist);
|
|
31389
|
-
// }
|
|
31390
|
-
|
|
31391
|
-
// function makeRoundCap(x, y, segmentDir, dist) {
|
|
31392
|
-
// var points = [];
|
|
31393
|
-
// var increment = 10;
|
|
31394
|
-
// var startDir = segmentDir - 90;
|
|
31395
|
-
// var angle = increment;
|
|
31396
|
-
// while (angle < 180) {
|
|
31397
|
-
// points.push(geod(x, y, startDir + angle, dist));
|
|
31398
|
-
// angle += increment;
|
|
31399
|
-
// }
|
|
31400
|
-
// return points;
|
|
31401
|
-
// }
|
|
31402
|
-
|
|
31403
|
-
// get angle between two extruded segments in degrees
|
|
31404
|
-
// positive angle means join in convex (range: 0-180 degrees)
|
|
31405
|
-
// negative angle means join is concave (range: -180-0 degrees)
|
|
31406
|
-
function getJoinAngle(direction1, direction2) {
|
|
31407
|
-
var delta = direction2 - direction1;
|
|
31408
|
-
if (delta > 180) {
|
|
31409
|
-
delta -= 360;
|
|
31410
|
-
}
|
|
31411
|
-
if (delta < -180) {
|
|
31412
|
-
delta += 360;
|
|
31413
|
-
}
|
|
31414
|
-
return delta;
|
|
31415
|
-
}
|
|
31416
|
-
|
|
31417
|
-
|
|
31418
|
-
// TODO: handle polygon holes
|
|
31419
|
-
function addBufferVertex(d) {
|
|
31420
|
-
var arr = left;
|
|
31421
|
-
var a, b, c, c0, hit;
|
|
31422
|
-
// c is the start point of the segment formed by appending point d to the polyline.
|
|
31423
|
-
c0 = c = arr[arr.length - 1];
|
|
31424
|
-
for (var i=0, idx = arr.length - 3; idx >= 0; i++, idx--) {
|
|
31425
|
-
a = arr[idx];
|
|
31426
|
-
b = arr[idx + 1];
|
|
31427
|
-
// TODO: consider using a geodetic intersection function for lat-long datasets
|
|
31428
|
-
hit = bufferIntersection(a[0], a[1], b[0], b[1], c[0], c[1], d[0], d[1]);
|
|
31429
|
-
if (hit) {
|
|
31430
|
-
if (segmentTurn(a, b, c, d) == 1) {
|
|
31431
|
-
// interpretation: segment cd crosses segment ab from outside to inside
|
|
31432
|
-
// the buffer -- we need to start a new partial; otherwise,
|
|
31433
|
-
// the following code would likely remove a loop representing
|
|
31434
|
-
// an oxbow-type hole in the buffer.
|
|
31435
|
-
//
|
|
31436
|
-
finishPartial();
|
|
31437
|
-
break;
|
|
31438
|
-
}
|
|
31439
|
-
// TODO: handle collinear segments (consider creating new partial)
|
|
31440
|
-
// if (hit.length != 2) console.log('COLLINEAR', hit)
|
|
31441
|
-
|
|
31442
|
-
// segments intersect, indicating a spurious loop: remove the loop and
|
|
31443
|
-
// replace the endpoints of the intersecting segments with the intersection point.
|
|
31444
|
-
while (arr.length > idx + 1) arr.pop();
|
|
31445
|
-
appendPoint(arr, hit);
|
|
31446
|
-
c = hit; // update starting point of the newly added segment
|
|
31447
|
-
}
|
|
31448
|
-
|
|
31449
|
-
// Maintain a bounding box around vertices before the backtrack limit.
|
|
31450
|
-
// If the latest segment intersects this bounding box, there could be a self-
|
|
31451
|
-
// intersection -- start a new partial to prevent self-intersection.
|
|
31452
|
-
//
|
|
31453
|
-
if (i >= backtrackSteps) {
|
|
31454
|
-
if (!bounds) {
|
|
31455
|
-
bounds = new Bounds();
|
|
31456
|
-
bounds.mergePoint(a[0], a[1]);
|
|
31457
|
-
}
|
|
31458
|
-
bounds.mergePoint(b[0], b[1]);
|
|
31459
|
-
if (testSegmentBoundsIntersection(c0, d, bounds)) {
|
|
31460
|
-
finishPartial();
|
|
31461
|
-
}
|
|
31462
|
-
break;
|
|
31463
|
-
}
|
|
31464
|
-
}
|
|
31465
|
-
|
|
31466
|
-
appendPoint(arr, d);
|
|
31467
|
-
}
|
|
31468
|
-
|
|
31469
|
-
function finishPartial() {
|
|
31470
|
-
// Get endpoints of the two polylines, for starting the next partial
|
|
31471
|
-
var leftEP = left[left.length - 1];
|
|
31472
|
-
var centerEP = center[center.length - 1];
|
|
31473
|
-
|
|
31474
|
-
// Make a polygon ring
|
|
31475
|
-
var ring = [];
|
|
31476
|
-
extendArray(ring, left);
|
|
31477
|
-
center.reverse();
|
|
31478
|
-
extendArray(ring, center);
|
|
31479
|
-
ring.push(ring[0]); // close ring
|
|
31480
|
-
partials.push(ring);
|
|
31481
|
-
|
|
31482
|
-
// Start next partial
|
|
31483
|
-
left.push(leftEP);
|
|
31484
|
-
center.push(centerEP);
|
|
31485
|
-
|
|
31486
|
-
// clear bbox
|
|
31487
|
-
// bbox = null;
|
|
31488
|
-
}
|
|
31489
|
-
|
|
31490
|
-
function extendArray(arr, arr2) {
|
|
31491
|
-
arr2.reverse();
|
|
31492
|
-
while(arr2.length > 0) arr.push(arr2.pop());
|
|
31493
|
-
}
|
|
31494
|
-
|
|
31495
|
-
return function(path, dist) {
|
|
31496
|
-
// var x0, y0;
|
|
31497
|
-
var x1, y1, x2, y2;
|
|
31498
|
-
var p1, p2;
|
|
31499
|
-
// var firstBearing;
|
|
31500
|
-
var bearing, prevBearing, joinAngle;
|
|
31501
|
-
partials = [];
|
|
31502
|
-
left = [];
|
|
31503
|
-
center = [];
|
|
31504
|
-
pathIter.init(path);
|
|
31505
|
-
|
|
31506
|
-
// if (pathIter.hasNext()) {
|
|
31507
|
-
// x0 = x2 = pathIter.x;
|
|
31508
|
-
// y0 = y2 = pathIter.y;
|
|
31509
|
-
// }
|
|
31510
|
-
while (pathIter.hasNext()) {
|
|
31511
|
-
// TODO: use a tolerance
|
|
31512
|
-
if (pathIter.x === x2 && pathIter.y === y2) continue; // skip duplicate points
|
|
31513
|
-
x1 = x2;
|
|
31514
|
-
y1 = y2;
|
|
31515
|
-
x2 = pathIter.x;
|
|
31516
|
-
y2 = pathIter.y;
|
|
31517
|
-
|
|
31518
|
-
prevBearing = bearing;
|
|
31519
|
-
bearing = getBearing(x1, y1, x2, y2);
|
|
31520
|
-
// shift original polyline segment to the left by buffer distance
|
|
31521
|
-
p1 = geod(x1, y1, bearing - 90, dist);
|
|
31522
|
-
p2 = geod(x2, y2, bearing - 90, dist);
|
|
31523
|
-
|
|
31524
|
-
if (center.length === 0) {
|
|
31525
|
-
// first loop, second point in this partial
|
|
31526
|
-
// if (partials.length === 0) {
|
|
31527
|
-
// firstBearing = bearing;
|
|
31528
|
-
// }
|
|
31529
|
-
left.push(p1, p2);
|
|
31530
|
-
center.push([x1, y1], [x2, y2]);
|
|
31531
|
-
} else {
|
|
31532
|
-
//
|
|
31533
|
-
joinAngle = getJoinAngle(prevBearing, bearing);
|
|
31534
|
-
if (veryCloseToPrevPoint(left, p1[0], p1[1])) {
|
|
31535
|
-
// skip first point
|
|
31536
|
-
addBufferVertex(p2);
|
|
31537
|
-
} else if (joinAngle > 0) {
|
|
31538
|
-
addRoundJoin(x1, y1, prevBearing - 90, joinAngle, dist);
|
|
31539
|
-
addBufferVertex(p1);
|
|
31540
|
-
addBufferVertex(p2);
|
|
31541
|
-
} else {
|
|
31542
|
-
addBufferVertex(p1);
|
|
31543
|
-
addBufferVertex(p2);
|
|
31544
|
-
}
|
|
31545
|
-
center.push([x2, y2]);
|
|
31546
|
-
}
|
|
31547
|
-
}
|
|
31548
|
-
|
|
31549
|
-
if (center.length > 1) {
|
|
31550
|
-
finishPartial();
|
|
31551
|
-
}
|
|
31552
|
-
// TODO: handle defective polylines
|
|
31553
|
-
|
|
31554
|
-
// if (x2 == x0 && y2 == y0) {
|
|
31555
|
-
// // add join to finish closed path
|
|
31556
|
-
// joinAngle = getJoinAngle(bearing, firstBearing);
|
|
31557
|
-
// if (joinAngle > 0) {
|
|
31558
|
-
// addRoundJoin(leftpart, x2, y2, bearing - 90, joinAngle, dist);
|
|
31559
|
-
// }
|
|
31560
|
-
// } else {
|
|
31561
|
-
// // add a cap to finish open path
|
|
31562
|
-
// leftpart.push.apply(leftpart, makeCap(x2, y2, bearing, dist));
|
|
31563
|
-
// }
|
|
31564
|
-
|
|
31565
|
-
return partials;
|
|
31566
|
-
};
|
|
31567
|
-
}
|
|
31568
|
-
|
|
31569
31918
|
// GeographicLib docs: https://geographiclib.sourceforge.io/html/js/
|
|
31570
31919
|
// https://geographiclib.sourceforge.io/html/js/module-GeographicLib_Geodesic.Geodesic.html
|
|
31571
31920
|
// https://geographiclib.sourceforge.io/html/js/tutorial-2-interface.html
|
|
@@ -31662,30 +32011,43 @@ ${svg}
|
|
|
31662
32011
|
});
|
|
31663
32012
|
|
|
31664
32013
|
function makePolylineBuffer(lyr, dataset, opts) {
|
|
32014
|
+
time('buffer');
|
|
31665
32015
|
var geojson = makeShapeBufferGeoJSON(lyr, dataset, opts);
|
|
31666
32016
|
var dataset2 = importGeoJSON(geojson, {});
|
|
31667
|
-
|
|
32017
|
+
if (!opts.debug_points) {
|
|
32018
|
+
dissolveBufferDataset(dataset2, opts);
|
|
32019
|
+
}
|
|
32020
|
+
timeEnd('buffer');
|
|
31668
32021
|
return dataset2;
|
|
31669
32022
|
}
|
|
31670
32023
|
|
|
31671
32024
|
function makeShapeBufferGeoJSON(lyr, dataset, opts) {
|
|
31672
32025
|
var distanceFn = getBufferDistanceFunction(lyr, dataset, opts);
|
|
31673
|
-
getBufferToleranceFunction(dataset, opts);
|
|
32026
|
+
// var toleranceFn = getBufferToleranceFunction(dataset, opts);
|
|
31674
32027
|
var geod = getFastGeodeticSegmentFunction(getDatasetCRS(dataset));
|
|
31675
32028
|
var getBearing = getBearingFunction(dataset);
|
|
31676
32029
|
var makerOpts = Object.assign({geometry_type: lyr.geometry_type}, opts);
|
|
31677
|
-
var
|
|
31678
|
-
|
|
31679
|
-
|
|
31680
|
-
|
|
31681
|
-
|
|
31682
|
-
|
|
31683
|
-
|
|
31684
|
-
|
|
32030
|
+
var makeShapeBuffer =
|
|
32031
|
+
opts.v2 && getPolylineBufferMaker$1(dataset.arcs, geod, getBearing, makerOpts) ||
|
|
32032
|
+
opts.v3 && getPolylineBufferMaker$2(dataset.arcs, geod, getBearing, makerOpts) ||
|
|
32033
|
+
getPolylineBufferMaker(dataset.arcs, geod, getBearing, makerOpts);
|
|
32034
|
+
// var records = lyr.data ? lyr.data.getRecords() : null;
|
|
32035
|
+
var arr = lyr.shapes.reduce(function(memo, shape, i) {
|
|
32036
|
+
var distance = distanceFn(i);
|
|
32037
|
+
if (!distance || !shape) return memo;
|
|
32038
|
+
// retn might be an array of features or a single feature
|
|
32039
|
+
var retn = makeShapeBuffer(shape, distance);
|
|
32040
|
+
return memo.concat(retn);
|
|
32041
|
+
}, []);
|
|
32042
|
+
var geojsonType = arr?.[0].type;
|
|
31685
32043
|
// TODO: make sure that importer supports null geometries (not standard GeoJSON);
|
|
31686
|
-
|
|
32044
|
+
// console.log(arr)
|
|
32045
|
+
return geojsonType == 'Feature' ? {
|
|
32046
|
+
type: 'FeatureCollection',
|
|
32047
|
+
features: arr
|
|
32048
|
+
} : {
|
|
31687
32049
|
type: 'GeometryCollection',
|
|
31688
|
-
geometries:
|
|
32050
|
+
geometries: arr
|
|
31689
32051
|
};
|
|
31690
32052
|
}
|
|
31691
32053
|
|
|
@@ -32137,8 +32499,7 @@ ${svg}
|
|
|
32137
32499
|
stop("Unsupported geometry type");
|
|
32138
32500
|
}
|
|
32139
32501
|
|
|
32140
|
-
|
|
32141
|
-
return [lyr2];
|
|
32502
|
+
return mergeOutputLayersIntoDataset(lyr, dataset, dataset2, opts);
|
|
32142
32503
|
}
|
|
32143
32504
|
|
|
32144
32505
|
// currently undocumented, used in tests
|
|
@@ -36789,9 +37150,6 @@ ${svg}
|
|
|
36789
37150
|
getLayerInfo: getLayerInfo
|
|
36790
37151
|
});
|
|
36791
37152
|
|
|
36792
|
-
// import { importGeoJSON } from '../geojson/geojson-import';
|
|
36793
|
-
|
|
36794
|
-
|
|
36795
37153
|
function addTargetProxies(targets, ctx) {
|
|
36796
37154
|
if (targets && targets.length > 0) {
|
|
36797
37155
|
var proxies = expandCommandTargets(targets).reduce(function(memo, target) {
|
|
@@ -36814,11 +37172,19 @@ ${svg}
|
|
|
36814
37172
|
var proxy = getLayerInfo(target.layer, target.dataset); // layer_name, feature_count etc
|
|
36815
37173
|
proxy.layer = target.layer;
|
|
36816
37174
|
proxy.dataset = target.dataset;
|
|
36817
|
-
|
|
36818
|
-
|
|
36819
|
-
|
|
37175
|
+
|
|
37176
|
+
Object.defineProperty(proxy, 'geojson', {
|
|
37177
|
+
set: setGeoJSON,
|
|
37178
|
+
get: getGeoJSON
|
|
36820
37179
|
});
|
|
36821
37180
|
|
|
37181
|
+
function setGeoJSON(o) {
|
|
37182
|
+
var dataset2 = importGeoJSON(o);
|
|
37183
|
+
var lyr2 = dataset2.layers[0];
|
|
37184
|
+
lyr2.name = target.layer.name;
|
|
37185
|
+
replaceLayerContents(target.layer, target.dataset, dataset2);
|
|
37186
|
+
}
|
|
37187
|
+
|
|
36822
37188
|
function getGeoJSON() {
|
|
36823
37189
|
var features = exportLayerAsGeoJSON(target.layer, target.dataset, {rfc7946: true}, true);
|
|
36824
37190
|
return {
|
|
@@ -41337,7 +41703,7 @@ ${svg}
|
|
|
41337
41703
|
function findPathMidpoint(path, arcs, useNearestVertex) {
|
|
41338
41704
|
var halfLen = calcPathLen(path, arcs, false) / 2;
|
|
41339
41705
|
var partialLen = 0;
|
|
41340
|
-
var p;
|
|
41706
|
+
var p = null;
|
|
41341
41707
|
forEachSegmentInPath(path, arcs, function(i, j, xx, yy) {
|
|
41342
41708
|
var a = xx[i],
|
|
41343
41709
|
b = yy[i],
|
|
@@ -41357,7 +41723,7 @@ ${svg}
|
|
|
41357
41723
|
partialLen += segLen;
|
|
41358
41724
|
});
|
|
41359
41725
|
if (!p) {
|
|
41360
|
-
|
|
41726
|
+
debug('[findPathMidpoint()] defective path:', path);
|
|
41361
41727
|
}
|
|
41362
41728
|
return p;
|
|
41363
41729
|
}
|
|
@@ -46293,7 +46659,7 @@ ${svg}
|
|
|
46293
46659
|
});
|
|
46294
46660
|
}
|
|
46295
46661
|
|
|
46296
|
-
var version = "0.6.
|
|
46662
|
+
var version = "0.6.112";
|
|
46297
46663
|
|
|
46298
46664
|
// Parse command line args into commands and run them
|
|
46299
46665
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -46896,67 +47262,6 @@ ${svg}
|
|
|
46896
47262
|
return false;
|
|
46897
47263
|
}
|
|
46898
47264
|
|
|
46899
|
-
// TODO: Need to rethink polygon repair: these function can cause problems
|
|
46900
|
-
// when part of a self-intersecting polygon is removed
|
|
46901
|
-
//
|
|
46902
|
-
function repairPolygonGeometry(layers, dataset, opts) {
|
|
46903
|
-
var nodes = addIntersectionCuts(dataset);
|
|
46904
|
-
layers.forEach(function(lyr) {
|
|
46905
|
-
repairSelfIntersections(lyr, nodes);
|
|
46906
|
-
});
|
|
46907
|
-
return layers;
|
|
46908
|
-
}
|
|
46909
|
-
|
|
46910
|
-
// Remove any small shapes formed by twists in each ring
|
|
46911
|
-
// // OOPS, NO // Retain only the part with largest area
|
|
46912
|
-
// // this causes problems when a cut-off hole has a matching ring in another polygon
|
|
46913
|
-
// TODO: consider cases where cut-off parts should be retained
|
|
46914
|
-
//
|
|
46915
|
-
function repairSelfIntersections(lyr, nodes) {
|
|
46916
|
-
var splitter = getSelfIntersectionSplitter(nodes);
|
|
46917
|
-
|
|
46918
|
-
lyr.shapes = lyr.shapes.map(function(shp, i) {
|
|
46919
|
-
return cleanPolygon(shp);
|
|
46920
|
-
});
|
|
46921
|
-
|
|
46922
|
-
function cleanPolygon(shp) {
|
|
46923
|
-
var cleanedPolygon = [];
|
|
46924
|
-
forEachShapePart(shp, function(ids) {
|
|
46925
|
-
// TODO: consider returning null if path can't be split
|
|
46926
|
-
var splitIds = splitter(ids);
|
|
46927
|
-
if (splitIds.length === 0) {
|
|
46928
|
-
error("[cleanPolygon()] Defective path:", ids);
|
|
46929
|
-
} else if (splitIds.length == 1) {
|
|
46930
|
-
cleanedPolygon.push(splitIds[0]);
|
|
46931
|
-
} else {
|
|
46932
|
-
var shapeArea = geom.getPlanarPathArea(ids, nodes.arcs),
|
|
46933
|
-
sign = shapeArea > 0 ? 1 : -1,
|
|
46934
|
-
mainRing;
|
|
46935
|
-
|
|
46936
|
-
splitIds.reduce(function(max, ringIds, i) {
|
|
46937
|
-
var pathArea = geom.getPlanarPathArea(ringIds, nodes.arcs) * sign;
|
|
46938
|
-
if (pathArea > max) {
|
|
46939
|
-
mainRing = ringIds;
|
|
46940
|
-
max = pathArea;
|
|
46941
|
-
}
|
|
46942
|
-
return max;
|
|
46943
|
-
}, 0);
|
|
46944
|
-
|
|
46945
|
-
if (mainRing) {
|
|
46946
|
-
cleanedPolygon.push(mainRing);
|
|
46947
|
-
}
|
|
46948
|
-
}
|
|
46949
|
-
});
|
|
46950
|
-
return cleanedPolygon.length > 0 ? cleanedPolygon : null;
|
|
46951
|
-
}
|
|
46952
|
-
}
|
|
46953
|
-
|
|
46954
|
-
var PolygonRepair = /*#__PURE__*/Object.freeze({
|
|
46955
|
-
__proto__: null,
|
|
46956
|
-
repairPolygonGeometry: repairPolygonGeometry,
|
|
46957
|
-
repairSelfIntersections: repairSelfIntersections
|
|
46958
|
-
});
|
|
46959
|
-
|
|
46960
47265
|
// Attach functions exported by modules to the "internal" object,
|
|
46961
47266
|
// so they can be run by tests and by the GUI.
|
|
46962
47267
|
// TODO: rewrite tests to import functions directly from modules,
|
|
@@ -47003,7 +47308,7 @@ ${svg}
|
|
|
47003
47308
|
ArcUtils,
|
|
47004
47309
|
Bbox2Clipping,
|
|
47005
47310
|
BinArray$1,
|
|
47006
|
-
BufferCommon,
|
|
47311
|
+
// BufferCommon,
|
|
47007
47312
|
Calc,
|
|
47008
47313
|
CalcUtils,
|
|
47009
47314
|
Catalog$1,
|
|
@@ -47058,7 +47363,7 @@ ${svg}
|
|
|
47058
47363
|
OverlayUtils,
|
|
47059
47364
|
Pack, Unpack,
|
|
47060
47365
|
ParseCommands,
|
|
47061
|
-
PathBuffer,
|
|
47366
|
+
// PathBuffer,
|
|
47062
47367
|
PathEndpoints,
|
|
47063
47368
|
PathExport,
|
|
47064
47369
|
Pathfinder,
|