turf icon indicating copy to clipboard operation
turf copied to clipboard

buffer endcap and join strategies

Open dhivehi opened this issue 7 years ago • 5 comments

Currently turf buffer seems to support "end_round" as end strategy and "join_round" as join strategy. It would be nice if your can provide the strategies as follows:

  • For join strategies, 'join_round' and 'join_miter'.
  • For end strategies, 'end_round' and 'end_flat'.

More ref: https://dev.mysql.com/doc/refman/5.7/en/spatial-operator-functions.html#function_st-buffer-strategy

dhivehi avatar Apr 02 '17 16:04 dhivehi

Thanks for the suggestions @dhivehi

Currently the buffer module is a wrapper around jsts and we're trying to remove this dependency. Once we've done that hopefully we'll be in a better position to implement those additional suggestions.

Cheers

rowanwins avatar Apr 05 '17 12:04 rowanwins

:+1:

nboisteault avatar Jun 20 '17 08:06 nboisteault

Are there any plans for incorporating this into turf? I recently did this by using mainstream jsts instead of the turf ES6 fork of JSTS, and passing in the endCap here:

  // 2 is endCap flat
  let buffered = BufferOp.bufferOp(geom, distance, undefined, 2);

but it required copying some of the turf buffer code to achieve it.

Any guidance appreciated. Are you open for PRs to change to mainstream jsts and add this to the buffer operation? Or what would be a good strategy?

bartvde avatar Nov 01 '19 07:11 bartvde

@bartvde long shot but are you able to remember how using jsts went? I'm really needing a mitre join buffer in js and this is looking like one of the best options

oli-clive-griffin avatar Jun 08 '23 00:06 oli-clive-griffin

For those wanting to use this right now using jsts, you can use patch-package to add the required changes to 6.5.0 until support for this gets added.

Below is the patch file I used. With these changes you can run :

buffer(feature, 100, { units: 'meters', joinStyle: 'mitre' })

The documentation for the parameters can be found in the docs for the java library https://locationtech.github.io/jts/javadoc/. I'm unsure what the purpose of the steps parameter was in the original version, but I used it as the 'Quadrant segments' parameter in BufferParameters.

diff --git a/node_modules/@turf/buffer/dist/es/index.js b/node_modules/@turf/buffer/dist/es/index.js
index 0ba5731..efe8a35 100644
--- a/node_modules/@turf/buffer/dist/es/index.js
+++ b/node_modules/@turf/buffer/dist/es/index.js
@@ -1,9 +1,22 @@
 import center from '@turf/center';
 import { GeoJSONReader, BufferOp, GeoJSONWriter } from 'turf-jsts';
+import { BufferParameters } from 'turf-jsts/src/org/locationtech/jts/operation/buffer';
 import { featureEach, geomEach } from '@turf/meta';
 import { geoAzimuthalEquidistant } from 'd3-geo';
 import { featureCollection, earthRadius, radiansToLength, lengthToRadians, feature } from '@turf/helpers';
 
+const endCap = {
+  round: BufferParameters.CAP_ROUND,
+  flat: BufferParameters.CAP_FLAT,
+  square: BufferParameters.CAP_SQUARE,
+};
+
+const join = {
+  round: BufferParameters.JOIN_ROUND,
+  mitre: BufferParameters.JOIN_MITRE,
+  bevel: BufferParameters.JOIN_BEVEL,
+};
+
 /**
  * Calculates a buffer for input features for a given radius. Units supported are miles, kilometers, and degrees.
  *
@@ -19,6 +32,9 @@ import { featureCollection, earthRadius, radiansToLength, lengthToRadians, featu
  * @param {Object} [options={}] Optional parameters
  * @param {string} [options.units="kilometers"] any of the options supported by turf units
  * @param {number} [options.steps=8] number of steps
+ * @param {string} [options.endCapStyle="round"] the end cap style to use
+ * @param {string} [options.joinStyle="round"] the join style to use
+ * @param {number} [options.mitreLimit=10] the limit on the mitre ratio used for very sharp corners
  * @returns {FeatureCollection|Feature<Polygon|MultiPolygon>|undefined} buffered features
  * @example
  * var point = turf.point([-90.548630, 14.616599]);
@@ -34,6 +50,9 @@ function buffer(geojson, radius, options) {
   // use user supplied options or default values
   var units = options.units || "kilometers";
   var steps = options.steps || 8;
+  var endCapStyle = endCap[options.endCapStyle || "round"];
+  var joinStyle = join[options.joinStyle || "round"];
+  var mitreLimit = options.mitreLimit || 10;
 
   // validation
   if (!geojson) throw new Error("geojson is required");
@@ -48,13 +67,13 @@ function buffer(geojson, radius, options) {
   switch (geojson.type) {
     case "GeometryCollection":
       geomEach(geojson, function (geometry) {
-        var buffered = bufferFeature(geometry, radius, units, steps);
+        var buffered = bufferFeature(geometry, radius, units, steps, endCapStyle, joinStyle, mitreLimit);
         if (buffered) results.push(buffered);
       });
       return featureCollection(results);
     case "FeatureCollection":
       featureEach(geojson, function (feature) {
-        var multiBuffered = bufferFeature(feature, radius, units, steps);
+        var multiBuffered = bufferFeature(feature, radius, units, steps, endCapStyle, joinStyle, mitreLimit);
         if (multiBuffered) {
           featureEach(multiBuffered, function (buffered) {
             if (buffered) results.push(buffered);
@@ -63,7 +82,7 @@ function buffer(geojson, radius, options) {
       });
       return featureCollection(results);
   }
-  return bufferFeature(geojson, radius, units, steps);
+  return bufferFeature(geojson, radius, units, steps, endCapStyle, joinStyle, mitreLimit);
 }
 
 /**
@@ -76,7 +95,7 @@ function buffer(geojson, radius, options) {
  * @param {number} [steps=8] number of steps
  * @returns {Feature<Polygon|MultiPolygon>} buffered feature
  */
-function bufferFeature(geojson, radius, units, steps) {
+function bufferFeature(geojson, radius, units, steps, endCapStyle, joinStyle, mitreLimit) {
   var properties = geojson.properties || {};
   var geometry = geojson.type === "Feature" ? geojson.geometry : geojson;
 
@@ -84,7 +103,7 @@ function bufferFeature(geojson, radius, units, steps) {
   if (geometry.type === "GeometryCollection") {
     var results = [];
     geomEach(geojson, function (geometry) {
-      var buffered = bufferFeature(geometry, radius, units, steps);
+      var buffered = bufferFeature(geometry, radius, units, steps, endCapStyle, joinStyle, mitreLimit);
       if (buffered) results.push(buffered);
     });
     return featureCollection(results);
@@ -101,7 +120,9 @@ function bufferFeature(geojson, radius, units, steps) {
   var reader = new GeoJSONReader();
   var geom = reader.read(projected);
   var distance = radiansToLength(lengthToRadians(radius, units), "meters");
-  var buffered = BufferOp.bufferOp(geom, distance, steps);
+  var bufferParams = new BufferParameters(steps, endCapStyle, joinStyle, mitreLimit);
+  var bufferOp = new BufferOp(geom, bufferParams);
+  var buffered = bufferOp.getResultGeometry(distance);
   var writer = new GeoJSONWriter();
   buffered = writer.write(buffered);
 
diff --git a/node_modules/@turf/buffer/index.d.ts b/node_modules/@turf/buffer/index.d.ts
index 825769b..551ad90 100644
--- a/node_modules/@turf/buffer/index.d.ts
+++ b/node_modules/@turf/buffer/index.d.ts
@@ -15,6 +15,9 @@ import {
 interface Options {
   units?: Units;
   steps?: number;
+  endCapStyle?: "round" | "flat" | "square";
+  joinStyle?: "round" | "mitre" | "bevel";
+  mitreLimit?: number;
 }
 
 /**

axekan avatar Apr 24 '24 15:04 axekan