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

Initial commit of boolean DE-9IM module #707

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
09abe34
Initial commit of boolean DE-9IM module
rowanwins May 2, 2017
93a3400
Update contains
DenisCarriere May 3, 2017
6a9b1ba
Create boolean-contains own module
DenisCarriere May 3, 2017
727c80a
Add benchmark boolean-contains
DenisCarriere May 3, 2017
ede82d6
Add GeometryObject support
DenisCarriere May 3, 2017
d1644ec
Place `within` to own repo @turf/boolean-within
DenisCarriere May 7, 2017
ff3e6e3
Drop contains tests
DenisCarriere May 7, 2017
e8c00d6
Create @turf/boolean-equal repo
DenisCarriere May 7, 2017
a3497f3
Update JSDocs boolean-contains
DenisCarriere May 7, 2017
19ae32d
Create @turf/boolean-touch repo
DenisCarriere May 7, 2017
2a50cdc
normalize package.json
DenisCarriere May 7, 2017
65c2d0c
drop turf-boolean repo
DenisCarriere May 7, 2017
1839242
Add bench to @turf/boolean-within
DenisCarriere May 7, 2017
d40e629
Exclude error tests (for now)
DenisCarriere May 7, 2017
c4bcb07
ESLint fixes
DenisCarriere May 7, 2017
8bff7d6
Add typescript definition to boolean modules
DenisCarriere May 7, 2017
6ecbc3e
Use inside module instead of private method
DenisCarriere May 7, 2017
d94a78e
Add test true/false fixtures boolean-contains
DenisCarriere May 8, 2017
fd442ae
Improve isPolyInPoly 5M ops/sec
DenisCarriere May 8, 2017
f2b9531
Initial commit of disjoint, still a bit to go
rowanwins May 13, 2017
87500ef
Update Readmes & minor refactoring
DenisCarriere May 15, 2017
f4f8a18
Exclude failing tests
DenisCarriere May 15, 2017
cb32dd5
Add yarn locks
DenisCarriere May 28, 2017
d772b9a
add Skip to tests
DenisCarriere May 28, 2017
78a0af8
boolean-overlap improvements
rowanwins May 30, 2017
ba618ad
Support geometries and features
rowanwins May 31, 2017
3d40ec8
Change test approach. Fix multipoint check.
rowanwins Jun 1, 2017
7d715e6
Using Glob in tests
DenisCarriere Jun 1, 2017
c1c898c
Skip polygonsShareBoundary
DenisCarriere Jun 1, 2017
300208b
Add required files
DenisCarriere Jun 1, 2017
2ab6a2b
Merge branch 'master' into booleans
DenisCarriere Jun 1, 2017
0827683
Add precision & direction params
DenisCarriere Jun 6, 2017
f40b3d1
New module @turf/boolean-clockwise (#789)
stebogit Jun 18, 2017
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
31 changes: 31 additions & 0 deletions packages/turf-boolean-contains/bench.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const Benchmark = require('benchmark');
const {point, multiPoint, lineString, polygon} = require('@turf/helpers');
const contains = require('./');

// Fixtures
const pt = point([1, 1]);
const mp = multiPoint([[1, 1], [12, 12]]);
const line = lineString([[1, 1], [1, 2], [1, 3], [1, 4]]);
const poly = polygon([[[1, 1], [1, 10], [10, 10], [10, 1], [1, 1]]]);

/**
* Benchmark Results
*
* point - point x 25,229,201 ops/sec ±2.10% (90 runs sampled)
* point - multipoint:
* point - line:
* point - polygon:
* multipoint - point x 865,610 ops/sec ±1.71% (86 runs sampled)
* polygon - line x 991,549 ops/sec ±1.71% (90 runs sampled)
*/
const suite = new Benchmark.Suite('turf-boolean-contains');
suite
.add('point - point', () => contains(pt, pt))
.add('point - multipoint', () => contains(pt, mp))
.add('point - line', () => contains(pt, line))
.add('point - polygon', () => contains(pt, polygon))
.add('multipoint - point', () => contains(mp, pt))
.add('polygon - line', () => contains(poly, line))
.on('cycle', e => console.log(String(e.target)))
.on('complete', () => {})
.run();
208 changes: 208 additions & 0 deletions packages/turf-boolean-contains/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
var inside = require('@turf/inside');
var helpers = require('@turf/helpers');
var deepEqual = require('deep-equal');
var lineOverlap = require('@turf/line-overlap');

/**
* Contains returns true if the second geometry is completely contained by the first geometry.
* The contains predicate returns the exact opposite result of the within predicate.
*
* @name contains
* @param {Geometry|Feature<any>} feature1 source
* @param {Geometry|Feature<any>} feature2 target
* @returns {Boolean} true/false
* @example
* var along = turf.contains(line, point);
*/
module.exports = function (feature1, feature2) {
var geom1 = getGeom(feature1);
var geom2 = getGeom(feature2);

switch (geom1.type) {
case 'Point':
switch (geom2.type) {
case 'Point':
return deepEqual(geom1.coordinates, geom2.coordinates);
}
throw new Error('feature2 ' + geom2.type + ' geometry not supported');
case 'MultiPoint':
switch (geom2.type) {
case 'Point':
return isPointInMultiPoint(geom1, geom2);
case 'MultiPoint':
return isMultiPointInMultiPoint(geom1, geom2);
}
throw new Error('feature2 ' + geom2.type + ' geometry not supported');
case 'LineString':
switch (geom2.type) {
case 'Point':
return isPointOnLine(geom1, geom2);
case 'LineString':
return isLineOnLine(geom1, geom2);
case 'MultiPoint':
return isMultiPointOnLine(geom1, geom2);
}
throw new Error('feature2 ' + geom2.type + ' geometry not supported');
case 'Polygon':
switch (geom2.type) {
case 'Point':
return isPointInPoly(geom1, geom2);
case 'LineString':
return isLineInPoly(geom1, geom2);
case 'Polygon':
return isPolyInPoly(geom1, geom2);
case 'MultiPoint':
return isMultiPointInPoly(geom1, geom2);
}
throw new Error('feature2 ' + geom2.type + ' geometry not supported');
default:
throw new Error('feature1 ' + geom1.type + ' geometry not supported');
}
};

function isPointInMultiPoint(MultiPoint, Point) {
var i;
var output = false;
for (i = 0; i < MultiPoint.coordinates.length; i++) {
if (deepEqual(MultiPoint.coordinates[i], Point.coordinates)) {
output = true;
break;
}
}
return output;
}

function isMultiPointInMultiPoint(MultiPoint1, MultiPoint2) {
var foundAMatch = 0;
for (var i = 0; i < MultiPoint2.coordinates.length; i++) {
var anyMatch = false;
for (var i2 = 0; i2 < MultiPoint1.coordinates.length; i2++) {
if (deepEqual(MultiPoint2.coordinates[i], MultiPoint1.coordinates[i2])) {
foundAMatch++;
anyMatch = true;
break;
}
}
if (!anyMatch) {
return false;
}
}
return foundAMatch > 0 && foundAMatch < MultiPoint1.coordinates.length;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second condition here excludes the two geometries being equal. Not sure if that is actually in the definition of contains. According to DE-9IM the definition is:

  • No point of MultiPoint2 must be in the exterior of MultiPoint1
  • At least 1 point in the interior of MultiPoint2 must be in the interior of MultiPoint1

Since the points themselves are their own interior, the two being equal would seem to meet that criteria. Could be wrong, the DE-9IM definitions are sometimes tricky to parse.

}

// http://stackoverflow.com/a/11908158/1979085
function isPointOnLine(LineString, Point) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be allowing the point to land on the line end points. That would place the point on the line boundary and therefore no point lies on the interior of the line (see above definition of contains). I think the end points should be excluded.

var output = false;
for (var i = 0; i < LineString.coordinates.length - 1; i++) {
if (isPointOnLineSegment(LineString.coordinates[i], LineString.coordinates[i + 1], Point.coordinates)) {
output = true;
break;
}
}
return output;
}

function isMultiPointOnLine(LineString, MultiPoint) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above. No check that any point is in the interior of line. As you probably have noticed already the edge cases are the real pain when implementing these booleans functions.

var output = true;
for (var i = 0; i < MultiPoint.coordinates.length; i++) {
var pointIsOnLine = false;
for (var i2 = 0; i2 < LineString.coordinates.length - 1; i2++) {
if (isPointOnLineSegment(LineString.coordinates[i2], LineString.coordinates[i2 + 1], MultiPoint.coordinates[i])) {
pointIsOnLine = true;
break;
}
}
if (!pointIsOnLine) {
output = false;
break;
}
}
return output;
}

function isPointInPoly(Polygon, Point) {
return inside(Point, Polygon);
}

function isMultiPointInPoly(Polygon, MultiPoint) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same interior point issue as above.

var output = true;
for (var i = 0; i < MultiPoint.coordinates.length; i++) {
var isInside = isPointInPoly(Polygon, helpers.point(MultiPoint.coordinates[1]));
if (!isInside) {
output = false;
break;
}
}
return output;
}

// TO DO - Work out how to check if line is in line (can potentially use line overlap module)
// Also need to make sure lines are exactly the same, eg the second must be smaller than the first
function isLineOnLine(LineString1, LineString2) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

 t.equal(contains(line1, line3), false, 'A line fails that lies partially outside the other line');

Test fail because line should be completely on line (returns true because of partial match).

Might be worth adding a isLineCompletelyOnLine(line1, line2) method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gday @DenisCarriere

As a general comment I feel like the line booleans are the trickiest to get right at this stage, acknowledge that there is some work needed there

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never said it was easy 👍 Keep it up!

var output = true;
var overlappingLine = lineOverlap(helpers.feature(LineString1), helpers.feature(LineString2)).features[0];
if (!overlappingLine) return false;
overlappingLine = getGeom(overlappingLine);
if (overlappingLine.coordinates[0] === LineString1.coordinates[0] &&
overlappingLine.coordinates[overlappingLine.coordinates.length - 1] === LineString1.coordinates[LineString1.coordinates.length - 1]) {
output = false;
}
return output;
}

function isLineInPoly(Polygon, Linestring) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this approach will only work for convex polygons. Concave or polygons with holes can fail this. Think of a donut with a line that only crosses the hole.

I think it would be safer to first check if the line intersects the polygon boundaries (including holes). If it doesn't then check that at least one point is in polygon interior.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Polygons with holes will be hard to implement, @rowanwins I think at first lets just get it working with only outer rings and afterwards we work on the inner ring validation (donuts).

var output = true;
for (var i = 0; i < Linestring.coordinates.length; i++) {
var isInside = isPointInPoly(Polygon, Linestring.coordinates[i]);
if (!isInside) {
output = false;
break;
}
}
return output;
}

// See http://stackoverflow.com/a/4833823/1979085
// TO DO - Need to handle if the polys are the same, eg there must be some outer coordinates different
function isPolyInPoly(Polygon1, Polygon2) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this has the same issue as isLineInPoly above.

for (var i = 0; i < Polygon2.coordinates[0].length; i++) {
if (!isPointInPoly(Polygon1, helpers.point(Polygon2.coordinates[0][i]))) {
return false;
}
}

if (deepEqual(Polygon1.coordinates, Polygon2.coordinates)) {
return false;
}
return true;
}

function isPointOnLineSegment(LineSegmentStart, LineSegmentEnd, Point) {
var dxc = Point[0] - LineSegmentStart[0];
var dyc = Point[1] - LineSegmentStart[1];
var dxl = LineSegmentEnd[0] - LineSegmentStart[0];
var dyl = LineSegmentEnd[1] - LineSegmentStart[1];
var cross = dxc * dyl - dyc * dxl;
if (cross !== 0) {
return false;
}
if (Math.abs(dxl) >= Math.abs(dyl)) {
return dxl > 0 ? LineSegmentStart[0] <= Point[0] && Point[0] <= LineSegmentEnd[0] : LineSegmentEnd[0] <= Point[0] && Point[0] <= LineSegmentStart[0];
} else {
return dyl > 0 ? LineSegmentStart[1] <= Point[1] && Point[1] <= LineSegmentEnd[1] : LineSegmentEnd[1] <= Point[1] && Point[1] <= LineSegmentStart[1];
}
}

/**
* Get Geometry from Feature or Geometry Object
*
* @private
* @param {Feature<any>|Geometry<any>} geojson GeoJSON Feature or Geometry Object
* @returns {Geometry<any>} GeoJSON Geometry Object
* @throws {Error} if geojson is not a Feature or Geometry Object
*/
function getGeom(geojson) {
if (geojson.geometry) return geojson.geometry;
if (geojson.coordinates) return geojson;
throw new Error('geojson must be a feature or geometry object');
}
45 changes: 45 additions & 0 deletions packages/turf-boolean-contains/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "@turf/boolean-contains",
"version": "4.0.0",
"description": "turf boolean-contains module",
"main": "index.js",
"types": "index.d.ts",
"files": [
"index.js",
"index.d.ts"
],
"scripts": {
"test": "node test.js",
"bench": "node bench.js"
},
"repository": {
"type": "git",
"url": "git://github.com/Turfjs/turf.git"
},
"keywords": [
"turf",
"contains",
"boolean",
"de-9im"
],
"author": "Turf Authors",
"contributors": [
"Rowan Winsemius <@rowanwins>",
"Denis Carriere <@DenisCarriere>"
],
"license": "MIT",
"bugs": {
"url": "/~https://github.com/Turfjs/turf/issues"
},
"homepage": "/~https://github.com/Turfjs/turf",
"devDependencies": {
"benchmark": "^2.1.4",
"tape": "^4.6.3"
},
"dependencies": {
"@turf/helpers": "^4.1.0",
"@turf/inside": "^4.1.0",
"@turf/line-overlap": "^4.1.0",
"deep-equal": "^1.0.1"
}
}
55 changes: 55 additions & 0 deletions packages/turf-boolean-contains/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const test = require('tape');
const {point, multiPoint, lineString, polygon} = require('@turf/helpers');
const contains = require('./');

test('turf-boolean-contains', t => {
const pt1 = point([1, 1]);
const pt2 = point([1, 4]);
const pt3 = point([4, 4]);
const pt4 = point([14, 14]);

const mp1 = multiPoint([[1, 1], [12, 12]]);
const mp2 = multiPoint([[1, 1], [1, 1.5]]);
const mp3 = multiPoint([[1, 1], [12, 12], [15, 15]]);

t.equal(contains(mp1, pt1), true, 'Point is contained within multipoint');
t.equal(contains(mp1, pt2), false, 'Point is not contained outside multipoint');

t.equal(contains(mp3, mp1), true, 'True if all multipoints are contained with within multipoints');
t.equal(contains(mp3, mp2), false, 'False if some multipoints are elsewhere');

const line1 = lineString([[1, 1], [1, 2], [1, 3], [1, 4]]);
t.equal(contains(line1, pt1), true, 'Point is on line');
t.equal(contains(line1, pt3), false, 'Point is not on line');

t.equal(contains(line1, mp2), true, 'MultiPoint is on line');
t.equal(contains(line1, mp3), false, 'MultiPoint is not on line');

const poly1 = polygon([[[1, 1], [1, 10], [10, 10], [10, 1], [1, 1]]]);
t.equal(contains(poly1, pt3), true, 'A point lies inside the polygon boundary');
t.equal(contains(poly1, pt1), true, 'A point lies on the polygon boundary');
t.equal(contains(poly1, pt4), false, 'A point lies outside the polygon boundary fails');

const mp4 = multiPoint([[1, 1], [4, 4]]);
t.equal(contains(poly1, mp4), true, 'A multipoint lies inside the polygon boundary');
t.equal(contains(poly1, mp1), false, 'A multipoint with a point outside fails');


const line2 = lineString([[1, 2], [1, 3], [1, 3.5]]);
const line3 = lineString([[1, 2], [1, 3], [1, 15.5]]);

t.equal(contains(line1, line2), true, 'A line lies inside the other line');
t.equal(contains(line1, line3), false, 'A line fails that lies partially outside the other line');

t.equal(contains(poly1, line1), true, 'A line within the poly passes as true');
t.equal(contains(poly1, line3), false, 'A line that lies partially outside the poly is false');


const poly2 = polygon([[[1, 1], [1, 2], [1, 3], [1, 4], [1, 1]]]);
const poly3 = polygon([[[1, 1], [1, 20], [1, 3], [1, 4], [1, 1]]]);

t.equal(contains(poly1, poly2), true, 'A poly passes that is inside although allows touching edges');
t.equal(contains(poly1, poly3), false, 'A poly fails that has a vertice outside the poly');

t.end();
});
18 changes: 18 additions & 0 deletions packages/turf-boolean/equal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
var internalHelpers = require('./helpers');
var checkIfDeepCoordArraysMatch = internalHelpers.checkIfDeepCoordArraysMatch;

/**
* Contains returns true if the second geometry is completely contained by the first geometry.
* The contains predicate returns the exact opposite result of the within predicate.
*
* @name contains
* @param {feature1} feature1
* @param {feature2} feature2
* @returns {Boolean}
* @example
* var along = turf.contains(line, point);
*/

module.exports = function (feature1, feature2) {
return checkIfDeepCoordArraysMatch(feature1.geometry.coordinates, feature2.geometry.coordinates) && feature1.geometry.type === feature2.geometry.type;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to add a warning that this is only testing that the coordinates are identical. It is not testing that the geometries are equal. For example, the lines [[0,0], [2,0]] and [[0,0], [1,0], [2,0]] are equal but don't have identical coordinates.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one sounds like it would be real tricky to write. Would be worth including as a test case or add it to the JSDocs description.

};
Loading