Skip to content

Commit

Permalink
Merge branch 'master' into transform-scale-#895
Browse files Browse the repository at this point in the history
  • Loading branch information
DenisCarriere authored Aug 14, 2017
2 parents ec599cd + 355009a commit e674427
Show file tree
Hide file tree
Showing 13 changed files with 6,663 additions and 269 deletions.
2 changes: 1 addition & 1 deletion packages/turf-concave/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Internally, this uses [turf-tin](https://github.com/Turfjs/turf-tin) to generate
**Parameters**

- `points` **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)<[Point](http://geojson.org/geojson-spec.html#point)>** input points
- `maxEdge` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** the size of an edge necessary for part of the hull to become concave (in miles)
- `maxEdge` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** the length (in 'units') of an edge necessary for part of the hull to become concave
- `units` **\[[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)]** can be degrees, radians, miles, or kilometers (optional, default `kilometers`)

**Examples**
Expand Down
63 changes: 43 additions & 20 deletions packages/turf-concave/bench.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,45 @@
var concave = require('./');
var Benchmark = require('benchmark');
var fs = require('fs');
const fs = require('fs');
const path = require('path');
const load = require('load-json-file');
const Benchmark = require('benchmark');
const concave = require('./');

const directory = path.join(__dirname, 'test', 'in') + path.sep;
const fixtures = fs.readdirSync(directory).map(filename => {
return {
name: path.parse(filename).name,
geojson: load.sync(directory + filename)
};
});

/**
* Single Process Benchmark
*
* issue-333: 651.884ms
* pts1: 6.568ms
* pts2: 476.032ms
*/
for (const {name, geojson} of fixtures) {
const {maxEdge, units} = geojson.properties || {maxEdge: 1};
console.time(name);
concave(geojson, maxEdge, units);
console.timeEnd(name);
}

/**
* Benchmark Results
*
* issue-333 x 5.57 ops/sec ±10.65% (18 runs sampled)
* pts1 x 315 ops/sec ±3.48% (70 runs sampled)
* pts2 x 4.51 ops/sec ±2.28% (16 runs sampled)
*/
const suite = new Benchmark.Suite('turf-transform-scale');
for (const {name, geojson} of fixtures) {
const {maxEdge, units} = geojson.properties || {maxEdge: 1};
suite.add(name, () => concave(geojson, maxEdge, units));
}

var pts1 = JSON.parse(fs.readFileSync(__dirname+'/test/in/pts1.geojson'));
var pts2 = JSON.parse(fs.readFileSync(__dirname+'/test/in/pts2.geojson'));

var suite = new Benchmark.Suite('turf-concave');
suite
.add('turf-concave#simple',function () {
concave(pts1, 2.5, 'miles');
})
.add('turf-concave#complex',function () {
concave(pts2, 2.5, 'miles');
})
.on('cycle', function (event) {
console.log(String(event.target));
})
.on('complete', function () {

})
.run();
.on('cycle', e => console.log(String(e.target)))
.on('complete', () => {})
.run();
48 changes: 25 additions & 23 deletions packages/turf-concave/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
// 1. run tin on points
// 2. calculate lenth of all edges and area of all triangles
// 2. calculate length of all edges and area of all triangles
// 3. remove triangles that fail the max length test
// 4. buffer the results slightly
// 5. merge the results
var tin = require('@turf/tin');
var union = require('@turf/union');
var distance = require('@turf/distance');
var clone = require('@turf/clone');

/**
* Takes a set of {@link Point|points} and returns a concave hull polygon.
* Internally, this uses [turf-tin](https://github.com/Turfjs/turf-tin) to generate geometries.
*
* @param {FeatureCollection<Point>} points input points
* @param {number} maxEdge the size of an edge necessary for part of the hull to become concave (in miles)
* @param {number} maxEdge the length (in 'units') of an edge necessary for part of the hull to become concave
* @param {string} [units=kilometers] can be degrees, radians, miles, or kilometers
* @returns {Feature<Polygon>} a concave hull
* @returns {Feature<(Polygon|MultiPolygon)>} a concave hull
* @throws {Error} if maxEdge parameter is missing or unable to compute hull
* @example
* var points = turf.featureCollection([
Expand All @@ -31,40 +32,41 @@ var distance = require('@turf/distance');
* //addToMap
* var addToMap = [points, hull]
*/
function concave(points, maxEdge, units) {
if (typeof maxEdge !== 'number') throw new Error('maxEdge parameter is required');
module.exports = function (points, maxEdge, units) {
if (!points) throw new Error('points is required');
if (maxEdge === undefined || maxEdge === null) throw new Error('maxEdge is required');
if (typeof maxEdge !== 'number') throw new Error('invalid maxEdge');

var tinPolys = tin(points);
var filteredPolys = tinPolys.features.filter(filterTriangles);
tinPolys.features = filteredPolys;
if (tinPolys.features.length < 1) {
throw new Error('too few polygons found to compute concave hull');
}

function filterTriangles(triangle) {
tinPolys.features = tinPolys.features.filter(function (triangle) {
var pt1 = triangle.geometry.coordinates[0][0];
var pt2 = triangle.geometry.coordinates[0][1];
var pt3 = triangle.geometry.coordinates[0][2];
var dist1 = distance(pt1, pt2, units);
var dist2 = distance(pt2, pt3, units);
var dist3 = distance(pt1, pt3, units);
return (dist1 <= maxEdge && dist2 <= maxEdge && dist3 <= maxEdge);
}
});

return merge(tinPolys);
}
if (tinPolys.features.length < 1) throw new Error('too few polygons found to compute concave hull');

function merge(polygons) {
var merged = JSON.parse(JSON.stringify(polygons.features[0])),
features = polygons.features;
return merge(tinPolys.features);
};

/**
* Merges/Unifies all the features in a single polygon
*
* @private
* @param {Array<Feature>} features to be merged
* @returns {Feature<(Polygon|MultiPolygon)>} merged result
*/
function merge(features) {
var merged = clone(features[0]);
merged.properties = {};

for (var i = 0, len = features.length; i < len; i++) {
var poly = features[i];
if (poly.geometry) {
merged = union(merged, poly);
}
if (poly.geometry) merged = union(merged, poly);
}
return merged;
}

module.exports = concave;
13 changes: 10 additions & 3 deletions packages/turf-concave/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,30 @@
"keywords": [
"turf",
"gis",
"concave",
"geometry"
],
"author": "Turf Authors",
"contributors": [
"Stefano Borghi <@stebogit>"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/Turfjs/turf/issues"
},
"homepage": "https://github.com/Turfjs/turf",
"devDependencies": {
"@turf/helpers": "^4.6.0",
"@turf/meta": "^4.6.0",
"benchmark": "^2.1.4",
"glob": "~4.3.5",
"tape": "^4.6.3"
"load-json-file": "^2.0.0",
"tape": "^4.6.3",
"write-json-file": "^2.1.4"
},
"dependencies": {
"@turf/distance": "^4.6.0",
"@turf/tin": "^4.6.0",
"@turf/union": "^4.6.0"
"@turf/union": "^4.6.0",
"@turf/clone": "^4.6.0"
}
}
98 changes: 48 additions & 50 deletions packages/turf-concave/test.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,54 @@
var concave = require('./');
var test = require('tape');
var fs = require('fs');
var featureCollection = require('@turf/helpers').featureCollection;
var point = require('@turf/helpers').point;

var pts1 = JSON.parse(fs.readFileSync(__dirname+'/test/fixtures/in/pts1.geojson'));
var pts2 = JSON.parse(fs.readFileSync(__dirname+'/test/fixtures/in/pts2.geojson'));

test('concave', function(t){

var ptsOnePoint = featureCollection([point([0, 0])]);
var ptsOnePointHull = null;
t.throws(function(){
ptsOnePointHull = concave(ptsOnePoint, 5.5, 'miles');
}, Error, 'fails with too few points');
t.notOk(ptsOnePointHull, 'hull not computed with too few points');

var ptsNoPointHull = null;
t.throws(function(){
ptsNoPointHull = concave(pts1, 0, 'miles');
}, Error, 'fails with small maxEdge');
t.notOk(ptsNoPointHull, 'hull not computed with small maxEdge');

t.end();
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 {point, featureCollection} = require('@turf/helpers');
const {featureEach} = require('@turf/meta');
const concave = 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 => {
return {
filename,
name: path.parse(filename).name,
geojson: load.sync(directories.in + filename)
};
});

test('concave', function(t){
var pts1HullMiles = concave(pts1, 5.5, 'miles');
var pts1HullKilometers = concave(pts1, 5.5 * 1.60934, 'kilometers');
t.ok(pts1HullMiles, 'computes hull');
t.equal(pts1HullMiles.type, 'Feature');
t.deepEqual(pts1HullMiles, pts1HullKilometers, 'miles and km should return the same result');

var pts2HullMiles = concave(pts2, 2, 'miles');
var pts2HullKilometers = concave(pts2, 2 * 1.60934, 'kilometers');
t.ok(pts2HullMiles, 'computes hull');
t.equal(pts2HullMiles.type, 'Feature');
t.deepEqual(pts2HullMiles, pts2HullKilometers, 'miles and km should return the same result');

// output results
pts1.features = pts1.features.map(stylePt);
pts1.features.push(pts1HullMiles);
pts2.features = pts2.features.map(stylePt);
pts2.features.push(pts2HullMiles);
fs.writeFileSync(__dirname+'/test/fixtures/out/pts1_out.geojson', JSON.stringify(pts1,null,2));
fs.writeFileSync(__dirname+'/test/fixtures/out/pts2_out.geojson', JSON.stringify(pts2,null,2));

t.end();
test('turf-line-split', t => {
for(const {filename, name, geojson} of fixtures) {
const {maxEdge, units} = geojson.properties || {maxEdge: 1};
const hull = concave(geojson, maxEdge, units);
featureEach(geojson, stylePt);
const results = featureCollection([...geojson.features, hull]);

if (process.env.REGEN) write.sync(directories.out + filename, results);
t.deepEquals(results, load.sync(directories.out + filename), name);
}
t.end();
});


const points = featureCollection([point([0, 0]), point([1, 1]), point([1, 0])]);
const onePoint = featureCollection([point([0, 0])]);

test('concave', t => {
t.throws(() => concave(onePoint, 5.5, 'miles'), /too few polygons found to compute concave hull/, 'too few points');
t.throws(() => concave(onePoint, 0), /too few polygons found to compute concave hull/, 'maxEdge too small');

t.throws(() => concave(null, 0), /points is required/, 'no points');
t.throws(() => concave(points, null), /maxEdge is required/, 'no maxEdge');
t.throws(() => concave(points, 1, 'foo'), /units is invalid/, 'invalid units');

t.end();
});

function stylePt(pt){
pt.properties['marker-color'] = '#f0f';
pt.properties['marker-size'] = 'small';
return pt;
pt.properties['marker-color'] = '#f0f';
pt.properties['marker-size'] = 'small';
}
Loading

0 comments on commit e674427

Please sign in to comment.