Skip to content

Commit decb5cd

Browse files
authored
@turf/boolean-within: add MultiPolygon support, de-duplicate logic (#3011)
1 parent c7e8fa3 commit decb5cd

27 files changed

Lines changed: 984 additions & 286 deletions

packages/turf-boolean-within/index.ts

Lines changed: 3 additions & 262 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,5 @@
1-
import {
2-
BBox,
3-
Feature,
4-
Geometry,
5-
LineString,
6-
MultiPoint,
7-
MultiPolygon,
8-
Point,
9-
Polygon,
10-
} from "geojson";
11-
import { bbox as calcBbox } from "@turf/bbox";
12-
import { booleanPointOnLine } from "@turf/boolean-point-on-line";
13-
import { booleanPointInPolygon } from "@turf/boolean-point-in-polygon";
14-
import { getGeom } from "@turf/invariant";
15-
import { feature, featureCollection, lineString } from "@turf/helpers";
16-
import { lineSplit } from "@turf/line-split";
1+
import { Feature, Geometry } from "geojson";
2+
import { booleanContains } from "@turf/boolean-contains";
173

184
/**
195
* Tests whether geometry a is contained by geometry b.
@@ -35,252 +21,7 @@ function booleanWithin(
3521
feature1: Feature<any> | Geometry,
3622
feature2: Feature<any> | Geometry
3723
): boolean {
38-
var geom1 = getGeom(feature1);
39-
var geom2 = getGeom(feature2);
40-
var type1 = geom1.type;
41-
var type2 = geom2.type;
42-
43-
switch (type1) {
44-
case "Point":
45-
switch (type2) {
46-
case "MultiPoint":
47-
return isPointInMultiPoint(geom1, geom2);
48-
case "LineString":
49-
return booleanPointOnLine(geom1, geom2, { ignoreEndVertices: true });
50-
case "Polygon":
51-
case "MultiPolygon":
52-
return booleanPointInPolygon(geom1, geom2, { ignoreBoundary: true });
53-
default:
54-
throw new Error("feature2 " + type2 + " geometry not supported");
55-
}
56-
case "MultiPoint":
57-
switch (type2) {
58-
case "MultiPoint":
59-
return isMultiPointInMultiPoint(geom1, geom2);
60-
case "LineString":
61-
return isMultiPointOnLine(geom1, geom2);
62-
case "Polygon":
63-
case "MultiPolygon":
64-
return isMultiPointInPoly(geom1, geom2);
65-
default:
66-
throw new Error("feature2 " + type2 + " geometry not supported");
67-
}
68-
case "LineString":
69-
switch (type2) {
70-
case "LineString":
71-
return isLineOnLine(geom1, geom2);
72-
case "Polygon":
73-
case "MultiPolygon":
74-
return isLineInPoly(geom1, geom2);
75-
default:
76-
throw new Error("feature2 " + type2 + " geometry not supported");
77-
}
78-
case "Polygon":
79-
switch (type2) {
80-
case "Polygon":
81-
case "MultiPolygon":
82-
return isPolyInPoly(geom1, geom2);
83-
default:
84-
throw new Error("feature2 " + type2 + " geometry not supported");
85-
}
86-
default:
87-
throw new Error("feature1 " + type1 + " geometry not supported");
88-
}
89-
}
90-
91-
function isPointInMultiPoint(point: Point, multiPoint: MultiPoint) {
92-
var i;
93-
var output = false;
94-
for (i = 0; i < multiPoint.coordinates.length; i++) {
95-
if (compareCoords(multiPoint.coordinates[i], point.coordinates)) {
96-
output = true;
97-
break;
98-
}
99-
}
100-
return output;
101-
}
102-
103-
function isMultiPointInMultiPoint(
104-
multiPoint1: MultiPoint,
105-
multiPoint2: MultiPoint
106-
) {
107-
for (var i = 0; i < multiPoint1.coordinates.length; i++) {
108-
var anyMatch = false;
109-
for (var i2 = 0; i2 < multiPoint2.coordinates.length; i2++) {
110-
if (
111-
compareCoords(multiPoint1.coordinates[i], multiPoint2.coordinates[i2])
112-
) {
113-
anyMatch = true;
114-
}
115-
}
116-
if (!anyMatch) {
117-
return false;
118-
}
119-
}
120-
return true;
121-
}
122-
123-
function isMultiPointOnLine(multiPoint: MultiPoint, lineString: LineString) {
124-
var foundInsidePoint = false;
125-
126-
for (var i = 0; i < multiPoint.coordinates.length; i++) {
127-
if (!booleanPointOnLine(multiPoint.coordinates[i], lineString)) {
128-
return false;
129-
}
130-
if (!foundInsidePoint) {
131-
foundInsidePoint = booleanPointOnLine(
132-
multiPoint.coordinates[i],
133-
lineString,
134-
{ ignoreEndVertices: true }
135-
);
136-
}
137-
}
138-
return foundInsidePoint;
139-
}
140-
141-
function isMultiPointInPoly(multiPoint: MultiPoint, polygon: Polygon) {
142-
var oneInside = false;
143-
for (var i = 0; i < multiPoint.coordinates.length; i++) {
144-
// All points must be inside polygon (boundary OK)
145-
if (!booleanPointInPolygon(multiPoint.coordinates[i], polygon)) {
146-
return false;
147-
}
148-
// Track if at least one point is strictly in the interior
149-
if (!oneInside) {
150-
oneInside = booleanPointInPolygon(multiPoint.coordinates[i], polygon, {
151-
ignoreBoundary: true,
152-
});
153-
}
154-
}
155-
// At least one point must be in the interior (not just on boundary)
156-
return oneInside;
157-
}
158-
159-
function isLineOnLine(lineString1: LineString, lineString2: LineString) {
160-
for (var i = 0; i < lineString1.coordinates.length; i++) {
161-
if (!booleanPointOnLine(lineString1.coordinates[i], lineString2)) {
162-
return false;
163-
}
164-
}
165-
return true;
166-
}
167-
168-
function splitLineIntoSegmentsOnPolygon(
169-
linestring: LineString,
170-
polygon: Polygon
171-
) {
172-
const coords = linestring.coordinates;
173-
174-
const outputSegments: Feature<LineString>[] = [];
175-
176-
for (let i = 0; i < coords.length - 1; i++) {
177-
const seg = lineString([coords[i], coords[i + 1]]);
178-
const split = lineSplit(seg, feature(polygon));
179-
180-
if (split.features.length === 0) {
181-
outputSegments.push(seg);
182-
} else {
183-
outputSegments.push(...split.features);
184-
}
185-
}
186-
187-
return featureCollection(outputSegments);
188-
}
189-
190-
function isLineInPoly(linestring: LineString, polygon: Polygon) {
191-
const polyBbox = calcBbox(polygon);
192-
const lineBbox = calcBbox(linestring);
193-
194-
if (!doBBoxOverlap(polyBbox, lineBbox)) {
195-
return false;
196-
}
197-
198-
for (const coord of linestring.coordinates) {
199-
if (!booleanPointInPolygon(coord, polygon)) {
200-
return false;
201-
}
202-
}
203-
204-
let isContainedByPolygonBoundary = false;
205-
// split intersecting segments and verify their inclusion
206-
const lineSegments = splitLineIntoSegmentsOnPolygon(linestring, polygon);
207-
208-
for (const lineSegment of lineSegments.features) {
209-
const midpoint = getMidpoint(
210-
lineSegment.geometry.coordinates[0],
211-
lineSegment.geometry.coordinates[1]
212-
);
213-
214-
// make sure all segments do not intersect with polygon exterior
215-
if (!booleanPointInPolygon(midpoint, polygon)) {
216-
return false;
217-
}
218-
219-
// make sure at least 1 segment intersects with the polygon's interior
220-
if (
221-
!isContainedByPolygonBoundary &&
222-
booleanPointInPolygon(midpoint, polygon, { ignoreBoundary: true })
223-
) {
224-
isContainedByPolygonBoundary = true;
225-
}
226-
}
227-
228-
return isContainedByPolygonBoundary;
229-
}
230-
231-
/**
232-
* Is Polygon2 in Polygon1
233-
* Only takes into account outer rings
234-
*
235-
* @private
236-
* @param {Polygon} geometry1
237-
* @param {Polygon|MultiPolygon} geometry2
238-
* @returns {boolean} true/false
239-
*/
240-
function isPolyInPoly(geometry1: Polygon, geometry2: Polygon | MultiPolygon) {
241-
var poly1Bbox = calcBbox(geometry1);
242-
var poly2Bbox = calcBbox(geometry2);
243-
if (!doBBoxOverlap(poly2Bbox, poly1Bbox)) {
244-
return false;
245-
}
246-
for (var i = 0; i < geometry1.coordinates[0].length; i++) {
247-
if (!booleanPointInPolygon(geometry1.coordinates[0][i], geometry2)) {
248-
return false;
249-
}
250-
}
251-
return true;
252-
}
253-
254-
function doBBoxOverlap(bbox1: BBox, bbox2: BBox) {
255-
if (bbox1[0] > bbox2[0]) return false;
256-
if (bbox1[2] < bbox2[2]) return false;
257-
if (bbox1[1] > bbox2[1]) return false;
258-
if (bbox1[3] < bbox2[3]) return false;
259-
return true;
260-
}
261-
262-
/**
263-
* compareCoords
264-
*
265-
* @private
266-
* @param {Position} pair1 point [x,y]
267-
* @param {Position} pair2 point [x,y]
268-
* @returns {boolean} true/false if coord pairs match
269-
*/
270-
function compareCoords(pair1: number[], pair2: number[]) {
271-
return pair1[0] === pair2[0] && pair1[1] === pair2[1];
272-
}
273-
274-
/**
275-
* getMidpoint
276-
*
277-
* @private
278-
* @param {Position} pair1 point [x,y]
279-
* @param {Position} pair2 point [x,y]
280-
* @returns {Position} midpoint of pair1 and pair2
281-
*/
282-
function getMidpoint(pair1: number[], pair2: number[]) {
283-
return [(pair1[0] + pair2[0]) / 2, (pair1[1] + pair2[1]) / 2];
24+
return booleanContains(feature2, feature1);
28425
}
28526

28627
export { booleanWithin };

packages/turf-boolean-within/package.json

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
"author": "Turf Authors",
66
"contributors": [
77
"Rowan Winsemius <@rowanwins>",
8-
"Samuel Arbibe <@samuelarbibe>"
8+
"Denis Carriere <@DenisCarriere>",
9+
"Samuel Arbibe <@samuelarbibe>",
10+
"Espen Hovlandsdal <@rexxars>"
911
],
1012
"license": "MIT",
1113
"bugs": {
@@ -57,6 +59,7 @@
5759
"test:types": "tsc --esModuleInterop --module node16 --moduleResolution node16 --noEmit --strict types.ts"
5860
},
5961
"devDependencies": {
62+
"@turf/helpers": "workspace:*",
6063
"@types/benchmark": "catalog:",
6164
"@types/tape": "catalog:",
6265
"benchmark": "catalog:",
@@ -69,12 +72,7 @@
6972
"typescript": "catalog:"
7073
},
7174
"dependencies": {
72-
"@turf/bbox": "workspace:*",
73-
"@turf/boolean-point-in-polygon": "workspace:*",
74-
"@turf/boolean-point-on-line": "workspace:*",
75-
"@turf/helpers": "workspace:*",
76-
"@turf/invariant": "workspace:*",
77-
"@turf/line-split": "workspace:*",
75+
"@turf/boolean-contains": "workspace:*",
7876
"@types/geojson": "catalog:",
7977
"tslib": "catalog:"
8078
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"type": "FeatureCollection",
3+
"features": [
4+
{
5+
"type": "Feature",
6+
"properties": {},
7+
"geometry": {
8+
"type": "LineString",
9+
"coordinates": [
10+
[0, 2],
11+
[0, 8]
12+
]
13+
}
14+
},
15+
{
16+
"type": "Feature",
17+
"properties": {},
18+
"geometry": {
19+
"type": "MultiPolygon",
20+
"coordinates": [
21+
[
22+
[
23+
[0, 0],
24+
[10, 0],
25+
[10, 10],
26+
[0, 10],
27+
[0, 0]
28+
]
29+
],
30+
[
31+
[
32+
[20, 0],
33+
[30, 0],
34+
[30, 10],
35+
[20, 10],
36+
[20, 0]
37+
]
38+
]
39+
]
40+
}
41+
}
42+
]
43+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"type": "FeatureCollection",
3+
"features": [
4+
{
5+
"type": "Feature",
6+
"properties": {},
7+
"geometry": {
8+
"type": "LineString",
9+
"coordinates": [
10+
[12, 5],
11+
[18, 5]
12+
]
13+
}
14+
},
15+
{
16+
"type": "Feature",
17+
"properties": {},
18+
"geometry": {
19+
"type": "MultiPolygon",
20+
"coordinates": [
21+
[
22+
[
23+
[0, 0],
24+
[10, 0],
25+
[10, 10],
26+
[0, 10],
27+
[0, 0]
28+
]
29+
],
30+
[
31+
[
32+
[20, 0],
33+
[30, 0],
34+
[30, 10],
35+
[20, 10],
36+
[20, 0]
37+
]
38+
]
39+
]
40+
}
41+
}
42+
]
43+
}

0 commit comments

Comments
 (0)