Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved/fixed @turf/buffer using equidistant buffers #718

Merged
merged 2 commits into from
May 8, 2017
Merged
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
4 changes: 1 addition & 3 deletions packages/turf-buffer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ var point = {
"coordinates": [-90.548630, 14.616599]
}
};
var unit = 'miles';

var buffered = turf.buffer(point, 500, unit);
var buffered = turf.buffer(point, 500, 'miles');

//addToMap
var addToMap = [point, buffered]
Expand Down
29 changes: 13 additions & 16 deletions packages/turf-buffer/bench.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const Benchmark = require('benchmark');
const path = require('path');
const fs = require('fs');
const path = require('path');
const load = require('load-json-file');
const Benchmark = require('benchmark');
const buffer = require('./');

const directory = path.join(__dirname, 'test', 'in') + path.sep;
Expand All @@ -16,23 +16,20 @@ const fixtures = fs.readdirSync(directory).map(filename => {
/**
* Benchmark Results
*
* feature-collection-points x 9,335 ops/sec ±1.37% (88 runs sampled)
* geometry-collection-points x 9,505 ops/sec ±1.57% (86 runs sampled)
* linestring x 7,977 ops/sec ±17.93% (75 runs sampled)
* multi-linestring x 1,371 ops/sec ±2.00% (88 runs sampled)
* multi-point x 515 ops/sec ±3.35% (85 runs sampled)
* multi-polygon x 2,549 ops/sec ±3.03% (88 runs sampled)
* north-latitude-points x 812 ops/sec ±2.31% (88 runs sampled)
* point x 42,867 ops/sec ±1.38% (89 runs sampled)
* polygon-with-holes x 7,397 ops/sec ±1.93% (88 runs sampled)
* feature-collection-points x 1,205 ops/sec ±11.92% (72 runs sampled)
* geometry-collection-points x 2,102 ops/sec ±4.49% (78 runs sampled)
* linestring x 3,434 ops/sec ±3.17% (80 runs sampled)
* multi-linestring x 675 ops/sec ±2.89% (85 runs sampled)
* multi-point x 2,077 ops/sec ±5.69% (73 runs sampled)
* multi-polygon x 1,120 ops/sec ±5.97% (80 runs sampled)
* north-latitude-points x 1,649 ops/sec ±2.09% (86 runs sampled)
* northern-polygon x 4,658 ops/sec ±3.08% (78 runs sampled)
* point x 65,020 ops/sec ±1.29% (85 runs sampled)
* polygon-with-holes x 2,795 ops/sec ±2.98% (81 runs sampled)
*/
const suite = new Benchmark.Suite('turf-buffer');
for (const {name, geojson} of fixtures) {
let {radius, units, padding} = geojson.properties || {};
radius = radius || 50;
units = units || 'miles';

suite.add(name, () => buffer(geojson, radius, units, padding));
suite.add(name, () => buffer(geojson, 50, 'miles'));
}

suite
Expand Down
145 changes: 99 additions & 46 deletions packages/turf-buffer/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
var d3 = require('d3-geo');
var jsts = require('jsts');
var helpers = require('@turf/helpers');
var circle = require('@turf/circle');
var dissolve = require('@turf/dissolve');
var meta = require('@turf/meta');
var coordEach = meta.coordEach;
var circle = require('@turf/circle');
var center = require('@turf/center');
var helpers = require('@turf/helpers');
var feature = helpers.feature;
var geomEach = meta.geomEach;
var featureEach = meta.featureEach;
var featureCollection = helpers.featureCollection;
var distanceToDegrees = helpers.distanceToDegrees;
var point = helpers.point;
var radiansToDistance = helpers.radiansToDistance;
var distanceToRadians = helpers.distanceToRadians;

/**
* Calculates a buffer for input features for a given radius. Units supported are miles, kilometers, and degrees.
Expand All @@ -27,76 +29,127 @@ var point = helpers.point;
* "coordinates": [-90.548630, 14.616599]
* }
* };
* var unit = 'miles';
*
* var buffered = turf.buffer(point, 500, unit);
* var buffered = turf.buffer(point, 500, 'miles');
*
* //addToMap
* var addToMap = [point, buffered]
*/

module.exports = function (geojson, radius, units, steps) {
// validation
if (radius === undefined || radius === null) throw new Error('radius is required');
if (!geojson) throw new Error('geojson is required');
if (!radius) throw new Error('radius is required');
if (radius <= 0) throw new Error('radius must be greater than 0');
if (steps <= 0) throw new Error('steps must be greater than 0');

// prevent input mutation
// geojson = JSON.parse(JSON.stringify(geojson));

// default params
steps = steps || 64;
units = units || 'kilometers';

var results = [];
switch (geojson.type) {
case 'GeometryCollection':
geomEach(geojson, function (geometry) {
results.push(buffer(geometry, radius, units, steps));
});
return featureCollection(results);
case 'FeatureCollection':
var results = [];
var features = (geojson.features) ? geojson.features : geojson.geometries || [];

features.forEach(function (feature) {
featureEach(buffer(feature, radius, units, steps), function (buffered) {
results.push(buffered);
});
featureEach(geojson, function (feature) {
results.push(buffer(feature, radius, units, steps));
});
return featureCollection(results);
}
return buffer(geojson, radius, units, steps);
};

/**
* Buffer single Feature
* Buffer single Feature/Geometry
*
* @private
* @param {Feature<any>} feature input to be buffered
* @param {Feature<any>} geojson input to be buffered
* @param {number} radius distance to draw the buffer
* @param {string} [units='kilometers'] any of the options supported by turf units
* @param {number} [steps=64] number of steps
* @returns {Feature<Polygon|MultiPolygon>} buffered feature
*/
function buffer(feature, radius, units, steps) {
var properties = feature.properties || {};
var distance = distanceToDegrees(radius, units);
var geometry = (feature.type === 'Feature') ? feature.geometry : feature;
function buffer(geojson, radius, units, steps) {
var properties = geojson.properties || {};
var geometry = (geojson.type === 'Feature') ? geojson.geometry : geojson;

// Geometry Types faster than jsts
switch (geometry.type) {
case 'Point':
var poly = circle(feature, radius, steps, units);
poly.properties = properties;
return poly;
case 'MultiPoint':
var polys = [];
coordEach(feature, function (coord) {
var poly = circle(point(coord, properties), radius, steps, units);
poly.properties = properties;
polys.push(poly);
});
return dissolve(featureCollection(polys));
case 'LineString':
case 'MultiLineString':
case 'Polygon':
case 'MultiPolygon':
var reader = new jsts.io.GeoJSONReader();
var geom = reader.read(geometry);
var buffered = geom.buffer(distance);
var writer = new jsts.io.GeoJSONWriter();
buffered = writer.write(buffered);
return helpers.feature(buffered, properties);
default:
throw new Error('geometry type ' + geometry.type + ' not supported');
return circle(geometry.coordinates, radius, steps, units, properties);
}

// Project GeoJSON to Transverse Mercator projection (convert to Meters)
var distance = radiansToDistance(distanceToRadians(radius, units), 'meters');
var projection = defineProjection(geojson);
var projected = {
type: geometry.type,
coordinates: projectCoords(geometry.coordinates, projection)
};

// JSTS buffer operation
var reader = new jsts.io.GeoJSONReader();
var geom = reader.read(projected);
var buffered = geom.buffer(distance);
var writer = new jsts.io.GeoJSONWriter();
buffered = writer.write(buffered);

// Unproject coordinates (convert to Degrees)
buffered.coordinates = unprojectCoords(buffered.coordinates, projection);
return feature(buffered, properties);
}


/**
* Project coordinates to projection
*
* @private
* @param {Array<any>} coords to project
* @param {GeoProjection} projection D3 Geo Projection
* @returns {Array<any>} projected coordinates
*/
function projectCoords(coords, projection) {
if (typeof coords[0] !== 'object') return projection(coords);
return coords.map(function (coord) {
return projectCoords(coord, projection);
});
}

/**
* Un-Project coordinates to projection
*
* @private
* @param {Array<any>} coords to un-project
* @param {GeoProjection} projection D3 Geo Projection
* @returns {Array<any>} un-projected coordinates
*/
function unprojectCoords(coords, projection) {
if (typeof coords[0] !== 'object') return projection.invert(coords);
return coords.map(function (coord) {
return unprojectCoords(coord, projection);
});
}

/**
* Define Transverse Mercator projection
*
* @private
* @param {Geometry|Feature<any>} geojson Base projection on center of GeoJSON
* @returns {GeoProjection} D3 Geo Transverse Mercator Projection
*/
function defineProjection(geojson) {
var coords = center(geojson).geometry.coordinates.reverse();
var rotate = coords.map(function (coord) { return -coord; });
var projection = d3.geoTransverseMercator()
.center(coords)
.rotate(rotate)
.scale(6373000);

return projection;
}
3 changes: 2 additions & 1 deletion packages/turf-buffer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@
"write-json-file": "^2.0.0"
},
"dependencies": {
"@turf/center": "^4.2.0",
"@turf/circle": "^4.2.0",
"@turf/dissolve": "^4.2.0",
"@turf/helpers": "^4.2.0",
"@turf/meta": "^4.2.0",
"d3-geo": "^1.6.3",
"jsts": "1.3.0"
}
}
39 changes: 34 additions & 5 deletions packages/turf-buffer/test.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
const test = require('tape');
const fs = require('fs');
const test = require('tape');
const path = require('path');
const load = require('load-json-file');
const write = require('write-json-file');
const truncate = require('@turf/truncate');
const featureEach = require('@turf/meta').featureEach;
const featureCollection = require('@turf/helpers').featureCollection;
const {featureEach} = require('@turf/meta');
const {featureCollection, point, polygon} = require('@turf/helpers');
const buffer = require('./');

const directories = {
in: path.join(__dirname, 'test', 'in') + path.sep,
out: path.join(__dirname, 'test', 'out') + path.sep
};

const fixtures = fs.readdirSync(directories.in).map(filename => {
let fixtures = fs.readdirSync(directories.in).map(filename => {
return {
filename,
name: path.parse(filename).name,
geojson: load.sync(directories.in + filename)
};
});
// fixtures = fixtures.filter(({name}) => name === 'feature-collection-points');

test('turf-buffer', function (t) {
test('turf-buffer', t => {
for (const {filename, name, geojson} of fixtures) {
let {radius, units, padding} = geojson.properties || {};
radius = radius || 50;
Expand All @@ -39,3 +40,31 @@ test('turf-buffer', function (t) {
}
t.end();
});

test('turf-buffer - Support Geometry Objects', t => {
const pt = point([61, 5]);
const poly = polygon([[[11, 0], [22, 4], [31, 0], [31, 11], [21, 15], [11, 11], [11, 0]]]);

buffer(pt.geometry, 10);
buffer(poly.geometry, 10);
t.end();
});

test('turf-buffer - Prevent Input Mutation', t => {
const pt = point([61, 5]);
const poly = polygon([[[11, 0], [22, 4], [31, 0], [31, 11], [21, 15], [11, 11], [11, 0]]]);
const collection = featureCollection([pt, poly]);

const beforePt = JSON.parse(JSON.stringify(pt));
const beforePoly = JSON.parse(JSON.stringify(poly));
const beforeCollection = JSON.parse(JSON.stringify(collection));

buffer(pt, 10);
buffer(poly, 10);
buffer(collection, 10);

t.deepEqual(pt, beforePt, 'pt should not mutate');
t.deepEqual(poly, beforePoly, 'poly should not mutate');
t.deepEqual(collection, beforeCollection, 'collection should not mutate');
t.end();
});
15 changes: 15 additions & 0 deletions packages/turf-buffer/test/in/feature-collection-points.geojson
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
[
125,
-25
],
[
126,
-25
]
]
}
Expand All @@ -32,6 +36,17 @@
-27.5
]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
126,
-24.5
]
}
}
]
}
4 changes: 1 addition & 3 deletions packages/turf-buffer/test/in/linestring.geojson
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{
"type": "Feature",
"properties": {
"foo": "bar"
},
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
Expand Down
10 changes: 1 addition & 9 deletions packages/turf-buffer/test/in/northern-polygon.geojson
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@

{
"type": "Feature",
"properties": {
"stroke": "#ff0000",
"stroke-width": 6,
"stroke-opacity": 1,
"fill": "#ff6666",
"fill-opacity": 0.5,
"radius": 15,
"units": "miles"
},
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
Expand Down
4 changes: 1 addition & 3 deletions packages/turf-buffer/test/in/point.geojson
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{
"type": "Feature",
"properties": {
"foo": "bar"
},
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
Expand Down
Loading