Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/turf-buffer/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Units } from "@turf/helpers";
interface Options {
units?: Units;
steps?: number;
endCapStyle?: "round" | "flat" | "butt" | "square";
}

/**
Expand Down
56 changes: 50 additions & 6 deletions packages/turf-buffer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ function buffer(geojson, radius, options) {
// use user supplied options or default values
var units = options.units || "kilometers";
var steps = options.steps || 8;
var endCapStyle = options.endCapStyle || "round";

// validation
if (!geojson) throw new Error("geojson is required");
Expand All @@ -56,13 +57,25 @@ 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
);
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
);
if (multiBuffered) {
featureEach(multiBuffered, function (buffered) {
if (buffered) results.push(buffered);
Expand All @@ -71,7 +84,7 @@ function buffer(geojson, radius, options) {
});
return featureCollection(results);
}
return bufferFeature(geojson, radius, units, steps);
return bufferFeature(geojson, radius, units, steps, endCapStyle);
}

/**
Expand All @@ -82,22 +95,28 @@ function buffer(geojson, radius, options) {
* @param {number} radius distance to draw the buffer
* @param {Units} [units='kilometers'] Supports all valid Turf {@link https://turfjs.org/docs/api/types/Units Units}.
* @param {number} [steps=8] number of steps
* @param {'round'|'flat'|'butt'|'square'} [endCapStyle='round'] end cap style
* @returns {Feature<Polygon|MultiPolygon>} buffered feature
*/
function bufferFeature(geojson, radius, units, steps) {
function bufferFeature(geojson, radius, units, steps, endCapStyle) {
var properties = geojson.properties || {};
var geometry = geojson.type === "Feature" ? geojson.geometry : geojson;

// Geometry Types faster than jsts
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);
if (buffered) results.push(buffered);
});
return featureCollection(results);
}

// For point-type geometries, set the endCapStyle to undefined since they should not be affected by end cap styles
if (geometry.type === "Point" || geometry.type === "MultiPoint") {
endCapStyle = undefined;
}

// Project GeoJSON to Azimuthal Equidistant projection (convert to Meters)
var projection = defineProjection(geometry);
var projected = {
Expand All @@ -109,7 +128,32 @@ 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 buffered;

// Apply endCapStyle if valid - points are always round and polygons ignore endCapStyle
if (endCapStyle) {
var CAP_STYLE_MAP = {
round: BufferOp.CAP_ROUND,
flat: BufferOp.CAP_FLAT,
butt: BufferOp.CAP_BUTT,
square: BufferOp.CAP_SQUARE,
};

var capStyle = CAP_STYLE_MAP[endCapStyle];
if (capStyle === undefined) {
throw new Error(
"Invalid endCapStyle: " +
endCapStyle +
" (expected one of 'round','flat','butt','square')"
);
}
// Use the 4-arg overload when endCapStyle is specified
buffered = BufferOp.bufferOp(geom, distance, steps, capStyle);
} else {
// Otherwise use the 3-arg overload (like originally implemented)
buffered = BufferOp.bufferOp(geom, distance, steps);
}
var writer = new GeoJSONWriter();
buffered = writer.write(buffered);

Expand Down
3 changes: 2 additions & 1 deletion packages/turf-buffer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"contributors": [
"Tom MacWright <@tmcw>",
"Denis Carriere <@DenisCarriere>",
"Stefano Borghi <@stebogit>"
"Stefano Borghi <@stebogit>",
"Ryan Pimiskern <@plymer>"
],
"license": "MIT",
"bugs": {
Expand Down
97 changes: 97 additions & 0 deletions packages/turf-buffer/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { writeJsonFileSync } from "write-json-file";
import { truncate } from "@turf/truncate";
import { featureEach } from "@turf/meta";
import {
lineString,
featureCollection,
point,
polygon,
Expand Down Expand Up @@ -182,6 +183,102 @@ test("turf-buffer - undefined return", (t) => {
t.end();
});

test("turf-buffer - endcap styles", (t) => {
const pt = point([-97, 49.8]);

const pointFc = featureCollection([pt]);

const pointDefault = buffer(pointFc, 10, { units: "miles" });
const pointRound = buffer(pointFc, 10, {
units: "miles",
endCapStyle: "round",
});
const pointButt = buffer(pointFc, 10, {
units: "miles",
endCapStyle: "butt",
});

t.deepEqual(
pointDefault,
pointRound,
"point - default and round produce the same result"
);
t.deepEqual(
pointDefault,
pointButt,
"point - buffers are not affected by end cap style"
);

const poly = polygon([
[
[11, 0],
[22, 4],
[31, 0],
[31, 11],
[21, 15],
[11, 11],
[11, 0],
],
]);

const polyDefault = buffer(poly, 10, { units: "miles" });
const polyFlat = buffer(poly, 10, {
units: "miles",
endCapStyle: "flat",
});
t.deepEqual(
polyDefault,
polyFlat,
"polygon - buffers are not affected by end cap style"
);

const ln = lineString([
[-113.5, 53.5],
[-114, 51.1],
[-97, 49.8],
]);

const lineDefault = buffer(ln, 10, { units: "miles" });
const lineRound = buffer(ln, 10, {
units: "miles",
endCapStyle: "round",
});
const lineFlat = buffer(ln, 10, { units: "miles", endCapStyle: "flat" });
const lineButt = buffer(ln, 10, { units: "miles", endCapStyle: "butt" });
const lineSquare = buffer(ln, 10, {
units: "miles",
endCapStyle: "square",
});

t.deepEqual(
lineDefault,
lineRound,
"line - default and round produce the same result"
);
t.deepEqual(
lineFlat,
lineButt,
"line - flat and butt produce the same result"
);
t.isNotDeepEqual(
lineRound,
lineFlat,
"line - round and flat produce different results"
);
t.isNotDeepEqual(
lineRound,
lineSquare,
"line - round and square produce different results"
);
t.isNotDeepEqual(
lineSquare,
lineFlat,
"line - square and flat produce different results"
);

t.end();
});

function colorize(feature, color) {
color = color || "#F00";
if (feature.properties) {
Expand Down