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

Fix jsts empty (Multi)Polygon error @turf/difference #725

Merged
merged 2 commits into from
May 10, 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
48 changes: 27 additions & 21 deletions packages/turf-difference/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@

# difference

Finds the difference between two [polygons](http://geojson.org/geojson-spec.html#polygon) by clipping the second
polygon from the first.
Finds the difference between two [polygons](http://geojson.org/geojson-spec.html#polygon) by clipping the second polygon from the first.

**Parameters**

- `p1` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** input Polygon feature
- `p2` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** Polygon feature to difference from `p1`
- `polygon1` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<([Polygon](http://geojson.org/geojson-spec.html#polygon) \| [MultiPolygon](http://geojson.org/geojson-spec.html#multipolygon))>** input Polygon feature
- `polygon2` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<([Polygon](http://geojson.org/geojson-spec.html#polygon) \| [MultiPolygon](http://geojson.org/geojson-spec.html#multipolygon))>** Polygon feature to difference from polygon1

**Examples**

```javascript
var poly1 = {
var polygon1 = {
"type": "Feature",
"properties": {},
"properties": {
"fill": "#F00",
"fill-opacity": 0.1
},
"geometry": {
"type": "Polygon",
"coordinates": [[
Expand All @@ -27,31 +29,35 @@ var poly1 = {
]]
}
};
var poly2 = {
var polygon2 = {
"type": "Feature",
"properties": {},
"properties": {
"fill": "#00F",
"fill-opacity": 0.1
},
"geometry": {
"type": "Polygon",
"coordinates": [[
[-46.650009, -23.631314],
[-46.650009, -23.5237],
[-46.509246, -23.5237],
[-46.509246, -23.631314],
[-46.650009, -23.631314]
]]
"coordinates": [[[126, -28], [140, -28], [140, -20], [126, -20], [126, -28]]]
}
};

var difference = turf.difference(poly1, poly2);
var difference = turf.difference(polygon1, polygon2);

//addToMap
poly1.properties.fill = '#0f0';
poly2.properties.fill = '#00f';
difference.properties.fill = '#f00';
var addToMap = [poly1, poly2, difference];
var addToMap = [polygon1, polygon2, difference];
```

Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<([Polygon](http://geojson.org/geojson-spec.html#polygon) \| [MultiPolygon](http://geojson.org/geojson-spec.html#multipolygon))>** a Polygon or MultiPolygon feature showing the area of `p1` excluding the area of `p2`
Returns **([Feature](http://geojson.org/geojson-spec.html#feature-objects)<([Polygon](http://geojson.org/geojson-spec.html#polygon) \| [MultiPolygon](http://geojson.org/geojson-spec.html#multipolygon))> | [undefined](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined))** a Polygon or MultiPolygon feature showing the area of `polygon1` excluding the area of `polygon2` (if empty returns `undefined`)

# removeEmptyPolygon

Detect Empty Polygon

**Parameters**

- `geom` **[Geometry](http://geojson.org/geojson-spec.html#geometry)<([Polygon](http://geojson.org/geojson-spec.html#polygon) \| [MultiPolygon](http://geojson.org/geojson-spec.html#multipolygon))>** Geometry Object

Returns **([Geometry](http://geojson.org/geojson-spec.html#geometry)<([Polygon](http://geojson.org/geojson-spec.html#polygon) \| [MultiPolygon](http://geojson.org/geojson-spec.html#multipolygon))> | [undefined](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined))** removed any polygons with no areas

<!-- This file is automatically generated. Please don't edit it directly:
if you find an error, edit the source file (likely index.js), and re-run
Expand Down
54 changes: 35 additions & 19 deletions packages/turf-difference/bench.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
var difference = 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 difference = require('./');

var clip = JSON.parse(fs.readFileSync(__dirname+'/test/fixtures/in/differencedHole.geojson'));
var hole = JSON.parse(fs.readFileSync(__dirname+'/test/fixtures/in/differencedFC.geojson'));
const directory = path.join(__dirname, 'test', 'in') + path.sep;
let fixtures = fs.readdirSync(directory).map(filename => {
return {
filename,
name: path.parse(filename).name,
geojson: load.sync(directory + filename)
};
});
// fixtures = fixtures.filter(({name}) => name === 'issue-#721');

/**
* Benchmark Results
*
* ==using jsts==
* clip-polygons x 2,512 ops/sec ±30.29% (71 runs sampled)
* completely-overlapped x 6,777 ops/sec ±4.08% (78 runs sampled)
* create-hole x 5,451 ops/sec ±9.92% (75 runs sampled)
* issue-#721-inverse x 434,372 ops/sec ±3.40% (85 runs sampled)
* issue-#721 x 421,194 ops/sec ±4.35% (80 runs sampled)
* multi-polygon-input x 1,904 ops/sec ±5.16% (79 runs sampled)
* multi-polygon-target x 1,240 ops/sec ±5.44% (79 runs sampled)
* split-polygon x 2,468 ops/sec ±4.75% (82 runs sampled)
*/
const suite = new Benchmark.Suite('turf-difference');
for (const {name, geojson} of fixtures) {
suite.add(name, () => difference(geojson.features[0], geojson.features[1]));
}

var suite = new Benchmark.Suite('turf-difference');
suite
.add('turf-difference#clip',function () {
difference(clip[0], clip[1]);
})
.add('turf-difference#hole',function () {
difference(hole[0], hole[1]);
})
.on('cycle', function (event) {
console.log(String(event.target));
})
.on('complete', function () {

})
.run();
.on('cycle', e => console.log(String(e.target)))
.on('complete', () => {})
.run();
8 changes: 5 additions & 3 deletions packages/turf-difference/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/// <reference types="geojson" />

type Polygon = GeoJSON.Feature<GeoJSON.Polygon>;
type MultiPolygon = GeoJSON.Feature<GeoJSON.MultiPolygon>;
import {Polygon, MultiPolygon, Feature} from '@turf/meta'

type Input = Feature<Polygon|MultiPolygon> | Polygon | MultiPolygon;
type Output = Feature<Polygon|MultiPolygon> | undefined;

/**
* http://turfjs.org/docs/#difference
*/
declare function difference(poly1: Polygon, poly2: Polygon): Polygon|MultiPolygon;
declare function difference(polygon1: Input, polygon2: Input): Output;
declare namespace difference { }
export = difference;
105 changes: 55 additions & 50 deletions packages/turf-difference/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
// depend on jsts for now https://github.com/bjornharrtell/jsts/blob/master/examples/overlay.html
var jsts = require('jsts');
var area = require('@turf/area');
var feature = require('@turf/helpers').feature;
var getGeom = require('@turf/invariant').getGeom;
var flattenEach = require('@turf/meta').flattenEach;

/**
* Finds the difference between two {@link Polygon|polygons} by clipping the second
* polygon from the first.
* Finds the difference between two {@link Polygon|polygons} by clipping the second polygon from the first.
*
* @name difference
* @param {Feature<Polygon>} p1 input Polygon feature
* @param {Feature<Polygon>} p2 Polygon feature to difference from `p1`
* @return {Feature<(Polygon|MultiPolygon)>} a Polygon or MultiPolygon feature showing the area of `p1` excluding the area of `p2`
* @param {Feature<Polygon|MultiPolygon>} polygon1 input Polygon feature
* @param {Feature<Polygon|MultiPolygon>} polygon2 Polygon feature to difference from polygon1
* @returns {Feature<Polygon|MultiPolygon>|undefined} a Polygon or MultiPolygon feature showing the area of `polygon1` excluding the area of `polygon2` (if empty returns `undefined`)
* @example
* var poly1 = {
* var polygon1 = {
* "type": "Feature",
* "properties": {},
* "properties": {
* "fill": "#F00",
* "fill-opacity": 0.1
* },
* "geometry": {
* "type": "Polygon",
* "coordinates": [[
Expand All @@ -24,63 +30,62 @@ var jsts = require('jsts');
* ]]
* }
* };
* var poly2 = {
* var polygon2 = {
* "type": "Feature",
* "properties": {},
* "properties": {
* "fill": "#00F",
* "fill-opacity": 0.1
* },
* "geometry": {
* "type": "Polygon",
* "coordinates": [[
* [-46.650009, -23.631314],
* [-46.650009, -23.5237],
* [-46.509246, -23.5237],
* [-46.509246, -23.631314],
* [-46.650009, -23.631314]
* ]]
* "coordinates": [[[126, -28], [140, -28], [140, -20], [126, -20], [126, -28]]]
* }
* };
*
* var difference = turf.difference(poly1, poly2);
* var difference = turf.difference(polygon1, polygon2);
*
* //addToMap
* poly1.properties.fill = '#0f0';
* poly2.properties.fill = '#00f';
* difference.properties.fill = '#f00';
* var addToMap = [poly1, poly2, difference];
* var addToMap = [polygon1, polygon2, difference];
*/
module.exports = function (polygon1, polygon2) {
var geom1 = getGeom(polygon1);
var geom2 = getGeom(polygon2);
var properties = polygon1.properties || {};

module.exports = function (p1, p2) {
var poly1 = JSON.parse(JSON.stringify(p1));
var poly2 = JSON.parse(JSON.stringify(p2));
if (poly1.type !== 'Feature') {
poly1 = {
type: 'Feature',
properties: {},
geometry: poly1
};
}
if (poly2.type !== 'Feature') {
poly2 = {
type: 'Feature',
properties: {},
geometry: poly2
};
}
// Issue #721 - JSTS can't handle empty polygons
geom1 = removeEmptyPolygon(geom1);
geom2 = removeEmptyPolygon(geom2);
if (!geom1) return undefined;
if (!geom2) return feature(geom1, properties);

// JSTS difference operation
var reader = new jsts.io.GeoJSONReader();
var a = reader.read(JSON.stringify(poly1.geometry));
var b = reader.read(JSON.stringify(poly2.geometry));
var a = reader.read(geom1);
var b = reader.read(geom2);
var differenced = a.difference(b);

if (differenced.isEmpty()) return undefined;

var writer = new jsts.io.GeoJSONWriter();
var geojsonGeometry = writer.write(differenced);
var geom = writer.write(differenced);

poly1.geometry = differenced;

return {
type: 'Feature',
properties: poly1.properties,
geometry: geojsonGeometry
};
return feature(geom, properties);
};

/**
* Detect Empty Polygon
*
* @param {Geometry<Polygon|MultiPolygon>} geom Geometry Object
* @returns {Geometry<Polygon|MultiPolygon>|undefined} removed any polygons with no areas
*/
function removeEmptyPolygon(geom) {
switch (geom.type) {
case 'Polygon':
if (area(geom) > 1) return geom;
return undefined;
case 'MultiPolygon':
var coordinates = [];
flattenEach(geom, function (feature) {
if (area(feature) > 1) coordinates.push(feature.geometry.coordinates);
});
if (coordinates.length) return {type: 'MultiPolygon', coordinates: coordinates};
}
}
13 changes: 9 additions & 4 deletions packages/turf-difference/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,17 @@
},
"homepage": "https://github.com/Turfjs/turf",
"devDependencies": {
"benchmark": "^1.0.0",
"benchmark": "^2.1.4",
"glob": "~5.0.5",
"tape": "~4.0.0"
"load-json-file": "^2.0.0",
"tape": "^4.6.3",
"write-json-file": "^2.0.0"
},
"dependencies": {
"jsts": "1.3.0",
"@turf/helpers": "^4.2.0"
"@turf/area": "^4.2.0",
"@turf/helpers": "^4.2.0",
"@turf/invariant": "^4.2.0",
"@turf/meta": "^4.2.0",
"jsts": "^1.4.0"
}
}
Loading