Skip to content

Commit f615482

Browse files
samuelarbibesamuelarbibesmallsaucepan
authored
turf-boolean-contains + turf-boolean-within: Fix line in polygon (#2848)
* fix boolean-contains and boolean-within isLineInPoly * update esri-contains and esri-within diagrams * check if line is contained in boundary after splitting on intersections * fix line simply containes in polygon, and tidy up test examples * split linestring segments on polygon before checking midpoints * add explanatory comments and update docs --------- Co-authored-by: samuelarbibe <samuel.arbibe@gmail.com> Co-authored-by: James Beard <james@smallsaucepan.com>
1 parent 984b84c commit f615482

22 files changed

+335
-92
lines changed

packages/turf-boolean-contains/README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44

55
## booleanContains
66

7-
Boolean-contains returns True if the second geometry is completely contained by the first geometry.
8-
The interiors of both geometries must intersect and, the interior and boundary of the secondary (geometry b)
9-
must not intersect the exterior of the primary (geometry a).
10-
Boolean-contains returns the exact opposite result of the `@turf/boolean-within`.
7+
Tests whether geometry a contains geometry b.
8+
The interiors of both geometries must intersect, and the interior and boundary of geometry b must not intersect the exterior of geometry a.
9+
booleanContains(a, b) is equivalent to booleanWithin(b, a)
1110

1211
### Parameters
1312

@@ -55,4 +54,4 @@ $ npm install @turf/turf
5554

5655
### Diagrams
5756

58-
![esri-contains](diagrams/esri-contains.gif)
57+
![esri-contains](diagrams/esri-contains.png)
-33.3 KB
Binary file not shown.
6.32 KB
Loading

packages/turf-boolean-contains/index.ts

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ import { bbox as calcBbox } from "@turf/bbox";
1212
import { booleanPointInPolygon } from "@turf/boolean-point-in-polygon";
1313
import { booleanPointOnLine as isPointOnLine } from "@turf/boolean-point-on-line";
1414
import { getGeom } from "@turf/invariant";
15+
import { feature, featureCollection, lineString } from "@turf/helpers";
16+
import { lineSplit } from "@turf/line-split";
1517

1618
/**
17-
* Boolean-contains returns True if the second geometry is completely contained by the first geometry.
18-
* The interiors of both geometries must intersect and, the interior and boundary of the secondary (geometry b)
19-
* must not intersect the exterior of the primary (geometry a).
20-
* Boolean-contains returns the exact opposite result of the `@turf/boolean-within`.
19+
* Tests whether geometry a contains geometry b.
20+
* The interiors of both geometries must intersect, and the interior and boundary of geometry b must not intersect the exterior of geometry a.
21+
* booleanContains(a, b) is equivalent to booleanWithin(b, a)
2122
*
2223
* @function
2324
* @param {Geometry|Feature<any>} feature1 GeoJSON Feature or Geometry
@@ -177,30 +178,67 @@ function isLineOnLine(lineString1: LineString, lineString2: LineString) {
177178
return haveFoundInteriorPoint;
178179
}
179180

180-
function isLineInPoly(polygon: Polygon, linestring: LineString) {
181-
let output = false;
182-
let i = 0;
181+
function splitLineIntoSegmentsOnPolygon(
182+
linestring: LineString,
183+
polygon: Polygon
184+
) {
185+
const coords = linestring.coordinates;
186+
187+
const outputSegments: Feature<LineString>[] = [];
188+
189+
for (let i = 0; i < coords.length - 1; i++) {
190+
const seg = lineString([coords[i], coords[i + 1]]);
191+
const split = lineSplit(seg, feature(polygon));
192+
193+
if (split.features.length === 0) {
194+
outputSegments.push(seg);
195+
} else {
196+
outputSegments.push(...split.features);
197+
}
198+
}
199+
200+
return featureCollection(outputSegments);
201+
}
183202

203+
function isLineInPoly(polygon: Polygon, linestring: LineString) {
184204
const polyBbox = calcBbox(polygon);
185205
const lineBbox = calcBbox(linestring);
206+
186207
if (!doBBoxOverlap(polyBbox, lineBbox)) {
187208
return false;
188209
}
189-
for (i; i < linestring.coordinates.length - 1; i++) {
190-
const midPoint = getMidpoint(
191-
linestring.coordinates[i],
192-
linestring.coordinates[i + 1]
210+
211+
for (const coord of linestring.coordinates) {
212+
if (!booleanPointInPolygon(coord, polygon)) {
213+
return false;
214+
}
215+
}
216+
217+
let isContainedByPolygonBoundary = false;
218+
// split intersecting segments and verify their inclusion
219+
const lineSegments = splitLineIntoSegmentsOnPolygon(linestring, polygon);
220+
221+
for (const lineSegment of lineSegments.features) {
222+
const midpoint = getMidpoint(
223+
lineSegment.geometry.coordinates[0],
224+
lineSegment.geometry.coordinates[1]
193225
);
226+
227+
// make sure all segments do not intersect with polygon exterior
228+
if (!booleanPointInPolygon(midpoint, polygon)) {
229+
return false;
230+
}
231+
232+
// make sure at least 1 segment intersects with the polygon's interior
194233
if (
195-
booleanPointInPolygon({ type: "Point", coordinates: midPoint }, polygon, {
196-
ignoreBoundary: true,
197-
})
234+
!isContainedByPolygonBoundary &&
235+
booleanPointInPolygon(midpoint, polygon, { ignoreBoundary: true })
198236
) {
199-
output = true;
200-
break;
237+
isContainedByPolygonBoundary = true;
201238
}
202239
}
203-
return output;
240+
241+
return isContainedByPolygonBoundary;
204242
}
205243

206244
/**

packages/turf-boolean-contains/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"author": "Turf Authors",
66
"contributors": [
77
"Rowan Winsemius <@rowanwins>",
8-
"Denis Carriere <@DenisCarriere>"
8+
"Denis Carriere <@DenisCarriere>",
9+
"Samuel Arbibe <@samuelarbibe>"
910
],
1011
"license": "MIT",
1112
"bugs": {
@@ -73,6 +74,7 @@
7374
"@turf/boolean-point-on-line": "workspace:*",
7475
"@turf/helpers": "workspace:*",
7576
"@turf/invariant": "workspace:*",
77+
"@turf/line-split": "workspace:*",
7678
"@types/geojson": "^7946.0.10",
7779
"tslib": "^2.8.1"
7880
}

packages/turf-boolean-contains/test/false/LineString/Polygon/LineIsNotContainedByPolygon.geojson renamed to packages/turf-boolean-contains/test/false/Polygon/LineString/LineIsNotContainedByPolygon.geojson

File renamed without changes.

packages/turf-boolean-contains/test/false/LineString/Polygon/LineIsNotContainedByPolygonBoundary.geojson renamed to packages/turf-boolean-contains/test/false/Polygon/LineString/LineIsNotContainedByPolygonBoundary.geojson

File renamed without changes.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"type": "FeatureCollection",
3+
"features": [
4+
{
5+
"type": "Feature",
6+
"properties": {},
7+
"geometry": {
8+
"type": "Polygon",
9+
"coordinates": [
10+
[
11+
[9.6459207, -1.2281524],
12+
[11.0822393, -1.2461022],
13+
[11.405411, -0.1869151],
14+
[11.1181473, 1.9133448],
15+
[10.0588623, 1.4646914],
16+
[10.6692977, -0.0253296],
17+
[10.6692977, -0.0253296],
18+
[9.6459207, -1.2281524]
19+
]
20+
]
21+
}
22+
},
23+
{
24+
"type": "Feature",
25+
"properties": {},
26+
"geometry": {
27+
"type": "LineString",
28+
"coordinates": [
29+
[10.2166256, -0.7379163],
30+
[11.0362904, 1.794324]
31+
]
32+
}
33+
}
34+
]
35+
}

packages/turf-boolean-contains/test/true/LineString/Polygon/LineIsContainedByPolygon.geojson renamed to packages/turf-boolean-contains/test/true/Polygon/LineString/LineIsContainedByPolygon.geojson

File renamed without changes.

packages/turf-boolean-contains/test/true/LineString/Polygon/LineIsContainedByPolygonWithNoInternalVertices.geojson renamed to packages/turf-boolean-contains/test/true/Polygon/LineString/LineIsContainedByPolygonWithNoInternalVertices.geojson

File renamed without changes.

0 commit comments

Comments
 (0)