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/www/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
- first == "'" && last == "'" && !str.includes("','")) {
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
- if (!dataset2 || dataset2.layers.length != 1) {
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
- if (opts.no_replace) ; else {
7559
- lyr2 = Object.assign(lyr, {data: null, shapes: null}, lyr2);
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', { // use v2 method
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-division', {
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
- var nodes = addIntersectionCuts(dataset, {});
30933
- if (opts.debug_division) {
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
- var BufferCommon = /*#__PURE__*/Object.freeze({
31037
- __proto__: null,
31038
- dissolveBufferDataset: dissolveBufferDataset,
31039
- getArcDegreesFromTolerancePct: getArcDegreesFromTolerancePct,
31040
- getArcDegreesFromTolerancePct2: getArcDegreesFromTolerancePct2,
31041
- getBufferDistanceFunction: getBufferDistanceFunction,
31042
- getBufferToleranceFromCircleSegments: getBufferToleranceFromCircleSegments,
31043
- getBufferToleranceFromCircleSegments2: getBufferToleranceFromCircleSegments2,
31044
- getBufferToleranceFunction: getBufferToleranceFunction,
31045
- parseConstantBufferDistance: parseConstantBufferDistance
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
- dissolveBufferDataset(dataset2, opts);
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 factory = opts.v2 ? getPolylineBufferMaker2 : getPolylineBufferMaker;
31678
- var makeShapeBuffer = factory(dataset.arcs, geod, getBearing, makerOpts);
31679
- lyr.data ? lyr.data.getRecords() : null;
31680
- var geometries = lyr.shapes.map(function(shape, i) {
31681
- var dist = distanceFn(i);
31682
- if (!dist || !shape) return null;
31683
- return makeShapeBuffer(shape, dist, lyr.geometry_type);
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
- return {
32044
+ // console.log(arr)
32045
+ return geojsonType == 'Feature' ? {
32046
+ type: 'FeatureCollection',
32047
+ features: arr
32048
+ } : {
31687
32049
  type: 'GeometryCollection',
31688
- geometries: 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
- var lyr2 = mergeOutputLayerIntoDataset(lyr, dataset, dataset2, opts);
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
- addGetters(proxy, {
36818
- // export as an object, not a string or buffer
36819
- geojson: getGeoJSON
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
- error('Geometry error');
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.111";
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,