• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geo-engine / geoengine / 3929938005

pending completion
3929938005

push

github

GitHub
Merge #713

84930 of 96741 relevant lines covered (87.79%)

79640.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.72
/operators/src/processing/point_in_polygon.rs
1
mod tester;
2
mod wrapper;
3

4
use std::cmp::min;
5
use std::sync::Arc;
6

7
use futures::stream::BoxStream;
8
use futures::{StreamExt, TryStreamExt};
9
use geoengine_datatypes::dataset::DataId;
10
use geoengine_datatypes::primitives::VectorQueryRectangle;
11
use rayon::ThreadPool;
12
use serde::{Deserialize, Serialize};
13
use snafu::ensure;
14
use tracing::{span, Level};
15

16
use crate::adapters::FeatureCollectionChunkMerger;
17
use crate::engine::{
18
    CreateSpan, ExecutionContext, InitializedVectorOperator, Operator, OperatorName, QueryContext,
19
    TypedVectorQueryProcessor, VectorOperator, VectorQueryProcessor, VectorResultDescriptor,
20
};
21
use crate::engine::{OperatorData, QueryProcessor};
22
use crate::error;
23
use crate::util::Result;
24
use arrow::array::BooleanArray;
25
use async_trait::async_trait;
26
use geoengine_datatypes::collections::{
27
    FeatureCollectionInfos, FeatureCollectionModifications, GeometryCollection,
28
    MultiPointCollection, MultiPolygonCollection, VectorDataType,
29
};
30
pub use tester::PointInPolygonTester;
31
pub use wrapper::PointInPolygonTesterWithCollection;
32

33
/// The point in polygon filter requires two inputs in the following order:
34
/// 1. a `MultiPointCollection` source
35
/// 2. a `MultiPolygonCollection` source
36
/// Then, it filters the `MultiPolygonCollection`s so that only those features are retained that are in any polygon.
37
pub type PointInPolygonFilter = Operator<PointInPolygonFilterParams, PointInPolygonFilterSource>;
38

39
impl OperatorName for PointInPolygonFilter {
40
    const TYPE_NAME: &'static str = "PointInPolygonFilter";
41
}
42

43
#[derive(Debug, Clone, Deserialize, Serialize)]
×
44
pub struct PointInPolygonFilterParams {}
45

46
#[derive(Debug, Clone, Deserialize, Serialize)]
×
47
pub struct PointInPolygonFilterSource {
48
    pub points: Box<dyn VectorOperator>,
49
    pub polygons: Box<dyn VectorOperator>,
50
}
51

52
impl OperatorData for PointInPolygonFilterSource {
53
    fn data_ids_collect(&self, data_ids: &mut Vec<DataId>) {
×
54
        self.points.data_ids_collect(data_ids);
×
55
        self.polygons.data_ids_collect(data_ids);
×
56
    }
×
57
}
58

59
#[typetag::serde]
×
60
#[async_trait]
61
impl VectorOperator for PointInPolygonFilter {
62
    async fn _initialize(
6✔
63
        self: Box<Self>,
6✔
64
        context: &dyn ExecutionContext,
6✔
65
    ) -> Result<Box<dyn InitializedVectorOperator>> {
6✔
66
        let points = self.sources.points.initialize(context).await?;
6✔
67
        let polygons = self.sources.polygons.initialize(context).await?;
6✔
68

69
        let points_rd = points.result_descriptor();
6✔
70
        let polygons_rd = polygons.result_descriptor();
6✔
71

6✔
72
        ensure!(
6✔
73
            points_rd.data_type == VectorDataType::MultiPoint,
6✔
74
            error::InvalidType {
×
75
                expected: VectorDataType::MultiPoint.to_string(),
×
76
                found: points_rd.data_type.to_string(),
×
77
            }
×
78
        );
79
        ensure!(
6✔
80
            polygons_rd.data_type == VectorDataType::MultiPolygon,
6✔
81
            error::InvalidType {
×
82
                expected: VectorDataType::MultiPolygon.to_string(),
×
83
                found: polygons_rd.data_type.to_string(),
×
84
            }
×
85
        );
86

87
        ensure!(
6✔
88
            points_rd.spatial_reference == polygons_rd.spatial_reference,
6✔
89
            crate::error::InvalidSpatialReference {
1✔
90
                expected: points_rd.spatial_reference,
1✔
91
                found: polygons_rd.spatial_reference,
1✔
92
            }
1✔
93
        );
94

95
        // We use the result descriptor of the points because in the worst case no feature will be excluded.
96
        // We cannot use the polygon bbox because a `MultiPoint` could have one point within a polygon (and
97
        // thus be included in the result) and one point outside of the bbox of the polygons.
98
        let out_desc = points.result_descriptor().clone();
5✔
99

5✔
100
        let initialized_operator = InitializedPointInPolygonFilter {
5✔
101
            result_descriptor: out_desc,
5✔
102
            points,
5✔
103
            polygons,
5✔
104
        };
5✔
105

5✔
106
        Ok(initialized_operator.boxed())
5✔
107
    }
12✔
108

109
    span_fn!(PointInPolygonFilter);
×
110
}
111

112
pub struct InitializedPointInPolygonFilter {
113
    points: Box<dyn InitializedVectorOperator>,
114
    polygons: Box<dyn InitializedVectorOperator>,
115
    result_descriptor: VectorResultDescriptor,
116
}
117

118
impl InitializedVectorOperator for InitializedPointInPolygonFilter {
119
    fn query_processor(&self) -> Result<TypedVectorQueryProcessor> {
5✔
120
        let point_processor = self
5✔
121
            .points
5✔
122
            .query_processor()?
5✔
123
            .multi_point()
5✔
124
            .expect("checked in `PointInPolygonFilter` constructor");
5✔
125

126
        let polygon_processor = self
5✔
127
            .polygons
5✔
128
            .query_processor()?
5✔
129
            .multi_polygon()
5✔
130
            .expect("checked in `PointInPolygonFilter` constructor");
5✔
131

5✔
132
        Ok(TypedVectorQueryProcessor::MultiPoint(
5✔
133
            PointInPolygonFilterProcessor::new(point_processor, polygon_processor).boxed(),
5✔
134
        ))
5✔
135
    }
5✔
136

137
    fn result_descriptor(&self) -> &VectorResultDescriptor {
×
138
        &self.result_descriptor
×
139
    }
×
140
}
141

142
pub struct PointInPolygonFilterProcessor {
143
    points: Box<dyn VectorQueryProcessor<VectorType = MultiPointCollection>>,
144
    polygons: Box<dyn VectorQueryProcessor<VectorType = MultiPolygonCollection>>,
145
}
146

147
impl PointInPolygonFilterProcessor {
148
    pub fn new(
5✔
149
        points: Box<dyn VectorQueryProcessor<VectorType = MultiPointCollection>>,
5✔
150
        polygons: Box<dyn VectorQueryProcessor<VectorType = MultiPolygonCollection>>,
5✔
151
    ) -> Self {
5✔
152
        Self { points, polygons }
5✔
153
    }
5✔
154

155
    fn filter_parallel(
10✔
156
        points: &Arc<MultiPointCollection>,
10✔
157
        polygons: &MultiPolygonCollection,
10✔
158
        thread_pool: &ThreadPool,
10✔
159
    ) -> Vec<bool> {
10✔
160
        debug_assert!(!points.is_empty());
10✔
161

162
        // TODO: parallelize over coordinate rather than features
163

164
        let tester = Arc::new(PointInPolygonTester::new(polygons)); // TODO: multithread
10✔
165

10✔
166
        let parallelism = thread_pool.current_num_threads();
10✔
167
        let chunk_size = (points.len() as f64 / parallelism as f64).ceil() as usize;
10✔
168

10✔
169
        let mut result = vec![false; points.len()];
10✔
170

10✔
171
        thread_pool.scope(|scope| {
10✔
172
            let num_features = points.len();
10✔
173
            let feature_offsets = points.feature_offsets();
10✔
174
            let time_intervals = points.time_intervals();
10✔
175
            let coordinates = points.coordinates();
10✔
176

177
            for (chunk_index, chunk_result) in result.chunks_mut(chunk_size).enumerate() {
20✔
178
                let feature_index_start = chunk_index * chunk_size;
20✔
179
                let features_index_end = min(feature_index_start + chunk_size, num_features);
20✔
180
                let tester = tester.clone();
20✔
181

20✔
182
                scope.spawn(move |_| {
20✔
183
                    for (
184
                        feature_index,
22✔
185
                        ((coordinates_start_index, coordinates_end_index), time_interval),
22✔
186
                    ) in two_tuple_windows(
20✔
187
                        feature_offsets[feature_index_start..=features_index_end]
20✔
188
                            .iter()
20✔
189
                            .map(|&c| c as usize),
42✔
190
                    )
20✔
191
                    .zip(time_intervals[feature_index_start..features_index_end].iter())
20✔
192
                    .enumerate()
20✔
193
                    {
22✔
194
                        let is_multi_point_in_polygon_collection = coordinates
22✔
195
                            [coordinates_start_index..coordinates_end_index]
22✔
196
                            .iter()
22✔
197
                            .any(|coordinate| {
22✔
198
                                tester.any_polygon_contains_coordinate(coordinate, time_interval)
22✔
199
                            });
22✔
200

22✔
201
                        chunk_result[feature_index] = is_multi_point_in_polygon_collection;
22✔
202
                    }
22✔
203
                });
20✔
204
            }
20✔
205
        });
10✔
206

10✔
207
        result
10✔
208
    }
10✔
209

210
    async fn filter_points(
10✔
211
        ctx: &dyn QueryContext,
10✔
212
        points: Arc<MultiPointCollection>,
10✔
213
        polygons: MultiPolygonCollection,
10✔
214
        initial_filter: &BooleanArray,
10✔
215
    ) -> Result<BooleanArray> {
10✔
216
        let thread_pool = ctx.thread_pool().clone();
10✔
217

10✔
218
        let thread_points = points.clone();
10✔
219
        let filter = crate::util::spawn_blocking(move || {
10✔
220
            Self::filter_parallel(&thread_points, &polygons, &thread_pool)
10✔
221
        })
10✔
222
        .await?;
10✔
223

224
        arrow::compute::or(initial_filter, &filter.into()).map_err(Into::into)
10✔
225
    }
10✔
226
}
227

228
#[async_trait]
229
impl VectorQueryProcessor for PointInPolygonFilterProcessor {
230
    type VectorType = MultiPointCollection;
231

232
    async fn vector_query<'a>(
6✔
233
        &'a self,
6✔
234
        query: VectorQueryRectangle,
6✔
235
        ctx: &'a dyn QueryContext,
6✔
236
    ) -> Result<BoxStream<'a, Result<Self::VectorType>>> {
6✔
237
        let filtered_stream =
6✔
238
            self.points
6✔
239
                .query(query, ctx)
6✔
240
                .await?
×
241
                .and_then(move |points| async move {
8✔
242
                    if points.is_empty() {
8✔
243
                        return Ok(points);
1✔
244
                    }
7✔
245

7✔
246
                    let initial_filter = BooleanArray::from(vec![false; points.len()]);
7✔
247
                    let arc_points = Arc::new(points);
7✔
248

249
                    let filter = self
7✔
250
                        .polygons
7✔
251
                        .query(query, ctx)
7✔
252
                        .await?
×
253
                        .fold(Ok(initial_filter), |filter, polygons| async {
11✔
254
                            let polygons = polygons?;
11✔
255

256
                            if polygons.is_empty() {
11✔
257
                                return filter;
1✔
258
                            }
10✔
259

10✔
260
                            Self::filter_points(ctx, arc_points.clone(), polygons, &filter?).await
10✔
261
                        })
11✔
262
                        .await?;
10✔
263

264
                    arc_points.filter(filter).map_err(Into::into)
7✔
265
                });
8✔
266

6✔
267
        Ok(
6✔
268
            FeatureCollectionChunkMerger::new(filtered_stream.fuse(), ctx.chunk_byte_size().into())
6✔
269
                .boxed(),
6✔
270
        )
6✔
271
    }
12✔
272
}
273

274
/// Loop through an iterator by yielding the current and previous tuple. Starts with the
275
/// (first, second) item, so the iterator must have more than one item to create an output.
276
fn two_tuple_windows<I, T>(mut iter: I) -> impl Iterator<Item = (T, T)>
20✔
277
where
20✔
278
    I: Iterator<Item = T>,
20✔
279
    T: Copy,
20✔
280
{
20✔
281
    let mut last = iter.next();
20✔
282

20✔
283
    iter.map(move |item| {
22✔
284
        let output = (last.unwrap(), item);
22✔
285
        last = Some(item);
22✔
286
        output
22✔
287
    })
22✔
288
}
20✔
289

290
#[cfg(test)]
291
mod tests {
292

293
    use super::*;
294
    use std::str::FromStr;
295

296
    use geoengine_datatypes::primitives::{
297
        BoundingBox2D, Coordinate2D, MultiPoint, MultiPolygon, SpatialResolution, TimeInterval,
298
    };
299
    use geoengine_datatypes::spatial_reference::SpatialReference;
300
    use geoengine_datatypes::util::test::TestDefault;
301

302
    use crate::engine::{ChunkByteSize, MockExecutionContext, MockQueryContext};
303
    use crate::error::Error;
304
    use crate::mock::MockFeatureCollectionSource;
305

306
    #[test]
1✔
307
    fn point_in_polygon_boundary_conditions() {
1✔
308
        let collection = MultiPolygonCollection::from_data(
1✔
309
            vec![MultiPolygon::new(vec![vec![vec![
1✔
310
                (0.0, 0.0).into(),
1✔
311
                (10.0, 0.0).into(),
1✔
312
                (10.0, 10.0).into(),
1✔
313
                (0.0, 10.0).into(),
1✔
314
                (0.0, 0.0).into(),
1✔
315
            ]]])
1✔
316
            .unwrap()],
1✔
317
            vec![Default::default(); 1],
1✔
318
            Default::default(),
1✔
319
        )
1✔
320
        .unwrap();
1✔
321

1✔
322
        let tester = PointInPolygonTester::new(&collection);
1✔
323

1✔
324
        // the algorithm is not stable for boundary cases directly on the edges
1✔
325

1✔
326
        assert!(tester.any_polygon_contains_coordinate(
1✔
327
            &Coordinate2D::new(0.000_001, 0.000_001),
1✔
328
            &Default::default()
1✔
329
        ),);
1✔
330
        assert!(tester.any_polygon_contains_coordinate(
1✔
331
            &Coordinate2D::new(0.000_001, 0.1),
1✔
332
            &Default::default()
1✔
333
        ),);
1✔
334
        assert!(tester.any_polygon_contains_coordinate(
1✔
335
            &Coordinate2D::new(0.1, 0.000_001),
1✔
336
            &Default::default()
1✔
337
        ),);
1✔
338

339
        assert!(tester
1✔
340
            .any_polygon_contains_coordinate(&Coordinate2D::new(9.9, 9.9), &Default::default()),);
1✔
341
        assert!(tester
1✔
342
            .any_polygon_contains_coordinate(&Coordinate2D::new(10.0, 9.9), &Default::default()),);
1✔
343
        assert!(tester
1✔
344
            .any_polygon_contains_coordinate(&Coordinate2D::new(9.9, 10.0), &Default::default()),);
1✔
345

346
        assert!(!tester
1✔
347
            .any_polygon_contains_coordinate(&Coordinate2D::new(-0.1, -0.1), &Default::default()),);
1✔
348
        assert!(!tester
1✔
349
            .any_polygon_contains_coordinate(&Coordinate2D::new(0.0, -0.1), &Default::default()),);
1✔
350
        assert!(!tester
1✔
351
            .any_polygon_contains_coordinate(&Coordinate2D::new(-0.1, 0.0), &Default::default()),);
1✔
352

353
        assert!(!tester
1✔
354
            .any_polygon_contains_coordinate(&Coordinate2D::new(10.1, 10.1), &Default::default()),);
1✔
355
        assert!(!tester
1✔
356
            .any_polygon_contains_coordinate(&Coordinate2D::new(10.1, 9.9), &Default::default()),);
1✔
357
        assert!(!tester
1✔
358
            .any_polygon_contains_coordinate(&Coordinate2D::new(9.9, 10.1), &Default::default()),);
1✔
359
    }
1✔
360

361
    #[tokio::test]
1✔
362
    async fn all() -> Result<()> {
1✔
363
        let points = MultiPointCollection::from_data(
1✔
364
            MultiPoint::many(vec![(0.001, 0.1), (1.0, 1.1), (2.0, 3.1)]).unwrap(),
1✔
365
            vec![TimeInterval::new_unchecked(0, 1); 3],
1✔
366
            Default::default(),
1✔
367
        )?;
1✔
368

369
        let point_source = MockFeatureCollectionSource::single(points.clone()).boxed();
1✔
370

371
        let polygon_source =
1✔
372
            MockFeatureCollectionSource::single(MultiPolygonCollection::from_data(
373
                vec![MultiPolygon::new(vec![vec![vec![
1✔
374
                    (0.0, 0.0).into(),
1✔
375
                    (10.0, 0.0).into(),
1✔
376
                    (10.0, 10.0).into(),
1✔
377
                    (0.0, 10.0).into(),
1✔
378
                    (0.0, 0.0).into(),
1✔
379
                ]]])?],
1✔
380
                vec![TimeInterval::new_unchecked(0, 1); 1],
1✔
381
                Default::default(),
1✔
382
            )?)
×
383
            .boxed();
1✔
384

385
        let operator = PointInPolygonFilter {
1✔
386
            params: PointInPolygonFilterParams {},
1✔
387
            sources: PointInPolygonFilterSource {
1✔
388
                points: point_source,
1✔
389
                polygons: polygon_source,
1✔
390
            },
1✔
391
        }
1✔
392
        .boxed()
1✔
393
        .initialize(&MockExecutionContext::test_default())
1✔
394
        .await?;
×
395

396
        let query_processor = operator.query_processor()?.multi_point().unwrap();
1✔
397

1✔
398
        let query_rectangle = VectorQueryRectangle {
1✔
399
            spatial_bounds: BoundingBox2D::new((0., 0.).into(), (10., 10.).into()).unwrap(),
1✔
400
            time_interval: TimeInterval::default(),
1✔
401
            spatial_resolution: SpatialResolution::zero_point_one(),
1✔
402
        };
1✔
403
        let ctx = MockQueryContext::new(ChunkByteSize::MAX);
1✔
404

405
        let query = query_processor.query(query_rectangle, &ctx).await.unwrap();
1✔
406

407
        let result = query
1✔
408
            .map(Result::unwrap)
1✔
409
            .collect::<Vec<MultiPointCollection>>()
1✔
410
            .await;
1✔
411

412
        assert_eq!(result.len(), 1);
1✔
413

414
        assert_eq!(result[0], points);
1✔
415

416
        Ok(())
1✔
417
    }
418

419
    #[tokio::test]
1✔
420
    async fn none() -> Result<()> {
1✔
421
        let points = MultiPointCollection::from_data(
1✔
422
            MultiPoint::many(vec![(0.0, 0.1), (1.0, 1.1), (2.0, 3.1)]).unwrap(),
1✔
423
            vec![TimeInterval::new_unchecked(0, 1); 3],
1✔
424
            Default::default(),
1✔
425
        )?;
1✔
426

427
        let point_source = MockFeatureCollectionSource::single(points.clone()).boxed();
1✔
428

429
        let polygon_source = MockFeatureCollectionSource::single(
1✔
430
            MultiPolygonCollection::from_data(vec![], vec![], Default::default())?,
1✔
431
        )
432
        .boxed();
1✔
433

434
        let operator = PointInPolygonFilter {
1✔
435
            params: PointInPolygonFilterParams {},
1✔
436
            sources: PointInPolygonFilterSource {
1✔
437
                points: point_source,
1✔
438
                polygons: polygon_source,
1✔
439
            },
1✔
440
        }
1✔
441
        .boxed()
1✔
442
        .initialize(&MockExecutionContext::test_default())
1✔
443
        .await?;
×
444

445
        let query_processor = operator.query_processor()?.multi_point().unwrap();
1✔
446

1✔
447
        let query_rectangle = VectorQueryRectangle {
1✔
448
            spatial_bounds: BoundingBox2D::new((0., 0.).into(), (10., 10.).into()).unwrap(),
1✔
449
            time_interval: TimeInterval::default(),
1✔
450
            spatial_resolution: SpatialResolution::zero_point_one(),
1✔
451
        };
1✔
452
        let ctx = MockQueryContext::new(ChunkByteSize::MAX);
1✔
453

454
        let query = query_processor.query(query_rectangle, &ctx).await.unwrap();
1✔
455

456
        let result = query
1✔
457
            .map(Result::unwrap)
1✔
458
            .collect::<Vec<MultiPointCollection>>()
1✔
459
            .await;
×
460

461
        assert_eq!(result.len(), 0);
1✔
462

463
        Ok(())
1✔
464
    }
465

466
    #[tokio::test]
1✔
467
    async fn time() -> Result<()> {
1✔
468
        let points = MultiPointCollection::from_data(
1✔
469
            MultiPoint::many(vec![(1.0, 1.1), (2.0, 2.1), (3.0, 3.1)]).unwrap(),
1✔
470
            vec![
1✔
471
                TimeInterval::new(0, 1)?,
1✔
472
                TimeInterval::new(5, 6)?,
1✔
473
                TimeInterval::new(0, 5)?,
1✔
474
            ],
475
            Default::default(),
1✔
476
        )?;
×
477

478
        let point_source = MockFeatureCollectionSource::single(points.clone()).boxed();
1✔
479

480
        let polygon = MultiPolygon::new(vec![vec![vec![
1✔
481
            (0.0, 0.0).into(),
1✔
482
            (10.0, 0.0).into(),
1✔
483
            (10.0, 10.0).into(),
1✔
484
            (0.0, 10.0).into(),
1✔
485
            (0.0, 0.0).into(),
1✔
486
        ]]])?;
1✔
487

488
        let polygon_source =
1✔
489
            MockFeatureCollectionSource::single(MultiPolygonCollection::from_data(
490
                vec![polygon.clone(), polygon],
1✔
491
                vec![TimeInterval::new(0, 1)?, TimeInterval::new(1, 2)?],
1✔
492
                Default::default(),
1✔
493
            )?)
×
494
            .boxed();
1✔
495

496
        let operator = PointInPolygonFilter {
1✔
497
            params: PointInPolygonFilterParams {},
1✔
498
            sources: PointInPolygonFilterSource {
1✔
499
                points: point_source,
1✔
500
                polygons: polygon_source,
1✔
501
            },
1✔
502
        }
1✔
503
        .boxed()
1✔
504
        .initialize(&MockExecutionContext::test_default())
1✔
505
        .await?;
×
506

507
        let query_processor = operator.query_processor()?.multi_point().unwrap();
1✔
508

1✔
509
        let query_rectangle = VectorQueryRectangle {
1✔
510
            spatial_bounds: BoundingBox2D::new((0., 0.).into(), (10., 10.).into()).unwrap(),
1✔
511
            time_interval: TimeInterval::default(),
1✔
512
            spatial_resolution: SpatialResolution::zero_point_one(),
1✔
513
        };
1✔
514
        let ctx = MockQueryContext::new(ChunkByteSize::MAX);
1✔
515

516
        let query = query_processor.query(query_rectangle, &ctx).await.unwrap();
1✔
517

518
        let result = query
1✔
519
            .map(Result::unwrap)
1✔
520
            .collect::<Vec<MultiPointCollection>>()
1✔
521
            .await;
1✔
522

523
        assert_eq!(result.len(), 1);
1✔
524

525
        assert_eq!(result[0], points.filter(vec![true, false, true])?);
1✔
526

527
        Ok(())
1✔
528
    }
529

530
    // #[async::main]
531
    #[tokio::test]
1✔
532
    async fn multiple_inputs() -> Result<()> {
1✔
533
        let points1 = MultiPointCollection::from_data(
1✔
534
            MultiPoint::many(vec![(5.0, 5.1), (15.0, 15.1)]).unwrap(),
1✔
535
            vec![TimeInterval::new(0, 1)?; 2],
1✔
536
            Default::default(),
1✔
537
        )?;
×
538
        let points2 = MultiPointCollection::from_data(
1✔
539
            MultiPoint::many(vec![(6.0, 6.1), (16.0, 16.1)]).unwrap(),
1✔
540
            vec![TimeInterval::new(1, 2)?; 2],
1✔
541
            Default::default(),
1✔
542
        )?;
×
543

544
        let point_source =
1✔
545
            MockFeatureCollectionSource::multiple(vec![points1.clone(), points2.clone()]).boxed();
1✔
546

547
        let polygon1 = MultiPolygon::new(vec![vec![vec![
1✔
548
            (0.0, 0.0).into(),
1✔
549
            (10.0, 0.0).into(),
1✔
550
            (10.0, 10.0).into(),
1✔
551
            (0.0, 10.0).into(),
1✔
552
            (0.0, 0.0).into(),
1✔
553
        ]]])?;
1✔
554
        let polygon2 = MultiPolygon::new(vec![vec![vec![
1✔
555
            (10.0, 10.0).into(),
1✔
556
            (20.0, 10.0).into(),
1✔
557
            (20.0, 20.0).into(),
1✔
558
            (10.0, 20.0).into(),
1✔
559
            (10.0, 10.0).into(),
1✔
560
        ]]])?;
1✔
561

562
        let polygon_source = MockFeatureCollectionSource::multiple(vec![
1✔
563
            MultiPolygonCollection::from_data(
1✔
564
                vec![polygon1.clone()],
1✔
565
                vec![TimeInterval::new(0, 1)?],
1✔
566
                Default::default(),
1✔
567
            )?,
×
568
            MultiPolygonCollection::from_data(
569
                vec![polygon1, polygon2],
1✔
570
                vec![TimeInterval::new(1, 2)?, TimeInterval::new(1, 2)?],
1✔
571
                Default::default(),
1✔
572
            )?,
×
573
        ])
574
        .boxed();
1✔
575

576
        let operator = PointInPolygonFilter {
1✔
577
            params: PointInPolygonFilterParams {},
1✔
578
            sources: PointInPolygonFilterSource {
1✔
579
                points: point_source,
1✔
580
                polygons: polygon_source,
1✔
581
            },
1✔
582
        }
1✔
583
        .boxed()
1✔
584
        .initialize(&MockExecutionContext::test_default())
1✔
585
        .await?;
×
586

587
        let query_processor = operator.query_processor()?.multi_point().unwrap();
1✔
588

1✔
589
        let query_rectangle = VectorQueryRectangle {
1✔
590
            spatial_bounds: BoundingBox2D::new((0., 0.).into(), (10., 10.).into()).unwrap(),
1✔
591
            time_interval: TimeInterval::default(),
1✔
592
            spatial_resolution: SpatialResolution::zero_point_one(),
1✔
593
        };
1✔
594

1✔
595
        let ctx_one_chunk = MockQueryContext::new(ChunkByteSize::MAX);
1✔
596
        let ctx_minimal_chunks = MockQueryContext::new(ChunkByteSize::MIN);
1✔
597

598
        let query = query_processor
1✔
599
            .query(query_rectangle, &ctx_minimal_chunks)
1✔
600
            .await
×
601
            .unwrap();
1✔
602

603
        let result = query
1✔
604
            .map(Result::unwrap)
1✔
605
            .collect::<Vec<MultiPointCollection>>()
1✔
606
            .await;
4✔
607

608
        assert_eq!(result.len(), 2);
1✔
609

610
        assert_eq!(result[0], points1.filter(vec![true, false])?);
1✔
611
        assert_eq!(result[1], points2);
1✔
612

613
        let query = query_processor
1✔
614
            .query(query_rectangle, &ctx_one_chunk)
1✔
615
            .await
×
616
            .unwrap();
1✔
617

618
        let result = query
1✔
619
            .map(Result::unwrap)
1✔
620
            .collect::<Vec<MultiPointCollection>>()
1✔
621
            .await;
4✔
622

623
        assert_eq!(result.len(), 1);
1✔
624

625
        assert_eq!(
1✔
626
            result[0],
1✔
627
            points1.filter(vec![true, false])?.append(&points2)?
1✔
628
        );
629

630
        Ok(())
1✔
631
    }
632

633
    #[tokio::test]
1✔
634
    async fn empty_points() {
1✔
635
        let point_collection =
1✔
636
            MultiPointCollection::from_data(vec![], vec![], Default::default()).unwrap();
1✔
637

1✔
638
        let polygon_collection = MultiPolygonCollection::from_data(
1✔
639
            vec![MultiPolygon::new(vec![vec![vec![
1✔
640
                (0.0, 0.0).into(),
1✔
641
                (10.0, 0.0).into(),
1✔
642
                (10.0, 10.0).into(),
1✔
643
                (0.0, 10.0).into(),
1✔
644
                (0.0, 0.0).into(),
1✔
645
            ]]])
1✔
646
            .unwrap()],
1✔
647
            vec![TimeInterval::default()],
1✔
648
            Default::default(),
1✔
649
        )
1✔
650
        .unwrap();
1✔
651

652
        let operator = PointInPolygonFilter {
1✔
653
            params: PointInPolygonFilterParams {},
1✔
654
            sources: PointInPolygonFilterSource {
1✔
655
                points: MockFeatureCollectionSource::single(point_collection).boxed(),
1✔
656
                polygons: MockFeatureCollectionSource::single(polygon_collection).boxed(),
1✔
657
            },
1✔
658
        }
1✔
659
        .boxed()
1✔
660
        .initialize(&MockExecutionContext::test_default())
1✔
661
        .await
×
662
        .unwrap();
1✔
663

1✔
664
        let query_rectangle = VectorQueryRectangle {
1✔
665
            spatial_bounds: BoundingBox2D::new((-10., -10.).into(), (10., 10.).into()).unwrap(),
1✔
666
            time_interval: TimeInterval::default(),
1✔
667
            spatial_resolution: SpatialResolution::zero_point_one(),
1✔
668
        };
1✔
669

1✔
670
        let query_processor = operator.query_processor().unwrap().multi_point().unwrap();
1✔
671

1✔
672
        let query_context = MockQueryContext::test_default();
1✔
673

674
        let query = query_processor
1✔
675
            .query(query_rectangle, &query_context)
1✔
676
            .await
×
677
            .unwrap();
1✔
678

679
        let result = query
1✔
680
            .map(Result::unwrap)
1✔
681
            .collect::<Vec<MultiPointCollection>>()
1✔
682
            .await;
×
683

684
        assert_eq!(result.len(), 0);
1✔
685
    }
686

687
    #[tokio::test]
1✔
688
    async fn it_checks_sref() {
1✔
689
        let point_collection =
1✔
690
            MultiPointCollection::from_data(vec![], vec![], Default::default()).unwrap();
1✔
691

1✔
692
        let polygon_collection = MultiPolygonCollection::from_data(
1✔
693
            vec![MultiPolygon::new(vec![vec![vec![
1✔
694
                (0.0, 0.0).into(),
1✔
695
                (10.0, 0.0).into(),
1✔
696
                (10.0, 10.0).into(),
1✔
697
                (0.0, 10.0).into(),
1✔
698
                (0.0, 0.0).into(),
1✔
699
            ]]])
1✔
700
            .unwrap()],
1✔
701
            vec![TimeInterval::default()],
1✔
702
            Default::default(),
1✔
703
        )
1✔
704
        .unwrap();
1✔
705

706
        let operator = PointInPolygonFilter {
1✔
707
            params: PointInPolygonFilterParams {},
1✔
708
            sources: PointInPolygonFilterSource {
1✔
709
                points: MockFeatureCollectionSource::with_collections_and_sref(
1✔
710
                    vec![point_collection],
1✔
711
                    SpatialReference::epsg_4326(),
1✔
712
                )
1✔
713
                .boxed(),
1✔
714
                polygons: MockFeatureCollectionSource::with_collections_and_sref(
1✔
715
                    vec![polygon_collection],
1✔
716
                    SpatialReference::from_str("EPSG:3857").unwrap(),
1✔
717
                )
1✔
718
                .boxed(),
1✔
719
            },
1✔
720
        }
1✔
721
        .boxed()
1✔
722
        .initialize(&MockExecutionContext::test_default())
1✔
723
        .await;
×
724

725
        assert!(matches!(
1✔
726
            operator,
1✔
727
            Err(Error::InvalidSpatialReference {
728
                expected: _,
729
                found: _,
730
            })
731
        ));
732
    }
733
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc