Skip to content

Commit

Permalink
Fix jsts empty (Multi)Polygon error @turf/difference (#725)
Browse files Browse the repository at this point in the history
* Update fixtures from #644

* Fix jsts empty (Multi)Polygon error
- Major refactoring to `index.js` & `test.js`
- Update JSDocs & Typescript definition to support MultiPolygon
#721
  • Loading branch information
DenisCarriere authored May 10, 2017
1 parent 862ae21 commit 60b2ee0
Show file tree
Hide file tree
Showing 31 changed files with 2,673 additions and 544 deletions.
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

0 comments on commit 60b2ee0

Please sign in to comment.