Skip to content
Closed
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
6 changes: 3 additions & 3 deletions packages/turf-buffer/bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ const fixtures = fs.readdirSync(directory).map((filename) => {
const suite = new Benchmark.Suite("turf-buffer");
for (const { name, geojson } of fixtures) {
console.time(name);
buffer(geojson, 50, { units: "miles" });
buffer(geojson as any, 50, { units: "miles" });
console.timeEnd(name);
suite.add(name, () => buffer(geojson, 50, { units: "miles" }));
suite.add(name, () => buffer(geojson as any, 50, { units: "miles" }));
}

suite.on("cycle", (e) => console.log(String(e.target))).run();
suite.on("cycle", (e: any) => console.log(String(e.target))).run();
42 changes: 0 additions & 42 deletions packages/turf-buffer/index.d.ts

This file was deleted.

134 changes: 99 additions & 35 deletions packages/turf-buffer/index.js → packages/turf-buffer/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import { center } from "@turf/center";
import jsts from "@turf/jsts";
import { geomEach, featureEach } from "@turf/meta";
import { geoAzimuthalEquidistant } from "d3-geo";
import { geoAzimuthalEquidistant, GeoProjection } from "d3-geo";
import {
feature,
featureCollection,
radiansToLength,
lengthToRadians,
earthRadius,
Units,
AllGeoJSON,
} from "@turf/helpers";
import {
Feature,
FeatureCollection,
Geometry,
GeometryCollection,
MultiPolygon,
Polygon,
} from "geojson";

const { BufferOp, GeoJSONReader, GeoJSONWriter } = jsts;

Expand All @@ -35,31 +45,49 @@ const { BufferOp, GeoJSONReader, GeoJSONWriter } = jsts;
* //addToMap
* var addToMap = [point, buffered]
*/
function buffer(geojson, radius, options) {
// Optional params
options = options || {};

// use user supplied options or default values
var units = options.units || "kilometers";
var steps = options.steps || 8;

function buffer<T extends Geometry | Feature | FeatureCollection>(
geojson: T,
radius?: number,
options: {
units?: Units;
steps?: number;
} = {}
): T extends FeatureCollection | GeometryCollection
? FeatureCollection<Polygon | MultiPolygon> | undefined
: Feature<Polygon | MultiPolygon> | undefined {
// validation
if (!geojson) throw new Error("geojson is required");
if (typeof options !== "object") throw new Error("options must be an object");

// use user supplied options or default values
const { units = "kilometers", steps = 8 } = options;

if (typeof steps !== "number") throw new Error("steps must be an number");

// Allow negative buffers ("erosion") or zero-sized buffers ("repair geometry")
if (radius === undefined) throw new Error("radius is required");
if (steps <= 0) throw new Error("steps must be greater than 0");

var results = [];
var results: Feature<Polygon | MultiPolygon>[] = [];
switch (geojson.type) {
case "GeometryCollection":
geomEach(geojson, function (geometry) {
var buffered = bufferFeature(geometry, radius, units, steps);
if (buffered) results.push(buffered);
if (buffered) {
if (buffered.type === "Feature") {
results.push(buffered);
} else {
for (const feature of buffered.features) {
results.push(feature);
}
}
}
});
return featureCollection(results);
return featureCollection(results) as T extends
| FeatureCollection
| GeometryCollection
? FeatureCollection<Polygon | MultiPolygon> | undefined
: Feature<Polygon | MultiPolygon> | undefined;
case "FeatureCollection":
featureEach(geojson, function (feature) {
var multiBuffered = bufferFeature(feature, radius, units, steps);
Expand All @@ -69,9 +97,17 @@ function buffer(geojson, radius, options) {
});
}
});
return featureCollection(results);
return featureCollection(results) as T extends
| FeatureCollection
| GeometryCollection
? FeatureCollection<Polygon | MultiPolygon> | undefined
: Feature<Polygon | MultiPolygon> | undefined;
}
return bufferFeature(geojson, radius, units, steps);
return bufferFeature(geojson, radius, units, steps) as T extends
| FeatureCollection
| GeometryCollection
? FeatureCollection<Polygon | MultiPolygon> | undefined
: Feature<Polygon | MultiPolygon> | undefined;
}

/**
Expand All @@ -84,25 +120,47 @@ function buffer(geojson, radius, options) {
* @param {number} [steps=8] number of steps
* @returns {Feature<Polygon|MultiPolygon>} buffered feature
*/
function bufferFeature(geojson, radius, units, steps) {
var properties = geojson.properties || {};
function bufferFeature<T extends AllGeoJSON>(
geojson: T,
radius: number,
units: Units,
steps: number
):
| (T extends GeometryCollection
? FeatureCollection<Polygon | MultiPolygon>
: Feature<Polygon | MultiPolygon>)
| undefined {
var properties = (geojson as Feature).properties ?? {};
var geometry = geojson.type === "Feature" ? geojson.geometry : geojson;

// Geometry Types faster than jsts
if (geometry.type === "GeometryCollection") {
var results = [];
var results: Feature<Polygon | MultiPolygon>[] = [];
geomEach(geojson, function (geometry) {
var buffered = bufferFeature(geometry, radius, units, steps);
if (buffered) results.push(buffered);
if (buffered) {
if (buffered.type === "Feature") {
results.push(buffered);
} else {
for (const feature of buffered.features) {
results.push(feature);
}
}
}
});
return featureCollection(results);
return featureCollection(results) as T extends GeometryCollection
? FeatureCollection<Polygon | MultiPolygon>
: Feature<Polygon | MultiPolygon>;
}

// Project GeoJSON to Azimuthal Equidistant projection (convert to Meters)
var projection = defineProjection(geometry);
var projected = {
type: geometry.type,
coordinates: projectCoords(geometry.coordinates, projection),
coordinates: projectCoords(
(geometry as Exclude<Geometry, GeometryCollection>).coordinates,
projection
),
};

// JSTS buffer operation
Expand All @@ -111,18 +169,20 @@ function bufferFeature(geojson, radius, units, steps) {
var distance = radiansToLength(lengthToRadians(radius, units), "meters");
var buffered = BufferOp.bufferOp(geom, distance, steps);
var writer = new GeoJSONWriter();
buffered = writer.write(buffered);
const bufferResult = writer.write(buffered) as Polygon | MultiPolygon;

// Detect if empty geometries
if (coordsIsNaN(buffered.coordinates)) return undefined;
if (coordsIsNaN(bufferResult.coordinates)) return undefined;

// Unproject coordinates (convert to Degrees)
var result = {
type: buffered.type,
coordinates: unprojectCoords(buffered.coordinates, projection),
};
type: bufferResult.type,
coordinates: unprojectCoords(bufferResult.coordinates, projection),
} as Polygon | MultiPolygon;

return feature(result, properties);
return feature(result, properties) as T extends GeometryCollection
? FeatureCollection<Polygon | MultiPolygon>
: Feature<Polygon | MultiPolygon>;
}

/**
Expand All @@ -132,7 +192,7 @@ function bufferFeature(geojson, radius, units, steps) {
* @param {Array<any>} coords GeoJSON Coordinates
* @returns {boolean} if NaN exists
*/
function coordsIsNaN(coords) {
function coordsIsNaN(coords: any[]) {
if (Array.isArray(coords[0])) return coordsIsNaN(coords[0]);
return isNaN(coords[0]);
}
Expand All @@ -145,11 +205,13 @@ function coordsIsNaN(coords) {
* @param {GeoProjection} proj D3 Geo Projection
* @returns {Array<any>} projected coordinates
*/
function projectCoords(coords, proj) {
if (typeof coords[0] !== "object") return proj(coords);
function projectCoords<C extends any[]>(coords: C, proj: GeoProjection): C {
if (typeof coords[0] !== "object") {
return proj(coords as unknown as [number, number]) as any;
}
return coords.map(function (coord) {
return projectCoords(coord, proj);
});
}) as C;
}

/**
Expand All @@ -160,11 +222,13 @@ function projectCoords(coords, proj) {
* @param {GeoProjection} proj D3 Geo Projection
* @returns {Array<any>} un-projected coordinates
*/
function unprojectCoords(coords, proj) {
if (typeof coords[0] !== "object") return proj.invert(coords);
function unprojectCoords<C extends any[]>(coords: C, proj: GeoProjection): C {
if (typeof coords[0] !== "object") {
return proj.invert!(coords as unknown as [number, number]) as any;
}
return coords.map(function (coord) {
return unprojectCoords(coord, proj);
});
}) as C;
}

/**
Expand All @@ -174,9 +238,9 @@ function unprojectCoords(coords, proj) {
* @param {Geometry|Feature<any>} geojson Base projection on center of GeoJSON
* @returns {GeoProjection} D3 Geo Azimuthal Equidistant Projection
*/
function defineProjection(geojson) {
function defineProjection(geojson: AllGeoJSON): GeoProjection {
var coords = center(geojson).geometry.coordinates;
var rotation = [-coords[0], -coords[1]];
var rotation: [number, number] = [-coords[0], -coords[1]];
return geoAzimuthalEquidistant().rotate(rotation).scale(earthRadius);
}

Expand Down
4 changes: 3 additions & 1 deletion packages/turf-buffer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"tape": "^5.9.0",
"tsup": "^8.4.0",
"tsx": "^4.19.4",
"typescript": "^5.8.3",
"write-json-file": "^6.0.0"
},
"dependencies": {
Expand All @@ -78,6 +79,7 @@
"@turf/meta": "workspace:*",
"@turf/projection": "workspace:*",
"@types/geojson": "^7946.0.10",
"d3-geo": "1.7.1"
"d3-geo": "1.7.1",
"tslib": "^2.8.1"
}
}
11 changes: 6 additions & 5 deletions packages/turf-buffer/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
geometryCollection,
} from "@turf/helpers";
import { buffer } from "./index.js";
import { Feature, Point, Polygon } from "geojson";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

Expand All @@ -34,14 +35,14 @@ test("turf-buffer", (t) => {
fixtures.forEach((fixture) => {
const filename = fixture.filename;
const name = fixture.name;
const geojson = fixture.geojson;
const properties = geojson.properties || {};
const geojson = fixture.geojson as any;
const properties = (geojson as Feature).properties || {};
const radius = properties.radius || 50;
const units = properties.units || "miles";
const steps = properties.steps;

const buffered = truncate(
buffer(geojson, radius, { units: units, steps: steps })
buffer(geojson, radius, { units: units, steps: steps })!
);

// Add Results to FeatureCollection
Expand Down Expand Up @@ -112,7 +113,7 @@ test("turf-buffer - Prevent Input Mutation", (t) => {
[11, 0],
],
]);
const collection = featureCollection([pt, poly]);
const collection = featureCollection<Point | Polygon>([pt, poly]);

const beforePt = JSON.parse(JSON.stringify(pt));
const beforePoly = JSON.parse(JSON.stringify(poly));
Expand Down Expand Up @@ -182,7 +183,7 @@ test("turf-buffer - undefined return", (t) => {
t.end();
});

function colorize(feature, color) {
function colorize(feature: Feature, color: string) {
color = color || "#F00";
if (feature.properties) {
feature.properties.stroke = color;
Expand Down
Loading
Loading