mapshaper 0.6.111 → 0.6.113
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 +1124 -630
- package/package.json +3 -2
- package/www/basemap.js +6 -4
- package/www/elements.css +1 -1
- package/www/images/eye3.png +0 -0
- package/www/index.html +27 -8
- package/www/mapshaper-gui.js +276 -62
- package/www/mapshaper.js +1124 -630
- package/www/modules.js +147 -0
- package/www/page.css +71 -34
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,
|
|
@@ -2088,13 +2107,13 @@
|
|
|
2088
2107
|
|
|
2089
2108
|
// TODO: remove this constant, use actual data from dataset CRS,
|
|
2090
2109
|
// also consider using ellipsoidal formulas where greater accuracy might be important.
|
|
2091
|
-
var R$
|
|
2110
|
+
var R$2 = WGS84.SEMIMAJOR_AXIS;
|
|
2092
2111
|
var D2R = Math.PI / 180;
|
|
2093
2112
|
var R2D = 180 / Math.PI;
|
|
2094
2113
|
|
|
2095
2114
|
// Equirectangular projection
|
|
2096
2115
|
function degreesToMeters(deg) {
|
|
2097
|
-
return deg * D2R * R$
|
|
2116
|
+
return deg * D2R * R$2;
|
|
2098
2117
|
}
|
|
2099
2118
|
|
|
2100
2119
|
function distance3D(ax, ay, az, bx, by, bz) {
|
|
@@ -2191,7 +2210,8 @@
|
|
|
2191
2210
|
lat1 *= D2R;
|
|
2192
2211
|
lat2 *= D2R;
|
|
2193
2212
|
var y = Math.sin(lng2-lng1) * Math.cos(lat2),
|
|
2194
|
-
x = Math.cos(lat1)*Math.sin(lat2) -
|
|
2213
|
+
x = Math.cos(lat1) * Math.sin(lat2) -
|
|
2214
|
+
Math.sin(lat1) * Math.cos(lat2) * Math.cos(lng2-lng1);
|
|
2195
2215
|
return Math.atan2(y, x);
|
|
2196
2216
|
}
|
|
2197
2217
|
|
|
@@ -2251,9 +2271,9 @@
|
|
|
2251
2271
|
lng *= D2R;
|
|
2252
2272
|
lat *= D2R;
|
|
2253
2273
|
cosLat = Math.cos(lat);
|
|
2254
|
-
p[0] = Math.cos(lng) * cosLat * R$
|
|
2255
|
-
p[1] = Math.sin(lng) * cosLat * R$
|
|
2256
|
-
p[2] = Math.sin(lat) * R$
|
|
2274
|
+
p[0] = Math.cos(lng) * cosLat * R$2;
|
|
2275
|
+
p[1] = Math.sin(lng) * cosLat * R$2;
|
|
2276
|
+
p[2] = Math.sin(lat) * R$2;
|
|
2257
2277
|
}
|
|
2258
2278
|
|
|
2259
2279
|
// Haversine formula (well conditioned at small distances)
|
|
@@ -2272,7 +2292,7 @@
|
|
|
2272
2292
|
function greatCircleDistance(lng1, lat1, lng2, lat2) {
|
|
2273
2293
|
var D2R = Math.PI / 180,
|
|
2274
2294
|
dist = sphericalDistance(lng1 * D2R, lat1 * D2R, lng2 * D2R, lat2 * D2R);
|
|
2275
|
-
return dist * R$
|
|
2295
|
+
return dist * R$2;
|
|
2276
2296
|
}
|
|
2277
2297
|
|
|
2278
2298
|
|
|
@@ -2370,7 +2390,7 @@
|
|
|
2370
2390
|
var Geom = /*#__PURE__*/Object.freeze({
|
|
2371
2391
|
__proto__: null,
|
|
2372
2392
|
D2R: D2R,
|
|
2373
|
-
R: R$
|
|
2393
|
+
R: R$2,
|
|
2374
2394
|
R2D: R2D,
|
|
2375
2395
|
bearing: bearing,
|
|
2376
2396
|
bearing2D: bearing2D,
|
|
@@ -3888,10 +3908,12 @@
|
|
|
3888
3908
|
for (var i=0; i<n; i++) {
|
|
3889
3909
|
retn = cb(parts[i], i, parts, shpId);
|
|
3890
3910
|
if (retn === null) {
|
|
3891
|
-
nulls++;
|
|
3892
3911
|
parts[i] = null;
|
|
3893
3912
|
} else if (utils.isArray(retn)) {
|
|
3894
|
-
parts[i] = retn;
|
|
3913
|
+
parts[i] = retn.length > 0 ? retn : null;
|
|
3914
|
+
}
|
|
3915
|
+
if (parts[i] === null) {
|
|
3916
|
+
nulls++;
|
|
3895
3917
|
}
|
|
3896
3918
|
}
|
|
3897
3919
|
if (nulls == n) {
|
|
@@ -7539,7 +7561,15 @@
|
|
|
7539
7561
|
}
|
|
7540
7562
|
|
|
7541
7563
|
function mergeOutputLayerIntoDataset(lyr, dataset, dataset2, opts) {
|
|
7542
|
-
|
|
7564
|
+
var output = mergeOutputLayersIntoDataset(lyr, dataset, dataset2, opts);
|
|
7565
|
+
if (output.length != 1) {
|
|
7566
|
+
error('Expected 1 output layer, received:', output.length);
|
|
7567
|
+
}
|
|
7568
|
+
return output[0];
|
|
7569
|
+
}
|
|
7570
|
+
|
|
7571
|
+
function mergeOutputLayersIntoDataset(lyr, dataset, dataset2, opts) {
|
|
7572
|
+
if (!dataset2 || dataset2.layers.length === 0) {
|
|
7543
7573
|
error('Invalid source dataset');
|
|
7544
7574
|
}
|
|
7545
7575
|
if (dataset.layers.includes(lyr) === false) {
|
|
@@ -7548,24 +7578,21 @@
|
|
|
7548
7578
|
// this command returns merged layers instead of adding them to target dataset
|
|
7549
7579
|
var outputLayers = mergeDatasetsIntoDataset(dataset, [dataset2]);
|
|
7550
7580
|
var lyr2 = outputLayers[0];
|
|
7551
|
-
|
|
7552
7581
|
// TODO: find a more reliable way of knowing when to copy data
|
|
7553
7582
|
var copyData = !lyr2.data && lyr.data && getFeatureCount(lyr2) == lyr.data.size();
|
|
7554
|
-
|
|
7555
7583
|
if (copyData) {
|
|
7556
7584
|
lyr2.data = opts.no_replace ? lyr.data.clone() : lyr.data;
|
|
7557
7585
|
}
|
|
7558
|
-
|
|
7559
|
-
|
|
7586
|
+
lyr2.name = opts.name || lyr.name;
|
|
7587
|
+
if (!opts.no_replace) {
|
|
7588
|
+
outputLayers[0] = Object.assign(lyr, {data: null, shapes: null}, lyr2);
|
|
7560
7589
|
if (layerHasPaths(lyr)) {
|
|
7561
7590
|
// Remove unused arcs from replaced layer
|
|
7562
7591
|
// TODO: consider using clean insead of this
|
|
7563
7592
|
dissolveArcs(dataset);
|
|
7564
7593
|
}
|
|
7565
7594
|
}
|
|
7566
|
-
|
|
7567
|
-
lyr2.name = opts.name || lyr2.name;
|
|
7568
|
-
return lyr2;
|
|
7595
|
+
return outputLayers;
|
|
7569
7596
|
}
|
|
7570
7597
|
|
|
7571
7598
|
// Transform the points in a dataset in-place; don't clean up corrupted shapes
|
|
@@ -7593,6 +7620,7 @@
|
|
|
7593
7620
|
getDatasetBounds: getDatasetBounds,
|
|
7594
7621
|
mergeDatasetInfo: mergeDatasetInfo,
|
|
7595
7622
|
mergeOutputLayerIntoDataset: mergeOutputLayerIntoDataset,
|
|
7623
|
+
mergeOutputLayersIntoDataset: mergeOutputLayersIntoDataset,
|
|
7596
7624
|
pruneArcs: pruneArcs,
|
|
7597
7625
|
replaceLayerContents: replaceLayerContents,
|
|
7598
7626
|
replaceLayers: replaceLayers,
|
|
@@ -11548,7 +11576,7 @@
|
|
|
11548
11576
|
shapes: lyr.shapes || null,
|
|
11549
11577
|
data: data,
|
|
11550
11578
|
menu_order: lyr.menu_order || null,
|
|
11551
|
-
pinned: lyr.pinned || false,
|
|
11579
|
+
pinned: lyr.pinned || opts.show_all || false,
|
|
11552
11580
|
active: !!(lyr.active || lyr == opts.active_layer) // lyr.active: deprecated
|
|
11553
11581
|
};
|
|
11554
11582
|
}
|
|
@@ -16774,7 +16802,6 @@
|
|
|
16774
16802
|
|
|
16775
16803
|
function cleanPath(path, arcs) {
|
|
16776
16804
|
var nulls = 0;
|
|
16777
|
-
// console.log("[cleanPath()]", path, path?.[0])
|
|
16778
16805
|
for (var i=0, n=path.length; i<n; i++) {
|
|
16779
16806
|
if (arcs.arcIsDegenerate(path[i])) {
|
|
16780
16807
|
nulls++;
|
|
@@ -16922,47 +16949,6 @@
|
|
|
16922
16949
|
splitPathByIds: splitPathByIds
|
|
16923
16950
|
});
|
|
16924
16951
|
|
|
16925
|
-
// TODO: also delete positive-space rings nested inside holes
|
|
16926
|
-
function deleteHoles(lyr, arcs) {
|
|
16927
|
-
editShapes(lyr.shapes, function(path) {
|
|
16928
|
-
if (geom.getPathArea(path, arcs) <= 0) {
|
|
16929
|
-
return null; // null deletes the path
|
|
16930
|
-
}
|
|
16931
|
-
});
|
|
16932
|
-
}
|
|
16933
|
-
|
|
16934
|
-
// Returns a function that separates rings in a polygon into space-enclosing rings
|
|
16935
|
-
// and holes. Also fixes self-intersections.
|
|
16936
|
-
//
|
|
16937
|
-
function getHoleDivider(nodes, spherical) {
|
|
16938
|
-
var split = getSelfIntersectionSplitter(nodes);
|
|
16939
|
-
// var split = internal.getSelfIntersectionSplitter_v1(nodes); console.log('split')
|
|
16940
|
-
|
|
16941
|
-
return function(rings, cw, ccw) {
|
|
16942
|
-
var pathArea = spherical ? geom.getSphericalPathArea : geom.getPlanarPathArea;
|
|
16943
|
-
forEachShapePart(rings, function(ringIds) {
|
|
16944
|
-
var splitRings = split(ringIds);
|
|
16945
|
-
if (splitRings.length === 0) {
|
|
16946
|
-
debug("[getRingDivider()] Defective path:", ringIds);
|
|
16947
|
-
}
|
|
16948
|
-
splitRings.forEach(function(ringIds, i) {
|
|
16949
|
-
var ringArea = pathArea(ringIds, nodes.arcs);
|
|
16950
|
-
if (ringArea > 0) {
|
|
16951
|
-
cw.push(ringIds);
|
|
16952
|
-
} else if (ringArea < 0) {
|
|
16953
|
-
ccw.push(ringIds);
|
|
16954
|
-
}
|
|
16955
|
-
});
|
|
16956
|
-
});
|
|
16957
|
-
};
|
|
16958
|
-
}
|
|
16959
|
-
|
|
16960
|
-
var PolygonHoles = /*#__PURE__*/Object.freeze({
|
|
16961
|
-
__proto__: null,
|
|
16962
|
-
deleteHoles: deleteHoles,
|
|
16963
|
-
getHoleDivider: getHoleDivider
|
|
16964
|
-
});
|
|
16965
|
-
|
|
16966
16952
|
// Remap any references to duplicate arcs in paths to use the same arcs
|
|
16967
16953
|
// Remove any unused arcs from the dataset's ArcCollection.
|
|
16968
16954
|
// Return a NodeCollection
|
|
@@ -17567,6 +17553,47 @@
|
|
|
17567
17553
|
return idx;
|
|
17568
17554
|
}
|
|
17569
17555
|
|
|
17556
|
+
// TODO: also delete positive-space rings nested inside holes
|
|
17557
|
+
function deleteHoles(lyr, arcs) {
|
|
17558
|
+
editShapes(lyr.shapes, function(path) {
|
|
17559
|
+
if (geom.getPathArea(path, arcs) <= 0) {
|
|
17560
|
+
return null; // null deletes the path
|
|
17561
|
+
}
|
|
17562
|
+
});
|
|
17563
|
+
}
|
|
17564
|
+
|
|
17565
|
+
// Returns a function that separates rings in a polygon into space-enclosing rings
|
|
17566
|
+
// and holes. Also fixes self-intersections.
|
|
17567
|
+
//
|
|
17568
|
+
function getHoleDivider(nodes, spherical) {
|
|
17569
|
+
var split = getSelfIntersectionSplitter(nodes);
|
|
17570
|
+
// var split = internal.getSelfIntersectionSplitter_v1(nodes); console.log('split')
|
|
17571
|
+
|
|
17572
|
+
return function(rings, cw, ccw) {
|
|
17573
|
+
var pathArea = spherical ? geom.getSphericalPathArea : geom.getPlanarPathArea;
|
|
17574
|
+
forEachShapePart(rings, function(ringIds) {
|
|
17575
|
+
var splitRings = split(ringIds);
|
|
17576
|
+
if (splitRings.length === 0) {
|
|
17577
|
+
debug("[getRingDivider()] Defective path:", ringIds);
|
|
17578
|
+
}
|
|
17579
|
+
splitRings.forEach(function(ringIds, i) {
|
|
17580
|
+
var ringArea = pathArea(ringIds, nodes.arcs);
|
|
17581
|
+
if (ringArea > 0) {
|
|
17582
|
+
cw.push(ringIds);
|
|
17583
|
+
} else if (ringArea < 0) {
|
|
17584
|
+
ccw.push(ringIds);
|
|
17585
|
+
}
|
|
17586
|
+
});
|
|
17587
|
+
});
|
|
17588
|
+
};
|
|
17589
|
+
}
|
|
17590
|
+
|
|
17591
|
+
var PolygonHoles = /*#__PURE__*/Object.freeze({
|
|
17592
|
+
__proto__: null,
|
|
17593
|
+
deleteHoles: deleteHoles,
|
|
17594
|
+
getHoleDivider: getHoleDivider
|
|
17595
|
+
});
|
|
17596
|
+
|
|
17570
17597
|
// Associate mosaic tiles with shapes (i.e. identify the groups of tiles that
|
|
17571
17598
|
// belong to each shape)
|
|
17572
17599
|
//
|
|
@@ -17675,7 +17702,6 @@
|
|
|
17675
17702
|
function MosaicIndex(lyr, nodes, optsArg) {
|
|
17676
17703
|
var opts = optsArg || {};
|
|
17677
17704
|
var shapes = lyr.shapes;
|
|
17678
|
-
getHoleDivider(nodes);
|
|
17679
17705
|
var mosaic = buildPolygonMosaic(nodes).mosaic;
|
|
17680
17706
|
// map arc ids to tile ids
|
|
17681
17707
|
var arcTileIndex = new ShapeArcIndex(mosaic, nodes.arcs);
|
|
@@ -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', {
|
|
24784
24829
|
type: 'flag'
|
|
24785
24830
|
})
|
|
24786
|
-
.option('debug-
|
|
24831
|
+
.option('debug-offset', {
|
|
24787
24832
|
type: 'flag'
|
|
24788
24833
|
})
|
|
24834
|
+
.option('debug-winding', {
|
|
24835
|
+
type: 'flag'
|
|
24836
|
+
})
|
|
24837
|
+
.option('debug-points', {
|
|
24838
|
+
type: 'flag'
|
|
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,15 +30985,120 @@ ${svg}
|
|
|
30925
30985
|
return dataset;
|
|
30926
30986
|
}
|
|
30927
30987
|
|
|
30928
|
-
function
|
|
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
|
+
|
|
31079
|
+
function dissolveBufferDataset2(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});
|
|
30936
31087
|
var mosaicIndex = new MosaicIndex(lyr, nodes, {flat: false, no_holes: false});
|
|
31088
|
+
|
|
31089
|
+
// rewindPolygonParts(lyr, nodes);
|
|
31090
|
+
lyr.shapes = lyr.shapes.map(function(shp, i) {
|
|
31091
|
+
var tiles = mosaicIndex.getTilesByShapeIds([i]);
|
|
31092
|
+
if (!tiles.length) return null;
|
|
31093
|
+
return tiles.reduce(function(memo, tile) {
|
|
31094
|
+
return memo.concat(tile);
|
|
31095
|
+
}, []);
|
|
31096
|
+
});
|
|
31097
|
+
|
|
31098
|
+
if (opts.debug_winding) {
|
|
31099
|
+
return;
|
|
31100
|
+
}
|
|
31101
|
+
|
|
30937
31102
|
if (opts.debug_mosaic) {
|
|
30938
31103
|
tmp = composeMosaicLayer(lyr, mosaicIndex.mosaic);
|
|
30939
31104
|
lyr.shapes = tmp.shapes;
|
|
@@ -30955,47 +31120,46 @@ ${svg}
|
|
|
30955
31120
|
}
|
|
30956
31121
|
}
|
|
30957
31122
|
|
|
30958
|
-
|
|
30959
|
-
|
|
30960
|
-
|
|
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);
|
|
31123
|
+
// TODO: try geodesic option
|
|
31124
|
+
function getIntersectionFunction(crs) {
|
|
31125
|
+
return bufferIntersection$2;
|
|
30985
31126
|
}
|
|
30986
31127
|
|
|
30987
|
-
function
|
|
30988
|
-
|
|
30989
|
-
|
|
30990
|
-
|
|
30991
|
-
|
|
30992
|
-
|
|
30993
|
-
|
|
30994
|
-
|
|
30995
|
-
|
|
31128
|
+
function dissolveBufferDataset(dataset, optsArg) {
|
|
31129
|
+
var opts = {};
|
|
31130
|
+
var lyr = dataset.layers[0];
|
|
31131
|
+
var tmp;
|
|
31132
|
+
if (opts.debug_offset) {
|
|
31133
|
+
return; // raw offset path
|
|
31134
|
+
}
|
|
31135
|
+
var nodes = addIntersectionCuts(dataset, {rebuild_topology: true});
|
|
31136
|
+
if (opts.debug_winding) {
|
|
31137
|
+
rewindPolygonParts(lyr, nodes);
|
|
31138
|
+
// debugRingsAndHoles(lyr, nodes);
|
|
31139
|
+
return;
|
|
31140
|
+
}
|
|
31141
|
+
rewindPolygonParts(lyr, nodes);
|
|
30996
31142
|
|
|
30997
|
-
|
|
30998
|
-
|
|
31143
|
+
var mosaicIndex = new MosaicIndex(lyr, nodes, {flat: false, no_holes: false});
|
|
31144
|
+
if (opts.debug_mosaic) {
|
|
31145
|
+
tmp = composeMosaicLayer(lyr, mosaicIndex.mosaic);
|
|
31146
|
+
lyr.shapes = tmp.shapes;
|
|
31147
|
+
lyr.data = tmp.data;
|
|
31148
|
+
return;
|
|
31149
|
+
}
|
|
31150
|
+
var pathfind = getRingIntersector(mosaicIndex.nodes);
|
|
31151
|
+
var shapes2 = lyr.shapes.map(function(shp, shapeId) {
|
|
31152
|
+
var tiles = mosaicIndex.getTilesByShapeIds([shapeId]);
|
|
31153
|
+
var rings = [];
|
|
31154
|
+
for (var i=0; i<tiles.length; i++) {
|
|
31155
|
+
rings.push(tiles[i][0]);
|
|
31156
|
+
}
|
|
31157
|
+
return pathfind(rings, 'dissolve');
|
|
31158
|
+
});
|
|
31159
|
+
lyr.shapes = shapes2;
|
|
31160
|
+
if (!opts.no_dissolve) {
|
|
31161
|
+
dissolveArcs(dataset);
|
|
31162
|
+
}
|
|
30999
31163
|
}
|
|
31000
31164
|
|
|
31001
31165
|
// return constant distance in meters, or return null if unparsable
|
|
@@ -31005,16 +31169,6 @@ ${svg}
|
|
|
31005
31169
|
return convertDistanceParam(str, crs) || null;
|
|
31006
31170
|
}
|
|
31007
31171
|
|
|
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
31172
|
function getBufferDistanceFunction(lyr, dataset, opts) {
|
|
31019
31173
|
if (!opts.radius) {
|
|
31020
31174
|
stop('Missing expected radius parameter');
|
|
@@ -31033,110 +31187,536 @@ ${svg}
|
|
|
31033
31187
|
};
|
|
31034
31188
|
}
|
|
31035
31189
|
|
|
31036
|
-
|
|
31190
|
+
|
|
31191
|
+
function bufferIntersection$2(a, b, c, d) {
|
|
31192
|
+
return bufferIntersection2(a[0], a[1], b[0], b[1], c[0], c[1], d[0], d[1]);
|
|
31193
|
+
}
|
|
31194
|
+
|
|
31195
|
+
// Exclude segments with non-intersecting bounding boxes before
|
|
31196
|
+
// calling intersection function
|
|
31197
|
+
// Possibly slightly faster than direct call... not worth it?
|
|
31198
|
+
function bufferIntersection2(ax, ay, bx, by, cx, cy, dx, dy) {
|
|
31199
|
+
if (ax < cx && ax < dx && bx < cx && bx < dx ||
|
|
31200
|
+
ax > cx && ax > dx && bx > cx && bx > dx ||
|
|
31201
|
+
ay < cy && ay < dy && by < cy && by < dy ||
|
|
31202
|
+
ay > cy && ay > dy && by > cy && by > dy) return null;
|
|
31203
|
+
return segmentIntersection(ax, ay, bx, by, cx, cy, dx, dy);
|
|
31204
|
+
}
|
|
31205
|
+
|
|
31206
|
+
function BufferBuilder(bufferIntersection, opts) {
|
|
31207
|
+
var self = {};
|
|
31208
|
+
var buffer, path, insideFlags;
|
|
31209
|
+
var points = [];
|
|
31210
|
+
var backtrackSteps = opts.backtrack >= 0 ? opts.backtrack : 100;
|
|
31211
|
+
var backtrackStopIdx = 0; // lowest vertex id in backtrack pass
|
|
31212
|
+
|
|
31213
|
+
init();
|
|
31214
|
+
|
|
31215
|
+
function init() {
|
|
31216
|
+
buffer = [];
|
|
31217
|
+
path = [];
|
|
31218
|
+
insideFlags = [];
|
|
31219
|
+
backtrackStopIdx = 0;
|
|
31220
|
+
}
|
|
31221
|
+
|
|
31222
|
+
self.size = function() {
|
|
31223
|
+
return path.length + buffer.length;
|
|
31224
|
+
};
|
|
31225
|
+
|
|
31226
|
+
self.getDebugPoints = function() {
|
|
31227
|
+
var tmp = points;
|
|
31228
|
+
points = [];
|
|
31229
|
+
return tmp;
|
|
31230
|
+
};
|
|
31231
|
+
|
|
31232
|
+
function makeDebugPoints() {
|
|
31233
|
+
points = points.concat(buffer.map((p, i) => {
|
|
31234
|
+
return {
|
|
31235
|
+
type: 'Feature',
|
|
31236
|
+
geometry: {
|
|
31237
|
+
type: 'Point',
|
|
31238
|
+
coordinates: p
|
|
31239
|
+
},
|
|
31240
|
+
properties: {
|
|
31241
|
+
id: i,
|
|
31242
|
+
inside: insideFlags[i],
|
|
31243
|
+
fill: insideFlags[i] ? 'magenta' : 'dodgerblue'
|
|
31244
|
+
}
|
|
31245
|
+
};
|
|
31246
|
+
}));
|
|
31247
|
+
}
|
|
31248
|
+
|
|
31249
|
+
self.addPathVertex = function(p) {
|
|
31250
|
+
path.push(p);
|
|
31251
|
+
};
|
|
31252
|
+
|
|
31253
|
+
self.addBufferVertices = function(arr) {
|
|
31254
|
+
for (var i=0; i<arr.length; i++) {
|
|
31255
|
+
self.addBufferVertex(arr[i]);
|
|
31256
|
+
}
|
|
31257
|
+
};
|
|
31258
|
+
|
|
31259
|
+
self.addBufferVertex = function(p, isLeftTurn) {
|
|
31260
|
+
var bufferLen = insideFlags.length;
|
|
31261
|
+
var prevIdx = bufferLen - 1;
|
|
31262
|
+
if (isLeftTurn) {
|
|
31263
|
+
// first extruded point after left turn
|
|
31264
|
+
// assume that the point is inside the buffer
|
|
31265
|
+
insideFlags.push(true);
|
|
31266
|
+
buffer.push(p);
|
|
31267
|
+
return;
|
|
31268
|
+
}
|
|
31269
|
+
if (prevIdx == -1) {
|
|
31270
|
+
// first point in buffer
|
|
31271
|
+
buffer.push(p);
|
|
31272
|
+
insideFlags.push(false);
|
|
31273
|
+
return;
|
|
31274
|
+
}
|
|
31275
|
+
var prevP = buffer[prevIdx];
|
|
31276
|
+
var prevFlag = insideFlags[prevIdx];
|
|
31277
|
+
if (pointsAreSame(prevP, p)) {
|
|
31278
|
+
// console.log('SAME POINT')
|
|
31279
|
+
return;
|
|
31280
|
+
}
|
|
31281
|
+
// if no segment cross is detected below, inside/outside stays the same
|
|
31282
|
+
var newFlag = prevFlag;
|
|
31283
|
+
var hit, a, b, flagA, flagB;
|
|
31284
|
+
for (var i=0, idx = bufferLen - 3; i < backtrackSteps && idx >= backtrackStopIdx; i++, idx--) {
|
|
31285
|
+
a = buffer[idx];
|
|
31286
|
+
b = buffer[idx + 1];
|
|
31287
|
+
// TODO: consider using a geodetic intersection function for lat-long datasets
|
|
31288
|
+
// TODO: consider case of an endpoint hit
|
|
31289
|
+
// TODO: consider case of collinear hit
|
|
31290
|
+
hit = bufferIntersection(a, b, prevP, p);
|
|
31291
|
+
if (!hit) {
|
|
31292
|
+
// continue scanning backward for an intersection
|
|
31293
|
+
continue;
|
|
31294
|
+
}
|
|
31295
|
+
|
|
31296
|
+
flagA = insideFlags[idx];
|
|
31297
|
+
flagB = insideFlags[idx + 1];
|
|
31298
|
+
if (flagA && flagB) {
|
|
31299
|
+
// newest segment collides with an interior segment - do not rewind
|
|
31300
|
+
continue;
|
|
31301
|
+
}
|
|
31302
|
+
|
|
31303
|
+
// newest segment collides with an exterior segment
|
|
31304
|
+
// * assume we're going from inside to outside
|
|
31305
|
+
// * and can therefore remove an interior loop
|
|
31306
|
+
// TODO: improve (this assumption does not hold when closing an oxbow type loop)
|
|
31307
|
+
while (buffer.length > idx + 1) {
|
|
31308
|
+
buffer.pop();
|
|
31309
|
+
insideFlags.pop();
|
|
31310
|
+
}
|
|
31311
|
+
buffer.push(hit);
|
|
31312
|
+
insideFlags.push(false);
|
|
31313
|
+
newFlag = false;
|
|
31314
|
+
// console.log("going out")
|
|
31315
|
+
break;
|
|
31316
|
+
}
|
|
31317
|
+
|
|
31318
|
+
buffer.push(p);
|
|
31319
|
+
insideFlags.push(newFlag);
|
|
31320
|
+
};
|
|
31321
|
+
|
|
31322
|
+
self.done = function() {
|
|
31323
|
+
if (opts.debug_points) {
|
|
31324
|
+
makeDebugPoints();
|
|
31325
|
+
}
|
|
31326
|
+
var ring = path.reverse().concat(buffer);
|
|
31327
|
+
if (ring.length < 3) {
|
|
31328
|
+
error('Defective buffer ring:', ring);
|
|
31329
|
+
}
|
|
31330
|
+
ring.push(ring[0]);
|
|
31331
|
+
init();
|
|
31332
|
+
return ring;
|
|
31333
|
+
};
|
|
31334
|
+
|
|
31335
|
+
return self;
|
|
31336
|
+
}
|
|
31337
|
+
|
|
31338
|
+
function pointsAreSame(a, b) {
|
|
31339
|
+
return a[0] === b[0] && a[1] === b[1];
|
|
31340
|
+
}
|
|
31341
|
+
|
|
31342
|
+
// function countExtendedHits(p0, p1, buffer) {
|
|
31343
|
+
// var bbox = getBbox();
|
|
31344
|
+
// var width = Math.max(bbox[2] - bbox[0], bbox[3] - bbox[1]);
|
|
31345
|
+
// var p2 = [extend(p1[0], p1[0] - p0[0], size), extend(p1[1], p1[1] - p0[1], size)];
|
|
31346
|
+
// var hits = 0;
|
|
31347
|
+
// for (var i=backtrackStopIdx, n = buffer.length-1; i<n; i++) {
|
|
31348
|
+
// if (bufferIntersection(buffer[i], buffer[i+1], p1, p2)) {
|
|
31349
|
+
// hits++;
|
|
31350
|
+
// }
|
|
31351
|
+
// }
|
|
31352
|
+
// return hits;
|
|
31353
|
+
// };
|
|
31354
|
+
|
|
31355
|
+
var R$1 = WGS84.SEMIMAJOR_AXIS;
|
|
31356
|
+
|
|
31357
|
+
// GeographicLib docs: https://geographiclib.sourceforge.io/html/js/
|
|
31358
|
+
// https://geographiclib.sourceforge.io/html/js/module-GeographicLib_Geodesic.Geodesic.html
|
|
31359
|
+
// https://geographiclib.sourceforge.io/html/js/tutorial-2-interface.html
|
|
31360
|
+
function getGeodesic(P) {
|
|
31361
|
+
if (!isLatLngCRS(P)) error('Expected an unprojected CRS');
|
|
31362
|
+
var f = P.es / (1 + Math.sqrt(P.one_es));
|
|
31363
|
+
// var GeographicLib = require('mproj').internal.GeographicLib;
|
|
31364
|
+
var GeographicLib = require$1('geographiclib-geodesic');
|
|
31365
|
+
// return new GeographicLib.Geodesic.Geodesic(P.a, 0)
|
|
31366
|
+
return new GeographicLib.Geodesic.Geodesic(P.a, f);
|
|
31367
|
+
}
|
|
31368
|
+
|
|
31369
|
+
function interpolatePoint2D(ax, ay, bx, by, k) {
|
|
31370
|
+
var j = 1 - k;
|
|
31371
|
+
return [ax * j + bx * k, ay * j + by * k];
|
|
31372
|
+
}
|
|
31373
|
+
|
|
31374
|
+
function getInterpolationFunction(P) {
|
|
31375
|
+
var spherical = P && isLatLngCRS(P);
|
|
31376
|
+
if (!spherical) return interpolatePoint2D;
|
|
31377
|
+
var geod = getGeodesic(P);
|
|
31378
|
+
return function(lng, lat, lng2, lat2, k) {
|
|
31379
|
+
var r = geod.Inverse(lat, lng, lat2, lng2);
|
|
31380
|
+
var dist = r.s12 * k;
|
|
31381
|
+
var r2 = geod.Direct(lat, lng, r.azi1, dist);
|
|
31382
|
+
return [r2.lon2, r2.lat2];
|
|
31383
|
+
};
|
|
31384
|
+
}
|
|
31385
|
+
|
|
31386
|
+
function getPlanarSegmentEndpoint(x, y, bearing, meterDist) {
|
|
31387
|
+
var rad = bearing / 180 * Math.PI;
|
|
31388
|
+
var dx = Math.sin(rad) * meterDist;
|
|
31389
|
+
var dy = Math.cos(rad) * meterDist;
|
|
31390
|
+
return [x + dx, y + dy];
|
|
31391
|
+
}
|
|
31392
|
+
|
|
31393
|
+
// source: https://github.com/mapbox/cheap-ruler/blob/master/index.js
|
|
31394
|
+
function fastGeodeticSegmentFunction(lng, lat, bearing, meterDist) {
|
|
31395
|
+
var D2R = Math.PI / 180;
|
|
31396
|
+
var cos = Math.cos(lat * D2R);
|
|
31397
|
+
var cos2 = 2 * cos * cos - 1;
|
|
31398
|
+
var cos3 = 2 * cos * cos2 - cos;
|
|
31399
|
+
var cos4 = 2 * cos * cos3 - cos2;
|
|
31400
|
+
var cos5 = 2 * cos * cos4 - cos3;
|
|
31401
|
+
var kx = (111.41513 * cos - 0.09455 * cos3 + 0.00012 * cos5) * 1000;
|
|
31402
|
+
var ky = (111.13209 - 0.56605 * cos2 + 0.0012 * cos4) * 1000;
|
|
31403
|
+
var bearingRad = bearing * D2R;
|
|
31404
|
+
var lat2 = lat + Math.cos(bearingRad) * meterDist / ky;
|
|
31405
|
+
var lng2 = lng + Math.sin(bearingRad) * meterDist / kx;
|
|
31406
|
+
return [lng2, lat2];
|
|
31407
|
+
}
|
|
31408
|
+
|
|
31409
|
+
function wrap(deg) {
|
|
31410
|
+
while (deg < -180) deg += 360;
|
|
31411
|
+
while (deg > 180) deg -= 360;
|
|
31412
|
+
return deg;
|
|
31413
|
+
}
|
|
31414
|
+
|
|
31415
|
+
function fastGeodeticBearingFunction(lng1, lat1, lng2, lat2) {
|
|
31416
|
+
var D2R = Math.PI / 180;
|
|
31417
|
+
var f = 1 / 298.257223563;
|
|
31418
|
+
var e2 = f * (2 - f);
|
|
31419
|
+
var m = R$1 * D2R;
|
|
31420
|
+
var coslat = Math.cos(lat1 * D2R);
|
|
31421
|
+
var w2 = 1 / (1 - e2 * (1 - coslat * coslat));
|
|
31422
|
+
var w = Math.sqrt(w2);
|
|
31423
|
+
var kx = m * w * coslat;
|
|
31424
|
+
var ky = m * w * w2 * (1 - e2);
|
|
31425
|
+
var dx = wrap(lng2 - lng1) * kx;
|
|
31426
|
+
var dy = (lat2 - lat1) * ky;
|
|
31427
|
+
return Math.atan2(dx, dy) / D2R;
|
|
31428
|
+
}
|
|
31429
|
+
|
|
31430
|
+
function getGeodeticSegmentFunction(P) {
|
|
31431
|
+
if (!isLatLngCRS(P)) {
|
|
31432
|
+
return getPlanarSegmentEndpoint;
|
|
31433
|
+
}
|
|
31434
|
+
var g = getGeodesic(P);
|
|
31435
|
+
return function(lng, lat, bearing, meterDist) {
|
|
31436
|
+
var o = g.Direct(lat, lng, bearing, meterDist);
|
|
31437
|
+
var p = [o.lon2, o.lat2];
|
|
31438
|
+
return p;
|
|
31439
|
+
};
|
|
31440
|
+
}
|
|
31441
|
+
|
|
31442
|
+
function getFastGeodeticSegmentFunction(P) {
|
|
31443
|
+
// CAREFUL: this function has higher error at very large distances and at the poles
|
|
31444
|
+
// also, it wouldn't work for other planets than Earth
|
|
31445
|
+
return isLatLngCRS(P) ? fastGeodeticSegmentFunction : getPlanarSegmentEndpoint;
|
|
31446
|
+
}
|
|
31447
|
+
|
|
31448
|
+
// return function to calculate bearing of a segment in degrees
|
|
31449
|
+
function getBearingFunction(dataset) {
|
|
31450
|
+
var P = getDatasetCRS(dataset);
|
|
31451
|
+
// var g = getGeodesic(P);
|
|
31452
|
+
// if (isLatLngCRS) {
|
|
31453
|
+
// return function(lng1, lat1, lng2, lat2) {
|
|
31454
|
+
// var tmp = g.Inverse(lat1, lng1, lat2, lng2);
|
|
31455
|
+
// return tmp.azi1;
|
|
31456
|
+
// // return bearingDegrees(lng1, lat1, lng2, lat2);
|
|
31457
|
+
// };
|
|
31458
|
+
// }
|
|
31459
|
+
// return isLatLngCRS(P) ? bearingDegrees : bearingDegrees2D;
|
|
31460
|
+
return isLatLngCRS(P) ? fastGeodeticBearingFunction : bearingDegrees2D;
|
|
31461
|
+
}
|
|
31462
|
+
|
|
31463
|
+
// get bearing in degrees from point ab to point cd
|
|
31464
|
+
function bearingDegrees(a, b, c, d) {
|
|
31465
|
+
return geom.bearing(a, b, c, d) * 180 / Math.PI;
|
|
31466
|
+
}
|
|
31467
|
+
|
|
31468
|
+
function bearingDegrees2D(a, b, c, d) {
|
|
31469
|
+
return geom.bearing2D(a, b, c, d) * 180 / Math.PI;
|
|
31470
|
+
}
|
|
31471
|
+
|
|
31472
|
+
var Geodesic = /*#__PURE__*/Object.freeze({
|
|
31037
31473
|
__proto__: null,
|
|
31038
|
-
|
|
31039
|
-
|
|
31040
|
-
|
|
31041
|
-
|
|
31042
|
-
|
|
31043
|
-
|
|
31044
|
-
|
|
31045
|
-
|
|
31474
|
+
bearingDegrees: bearingDegrees,
|
|
31475
|
+
bearingDegrees2D: bearingDegrees2D,
|
|
31476
|
+
getBearingFunction: getBearingFunction,
|
|
31477
|
+
getFastGeodeticSegmentFunction: getFastGeodeticSegmentFunction,
|
|
31478
|
+
getGeodeticSegmentFunction: getGeodeticSegmentFunction,
|
|
31479
|
+
getInterpolationFunction: getInterpolationFunction,
|
|
31480
|
+
getPlanarSegmentEndpoint: getPlanarSegmentEndpoint,
|
|
31481
|
+
interpolatePoint2D: interpolatePoint2D
|
|
31046
31482
|
});
|
|
31047
31483
|
|
|
31048
|
-
// Returns a function for generating GeoJSON geometries
|
|
31049
|
-
function getPolylineBufferMaker(
|
|
31050
|
-
var
|
|
31051
|
-
var
|
|
31052
|
-
|
|
31053
|
-
var
|
|
31484
|
+
// Returns a function for generating GeoJSON MultiPolygon geometries
|
|
31485
|
+
function getPolylineBufferMaker$2(dataset, opts) {
|
|
31486
|
+
// var sliceLen = opts.slice_length || Infinity;
|
|
31487
|
+
var crs = getDatasetCRS(dataset);
|
|
31488
|
+
var geod = getFastGeodeticSegmentFunction(crs);
|
|
31489
|
+
var bufferIntersection = getIntersectionFunction();
|
|
31490
|
+
var getBearing = getBearingFunction(dataset);
|
|
31491
|
+
var segsPerQuadrant = opts.arc_quality >= 2 ? opts.arc_quality : 12;
|
|
31492
|
+
var segAngle = 360 / segsPerQuadrant / 4;
|
|
31493
|
+
var capStyle = opts.cap_style || 'round'; // expect 'round' or 'flat'
|
|
31494
|
+
var pathIter = new ShapeIter(dataset.arcs);
|
|
31495
|
+
var builder = new BufferBuilder(bufferIntersection, opts);
|
|
31054
31496
|
|
|
31055
|
-
function
|
|
31056
|
-
var
|
|
31497
|
+
return function makeBufferGeoJSON(shape, distance) {
|
|
31498
|
+
var rings = [];
|
|
31499
|
+
(shape || []).forEach(function(path, i) {
|
|
31500
|
+
var pathRings = makeSinglePathRings(path, distance);
|
|
31501
|
+
rings = rings.concat(pathRings);
|
|
31502
|
+
});
|
|
31503
|
+
if (rings.length === 0) return null;
|
|
31504
|
+
var feat = {
|
|
31505
|
+
type: 'Feature',
|
|
31506
|
+
properties: null,
|
|
31507
|
+
geometry: {
|
|
31508
|
+
type: 'MultiPolygon',
|
|
31509
|
+
coordinates: rings.map(ring => [ring]) // to Polygon format
|
|
31510
|
+
}
|
|
31511
|
+
};
|
|
31512
|
+
if (opts.debug_points) {
|
|
31513
|
+
return [feat].concat(builder.getDebugPoints());
|
|
31514
|
+
}
|
|
31515
|
+
return feat;
|
|
31516
|
+
};
|
|
31517
|
+
|
|
31518
|
+
// each path may be converted into multiple buffer rings, which later
|
|
31519
|
+
// need to be dissolved
|
|
31520
|
+
function makeSinglePathRings(pathArcs, dist) {
|
|
31057
31521
|
var revPathArcs;
|
|
31058
|
-
|
|
31522
|
+
var rings = [];
|
|
31523
|
+
if (!opts.right || opts.left) {
|
|
31524
|
+
rings = rings.concat(makeLeftBufferRings(pathArcs, dist));
|
|
31525
|
+
}
|
|
31526
|
+
if (!opts.left || opts.right) {
|
|
31059
31527
|
revPathArcs = reversePath(pathArcs.concat());
|
|
31060
|
-
|
|
31528
|
+
rings = rings.concat(makeLeftBufferRings(revPathArcs, dist));
|
|
31061
31529
|
}
|
|
31062
|
-
|
|
31063
|
-
return outputGeom == 'polyline' ? pathCoords : [pathCoords];
|
|
31530
|
+
return rings;
|
|
31064
31531
|
}
|
|
31065
31532
|
|
|
31066
|
-
|
|
31067
|
-
var
|
|
31068
|
-
|
|
31069
|
-
|
|
31070
|
-
|
|
31071
|
-
|
|
31072
|
-
|
|
31533
|
+
function makeLeftBufferRings(path, dist) {
|
|
31534
|
+
var rings = [];
|
|
31535
|
+
var x0, y0, x1, y1, x2, y2; // path traversal coords
|
|
31536
|
+
var p1, p2; // extruded points
|
|
31537
|
+
var p1Prev, p2Prev;
|
|
31538
|
+
var bearing1, bearing2, bearing2Prev, joinAngle, hit;
|
|
31539
|
+
var firstBearing;
|
|
31540
|
+
var joinPoints;
|
|
31541
|
+
var i = 0;
|
|
31542
|
+
pathIter.init(path);
|
|
31543
|
+
|
|
31544
|
+
if (pathIter.hasNext()) {
|
|
31545
|
+
x0 = x2 = pathIter.x;
|
|
31546
|
+
y0 = y2 = pathIter.y;
|
|
31547
|
+
builder.addPathVertex([x0, y0]); // start building the path side
|
|
31548
|
+
i++;
|
|
31073
31549
|
}
|
|
31074
|
-
return geom.coordinates.length == 0 ? null : geom;
|
|
31075
|
-
};
|
|
31076
|
-
}
|
|
31077
31550
|
|
|
31551
|
+
while (pathIter.hasNext()) {
|
|
31552
|
+
// TODO: use a tolerance
|
|
31553
|
+
if (pathIter.x === x2 && pathIter.y === y2) {
|
|
31554
|
+
debug("skipping a duplicate point");
|
|
31555
|
+
continue;
|
|
31556
|
+
}
|
|
31078
31557
|
|
|
31079
|
-
|
|
31558
|
+
x1 = x2;
|
|
31559
|
+
y1 = y2;
|
|
31560
|
+
x2 = pathIter.x;
|
|
31561
|
+
y2 = pathIter.y;
|
|
31562
|
+
builder.addPathVertex([x2, y2]); // extend path
|
|
31080
31563
|
|
|
31081
|
-
|
|
31082
|
-
|
|
31083
|
-
|
|
31084
|
-
|
|
31085
|
-
|
|
31564
|
+
// bearing (direction) of the segment is slightly different at the first
|
|
31565
|
+
// and second endpoint, using geodesic math
|
|
31566
|
+
// TODO: no need to calculate twice with planar coordinates
|
|
31567
|
+
bearing1 = getBearing(x1, y1, x2, y2);
|
|
31568
|
+
bearing2 = getBearing(x2, y2, x1, y1) - 180;
|
|
31569
|
+
// extrude current segment to the left
|
|
31570
|
+
p1 = geod(x1, y1, bearing1 - 90, dist);
|
|
31571
|
+
p2 = geod(x2, y2, bearing2 - 90, dist);
|
|
31086
31572
|
|
|
31087
|
-
|
|
31088
|
-
|
|
31089
|
-
|
|
31090
|
-
|
|
31091
|
-
|
|
31092
|
-
|
|
31093
|
-
|
|
31573
|
+
if (i == 1) {
|
|
31574
|
+
firstBearing = bearing1;
|
|
31575
|
+
} else {
|
|
31576
|
+
joinAngle = getJoinAngle(bearing2Prev, bearing1);
|
|
31577
|
+
}
|
|
31578
|
+
|
|
31579
|
+
// various connections between current extruded segment and prev segment
|
|
31580
|
+
if (i == 1) {
|
|
31581
|
+
// first segment - no join
|
|
31582
|
+
builder.addBufferVertex(p1, false);
|
|
31583
|
+
} else if (joinAngle > segAngle * 1.5) {
|
|
31584
|
+
// round join
|
|
31585
|
+
// builder.addBufferVertex(p2Prev, false) // testing
|
|
31586
|
+
joinPoints = makeOutsideRoundJoin(x1, y1, bearing2Prev - 90, joinAngle, dist);
|
|
31587
|
+
builder.addBufferVertices(joinPoints);
|
|
31588
|
+
// builder.addBufferVertex(p1, false) // testing
|
|
31589
|
+
p1 = joinPoints.pop();
|
|
31590
|
+
} else if (joinAngle > 0 && (hit = elbowJoin(p1Prev, p2Prev, p1, p2)) ||
|
|
31591
|
+
joinAngle < 0 && (hit = bufferIntersection(p1Prev, p2Prev, p1, p2))) {
|
|
31592
|
+
// elbow join (concave or convex)
|
|
31593
|
+
builder.addBufferVertex(hit, false);
|
|
31594
|
+
p1 = hit;
|
|
31595
|
+
} else if (joinAngle == 0) {
|
|
31596
|
+
// collinear segments (can we really skip p2Prev on a sphere?)
|
|
31597
|
+
builder.addBufferVertex(p1, false);
|
|
31598
|
+
} else {
|
|
31599
|
+
// if (joinAngle > 0) {
|
|
31600
|
+
// probably a leftward bend and extruded segments do not intersect
|
|
31601
|
+
// // console.log("UNEXPECTED SEGMENT JOIN")
|
|
31602
|
+
// }
|
|
31603
|
+
builder.addBufferVertex(p2Prev);
|
|
31604
|
+
builder.addBufferVertex(p1, joinAngle < 0);
|
|
31605
|
+
}
|
|
31606
|
+
|
|
31607
|
+
bearing2Prev = bearing2;
|
|
31608
|
+
p1Prev = p1;
|
|
31609
|
+
p2Prev = p2;
|
|
31610
|
+
i++;
|
|
31611
|
+
}
|
|
31612
|
+
|
|
31613
|
+
// TODO: add this to cap and join code below
|
|
31614
|
+
if (p2Prev) {
|
|
31615
|
+
builder.addBufferVertex(p2Prev);
|
|
31616
|
+
}
|
|
31617
|
+
|
|
31618
|
+
if (x2 == x0 && y2 == y0) { // closed path
|
|
31619
|
+
// add join to finish closed path
|
|
31620
|
+
// TODO - figure out which bearing to use
|
|
31621
|
+
joinAngle = getJoinAngle(bearing2, firstBearing);
|
|
31622
|
+
if (joinAngle > 0) {
|
|
31623
|
+
builder.addBufferVertices(makeFinalJoin(x2, y2, bearing1 - 90, joinAngle, dist));
|
|
31624
|
+
}
|
|
31625
|
+
} else if (capStyle == 'round') { // open path
|
|
31626
|
+
// add a cap to finish open path
|
|
31627
|
+
builder.addBufferVertices(makeRoundCap(x2, y2, bearing2 - 90, dist));
|
|
31094
31628
|
}
|
|
31629
|
+
|
|
31630
|
+
rings.push(builder.done());
|
|
31631
|
+
return rings;
|
|
31095
31632
|
}
|
|
31096
31633
|
|
|
31097
|
-
// function
|
|
31098
|
-
//
|
|
31099
|
-
//
|
|
31100
|
-
// var dir = startDir + increment;
|
|
31101
|
-
// while (dir < endDir) {
|
|
31102
|
-
// addBufferVertex(arr, geod(x, y, dir, dist), backtrackSteps);
|
|
31103
|
-
// dir += increment;
|
|
31104
|
-
// }
|
|
31634
|
+
// function extendArray(arr, arr2) {
|
|
31635
|
+
// arr2.reverse();
|
|
31636
|
+
// while(arr2.length > 0) arr.push(arr2.pop());
|
|
31105
31637
|
// }
|
|
31106
31638
|
|
|
31107
|
-
|
|
31108
|
-
|
|
31109
|
-
|
|
31110
|
-
|
|
31111
|
-
return dist < tol;
|
|
31639
|
+
function makeFinalJoin(x, y, direction, angle, dist) {
|
|
31640
|
+
var points = makeRoundJoin(x, y, direction, angle, dist);
|
|
31641
|
+
points.push(geod(x, y, direction + angle, dist));
|
|
31642
|
+
return points;
|
|
31112
31643
|
}
|
|
31113
31644
|
|
|
31114
|
-
function
|
|
31115
|
-
var
|
|
31116
|
-
|
|
31645
|
+
function makeRoundCap(x, y, startDir, dist) {
|
|
31646
|
+
var points = makeRoundJoin(x, y, startDir, 180, dist);
|
|
31647
|
+
points.push(geod(x, y, startDir + 180, dist)); // add final vertex
|
|
31648
|
+
return points;
|
|
31117
31649
|
}
|
|
31118
31650
|
|
|
31119
|
-
|
|
31120
|
-
|
|
31121
|
-
|
|
31122
|
-
|
|
31651
|
+
// The vertices of this join are outside of the arc that it approximates and
|
|
31652
|
+
// the segments of the join touch the arc at their midpoints.
|
|
31653
|
+
// The first and last of the returned vertices extend the segments on either
|
|
31654
|
+
// side of the join.
|
|
31655
|
+
function makeOutsideRoundJoin(cx, cy, startBearing, arcAngle, dist) {
|
|
31656
|
+
// point count of 1 would be an elbow joint
|
|
31657
|
+
// (elbow joins should be created elsewhere)
|
|
31658
|
+
var pointCount = Math.max(1, Math.round(arcAngle / segAngle));
|
|
31659
|
+
var stepAngle = arcAngle / pointCount;
|
|
31660
|
+
var points = [];
|
|
31661
|
+
var i = 0;
|
|
31662
|
+
var a, b, c, d, joinP, tanP, bearing;
|
|
31663
|
+
while (i <= pointCount) {
|
|
31664
|
+
bearing = startBearing + stepAngle * i;
|
|
31665
|
+
tanP = geod(cx, cy, bearing, dist);
|
|
31666
|
+
c = geod(tanP[0], tanP[1], bearing - 90, dist * 2);
|
|
31667
|
+
d = geod(tanP[0], tanP[1], bearing + 90, dist * 2);
|
|
31668
|
+
if (i > 0) {
|
|
31669
|
+
joinP = bufferIntersection(a, b, c, d);
|
|
31670
|
+
if (!joinP) {
|
|
31671
|
+
throw Error(`no intersection on ${i} of ${pointCount}`);
|
|
31672
|
+
} else {
|
|
31673
|
+
points.push(joinP);
|
|
31674
|
+
}
|
|
31675
|
+
}
|
|
31676
|
+
a = c;
|
|
31677
|
+
b = d;
|
|
31678
|
+
i++;
|
|
31123
31679
|
}
|
|
31680
|
+
return points;
|
|
31124
31681
|
}
|
|
31125
31682
|
|
|
31126
|
-
function
|
|
31127
|
-
|
|
31128
|
-
|
|
31129
|
-
|
|
31130
|
-
return
|
|
31683
|
+
function elbowJoin(a, b, c, d) {
|
|
31684
|
+
var k = 2 ** 13;
|
|
31685
|
+
var b2 = [extend(b[0], b[0] - a[0], k), extend(b[1], b[1] - a[1], k)];
|
|
31686
|
+
var c2 = [extend(c[0], c[0] - d[0], k), extend(c[1], c[1] - d[1], k)];
|
|
31687
|
+
return bufferIntersection(a, b2, c2, d);
|
|
31131
31688
|
}
|
|
31132
31689
|
|
|
31133
|
-
|
|
31690
|
+
|
|
31691
|
+
// function getBbox() {
|
|
31692
|
+
// var lastIdx = buffer.length - 1;
|
|
31693
|
+
// var x, y;
|
|
31694
|
+
// if (!_bbox) {
|
|
31695
|
+
// _bbox = [-Infinity, -Infinity, Infinity, Infinity];
|
|
31696
|
+
// }
|
|
31697
|
+
// while (_idx < lastIdx) {
|
|
31698
|
+
// _idx++;
|
|
31699
|
+
// x = buffer[_idx][0];
|
|
31700
|
+
// y = buffer[_idx][1];
|
|
31701
|
+
// _bbox[0] = Math.min(x, _bbox[0]);
|
|
31702
|
+
// _bbox[1] = Math.min(y, _bbox[1]);
|
|
31703
|
+
// _bbox[2] = Math.max(x, _bbox[2]);
|
|
31704
|
+
// _bbox[3] = Math.max(y, _bbox[3]);
|
|
31705
|
+
// }
|
|
31706
|
+
// return _bbox;
|
|
31707
|
+
// }
|
|
31708
|
+
|
|
31709
|
+
function extend(n, d, k) {
|
|
31710
|
+
return n + d * k;
|
|
31711
|
+
}
|
|
31712
|
+
|
|
31713
|
+
// get interior vertices of an interpolated CW arc
|
|
31714
|
+
function makeRoundJoin(cx, cy, startBearing, arcAngle, dist) {
|
|
31134
31715
|
var points = [];
|
|
31135
|
-
var increment =
|
|
31136
|
-
var startDir = segmentDir - 90;
|
|
31716
|
+
var increment = 90 / segsPerQuadrant;
|
|
31137
31717
|
var angle = increment;
|
|
31138
|
-
while (angle <
|
|
31139
|
-
points.push(geod(
|
|
31718
|
+
while (angle < arcAngle) {
|
|
31719
|
+
points.push(geod(cx, cy, startBearing + angle, dist));
|
|
31140
31720
|
angle += increment;
|
|
31141
31721
|
}
|
|
31142
31722
|
return points;
|
|
@@ -31156,113 +31736,259 @@ ${svg}
|
|
|
31156
31736
|
return delta;
|
|
31157
31737
|
}
|
|
31158
31738
|
|
|
31159
|
-
|
|
31160
|
-
var a, b, c, hit;
|
|
31161
|
-
for (var i=0, idx = arr.length - 3; i<maxBacktrack && idx >= 0; i++, idx--) {
|
|
31162
|
-
c = arr[arr.length - 1];
|
|
31163
|
-
a = arr[idx];
|
|
31164
|
-
b = arr[idx + 1];
|
|
31165
|
-
// TODO: consider using a geodetic intersection function for lat-long datasets
|
|
31166
|
-
hit = bufferIntersection(a[0], a[1], b[0], b[1], c[0], c[1], d[0], d[1]);
|
|
31167
|
-
if (hit) {
|
|
31168
|
-
// TODO: handle collinear segments
|
|
31169
|
-
// if (hit.length != 2) console.log('COLLINEAR', hit)
|
|
31170
|
-
// segments intersect -- replace two internal segment endpoints with xx point
|
|
31171
|
-
while (arr.length > idx + 1) arr.pop();
|
|
31172
|
-
appendPoint(arr, hit);
|
|
31173
|
-
}
|
|
31174
|
-
}
|
|
31739
|
+
}
|
|
31175
31740
|
|
|
31176
|
-
|
|
31741
|
+
// Returns a function for generating GeoJSON MultiPolygon geometries
|
|
31742
|
+
function getPolylineBufferMaker$1(dataset, opts) {
|
|
31743
|
+
var geod = getFastGeodeticSegmentFunction(getDatasetCRS(dataset));
|
|
31744
|
+
var getBearing = getBearingFunction(dataset);
|
|
31745
|
+
var sliceLen = opts.slice_length || Infinity;
|
|
31746
|
+
var backtrackSteps = opts.backtrack >= 0 ? opts.backtrack : 100;
|
|
31747
|
+
var segsPerQuadrant = opts.arc_quality >= 2 ? opts.arc_quality : 12;
|
|
31748
|
+
var capStyle = opts.cap_style || 'round'; // expect 'round' or 'flat'
|
|
31749
|
+
var pathIter = new ShapeIter(dataset.arcs);
|
|
31750
|
+
var left, center, rings;
|
|
31751
|
+
|
|
31752
|
+
return function makeBufferGeoJSON(shape, distance) {
|
|
31753
|
+
var rings = [];
|
|
31754
|
+
(shape || []).forEach(function(path, i) {
|
|
31755
|
+
var pathRings = makeSinglePathRings(path, distance);
|
|
31756
|
+
rings = rings.concat(pathRings);
|
|
31757
|
+
});
|
|
31758
|
+
if (rings.length === 0) return null;
|
|
31759
|
+
return {
|
|
31760
|
+
type: 'MultiPolygon',
|
|
31761
|
+
coordinates: rings.map(ring => [ring]) // to Polygon format
|
|
31762
|
+
};
|
|
31763
|
+
};
|
|
31764
|
+
|
|
31765
|
+
// each path may be converted into multiple buffer rings, which later
|
|
31766
|
+
// need to be dissolved
|
|
31767
|
+
function makeSinglePathRings(pathArcs, dist) {
|
|
31768
|
+
var revPathArcs;
|
|
31769
|
+
var rings = [];
|
|
31770
|
+
if (!opts.right || opts.left) {
|
|
31771
|
+
rings = rings.concat(makeLeftBufferRings(pathArcs, dist));
|
|
31772
|
+
}
|
|
31773
|
+
if (!opts.left || opts.right) {
|
|
31774
|
+
revPathArcs = reversePath(pathArcs.concat());
|
|
31775
|
+
rings = rings.concat(makeLeftBufferRings(revPathArcs, dist));
|
|
31776
|
+
}
|
|
31777
|
+
return rings;
|
|
31177
31778
|
}
|
|
31178
31779
|
|
|
31179
|
-
|
|
31180
|
-
|
|
31181
|
-
|
|
31182
|
-
|
|
31183
|
-
var
|
|
31780
|
+
function makeLeftBufferRings(path, dist) {
|
|
31781
|
+
left = [];
|
|
31782
|
+
center = [];
|
|
31783
|
+
rings = [];
|
|
31784
|
+
var x0, y0, x1, y1, x2, y2; // path traversal coords
|
|
31785
|
+
var p1, p2; // extruded points
|
|
31786
|
+
var prevP;
|
|
31787
|
+
var bearing1, bearing2, prevBearing, joinAngle;
|
|
31788
|
+
var firstBearing;
|
|
31184
31789
|
var i = 0;
|
|
31185
31790
|
pathIter.init(path);
|
|
31186
31791
|
|
|
31792
|
+
if (pathIter.hasNext()) {
|
|
31793
|
+
x0 = x2 = pathIter.x;
|
|
31794
|
+
y0 = y2 = pathIter.y;
|
|
31795
|
+
i++;
|
|
31796
|
+
}
|
|
31797
|
+
|
|
31187
31798
|
while (pathIter.hasNext()) {
|
|
31188
31799
|
// TODO: use a tolerance
|
|
31189
|
-
if (pathIter.x === x2 && pathIter.y === y2)
|
|
31800
|
+
if (pathIter.x === x2 && pathIter.y === y2) {
|
|
31801
|
+
debug("skipping a duplicate point");
|
|
31802
|
+
continue;
|
|
31803
|
+
}
|
|
31190
31804
|
x1 = x2;
|
|
31191
31805
|
y1 = y2;
|
|
31192
31806
|
x2 = pathIter.x;
|
|
31193
31807
|
y2 = pathIter.y;
|
|
31194
|
-
|
|
31195
|
-
|
|
31196
|
-
|
|
31197
|
-
|
|
31198
|
-
|
|
31199
|
-
|
|
31200
|
-
|
|
31201
|
-
|
|
31808
|
+
|
|
31809
|
+
// calculate bearing at both segment points
|
|
31810
|
+
// TODO: no need to calculate twice with planar coordinates
|
|
31811
|
+
prevBearing = bearing2;
|
|
31812
|
+
bearing1 = getBearing(x1, y1, x2, y2);
|
|
31813
|
+
bearing2 = getBearing(x2, y2, x1, y1) - 180;
|
|
31814
|
+
// extrude current segment to the left
|
|
31815
|
+
prevP = p2;
|
|
31816
|
+
p1 = geod(x1, y1, bearing1 - 90, dist);
|
|
31817
|
+
p2 = geod(x2, y2, bearing2 - 90, dist);
|
|
31818
|
+
|
|
31202
31819
|
if (i == 1) {
|
|
31203
|
-
firstBearing =
|
|
31204
|
-
|
|
31205
|
-
|
|
31206
|
-
left.push(p1, p2);
|
|
31820
|
+
firstBearing = bearing1;
|
|
31821
|
+
} else {
|
|
31822
|
+
joinAngle = getJoinAngle(prevBearing, bearing1);
|
|
31207
31823
|
}
|
|
31208
|
-
|
|
31209
|
-
|
|
31210
|
-
|
|
31211
|
-
|
|
31212
|
-
|
|
31213
|
-
|
|
31214
|
-
|
|
31215
|
-
|
|
31216
|
-
|
|
31217
|
-
|
|
31218
|
-
|
|
31219
|
-
|
|
31824
|
+
|
|
31825
|
+
// extend center coords (i.e. original path)
|
|
31826
|
+
if (center.length == 0) {
|
|
31827
|
+
center.push([x1, y1]);
|
|
31828
|
+
}
|
|
31829
|
+
center.push([x2, y2]);
|
|
31830
|
+
|
|
31831
|
+
// if (veryCloseToPrevPoint(left, p1[0], p1[1])) {
|
|
31832
|
+
// console.log("VERY CLOSE")
|
|
31833
|
+
// // skip first point
|
|
31834
|
+
// addBufferVertex(p2);
|
|
31835
|
+
// }
|
|
31836
|
+
|
|
31837
|
+
if (i > 1 && joinAngle > 0) {
|
|
31838
|
+
if (prevP) {
|
|
31839
|
+
// start join from last extruded vertex of previous buffer slice
|
|
31840
|
+
addBufferVertex(prevP);
|
|
31220
31841
|
}
|
|
31842
|
+
makeRoundJoin(x1, y1, prevBearing - 90, joinAngle, dist).forEach(addBufferVertex);
|
|
31843
|
+
addBufferVertex(p1);
|
|
31844
|
+
addBufferVertex(p2);
|
|
31845
|
+
// left.push(p1, p2)
|
|
31846
|
+
} else {
|
|
31847
|
+
// left.push(p1, p2)
|
|
31848
|
+
addBufferVertex(p1);
|
|
31849
|
+
addBufferVertex(p2);
|
|
31850
|
+
}
|
|
31851
|
+
|
|
31852
|
+
// TODO: finish
|
|
31853
|
+
if (center.length - 1 >= sliceLen) {
|
|
31854
|
+
finishRing();
|
|
31221
31855
|
}
|
|
31222
31856
|
i++;
|
|
31223
31857
|
}
|
|
31224
|
-
// TODO: handle defective polylines
|
|
31225
31858
|
|
|
31226
|
-
if (
|
|
31859
|
+
if (center.length > 1) {
|
|
31860
|
+
finishRing();
|
|
31861
|
+
}
|
|
31862
|
+
|
|
31863
|
+
if (x2 == x0 && y2 == y0) { // closed path
|
|
31227
31864
|
// add join to finish closed path
|
|
31228
|
-
|
|
31865
|
+
// TODO - figure out which bearing to use
|
|
31866
|
+
joinAngle = getJoinAngle(bearing2, firstBearing);
|
|
31229
31867
|
if (joinAngle > 0) {
|
|
31230
|
-
|
|
31868
|
+
appendJoinToRing(rings[rings.length-1], x2, y2, bearing1 - 90, joinAngle, dist);
|
|
31231
31869
|
}
|
|
31232
|
-
} else {
|
|
31870
|
+
} else { // open path
|
|
31233
31871
|
// add a cap to finish open path
|
|
31234
|
-
left.push.apply(left, makeCap(x2, y2, bearing, dist));
|
|
31872
|
+
// left.push.apply(left, makeCap(x2, y2, bearing, dist));
|
|
31873
|
+
appendCapToRing(rings[rings.length-1], x2, y2, bearing2, dist);
|
|
31235
31874
|
}
|
|
31236
|
-
return left;
|
|
31237
|
-
};
|
|
31238
|
-
}
|
|
31239
31875
|
|
|
31240
|
-
|
|
31241
|
-
|
|
31242
|
-
|
|
31876
|
+
return rings;
|
|
31877
|
+
}
|
|
31878
|
+
|
|
31879
|
+
function finishRing() {
|
|
31880
|
+
if (center.length < 2) {
|
|
31881
|
+
debug('defective path, skipping');
|
|
31882
|
+
return;
|
|
31883
|
+
}
|
|
31884
|
+
var ring = center.reverse().concat(left);
|
|
31885
|
+
ring.push(ring[0]);
|
|
31886
|
+
|
|
31887
|
+
// Start next partial (if there is one)
|
|
31888
|
+
left = [];
|
|
31889
|
+
center = [];
|
|
31890
|
+
rings.push(ring);
|
|
31891
|
+
}
|
|
31892
|
+
|
|
31893
|
+
// function extendArray(arr, arr2) {
|
|
31894
|
+
// arr2.reverse();
|
|
31895
|
+
// while(arr2.length > 0) arr.push(arr2.pop());
|
|
31896
|
+
// }
|
|
31897
|
+
|
|
31898
|
+
function appendJoinToRing(ring, x, y, direction, angle, dist) {
|
|
31899
|
+
var p1 = ring.pop();
|
|
31900
|
+
var coords = makeRoundJoin(x, y, direction, angle, dist);
|
|
31901
|
+
ring.push.apply(ring, coords);
|
|
31902
|
+
ring.push(geod(x, y, direction + angle, dist));
|
|
31903
|
+
ring.push(p1);
|
|
31904
|
+
}
|
|
31905
|
+
|
|
31906
|
+
function appendCapToRing(ring, x, y, direction, dist) {
|
|
31907
|
+
if (capStyle == 'flat' || ring.length < 4) {
|
|
31908
|
+
return;
|
|
31909
|
+
}
|
|
31910
|
+
var p1 = ring.pop();
|
|
31911
|
+
var coords = makeRoundCap(x, y, direction - 90, dist);
|
|
31912
|
+
ring.push.apply(ring, coords);
|
|
31913
|
+
ring.push(p1);
|
|
31914
|
+
}
|
|
31915
|
+
|
|
31916
|
+
function makeRoundCap(x, y, startDir, dist) {
|
|
31917
|
+
var points = makeRoundJoin(x, y, startDir, 180, dist);
|
|
31918
|
+
points.push(geod(x, y, startDir + 180, dist)); // add final vertex
|
|
31919
|
+
return points;
|
|
31920
|
+
}
|
|
31921
|
+
|
|
31922
|
+
// get interior vertices of an interpolated CW arc
|
|
31923
|
+
function makeRoundJoin(cx, cy, startBearing, arcAngle, dist) {
|
|
31924
|
+
var points = [];
|
|
31925
|
+
var increment = 90 / segsPerQuadrant;
|
|
31926
|
+
var angle = increment;
|
|
31927
|
+
while (angle < arcAngle) {
|
|
31928
|
+
points.push(geod(cx, cy, startBearing + angle, dist));
|
|
31929
|
+
angle += increment;
|
|
31930
|
+
}
|
|
31931
|
+
return points;
|
|
31932
|
+
}
|
|
31933
|
+
|
|
31934
|
+
// get angle between two extruded segments in degrees
|
|
31935
|
+
// positive angle means join in convex (range: 0-180 degrees)
|
|
31936
|
+
// negative angle means join is concave (range: -180-0 degrees)
|
|
31937
|
+
function getJoinAngle(direction1, direction2) {
|
|
31938
|
+
var delta = direction2 - direction1;
|
|
31939
|
+
if (delta > 180) {
|
|
31940
|
+
delta -= 360;
|
|
31941
|
+
}
|
|
31942
|
+
if (delta < -180) {
|
|
31943
|
+
delta += 360;
|
|
31944
|
+
}
|
|
31945
|
+
return delta;
|
|
31946
|
+
}
|
|
31947
|
+
|
|
31948
|
+
|
|
31949
|
+
function addBufferVertex(d) {
|
|
31950
|
+
var arr = left;
|
|
31951
|
+
var a, b, c, hit;
|
|
31952
|
+
// c is the start point of the segment formed by appending point d to the polyline.
|
|
31243
31953
|
c = arr[arr.length - 1];
|
|
31244
|
-
|
|
31245
|
-
|
|
31246
|
-
|
|
31247
|
-
|
|
31248
|
-
|
|
31249
|
-
// TODO:
|
|
31250
|
-
if (hit
|
|
31251
|
-
|
|
31252
|
-
|
|
31253
|
-
|
|
31254
|
-
|
|
31954
|
+
for (var i=0, idx = arr.length - 3; i < backtrackSteps && idx >= 0; i++, idx--) {
|
|
31955
|
+
a = arr[idx];
|
|
31956
|
+
b = arr[idx + 1];
|
|
31957
|
+
// TODO: consider using a geodetic intersection function for lat-long datasets
|
|
31958
|
+
hit = bufferIntersection$1(a[0], a[1], b[0], b[1], c[0], c[1], d[0], d[1]);
|
|
31959
|
+
// TODO: disregard hits caused by oxbows
|
|
31960
|
+
if (hit) {
|
|
31961
|
+
// TODO: handle collinear segments
|
|
31962
|
+
// if (hit.length != 2) console.log('COLLINEAR', hit)
|
|
31963
|
+
// segments intersect -- replace two internal segment endpoints with xx point
|
|
31964
|
+
while (arr.length > idx + 1) arr.pop();
|
|
31965
|
+
appendPoint(arr, hit);
|
|
31966
|
+
c = hit; // update starting point of the newly added segment
|
|
31967
|
+
}
|
|
31255
31968
|
}
|
|
31969
|
+
appendPoint(arr, d);
|
|
31256
31970
|
}
|
|
31257
31971
|
|
|
31258
|
-
|
|
31259
|
-
|
|
31972
|
+
}
|
|
31973
|
+
|
|
31974
|
+
// Test if two points are within a snapping tolerance
|
|
31975
|
+
// TODO: calculate the tolerance more sensibly
|
|
31976
|
+
function veryClose(x1, y1, x2, y2, tol) {
|
|
31977
|
+
var dist = geom.distance2D(x1, y1, x2, y2);
|
|
31978
|
+
return dist < tol;
|
|
31979
|
+
}
|
|
31980
|
+
|
|
31981
|
+
function appendPoint(arr, p) {
|
|
31982
|
+
var prev = arr[arr.length - 1];
|
|
31983
|
+
if (!prev || !veryClose(prev[0], prev[1], p[0], p[1], 1e-10)) {
|
|
31984
|
+
arr.push(p);
|
|
31985
|
+
}
|
|
31260
31986
|
}
|
|
31261
31987
|
|
|
31262
31988
|
// Exclude segments with non-intersecting bounding boxes before
|
|
31263
31989
|
// calling intersection function
|
|
31264
31990
|
// Possibly slightly faster than direct call... not worth it?
|
|
31265
|
-
function bufferIntersection(ax, ay, bx, by, cx, cy, dx, dy) {
|
|
31991
|
+
function bufferIntersection$1(ax, ay, bx, by, cx, cy, dx, dy) {
|
|
31266
31992
|
if (ax < cx && ax < dx && bx < cx && bx < dx ||
|
|
31267
31993
|
ax > cx && ax > dx && bx > cx && bx > dx ||
|
|
31268
31994
|
ay < cy && ay < dy && by < cy && by < dy ||
|
|
@@ -31270,84 +31996,53 @@ ${svg}
|
|
|
31270
31996
|
return geom.segmentIntersection(ax, ay, bx, by, cx, cy, dx, dy);
|
|
31271
31997
|
}
|
|
31272
31998
|
|
|
31273
|
-
|
|
31274
|
-
|
|
31275
|
-
|
|
31276
|
-
|
|
31277
|
-
|
|
31278
|
-
});
|
|
31279
|
-
|
|
31280
|
-
function getPolylineBufferMaker2(arcs, geod, getBearing, opts) {
|
|
31281
|
-
var makeLeftBuffer = getPathBufferMaker2(arcs, geod, getBearing, opts);
|
|
31999
|
+
// Returns a function for generating GeoJSON geometries (MultiLineString or MultiPolygon)
|
|
32000
|
+
function getPolylineBufferMaker(dataset, opts) {
|
|
32001
|
+
var geod = getFastGeodeticSegmentFunction(getDatasetCRS(dataset));
|
|
32002
|
+
var getBearing = getBearingFunction(dataset);
|
|
32003
|
+
var maker = getPathBufferMaker(dataset.arcs, geod, getBearing, opts);
|
|
31282
32004
|
var geomType = opts.geometry_type;
|
|
32005
|
+
// polyline output could be used for debugging
|
|
32006
|
+
var outputGeom = opts.output_geometry == 'polyline' ? 'polyline' : 'polygon';
|
|
31283
32007
|
|
|
31284
|
-
function
|
|
31285
|
-
|
|
31286
|
-
|
|
31287
|
-
|
|
31288
|
-
function needLeftBuffer(path, arcs) {
|
|
32008
|
+
function pathBufferCoords(pathArcs, dist) {
|
|
32009
|
+
var pathCoords = maker(pathArcs, dist);
|
|
32010
|
+
var revPathArcs;
|
|
31289
32011
|
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
32012
|
revPathArcs = reversePath(pathArcs.concat());
|
|
31314
|
-
|
|
32013
|
+
pathCoords = pathCoords.concat(maker(revPathArcs, dist));
|
|
31315
32014
|
}
|
|
31316
|
-
|
|
31317
|
-
return
|
|
32015
|
+
pathCoords.push(pathCoords[0]); // close path
|
|
32016
|
+
return outputGeom == 'polyline' ? pathCoords : [pathCoords];
|
|
31318
32017
|
}
|
|
31319
32018
|
|
|
31320
|
-
// Returns a GeoJSON Geometry (MultiLineString or MultiPolygon) or null
|
|
31321
32019
|
return function(shape, dist) {
|
|
31322
32020
|
var geom = {
|
|
31323
|
-
type: 'MultiPolygon',
|
|
32021
|
+
type: outputGeom == 'polyline' ? 'MultiLineString' : 'MultiPolygon',
|
|
31324
32022
|
coordinates: []
|
|
31325
32023
|
};
|
|
31326
32024
|
for (var i=0; i<shape.length; i++) {
|
|
31327
|
-
geom.coordinates
|
|
32025
|
+
geom.coordinates.push(pathBufferCoords(shape[i], dist));
|
|
31328
32026
|
}
|
|
31329
32027
|
return geom.coordinates.length == 0 ? null : geom;
|
|
31330
32028
|
};
|
|
31331
32029
|
}
|
|
31332
32030
|
|
|
31333
|
-
|
|
32031
|
+
|
|
32032
|
+
function getPathBufferMaker(arcs, geod, getBearing, opts) {
|
|
32033
|
+
|
|
31334
32034
|
var backtrackSteps = opts.backtrack >= 0 ? opts.backtrack : 50;
|
|
31335
32035
|
var pathIter = new ShapeIter(arcs);
|
|
31336
|
-
|
|
31337
|
-
var
|
|
31338
|
-
var bounds;
|
|
32036
|
+
var capStyle = opts.cap_style || 'round'; // expect 'round' or 'flat'
|
|
32037
|
+
// var tolerance;
|
|
31339
32038
|
// TODO: implement other join styles than round
|
|
31340
32039
|
|
|
31341
|
-
|
|
31342
|
-
|
|
31343
|
-
// }
|
|
31344
|
-
|
|
31345
|
-
function addRoundJoin(x, y, startDir, angle, dist) {
|
|
32040
|
+
function addRoundJoin(arr, x, y, startDir, angle, dist) {
|
|
31346
32041
|
var increment = 10;
|
|
31347
32042
|
var endDir = startDir + angle;
|
|
31348
32043
|
var dir = startDir + increment;
|
|
31349
32044
|
while (dir < endDir) {
|
|
31350
|
-
addBufferVertex(geod(x, y, dir, dist));
|
|
32045
|
+
addBufferVertex(arr, geod(x, y, dir, dist), backtrackSteps);
|
|
31351
32046
|
dir += increment;
|
|
31352
32047
|
}
|
|
31353
32048
|
}
|
|
@@ -31357,7 +32052,7 @@ ${svg}
|
|
|
31357
32052
|
// var endDir = startDir + angle;
|
|
31358
32053
|
// var dir = startDir + increment;
|
|
31359
32054
|
// while (dir < endDir) {
|
|
31360
|
-
// addBufferVertex(arr, geod(x, y, dir, dist));
|
|
32055
|
+
// addBufferVertex(arr, geod(x, y, dir, dist), backtrackSteps);
|
|
31361
32056
|
// dir += increment;
|
|
31362
32057
|
// }
|
|
31363
32058
|
// }
|
|
@@ -31381,24 +32076,24 @@ ${svg}
|
|
|
31381
32076
|
}
|
|
31382
32077
|
}
|
|
31383
32078
|
|
|
31384
|
-
|
|
31385
|
-
|
|
31386
|
-
|
|
31387
|
-
|
|
31388
|
-
|
|
31389
|
-
|
|
32079
|
+
function makeCap(x, y, direction, dist) {
|
|
32080
|
+
if (capStyle == 'flat') {
|
|
32081
|
+
return [[x, y]];
|
|
32082
|
+
}
|
|
32083
|
+
return makeRoundCap(x, y, direction, dist);
|
|
32084
|
+
}
|
|
31390
32085
|
|
|
31391
|
-
|
|
31392
|
-
|
|
31393
|
-
|
|
31394
|
-
|
|
31395
|
-
|
|
31396
|
-
|
|
31397
|
-
|
|
31398
|
-
|
|
31399
|
-
|
|
31400
|
-
|
|
31401
|
-
|
|
32086
|
+
function makeRoundCap(x, y, segmentDir, dist) {
|
|
32087
|
+
var points = [];
|
|
32088
|
+
var increment = 10;
|
|
32089
|
+
var startDir = segmentDir - 90;
|
|
32090
|
+
var angle = increment;
|
|
32091
|
+
while (angle < 180) {
|
|
32092
|
+
points.push(geod(x, y, startDir + angle, dist));
|
|
32093
|
+
angle += increment;
|
|
32094
|
+
}
|
|
32095
|
+
return points;
|
|
32096
|
+
}
|
|
31402
32097
|
|
|
31403
32098
|
// get angle between two extruded segments in degrees
|
|
31404
32099
|
// positive angle means join in convex (range: 0-180 degrees)
|
|
@@ -31414,99 +32109,34 @@ ${svg}
|
|
|
31414
32109
|
return delta;
|
|
31415
32110
|
}
|
|
31416
32111
|
|
|
31417
|
-
|
|
31418
|
-
|
|
31419
|
-
|
|
31420
|
-
|
|
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--) {
|
|
32112
|
+
function addBufferVertex(arr, d, maxBacktrack) {
|
|
32113
|
+
var a, b, c, hit;
|
|
32114
|
+
for (var i=0, idx = arr.length - 3; i<maxBacktrack && idx >= 0; i++, idx--) {
|
|
32115
|
+
c = arr[arr.length - 1];
|
|
31425
32116
|
a = arr[idx];
|
|
31426
32117
|
b = arr[idx + 1];
|
|
31427
32118
|
// TODO: consider using a geodetic intersection function for lat-long datasets
|
|
31428
32119
|
hit = bufferIntersection(a[0], a[1], b[0], b[1], c[0], c[1], d[0], d[1]);
|
|
31429
32120
|
if (hit) {
|
|
31430
|
-
|
|
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)
|
|
32121
|
+
// TODO: handle collinear segments
|
|
31440
32122
|
// 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.
|
|
32123
|
+
// segments intersect -- replace two internal segment endpoints with xx point
|
|
31444
32124
|
while (arr.length > idx + 1) arr.pop();
|
|
31445
32125
|
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
32126
|
}
|
|
31464
32127
|
}
|
|
31465
32128
|
|
|
31466
32129
|
appendPoint(arr, d);
|
|
31467
32130
|
}
|
|
31468
32131
|
|
|
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
32132
|
return function(path, dist) {
|
|
31496
|
-
|
|
31497
|
-
var x1, y1, x2, y2;
|
|
32133
|
+
var left = [];
|
|
32134
|
+
var x0, y0, x1, y1, x2, y2;
|
|
31498
32135
|
var p1, p2;
|
|
31499
|
-
|
|
31500
|
-
var
|
|
31501
|
-
partials = [];
|
|
31502
|
-
left = [];
|
|
31503
|
-
center = [];
|
|
32136
|
+
var bearing, prevBearing, firstBearing, joinAngle;
|
|
32137
|
+
var i = 0;
|
|
31504
32138
|
pathIter.init(path);
|
|
31505
32139
|
|
|
31506
|
-
// if (pathIter.hasNext()) {
|
|
31507
|
-
// x0 = x2 = pathIter.x;
|
|
31508
|
-
// y0 = y2 = pathIter.y;
|
|
31509
|
-
// }
|
|
31510
32140
|
while (pathIter.hasNext()) {
|
|
31511
32141
|
// TODO: use a tolerance
|
|
31512
32142
|
if (pathIter.x === x2 && pathIter.y === y2) continue; // skip duplicate points
|
|
@@ -31514,178 +32144,99 @@ ${svg}
|
|
|
31514
32144
|
y1 = y2;
|
|
31515
32145
|
x2 = pathIter.x;
|
|
31516
32146
|
y2 = pathIter.y;
|
|
31517
|
-
|
|
31518
|
-
|
|
31519
|
-
|
|
31520
|
-
|
|
31521
|
-
|
|
31522
|
-
|
|
31523
|
-
|
|
31524
|
-
|
|
31525
|
-
|
|
31526
|
-
|
|
31527
|
-
|
|
31528
|
-
|
|
32147
|
+
if (i >= 1) {
|
|
32148
|
+
prevBearing = bearing;
|
|
32149
|
+
bearing = getBearing(x1, y1, x2, y2);
|
|
32150
|
+
p1 = geod(x1, y1, bearing - 90, dist);
|
|
32151
|
+
p2 = geod(x2, y2, bearing - 90, dist);
|
|
32152
|
+
// left.push([x1, y1], p1) // debug extrusion lines
|
|
32153
|
+
// left.push([x2, y2], p2) // debug extrusion lines
|
|
32154
|
+
}
|
|
32155
|
+
if (i == 1) {
|
|
32156
|
+
firstBearing = bearing;
|
|
32157
|
+
x0 = x1;
|
|
32158
|
+
y0 = y1;
|
|
31529
32159
|
left.push(p1, p2);
|
|
31530
|
-
|
|
31531
|
-
|
|
31532
|
-
//
|
|
32160
|
+
}
|
|
32161
|
+
if (i > 1) {
|
|
31533
32162
|
joinAngle = getJoinAngle(prevBearing, bearing);
|
|
31534
32163
|
if (veryCloseToPrevPoint(left, p1[0], p1[1])) {
|
|
31535
32164
|
// skip first point
|
|
31536
|
-
addBufferVertex(p2);
|
|
32165
|
+
addBufferVertex(left, p2, backtrackSteps);
|
|
31537
32166
|
} else if (joinAngle > 0) {
|
|
31538
|
-
addRoundJoin(x1, y1, prevBearing - 90, joinAngle, dist);
|
|
31539
|
-
addBufferVertex(p1);
|
|
31540
|
-
addBufferVertex(p2);
|
|
32167
|
+
addRoundJoin(left, x1, y1, prevBearing - 90, joinAngle, dist);
|
|
32168
|
+
addBufferVertex(left, p1, backtrackSteps);
|
|
32169
|
+
addBufferVertex(left, p2, backtrackSteps);
|
|
31541
32170
|
} else {
|
|
31542
|
-
addBufferVertex(p1);
|
|
31543
|
-
addBufferVertex(p2);
|
|
32171
|
+
addBufferVertex(left, p1, backtrackSteps);
|
|
32172
|
+
addBufferVertex(left, p2, backtrackSteps);
|
|
31544
32173
|
}
|
|
31545
|
-
center.push([x2, y2]);
|
|
31546
32174
|
}
|
|
31547
|
-
|
|
31548
|
-
|
|
31549
|
-
if (center.length > 1) {
|
|
31550
|
-
finishPartial();
|
|
32175
|
+
i++;
|
|
31551
32176
|
}
|
|
31552
32177
|
// TODO: handle defective polylines
|
|
31553
32178
|
|
|
31554
|
-
|
|
31555
|
-
|
|
31556
|
-
|
|
31557
|
-
|
|
31558
|
-
|
|
31559
|
-
|
|
31560
|
-
|
|
31561
|
-
|
|
31562
|
-
|
|
31563
|
-
|
|
31564
|
-
|
|
31565
|
-
return partials;
|
|
31566
|
-
};
|
|
31567
|
-
}
|
|
31568
|
-
|
|
31569
|
-
// GeographicLib docs: https://geographiclib.sourceforge.io/html/js/
|
|
31570
|
-
// https://geographiclib.sourceforge.io/html/js/module-GeographicLib_Geodesic.Geodesic.html
|
|
31571
|
-
// https://geographiclib.sourceforge.io/html/js/tutorial-2-interface.html
|
|
31572
|
-
function getGeodesic(P) {
|
|
31573
|
-
if (!isLatLngCRS(P)) error('Expected an unprojected CRS');
|
|
31574
|
-
var f = P.es / (1 + Math.sqrt(P.one_es));
|
|
31575
|
-
var GeographicLib = require$1('mproj').internal.GeographicLib;
|
|
31576
|
-
return new GeographicLib.Geodesic.Geodesic(P.a, f);
|
|
31577
|
-
}
|
|
31578
|
-
|
|
31579
|
-
function interpolatePoint2D(ax, ay, bx, by, k) {
|
|
31580
|
-
var j = 1 - k;
|
|
31581
|
-
return [ax * j + bx * k, ay * j + by * k];
|
|
31582
|
-
}
|
|
31583
|
-
|
|
31584
|
-
function getInterpolationFunction(P) {
|
|
31585
|
-
var spherical = P && isLatLngCRS(P);
|
|
31586
|
-
if (!spherical) return interpolatePoint2D;
|
|
31587
|
-
var geod = getGeodesic(P);
|
|
31588
|
-
return function(lng, lat, lng2, lat2, k) {
|
|
31589
|
-
var r = geod.Inverse(lat, lng, lat2, lng2);
|
|
31590
|
-
var dist = r.s12 * k;
|
|
31591
|
-
var r2 = geod.Direct(lat, lng, r.azi1, dist);
|
|
31592
|
-
return [r2.lon2, r2.lat2];
|
|
31593
|
-
};
|
|
31594
|
-
}
|
|
31595
|
-
|
|
31596
|
-
function getPlanarSegmentEndpoint(x, y, bearing, meterDist) {
|
|
31597
|
-
var rad = bearing / 180 * Math.PI;
|
|
31598
|
-
var dx = Math.sin(rad) * meterDist;
|
|
31599
|
-
var dy = Math.cos(rad) * meterDist;
|
|
31600
|
-
return [x + dx, y + dy];
|
|
31601
|
-
}
|
|
31602
|
-
|
|
31603
|
-
// source: https://github.com/mapbox/cheap-ruler/blob/master/index.js
|
|
31604
|
-
function fastGeodeticSegmentFunction(lng, lat, bearing, meterDist) {
|
|
31605
|
-
var D2R = Math.PI / 180;
|
|
31606
|
-
var cos = Math.cos(lat * D2R);
|
|
31607
|
-
var cos2 = 2 * cos * cos - 1;
|
|
31608
|
-
var cos3 = 2 * cos * cos2 - cos;
|
|
31609
|
-
var cos4 = 2 * cos * cos3 - cos2;
|
|
31610
|
-
var cos5 = 2 * cos * cos4 - cos3;
|
|
31611
|
-
var kx = (111.41513 * cos - 0.09455 * cos3 + 0.00012 * cos5) * 1000;
|
|
31612
|
-
var ky = (111.13209 - 0.56605 * cos2 + 0.0012 * cos4) * 1000;
|
|
31613
|
-
var bearingRad = bearing * D2R;
|
|
31614
|
-
var lat2 = lat + Math.cos(bearingRad) * meterDist / ky;
|
|
31615
|
-
var lng2 = lng + Math.sin(bearingRad) * meterDist / kx;
|
|
31616
|
-
return [lng2, lat2];
|
|
31617
|
-
}
|
|
31618
|
-
|
|
31619
|
-
function getGeodeticSegmentFunction(P) {
|
|
31620
|
-
if (!isLatLngCRS(P)) {
|
|
31621
|
-
return getPlanarSegmentEndpoint;
|
|
31622
|
-
}
|
|
31623
|
-
var g = getGeodesic(P);
|
|
31624
|
-
return function(lng, lat, bearing, meterDist) {
|
|
31625
|
-
var o = g.Direct(lat, lng, bearing, meterDist);
|
|
31626
|
-
var p = [o.lon2, o.lat2];
|
|
31627
|
-
return p;
|
|
32179
|
+
if (x2 == x0 && y2 == y0) {
|
|
32180
|
+
// add join to finish closed path
|
|
32181
|
+
joinAngle = getJoinAngle(bearing, firstBearing);
|
|
32182
|
+
if (joinAngle > 0) {
|
|
32183
|
+
addRoundJoin(left, x2, y2, bearing - 90, joinAngle, dist);
|
|
32184
|
+
}
|
|
32185
|
+
} else {
|
|
32186
|
+
// add a cap to finish open path
|
|
32187
|
+
left.push.apply(left, makeCap(x2, y2, bearing, dist));
|
|
32188
|
+
}
|
|
32189
|
+
return left;
|
|
31628
32190
|
};
|
|
31629
32191
|
}
|
|
31630
32192
|
|
|
31631
|
-
|
|
31632
|
-
|
|
31633
|
-
|
|
31634
|
-
|
|
31635
|
-
|
|
31636
|
-
|
|
31637
|
-
|
|
31638
|
-
|
|
31639
|
-
return geom.
|
|
31640
|
-
}
|
|
31641
|
-
|
|
31642
|
-
function bearingDegrees2D(a, b, c, d) {
|
|
31643
|
-
return geom.bearing2D(a, b, c, d) * 180 / Math.PI;
|
|
31644
|
-
}
|
|
31645
|
-
|
|
31646
|
-
// return function to calculate bearing of a segment in degrees
|
|
31647
|
-
function getBearingFunction(dataset) {
|
|
31648
|
-
var P = getDatasetCRS(dataset);
|
|
31649
|
-
return isLatLngCRS(P) ? bearingDegrees : bearingDegrees2D;
|
|
32193
|
+
// Exclude segments with non-intersecting bounding boxes before
|
|
32194
|
+
// calling intersection function
|
|
32195
|
+
// Possibly slightly faster than direct call... not worth it?
|
|
32196
|
+
function bufferIntersection(ax, ay, bx, by, cx, cy, dx, dy) {
|
|
32197
|
+
if (ax < cx && ax < dx && bx < cx && bx < dx ||
|
|
32198
|
+
ax > cx && ax > dx && bx > cx && bx > dx ||
|
|
32199
|
+
ay < cy && ay < dy && by < cy && by < dy ||
|
|
32200
|
+
ay > cy && ay > dy && by > cy && by > dy) return null;
|
|
32201
|
+
return geom.segmentIntersection(ax, ay, bx, by, cx, cy, dx, dy);
|
|
31650
32202
|
}
|
|
31651
32203
|
|
|
31652
|
-
var Geodesic = /*#__PURE__*/Object.freeze({
|
|
31653
|
-
__proto__: null,
|
|
31654
|
-
bearingDegrees: bearingDegrees,
|
|
31655
|
-
bearingDegrees2D: bearingDegrees2D,
|
|
31656
|
-
getBearingFunction: getBearingFunction,
|
|
31657
|
-
getFastGeodeticSegmentFunction: getFastGeodeticSegmentFunction,
|
|
31658
|
-
getGeodeticSegmentFunction: getGeodeticSegmentFunction,
|
|
31659
|
-
getInterpolationFunction: getInterpolationFunction,
|
|
31660
|
-
getPlanarSegmentEndpoint: getPlanarSegmentEndpoint,
|
|
31661
|
-
interpolatePoint2D: interpolatePoint2D
|
|
31662
|
-
});
|
|
31663
|
-
|
|
31664
32204
|
function makePolylineBuffer(lyr, dataset, opts) {
|
|
32205
|
+
time('buffer');
|
|
31665
32206
|
var geojson = makeShapeBufferGeoJSON(lyr, dataset, opts);
|
|
31666
32207
|
var dataset2 = importGeoJSON(geojson, {});
|
|
31667
|
-
|
|
32208
|
+
if (!opts.debug_points) {
|
|
32209
|
+
dissolveBufferDataset2(dataset2, opts);
|
|
32210
|
+
}
|
|
32211
|
+
timeEnd('buffer');
|
|
31668
32212
|
return dataset2;
|
|
31669
32213
|
}
|
|
31670
32214
|
|
|
31671
32215
|
function makeShapeBufferGeoJSON(lyr, dataset, opts) {
|
|
31672
32216
|
var distanceFn = getBufferDistanceFunction(lyr, dataset, opts);
|
|
31673
|
-
getBufferToleranceFunction(dataset, opts);
|
|
31674
|
-
var geod = getFastGeodeticSegmentFunction(getDatasetCRS(dataset));
|
|
31675
|
-
var getBearing = getBearingFunction(dataset);
|
|
32217
|
+
// var toleranceFn = getBufferToleranceFunction(dataset, opts);
|
|
31676
32218
|
var makerOpts = Object.assign({geometry_type: lyr.geometry_type}, opts);
|
|
31677
|
-
var
|
|
31678
|
-
|
|
31679
|
-
|
|
31680
|
-
|
|
31681
|
-
|
|
31682
|
-
|
|
31683
|
-
|
|
31684
|
-
|
|
32219
|
+
var makeShapeBuffer =
|
|
32220
|
+
opts.v2 && getPolylineBufferMaker$1(dataset, makerOpts) ||
|
|
32221
|
+
opts.v3 && getPolylineBufferMaker$2(dataset, makerOpts) ||
|
|
32222
|
+
getPolylineBufferMaker(dataset, makerOpts);
|
|
32223
|
+
// var records = lyr.data ? lyr.data.getRecords() : null;
|
|
32224
|
+
var arr = lyr.shapes.reduce(function(memo, shape, i) {
|
|
32225
|
+
var distance = distanceFn(i);
|
|
32226
|
+
if (!distance || !shape) return memo;
|
|
32227
|
+
// retn might be an array of features or a single feature
|
|
32228
|
+
var retn = makeShapeBuffer(shape, distance);
|
|
32229
|
+
return memo.concat(retn);
|
|
32230
|
+
}, []);
|
|
32231
|
+
var geojsonType = arr?.[0].type;
|
|
31685
32232
|
// TODO: make sure that importer supports null geometries (not standard GeoJSON);
|
|
31686
|
-
|
|
32233
|
+
// console.log(arr)
|
|
32234
|
+
return geojsonType == 'Feature' ? {
|
|
32235
|
+
type: 'FeatureCollection',
|
|
32236
|
+
features: arr
|
|
32237
|
+
} : {
|
|
31687
32238
|
type: 'GeometryCollection',
|
|
31688
|
-
geometries:
|
|
32239
|
+
geometries: arr
|
|
31689
32240
|
};
|
|
31690
32241
|
}
|
|
31691
32242
|
|
|
@@ -32137,8 +32688,7 @@ ${svg}
|
|
|
32137
32688
|
stop("Unsupported geometry type");
|
|
32138
32689
|
}
|
|
32139
32690
|
|
|
32140
|
-
|
|
32141
|
-
return [lyr2];
|
|
32691
|
+
return mergeOutputLayersIntoDataset(lyr, dataset, dataset2, opts);
|
|
32142
32692
|
}
|
|
32143
32693
|
|
|
32144
32694
|
// currently undocumented, used in tests
|
|
@@ -36789,9 +37339,6 @@ ${svg}
|
|
|
36789
37339
|
getLayerInfo: getLayerInfo
|
|
36790
37340
|
});
|
|
36791
37341
|
|
|
36792
|
-
// import { importGeoJSON } from '../geojson/geojson-import';
|
|
36793
|
-
|
|
36794
|
-
|
|
36795
37342
|
function addTargetProxies(targets, ctx) {
|
|
36796
37343
|
if (targets && targets.length > 0) {
|
|
36797
37344
|
var proxies = expandCommandTargets(targets).reduce(function(memo, target) {
|
|
@@ -36814,11 +37361,19 @@ ${svg}
|
|
|
36814
37361
|
var proxy = getLayerInfo(target.layer, target.dataset); // layer_name, feature_count etc
|
|
36815
37362
|
proxy.layer = target.layer;
|
|
36816
37363
|
proxy.dataset = target.dataset;
|
|
36817
|
-
|
|
36818
|
-
|
|
36819
|
-
|
|
37364
|
+
|
|
37365
|
+
Object.defineProperty(proxy, 'geojson', {
|
|
37366
|
+
set: setGeoJSON,
|
|
37367
|
+
get: getGeoJSON
|
|
36820
37368
|
});
|
|
36821
37369
|
|
|
37370
|
+
function setGeoJSON(o) {
|
|
37371
|
+
var dataset2 = importGeoJSON(o);
|
|
37372
|
+
var lyr2 = dataset2.layers[0];
|
|
37373
|
+
lyr2.name = target.layer.name;
|
|
37374
|
+
replaceLayerContents(target.layer, target.dataset, dataset2);
|
|
37375
|
+
}
|
|
37376
|
+
|
|
36822
37377
|
function getGeoJSON() {
|
|
36823
37378
|
var features = exportLayerAsGeoJSON(target.layer, target.dataset, {rfc7946: true}, true);
|
|
36824
37379
|
return {
|
|
@@ -41337,7 +41892,7 @@ ${svg}
|
|
|
41337
41892
|
function findPathMidpoint(path, arcs, useNearestVertex) {
|
|
41338
41893
|
var halfLen = calcPathLen(path, arcs, false) / 2;
|
|
41339
41894
|
var partialLen = 0;
|
|
41340
|
-
var p;
|
|
41895
|
+
var p = null;
|
|
41341
41896
|
forEachSegmentInPath(path, arcs, function(i, j, xx, yy) {
|
|
41342
41897
|
var a = xx[i],
|
|
41343
41898
|
b = yy[i],
|
|
@@ -41357,7 +41912,7 @@ ${svg}
|
|
|
41357
41912
|
partialLen += segLen;
|
|
41358
41913
|
});
|
|
41359
41914
|
if (!p) {
|
|
41360
|
-
|
|
41915
|
+
debug('[findPathMidpoint()] defective path:', path);
|
|
41361
41916
|
}
|
|
41362
41917
|
return p;
|
|
41363
41918
|
}
|
|
@@ -46293,7 +46848,7 @@ ${svg}
|
|
|
46293
46848
|
});
|
|
46294
46849
|
}
|
|
46295
46850
|
|
|
46296
|
-
var version = "0.6.
|
|
46851
|
+
var version = "0.6.113";
|
|
46297
46852
|
|
|
46298
46853
|
// Parse command line args into commands and run them
|
|
46299
46854
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -46896,67 +47451,6 @@ ${svg}
|
|
|
46896
47451
|
return false;
|
|
46897
47452
|
}
|
|
46898
47453
|
|
|
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
47454
|
// Attach functions exported by modules to the "internal" object,
|
|
46961
47455
|
// so they can be run by tests and by the GUI.
|
|
46962
47456
|
// TODO: rewrite tests to import functions directly from modules,
|
|
@@ -47003,7 +47497,7 @@ ${svg}
|
|
|
47003
47497
|
ArcUtils,
|
|
47004
47498
|
Bbox2Clipping,
|
|
47005
47499
|
BinArray$1,
|
|
47006
|
-
BufferCommon,
|
|
47500
|
+
// BufferCommon,
|
|
47007
47501
|
Calc,
|
|
47008
47502
|
CalcUtils,
|
|
47009
47503
|
Catalog$1,
|
|
@@ -47058,7 +47552,7 @@ ${svg}
|
|
|
47058
47552
|
OverlayUtils,
|
|
47059
47553
|
Pack, Unpack,
|
|
47060
47554
|
ParseCommands,
|
|
47061
|
-
PathBuffer,
|
|
47555
|
+
// PathBuffer,
|
|
47062
47556
|
PathEndpoints,
|
|
47063
47557
|
PathExport,
|
|
47064
47558
|
Pathfinder,
|