Skip to content

Commit 00fe95f

Browse files
committed
Implement Points Within Polygon
1 parent 7b5e966 commit 00fe95f

File tree

3 files changed

+366
-0
lines changed

3 files changed

+366
-0
lines changed

lib/points_within_polygon.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
library turf_point_to_line_distance;
2+
3+
export 'package:geotypes/geotypes.dart';
4+
export 'src/points_within_polygon.dart';

lib/src/points_within_polygon.dart

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
2+
import 'package:turf/meta.dart';
3+
import 'package:turf/src/booleans/boolean_point_in_polygon.dart';
4+
5+
/// Returns every Point (or the subset of coordinates of
6+
/// a MultiPoint) that falls inside at least one Polygon/MultiPolygon.
7+
///
8+
/// The geometry type of each returned feature matches
9+
/// its input type: Point ➜ Point, MultiPoint ➜ trimmed MultiPoint.
10+
FeatureCollection<GeometryObject> pointsWithinPolygon(
11+
GeoJSONObject points,
12+
GeoJSONObject polygons,
13+
) {
14+
final List<Feature<GeometryObject>> results = [];
15+
16+
// Iterate over each Point or MultiPoint feature
17+
featureEach(points, (Feature current, int? _) {
18+
bool contained = false;
19+
20+
final geom = current.geometry;
21+
if (geom is Point) {
22+
// Check a single Point against every polygon
23+
geomEach(polygons, (poly, __, ___, ____, _____) {
24+
if (booleanPointInPolygon(geom.coordinates, poly as GeoJSONObject)) {
25+
contained = true;
26+
}
27+
});
28+
if (contained) results.add(current);
29+
}
30+
31+
else if (geom is MultiPoint) {
32+
final inside = <Position>[];
33+
34+
// Test every coordinate of the MultiPoint
35+
geomEach(polygons, (poly, __, ___, ____, _____) {
36+
for (final pos in geom.coordinates) {
37+
if (booleanPointInPolygon(pos, poly as GeoJSONObject)) {
38+
contained = true;
39+
inside.add(pos);
40+
}
41+
}
42+
});
43+
44+
if (contained) {
45+
results.add(
46+
Feature<MultiPoint>(
47+
geometry: MultiPoint(coordinates: inside),
48+
properties: current.properties,
49+
id: current.id,
50+
bbox: current.bbox,
51+
) as Feature<GeometryObject>,
52+
);
53+
}
54+
}
55+
56+
else {
57+
throw ArgumentError('Input geometry must be Point or MultiPoint');
58+
}
59+
});
60+
61+
return FeatureCollection<GeometryObject>(features: results);
62+
}
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
import 'package:test/test.dart';
2+
import 'package:turf/helpers.dart';
3+
4+
import 'package:turf/src/points_within_polygon.dart';
5+
6+
void main() {
7+
group('pointsWithinPolygon — Point', () {
8+
test('single point in single polygon', () {
9+
final poly = Feature<Polygon>(
10+
geometry: Polygon(coordinates: [
11+
[
12+
Position(0, 0),
13+
Position(0, 100),
14+
Position(100, 100),
15+
Position(100, 0),
16+
Position(0, 0),
17+
]
18+
]),
19+
);
20+
21+
final pt = Feature<Point>(
22+
geometry: Point(coordinates: Position(50, 50)),
23+
);
24+
25+
final counted = pointsWithinPolygon(
26+
FeatureCollection<Point>(features: [pt]),
27+
FeatureCollection<Polygon>(features: [poly]),
28+
);
29+
30+
expect(counted, isA<FeatureCollection>());
31+
expect(counted.features.length, equals(1));
32+
});
33+
34+
test('multiple points & multiple polygons', () {
35+
final poly1 = Feature<Polygon>(
36+
geometry: Polygon(coordinates: [
37+
[
38+
Position(0, 0),
39+
Position(10, 0),
40+
Position(10, 10),
41+
Position(0, 10),
42+
Position(0, 0),
43+
]
44+
]),
45+
);
46+
final poly2 = Feature<Polygon>(
47+
geometry: Polygon(coordinates: [
48+
[
49+
Position(10, 0),
50+
Position(20, 10),
51+
Position(20, 20),
52+
Position(20, 0),
53+
Position(10, 0),
54+
]
55+
]),
56+
);
57+
final polys = FeatureCollection<Polygon>(features: [poly1, poly2]);
58+
59+
final pts = FeatureCollection<Point>(features: [
60+
Feature<Point>(
61+
geometry: Point(coordinates: Position(1, 1)),
62+
properties: {'population': 500}),
63+
Feature<Point>(
64+
geometry: Point(coordinates: Position(1, 3)),
65+
properties: {'population': 400}),
66+
Feature<Point>(
67+
geometry: Point(coordinates: Position(14, 2)),
68+
properties: {'population': 600}),
69+
Feature<Point>(
70+
geometry: Point(coordinates: Position(13, 1)),
71+
properties: {'population': 500}),
72+
Feature<Point>(
73+
geometry: Point(coordinates: Position(19, 7)),
74+
properties: {'population': 200}),
75+
Feature<Point>(
76+
geometry: Point(coordinates: Position(100, 7)),
77+
properties: {'population': 200}),
78+
]);
79+
80+
final counted = pointsWithinPolygon(pts, polys);
81+
82+
expect(counted, isA<FeatureCollection>());
83+
expect(counted.features.length, equals(5));
84+
});
85+
});
86+
87+
group('pointsWithinPolygon — MultiPoint', () {
88+
test('single multipoint', () {
89+
final poly = FeatureCollection<Polygon>(features: [
90+
Feature<Polygon>(
91+
geometry: Polygon(coordinates: [
92+
[
93+
Position(0, 0),
94+
Position(0, 100),
95+
Position(100, 100),
96+
Position(100, 0),
97+
Position(0, 0)
98+
]
99+
]),
100+
)
101+
]);
102+
103+
final mptInside = Feature<MultiPoint>(
104+
geometry: MultiPoint(coordinates: [Position(50, 50)]),
105+
);
106+
final mptOutside = Feature<MultiPoint>(
107+
geometry: MultiPoint(coordinates: [Position(150, 150)]),
108+
);
109+
final mptMixed = Feature<MultiPoint>(
110+
geometry: MultiPoint(coordinates: [Position(50, 50), Position(150, 150)]),
111+
);
112+
113+
// inside
114+
final within = pointsWithinPolygon(mptInside, poly);
115+
expect(within.features.length, equals(1));
116+
expect((within.features.first.geometry! as MultiPoint).coordinates.length, equals(1));
117+
118+
// feature-collection wrapper
119+
final withinFC =
120+
pointsWithinPolygon(FeatureCollection<MultiPoint>(features: [mptInside]), poly);
121+
expect(withinFC.features.length, equals(1));
122+
123+
// outside
124+
final notWithin = pointsWithinPolygon(mptOutside, poly);
125+
expect(notWithin.features, isEmpty);
126+
127+
// mixed
128+
final partWithin = pointsWithinPolygon(mptMixed, poly);
129+
expect((partWithin.features.first.geometry! as MultiPoint).coordinates.length, equals(1));
130+
expect(
131+
(partWithin.features.first.geometry! as MultiPoint).coordinates.first,
132+
equals(mptMixed.geometry!.coordinates.first),
133+
);
134+
});
135+
136+
test('multiple multipoints & polygons', () {
137+
final poly1 = Feature<Polygon>(
138+
geometry: Polygon(coordinates: [
139+
[
140+
Position(0, 0),
141+
Position(0, 100),
142+
Position(100, 100),
143+
Position(100, 0),
144+
Position(0, 0)
145+
]
146+
]),
147+
);
148+
final poly2 = Feature<Polygon>(
149+
geometry: Polygon(coordinates: [
150+
[
151+
Position(10, 0),
152+
Position(20, 10),
153+
Position(20, 20),
154+
Position(20, 0),
155+
Position(10, 0)
156+
]
157+
]),
158+
);
159+
160+
final mpt1 =
161+
Feature<MultiPoint>(geometry: MultiPoint(coordinates: [Position(50, 50)]));
162+
final mpt2 =
163+
Feature<MultiPoint>(geometry: MultiPoint(coordinates: [Position(150, 150)]));
164+
final mpt3 = Feature<MultiPoint>(
165+
geometry: MultiPoint(coordinates: [Position(50, 50), Position(150, 150)]),
166+
);
167+
168+
final result = pointsWithinPolygon(
169+
FeatureCollection<MultiPoint>(features: [mpt1, mpt2, mpt3]),
170+
FeatureCollection<Polygon>(features: [poly1, poly2]),
171+
);
172+
173+
expect(result.features.length, equals(2));
174+
});
175+
});
176+
177+
group('pointsWithinPolygon — mixed Point & MultiPoint', () {
178+
test('mixed inputs', () {
179+
final poly = FeatureCollection<Polygon>(features: [
180+
Feature<Polygon>(
181+
geometry: Polygon(coordinates: [
182+
[
183+
Position(0, 0),
184+
Position(0, 100),
185+
Position(100, 100),
186+
Position(100, 0),
187+
Position(0, 0)
188+
]
189+
]),
190+
)
191+
]);
192+
193+
final pt = Feature<Point>(geometry: Point(coordinates: Position(50, 50)));
194+
final mptInside =
195+
Feature<MultiPoint>(geometry: MultiPoint(coordinates: [Position(50, 50)]));
196+
final mptOutside =
197+
Feature<MultiPoint>(geometry: MultiPoint(coordinates: [Position(150, 150)]));
198+
199+
final counted = pointsWithinPolygon(
200+
FeatureCollection( // dynamic FC so we can mix types
201+
features: [pt, mptInside, mptOutside],
202+
),
203+
poly,
204+
);
205+
206+
expect(counted.features.length, equals(2));
207+
expect(counted.features[0].geometry, isA<Point>());
208+
expect(counted.features[1].geometry, isA<MultiPoint>());
209+
});
210+
});
211+
212+
group('pointsWithinPolygon — extras & edge-cases', () {
213+
test('works with raw Geometry or single Feature inputs', () {
214+
final pts = FeatureCollection<Point>(features: [
215+
Feature<Point>(geometry: Point(coordinates: Position(-46.6318, -23.5523))),
216+
Feature<Point>(geometry: Point(coordinates: Position(-46.6246, -23.5325))),
217+
Feature<Point>(geometry: Point(coordinates: Position(-46.6062, -23.5513))),
218+
Feature<Point>(geometry: Point(coordinates: Position(-46.663, -23.554))),
219+
Feature<Point>(geometry: Point(coordinates: Position(-46.643, -23.557))),
220+
]);
221+
222+
final searchWithin = Feature<Polygon>(
223+
geometry: Polygon(coordinates: [
224+
[
225+
Position(-46.653, -23.543),
226+
Position(-46.634, -23.5346),
227+
Position(-46.613, -23.543),
228+
Position(-46.614, -23.559),
229+
Position(-46.631, -23.567),
230+
Position(-46.653, -23.56),
231+
Position(-46.653, -23.543),
232+
]
233+
]),
234+
);
235+
236+
expect(pointsWithinPolygon(pts, searchWithin), isNotNull);
237+
expect(pointsWithinPolygon(pts.features.first, searchWithin), isNotNull);
238+
expect(pointsWithinPolygon(pts, searchWithin.geometry!), isNotNull);
239+
});
240+
241+
test('no duplicates when a point is inside ≥2 polygons', () {
242+
final poly1 = Feature<Polygon>(
243+
geometry: Polygon(coordinates: [
244+
[
245+
Position(0, 0),
246+
Position(10, 0),
247+
Position(10, 10),
248+
Position(0, 10),
249+
Position(0, 0),
250+
]
251+
]),
252+
);
253+
final poly2 = Feature<Polygon>(
254+
geometry: Polygon(coordinates: [
255+
[
256+
Position(0, 0),
257+
Position(10, 0),
258+
Position(10, 10),
259+
Position(0, 10),
260+
Position(0, 0),
261+
]
262+
]),
263+
);
264+
final pt = Feature<Point>(geometry: Point(coordinates: Position(5, 5)));
265+
266+
final counted = pointsWithinPolygon(
267+
FeatureCollection<Point>(features: [pt]),
268+
FeatureCollection<Polygon>(features: [poly1, poly2]),
269+
);
270+
271+
expect(counted.features.length, equals(1));
272+
});
273+
274+
test('preserves properties on output multipoints', () {
275+
final poly = FeatureCollection<Polygon>(features: [
276+
Feature<Polygon>(
277+
geometry: Polygon(coordinates: [
278+
[
279+
Position(0, 0),
280+
Position(0, 100),
281+
Position(100, 100),
282+
Position(100, 0),
283+
Position(0, 0)
284+
]
285+
]),
286+
)
287+
]);
288+
289+
final mpt = Feature<MultiPoint>(
290+
geometry: MultiPoint(coordinates: [Position(50, 50), Position(150, 150)]),
291+
properties: {'prop': 'yes'},
292+
);
293+
294+
final out = pointsWithinPolygon(mpt, poly);
295+
296+
expect(out.features.length, equals(1));
297+
expect(out.features.first.properties, containsPair('prop', 'yes'));
298+
});
299+
});
300+
}

0 commit comments

Comments
 (0)