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

geo-engine / geoengine / 19241805651

10 Nov 2025 06:22PM UTC coverage: 88.832%. First build
19241805651

Pull #1083

github

web-flow
Merge dac631b93 into 113de40ca
Pull Request #1083: feat: new gdal source workflow optimization

6213 of 7052 new or added lines in 71 files covered. (88.1%)

116189 of 130797 relevant lines covered (88.83%)

496404.71 hits per line

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

93.02
/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::NamedData;
10
use geoengine_datatypes::primitives::VectorQueryRectangle;
11
use geoengine_datatypes::primitives::{CacheHint, SpatialResolution};
12
use rayon::ThreadPool;
13
use serde::{Deserialize, Serialize};
14
use snafu::ensure;
15

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

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

42
impl OperatorName for PointInPolygonFilter {
43
    const TYPE_NAME: &'static str = "PointInPolygonFilter";
44
}
45

46
#[derive(Debug, Clone, Deserialize, Serialize)]
47
pub struct PointInPolygonFilterParams {}
48

49
#[derive(Debug, Clone, Deserialize, Serialize)]
50
pub struct PointInPolygonFilterSource {
51
    pub points: Box<dyn VectorOperator>,
52
    pub polygons: Box<dyn VectorOperator>,
53
}
54

55
impl OperatorData for PointInPolygonFilterSource {
56
    fn data_names_collect(&self, data_names: &mut Vec<NamedData>) {
×
57
        self.points.data_names_collect(data_names);
×
58
        self.polygons.data_names_collect(data_names);
×
59
    }
×
60
}
61

62
struct InitializedPointInPolygonFilterSource {
63
    points: Box<dyn InitializedVectorOperator>,
64
    polygons: Box<dyn InitializedVectorOperator>,
65
}
66

67
#[async_trait]
68
impl InitializedSources<InitializedPointInPolygonFilterSource> for PointInPolygonFilterSource {
69
    async fn initialize_sources(
70
        self,
71
        path: WorkflowOperatorPath,
72
        context: &dyn ExecutionContext,
73
    ) -> Result<InitializedPointInPolygonFilterSource> {
6✔
74
        let points_path = path.clone_and_append(0);
75
        let polygons_path = path.clone_and_append(1);
76

77
        Ok(InitializedPointInPolygonFilterSource {
78
            points: self.points.initialize(points_path, context).await?,
79
            polygons: self.polygons.initialize(polygons_path, context).await?,
80
        })
81
    }
6✔
82
}
83

84
#[typetag::serde]
×
85
#[async_trait]
86
impl VectorOperator for PointInPolygonFilter {
87
    async fn _initialize(
88
        self: Box<Self>,
89
        path: WorkflowOperatorPath,
90
        context: &dyn ExecutionContext,
91
    ) -> Result<Box<dyn InitializedVectorOperator>> {
6✔
92
        let name = CanonicOperatorName::from(&self);
93

94
        let initialized_source = self
95
            .sources
96
            .initialize_sources(path.clone(), context)
97
            .await?;
98

99
        let points_rd = initialized_source.points.result_descriptor();
100
        let polygons_rd = initialized_source.polygons.result_descriptor();
101

102
        ensure!(
103
            points_rd.data_type == VectorDataType::MultiPoint,
104
            error::InvalidType {
105
                expected: VectorDataType::MultiPoint.to_string(),
106
                found: points_rd.data_type.to_string(),
107
            }
108
        );
109
        ensure!(
110
            polygons_rd.data_type == VectorDataType::MultiPolygon,
111
            error::InvalidType {
112
                expected: VectorDataType::MultiPolygon.to_string(),
113
                found: polygons_rd.data_type.to_string(),
114
            }
115
        );
116

117
        ensure!(
118
            points_rd.spatial_reference == polygons_rd.spatial_reference,
119
            crate::error::InvalidSpatialReference {
120
                expected: points_rd.spatial_reference,
121
                found: polygons_rd.spatial_reference,
122
            }
123
        );
124

125
        // We use the result descriptor of the points because in the worst case no feature will be excluded.
126
        // We cannot use the polygon bbox because a `MultiPoint` could have one point within a polygon (and
127
        // thus be included in the result) and one point outside of the bbox of the polygons.
128
        let out_desc = initialized_source.points.result_descriptor().clone();
129

130
        let initialized_operator = InitializedPointInPolygonFilter {
131
            name,
132
            path,
133
            result_descriptor: out_desc,
134
            points: initialized_source.points,
135
            polygons: initialized_source.polygons,
136
        };
137

138
        Ok(initialized_operator.boxed())
139
    }
6✔
140

141
    span_fn!(PointInPolygonFilter);
142
}
143

144
pub struct InitializedPointInPolygonFilter {
145
    name: CanonicOperatorName,
146
    path: WorkflowOperatorPath,
147
    points: Box<dyn InitializedVectorOperator>,
148
    polygons: Box<dyn InitializedVectorOperator>,
149
    result_descriptor: VectorResultDescriptor,
150
}
151

152
impl InitializedVectorOperator for InitializedPointInPolygonFilter {
153
    fn query_processor(&self) -> Result<TypedVectorQueryProcessor> {
5✔
154
        let point_processor = self
5✔
155
            .points
5✔
156
            .query_processor()?
5✔
157
            .multi_point()
5✔
158
            .expect("checked in `PointInPolygonFilter` constructor");
5✔
159

160
        let polygon_processor = self
5✔
161
            .polygons
5✔
162
            .query_processor()?
5✔
163
            .multi_polygon()
5✔
164
            .expect("checked in `PointInPolygonFilter` constructor");
5✔
165

166
        Ok(TypedVectorQueryProcessor::MultiPoint(
5✔
167
            PointInPolygonFilterProcessor::new(
5✔
168
                self.result_descriptor.clone(),
5✔
169
                point_processor,
5✔
170
                polygon_processor,
5✔
171
            )
5✔
172
            .boxed(),
5✔
173
        ))
5✔
174
    }
5✔
175

176
    fn result_descriptor(&self) -> &VectorResultDescriptor {
×
177
        &self.result_descriptor
×
178
    }
×
179

180
    fn canonic_name(&self) -> CanonicOperatorName {
×
181
        self.name.clone()
×
182
    }
×
183

NEW
184
    fn optimize(
×
NEW
185
        &self,
×
NEW
186
        target_resolution: SpatialResolution,
×
NEW
187
    ) -> Result<Box<dyn VectorOperator>, OptimizationError> {
×
188
        Ok(PointInPolygonFilter {
NEW
189
            params: PointInPolygonFilterParams {},
×
190
            sources: PointInPolygonFilterSource {
NEW
191
                points: self.points.optimize(target_resolution)?,
×
NEW
192
                polygons: self.polygons.optimize(target_resolution)?,
×
193
            },
194
        }
NEW
195
        .boxed())
×
NEW
196
    }
×
197

198
    fn name(&self) -> &'static str {
×
199
        PointInPolygonFilter::TYPE_NAME
×
200
    }
×
201

202
    fn path(&self) -> WorkflowOperatorPath {
×
203
        self.path.clone()
×
204
    }
×
205

206
    fn boxed(self) -> Box<dyn InitializedVectorOperator>
5✔
207
    where
5✔
208
        Self: Sized + 'static,
5✔
209
    {
210
        Box::new(self)
5✔
211
    }
5✔
212
}
213
pub struct PointInPolygonFilterProcessor {
214
    result_descriptor: VectorResultDescriptor,
215
    points: Box<dyn VectorQueryProcessor<VectorType = MultiPointCollection>>,
216
    polygons: Box<dyn VectorQueryProcessor<VectorType = MultiPolygonCollection>>,
217
}
218

219
impl PointInPolygonFilterProcessor {
220
    pub fn new(
5✔
221
        result_descriptor: VectorResultDescriptor,
5✔
222
        points: Box<dyn VectorQueryProcessor<VectorType = MultiPointCollection>>,
5✔
223
        polygons: Box<dyn VectorQueryProcessor<VectorType = MultiPolygonCollection>>,
5✔
224
    ) -> Self {
5✔
225
        Self {
5✔
226
            result_descriptor,
5✔
227
            points,
5✔
228
            polygons,
5✔
229
        }
5✔
230
    }
5✔
231

232
    fn filter_parallel(
10✔
233
        points: &Arc<MultiPointCollection>,
10✔
234
        polygons: &MultiPolygonCollection,
10✔
235
        thread_pool: &ThreadPool,
10✔
236
    ) -> Vec<bool> {
10✔
237
        debug_assert!(!points.is_empty());
10✔
238

239
        // TODO: parallelize over coordinate rather than features
240

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

243
        let parallelism = thread_pool.current_num_threads();
10✔
244
        let chunk_size = (points.len() as f64 / parallelism as f64).ceil() as usize;
10✔
245

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

248
        thread_pool.scope(|scope| {
10✔
249
            let num_features = points.len();
10✔
250
            let feature_offsets = points.feature_offsets();
10✔
251
            let time_intervals = points.time_intervals();
10✔
252
            let coordinates = points.coordinates();
10✔
253

254
            for (chunk_index, chunk_result) in result.chunks_mut(chunk_size).enumerate() {
22✔
255
                let feature_index_start = chunk_index * chunk_size;
22✔
256
                let features_index_end = min(feature_index_start + chunk_size, num_features);
22✔
257
                let tester = tester.clone();
22✔
258

259
                scope.spawn(move |_| {
22✔
260
                    for (
261
                        feature_index,
22✔
262
                        ((coordinates_start_index, coordinates_end_index), time_interval),
22✔
263
                    ) in two_tuple_windows(
22✔
264
                        feature_offsets[feature_index_start..=features_index_end]
22✔
265
                            .iter()
22✔
266
                            .map(|&c| c as usize),
44✔
267
                    )
268
                    .zip(time_intervals[feature_index_start..features_index_end].iter())
22✔
269
                    .enumerate()
22✔
270
                    {
271
                        let is_multi_point_in_polygon_collection = coordinates
22✔
272
                            [coordinates_start_index..coordinates_end_index]
22✔
273
                            .iter()
22✔
274
                            .any(|coordinate| {
22✔
275
                                tester.any_polygon_contains_coordinate(coordinate, time_interval)
22✔
276
                            });
22✔
277

278
                        chunk_result[feature_index] = is_multi_point_in_polygon_collection;
22✔
279
                    }
280
                });
22✔
281
            }
282
        });
10✔
283

284
        result
10✔
285
    }
10✔
286

287
    async fn filter_points(
10✔
288
        ctx: &dyn QueryContext,
10✔
289
        points: Arc<MultiPointCollection>,
10✔
290
        polygons: MultiPolygonCollection,
10✔
291
        initial_filter: &BooleanArray,
10✔
292
    ) -> Result<BooleanArray> {
10✔
293
        let thread_pool = ctx.thread_pool().clone();
10✔
294

295
        let thread_points = points.clone();
10✔
296
        let filter = crate::util::spawn_blocking(move || {
10✔
297
            Self::filter_parallel(&thread_points, &polygons, &thread_pool)
10✔
298
        })
10✔
299
        .await?;
10✔
300

301
        arrow::compute::or(initial_filter, &filter.into()).map_err(Into::into)
10✔
302
    }
10✔
303
}
304

305
#[async_trait]
306
impl VectorQueryProcessor for PointInPolygonFilterProcessor {
307
    type VectorType = MultiPointCollection;
308

309
    async fn vector_query<'a>(
310
        &'a self,
311
        query: VectorQueryRectangle,
312
        ctx: &'a dyn QueryContext,
313
    ) -> Result<BoxStream<'a, Result<Self::VectorType>>> {
6✔
314
        let filtered_stream =
315
            self.points
316
                .query(query.clone(), ctx)
317
                .await?
318
                .and_then(move |points| {
8✔
319
                    let query: VectorQueryRectangle = query.clone();
8✔
320
                    async move {
8✔
321
                        if points.is_empty() {
8✔
322
                            return Ok(points);
1✔
323
                        }
7✔
324

325
                        let initial_filter = BooleanArray::from(vec![false; points.len()]);
7✔
326
                        let arc_points = Arc::new(points);
7✔
327

328
                        let (filter, cache_hint) = self
7✔
329
                            .polygons
7✔
330
                            .query(query.clone(), ctx)
7✔
331
                            .await?
7✔
332
                            .try_fold(
7✔
333
                                (initial_filter, CacheHint::max_duration()),
7✔
334
                                |acc, polygons| {
11✔
335
                                    let arc_points = arc_points.clone();
11✔
336
                                    async move {
11✔
337
                                        let (filter, mut cache_hint) = acc;
11✔
338
                                        let polygons = polygons;
11✔
339

340
                                        cache_hint.merge_with(&polygons.cache_hint);
11✔
341

342
                                        if polygons.is_empty() {
11✔
343
                                            return Ok((filter, cache_hint));
1✔
344
                                        }
10✔
345

346
                                        Ok((
347
                                            Self::filter_points(
10✔
348
                                                ctx,
10✔
349
                                                arc_points.clone(),
10✔
350
                                                polygons,
10✔
351
                                                &filter,
10✔
352
                                            )
10✔
353
                                            .await?,
10✔
354
                                            cache_hint,
10✔
355
                                        ))
356
                                    }
11✔
357
                                },
11✔
358
                            )
359
                            .await?;
7✔
360

361
                        let mut new_points =
7✔
362
                            arc_points.filter(filter).map_err(Into::<Error>::into)?;
7✔
363
                        new_points.cache_hint = cache_hint;
7✔
364

365
                        Ok(new_points)
7✔
366
                    }
8✔
367
                });
8✔
368

369
        Ok(
370
            FeatureCollectionChunkMerger::new(filtered_stream.fuse(), ctx.chunk_byte_size().into())
371
                .boxed(),
372
        )
373
    }
6✔
374

375
    fn vector_result_descriptor(&self) -> &VectorResultDescriptor {
6✔
376
        &self.result_descriptor
6✔
377
    }
6✔
378
}
379

380
/// Loop through an iterator by yielding the current and previous tuple. Starts with the
381
/// (first, second) item, so the iterator must have more than one item to create an output.
382
fn two_tuple_windows<I, T>(mut iter: I) -> impl Iterator<Item = (T, T)>
22✔
383
where
22✔
384
    I: Iterator<Item = T>,
22✔
385
    T: Copy,
22✔
386
{
387
    let mut last = iter.next();
22✔
388

389
    iter.map(move |item| {
22✔
390
        let output = (
22✔
391
            last.expect("it should have a first tuple in a two-tuple window"),
22✔
392
            item,
22✔
393
        );
22✔
394
        last = Some(item);
22✔
395
        output
22✔
396
    })
22✔
397
}
22✔
398

399
#[cfg(test)]
400
mod tests {
401

402
    use super::*;
403
    use std::str::FromStr;
404

405
    use geoengine_datatypes::collections::ChunksEqualIgnoringCacheHint;
406
    use geoengine_datatypes::primitives::{
407
        BoundingBox2D, Coordinate2D, MultiPoint, MultiPolygon, TimeInterval,
408
    };
409
    use geoengine_datatypes::primitives::{CacheHint, ColumnSelection};
410
    use geoengine_datatypes::spatial_reference::SpatialReference;
411
    use geoengine_datatypes::util::test::TestDefault;
412

413
    use crate::engine::{ChunkByteSize, MockExecutionContext};
414
    use crate::error::Error;
415
    use crate::mock::MockFeatureCollectionSource;
416

417
    #[test]
418
    fn point_in_polygon_boundary_conditions() {
1✔
419
        let collection = MultiPolygonCollection::from_data(
1✔
420
            vec![
1✔
421
                MultiPolygon::new(vec![vec![vec![
1✔
422
                    (0.0, 0.0).into(),
1✔
423
                    (10.0, 0.0).into(),
1✔
424
                    (10.0, 10.0).into(),
1✔
425
                    (0.0, 10.0).into(),
1✔
426
                    (0.0, 0.0).into(),
1✔
427
                ]]])
428
                .unwrap(),
1✔
429
            ],
430
            vec![Default::default(); 1],
1✔
431
            Default::default(),
1✔
432
            CacheHint::default(),
1✔
433
        )
434
        .unwrap();
1✔
435

436
        let tester = PointInPolygonTester::new(&collection);
1✔
437

438
        // the algorithm is not stable for boundary cases directly on the edges
439

440
        assert!(tester.any_polygon_contains_coordinate(
1✔
441
            &Coordinate2D::new(0.000_001, 0.000_001),
1✔
442
            &Default::default()
1✔
443
        ),);
444
        assert!(tester.any_polygon_contains_coordinate(
1✔
445
            &Coordinate2D::new(0.000_001, 0.1),
1✔
446
            &Default::default()
1✔
447
        ),);
448
        assert!(tester.any_polygon_contains_coordinate(
1✔
449
            &Coordinate2D::new(0.1, 0.000_001),
1✔
450
            &Default::default()
1✔
451
        ),);
452

453
        assert!(
1✔
454
            tester
1✔
455
                .any_polygon_contains_coordinate(&Coordinate2D::new(9.9, 9.9), &Default::default()),
1✔
456
        );
457
        assert!(
1✔
458
            tester.any_polygon_contains_coordinate(
1✔
459
                &Coordinate2D::new(10.0, 9.9),
1✔
460
                &Default::default()
1✔
461
            ),
462
        );
463
        assert!(
1✔
464
            tester.any_polygon_contains_coordinate(
1✔
465
                &Coordinate2D::new(9.9, 10.0),
1✔
466
                &Default::default()
1✔
467
            ),
468
        );
469

470
        assert!(
1✔
471
            !tester.any_polygon_contains_coordinate(
1✔
472
                &Coordinate2D::new(-0.1, -0.1),
1✔
473
                &Default::default()
1✔
474
            ),
1✔
475
        );
476
        assert!(
1✔
477
            !tester.any_polygon_contains_coordinate(
1✔
478
                &Coordinate2D::new(0.0, -0.1),
1✔
479
                &Default::default()
1✔
480
            ),
1✔
481
        );
482
        assert!(
1✔
483
            !tester.any_polygon_contains_coordinate(
1✔
484
                &Coordinate2D::new(-0.1, 0.0),
1✔
485
                &Default::default()
1✔
486
            ),
1✔
487
        );
488

489
        assert!(
1✔
490
            !tester.any_polygon_contains_coordinate(
1✔
491
                &Coordinate2D::new(10.1, 10.1),
1✔
492
                &Default::default()
1✔
493
            ),
1✔
494
        );
495
        assert!(
1✔
496
            !tester.any_polygon_contains_coordinate(
1✔
497
                &Coordinate2D::new(10.1, 9.9),
1✔
498
                &Default::default()
1✔
499
            ),
1✔
500
        );
501
        assert!(
1✔
502
            !tester.any_polygon_contains_coordinate(
1✔
503
                &Coordinate2D::new(9.9, 10.1),
1✔
504
                &Default::default()
1✔
505
            ),
1✔
506
        );
507
    }
1✔
508

509
    #[tokio::test]
510
    async fn all() -> Result<()> {
1✔
511
        let points = MultiPointCollection::from_data(
1✔
512
            MultiPoint::many(vec![(0.001, 0.1), (1.0, 1.1), (2.0, 3.1)]).unwrap(),
1✔
513
            vec![TimeInterval::new_unchecked(0, 1); 3],
1✔
514
            Default::default(),
1✔
515
            CacheHint::default(),
1✔
516
        )?;
×
517

518
        let point_source = MockFeatureCollectionSource::single(points.clone()).boxed();
1✔
519

520
        let exe_ctx: MockExecutionContext = MockExecutionContext::test_default();
1✔
521

522
        let polygon_source =
1✔
523
            MockFeatureCollectionSource::single(MultiPolygonCollection::from_data(
1✔
524
                vec![MultiPolygon::new(vec![vec![vec![
1✔
525
                    (0.0, 0.0).into(),
1✔
526
                    (10.0, 0.0).into(),
1✔
527
                    (10.0, 10.0).into(),
1✔
528
                    (0.0, 10.0).into(),
1✔
529
                    (0.0, 0.0).into(),
1✔
530
                ]]])?],
×
531
                vec![TimeInterval::new_unchecked(0, 1); 1],
1✔
532
                Default::default(),
1✔
533
                CacheHint::default(),
1✔
534
            )?)
×
535
            .boxed();
1✔
536

537
        let operator = PointInPolygonFilter {
1✔
538
            params: PointInPolygonFilterParams {},
1✔
539
            sources: PointInPolygonFilterSource {
1✔
540
                points: point_source,
1✔
541
                polygons: polygon_source,
1✔
542
            },
1✔
543
        }
1✔
544
        .boxed()
1✔
545
        .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
546
        .await?;
1✔
547

548
        let query_processor = operator.query_processor()?.multi_point().unwrap();
1✔
549

550
        let query_rectangle = VectorQueryRectangle::new(
1✔
551
            BoundingBox2D::new((0., 0.).into(), (10., 10.).into()).unwrap(),
1✔
552
            TimeInterval::default(),
1✔
553
            ColumnSelection::all(),
1✔
554
        );
555
        let ctx = exe_ctx.mock_query_context(ChunkByteSize::MAX);
1✔
556

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

559
        let result = query
1✔
560
            .map(Result::unwrap)
1✔
561
            .collect::<Vec<MultiPointCollection>>()
1✔
562
            .await;
1✔
563

564
        assert_eq!(result.len(), 1);
1✔
565

566
        assert!(result[0].chunks_equal_ignoring_cache_hint(&points));
1✔
567

568
        Ok(())
2✔
569
    }
1✔
570

571
    #[tokio::test]
572
    async fn empty() -> Result<()> {
1✔
573
        let points = MultiPointCollection::from_data(
1✔
574
            MultiPoint::many(vec![(0.0, 0.1), (1.0, 1.1), (2.0, 3.1)]).unwrap(),
1✔
575
            vec![TimeInterval::new_unchecked(0, 1); 3],
1✔
576
            Default::default(),
1✔
577
            CacheHint::default(),
1✔
578
        )?;
×
579

580
        let point_source = MockFeatureCollectionSource::single(points.clone()).boxed();
1✔
581

582
        let polygon_source =
1✔
583
            MockFeatureCollectionSource::single(MultiPolygonCollection::from_data(
1✔
584
                vec![],
1✔
585
                vec![],
1✔
586
                Default::default(),
1✔
587
                CacheHint::default(),
1✔
588
            )?)
×
589
            .boxed();
1✔
590

591
        let exe_ctx: MockExecutionContext = MockExecutionContext::test_default();
1✔
592

593
        let operator = PointInPolygonFilter {
1✔
594
            params: PointInPolygonFilterParams {},
1✔
595
            sources: PointInPolygonFilterSource {
1✔
596
                points: point_source,
1✔
597
                polygons: polygon_source,
1✔
598
            },
1✔
599
        }
1✔
600
        .boxed()
1✔
601
        .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
602
        .await?;
1✔
603

604
        let query_processor = operator.query_processor()?.multi_point().unwrap();
1✔
605

606
        let query_rectangle = VectorQueryRectangle::new(
1✔
607
            BoundingBox2D::new((0., 0.).into(), (10., 10.).into()).unwrap(),
1✔
608
            TimeInterval::default(),
1✔
609
            ColumnSelection::all(),
1✔
610
        );
611
        let ctx = exe_ctx.mock_query_context(ChunkByteSize::MAX);
1✔
612

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

615
        let result = query
1✔
616
            .map(Result::unwrap)
1✔
617
            .collect::<Vec<MultiPointCollection>>()
1✔
618
            .await;
1✔
619

620
        assert_eq!(result.len(), 1);
1✔
621
        assert!(result[0].chunks_equal_ignoring_cache_hint(&MultiPointCollection::empty()));
1✔
622

623
        Ok(())
2✔
624
    }
1✔
625

626
    #[tokio::test]
627
    async fn time() -> Result<()> {
1✔
628
        let points = MultiPointCollection::from_data(
1✔
629
            MultiPoint::many(vec![(1.0, 1.1), (2.0, 2.1), (3.0, 3.1)]).unwrap(),
1✔
630
            vec![
1✔
631
                TimeInterval::new(0, 1)?,
1✔
632
                TimeInterval::new(5, 6)?,
1✔
633
                TimeInterval::new(0, 5)?,
1✔
634
            ],
635
            Default::default(),
1✔
636
            CacheHint::default(),
1✔
637
        )?;
×
638

639
        let point_source = MockFeatureCollectionSource::single(points.clone()).boxed();
1✔
640

641
        let polygon = MultiPolygon::new(vec![vec![vec![
1✔
642
            (0.0, 0.0).into(),
1✔
643
            (10.0, 0.0).into(),
1✔
644
            (10.0, 10.0).into(),
1✔
645
            (0.0, 10.0).into(),
1✔
646
            (0.0, 0.0).into(),
1✔
647
        ]]])?;
×
648

649
        let polygon_source =
1✔
650
            MockFeatureCollectionSource::single(MultiPolygonCollection::from_data(
1✔
651
                vec![polygon.clone(), polygon],
1✔
652
                vec![TimeInterval::new(0, 1)?, TimeInterval::new(1, 2)?],
1✔
653
                Default::default(),
1✔
654
                CacheHint::default(),
1✔
655
            )?)
×
656
            .boxed();
1✔
657

658
        let exe_ctx: MockExecutionContext = MockExecutionContext::test_default();
1✔
659

660
        let operator = PointInPolygonFilter {
1✔
661
            params: PointInPolygonFilterParams {},
1✔
662
            sources: PointInPolygonFilterSource {
1✔
663
                points: point_source,
1✔
664
                polygons: polygon_source,
1✔
665
            },
1✔
666
        }
1✔
667
        .boxed()
1✔
668
        .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
669
        .await?;
1✔
670

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

673
        let query_rectangle = VectorQueryRectangle::new(
1✔
674
            BoundingBox2D::new((0., 0.).into(), (10., 10.).into()).unwrap(),
1✔
675
            TimeInterval::default(),
1✔
676
            ColumnSelection::all(),
1✔
677
        );
678
        let ctx = exe_ctx.mock_query_context(ChunkByteSize::MAX);
1✔
679

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

682
        let result = query
1✔
683
            .map(Result::unwrap)
1✔
684
            .collect::<Vec<MultiPointCollection>>()
1✔
685
            .await;
1✔
686

687
        assert_eq!(result.len(), 1);
1✔
688

689
        assert!(
1✔
690
            result[0].chunks_equal_ignoring_cache_hint(&points.filter(vec![true, false, true])?)
1✔
691
        );
692

693
        Ok(())
2✔
694
    }
1✔
695

696
    // #[async::main]
697
    #[tokio::test]
698
    async fn multiple_inputs() -> Result<()> {
1✔
699
        let points1 = MultiPointCollection::from_data(
1✔
700
            MultiPoint::many(vec![(5.0, 5.1), (15.0, 15.1)]).unwrap(),
1✔
701
            vec![TimeInterval::new(0, 1)?; 2],
1✔
702
            Default::default(),
1✔
703
            CacheHint::default(),
1✔
704
        )?;
×
705
        let points2 = MultiPointCollection::from_data(
1✔
706
            MultiPoint::many(vec![(6.0, 6.1), (16.0, 16.1)]).unwrap(),
1✔
707
            vec![TimeInterval::new(1, 2)?; 2],
1✔
708
            Default::default(),
1✔
709
            CacheHint::default(),
1✔
710
        )?;
×
711

712
        let point_source =
1✔
713
            MockFeatureCollectionSource::multiple(vec![points1.clone(), points2.clone()]).boxed();
1✔
714

715
        let polygon1 = MultiPolygon::new(vec![vec![vec![
1✔
716
            (0.0, 0.0).into(),
1✔
717
            (10.0, 0.0).into(),
1✔
718
            (10.0, 10.0).into(),
1✔
719
            (0.0, 10.0).into(),
1✔
720
            (0.0, 0.0).into(),
1✔
721
        ]]])?;
×
722
        let polygon2 = MultiPolygon::new(vec![vec![vec![
1✔
723
            (10.0, 10.0).into(),
1✔
724
            (20.0, 10.0).into(),
1✔
725
            (20.0, 20.0).into(),
1✔
726
            (10.0, 20.0).into(),
1✔
727
            (10.0, 10.0).into(),
1✔
728
        ]]])?;
×
729

730
        let polygon_source = MockFeatureCollectionSource::multiple(vec![
1✔
731
            MultiPolygonCollection::from_data(
1✔
732
                vec![polygon1.clone()],
1✔
733
                vec![TimeInterval::new(0, 1)?],
1✔
734
                Default::default(),
1✔
735
                CacheHint::default(),
1✔
736
            )?,
×
737
            MultiPolygonCollection::from_data(
1✔
738
                vec![polygon1, polygon2],
1✔
739
                vec![TimeInterval::new(1, 2)?, TimeInterval::new(1, 2)?],
1✔
740
                Default::default(),
1✔
741
                CacheHint::default(),
1✔
742
            )?,
×
743
        ])
744
        .boxed();
1✔
745

746
        let exe_ctx: MockExecutionContext = MockExecutionContext::test_default();
1✔
747

748
        let operator = PointInPolygonFilter {
1✔
749
            params: PointInPolygonFilterParams {},
1✔
750
            sources: PointInPolygonFilterSource {
1✔
751
                points: point_source,
1✔
752
                polygons: polygon_source,
1✔
753
            },
1✔
754
        }
1✔
755
        .boxed()
1✔
756
        .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
757
        .await?;
1✔
758

759
        let query_processor = operator.query_processor()?.multi_point().unwrap();
1✔
760

761
        let query_rectangle = VectorQueryRectangle::new(
1✔
762
            BoundingBox2D::new((0., 0.).into(), (10., 10.).into()).unwrap(),
1✔
763
            TimeInterval::default(),
1✔
764
            ColumnSelection::all(),
1✔
765
        );
766

767
        let ctx_one_chunk = exe_ctx.mock_query_context(ChunkByteSize::MAX);
1✔
768
        let ctx_minimal_chunks = exe_ctx.mock_query_context(ChunkByteSize::MIN);
1✔
769

770
        let query = query_processor
1✔
771
            .query(query_rectangle.clone(), &ctx_minimal_chunks)
1✔
772
            .await
1✔
773
            .unwrap();
1✔
774

775
        let result = query
1✔
776
            .map(Result::unwrap)
1✔
777
            .collect::<Vec<MultiPointCollection>>()
1✔
778
            .await;
1✔
779

780
        assert_eq!(result.len(), 2);
1✔
781

782
        assert!(result[0].chunks_equal_ignoring_cache_hint(&points1.filter(vec![true, false])?));
1✔
783
        assert!(result[1].chunks_equal_ignoring_cache_hint(&points2));
1✔
784

785
        let query = query_processor
1✔
786
            .query(query_rectangle, &ctx_one_chunk)
1✔
787
            .await
1✔
788
            .unwrap();
1✔
789

790
        let result = query
1✔
791
            .map(Result::unwrap)
1✔
792
            .collect::<Vec<MultiPointCollection>>()
1✔
793
            .await;
1✔
794

795
        assert_eq!(result.len(), 1);
1✔
796

797
        assert!(result[0].chunks_equal_ignoring_cache_hint(
1✔
798
            &points1.filter(vec![true, false])?.append(&points2)?
1✔
799
        ));
800

801
        Ok(())
2✔
802
    }
1✔
803

804
    #[tokio::test]
805
    async fn empty_points() {
1✔
806
        let exe_ctx: MockExecutionContext = MockExecutionContext::test_default();
1✔
807
        let point_collection = MultiPointCollection::from_data(
1✔
808
            vec![],
1✔
809
            vec![],
1✔
810
            Default::default(),
1✔
811
            CacheHint::default(),
1✔
812
        )
813
        .unwrap();
1✔
814

815
        let polygon_collection = MultiPolygonCollection::from_data(
1✔
816
            vec![
1✔
817
                MultiPolygon::new(vec![vec![vec![
1✔
818
                    (0.0, 0.0).into(),
1✔
819
                    (10.0, 0.0).into(),
1✔
820
                    (10.0, 10.0).into(),
1✔
821
                    (0.0, 10.0).into(),
1✔
822
                    (0.0, 0.0).into(),
1✔
823
                ]]])
824
                .unwrap(),
1✔
825
            ],
826
            vec![TimeInterval::default()],
1✔
827
            Default::default(),
1✔
828
            CacheHint::default(),
1✔
829
        )
830
        .unwrap();
1✔
831

832
        let operator = PointInPolygonFilter {
1✔
833
            params: PointInPolygonFilterParams {},
1✔
834
            sources: PointInPolygonFilterSource {
1✔
835
                points: MockFeatureCollectionSource::single(point_collection).boxed(),
1✔
836
                polygons: MockFeatureCollectionSource::single(polygon_collection).boxed(),
1✔
837
            },
1✔
838
        }
1✔
839
        .boxed()
1✔
840
        .initialize(
1✔
841
            WorkflowOperatorPath::initialize_root(),
1✔
842
            &MockExecutionContext::test_default(),
1✔
843
        )
1✔
844
        .await
1✔
845
        .unwrap();
1✔
846

847
        let query_rectangle = VectorQueryRectangle::new(
1✔
848
            BoundingBox2D::new((-10., -10.).into(), (10., 10.).into()).unwrap(),
1✔
849
            TimeInterval::default(),
1✔
850
            ColumnSelection::all(),
1✔
851
        );
852

853
        let query_processor = operator.query_processor().unwrap().multi_point().unwrap();
1✔
854

855
        let query_context = exe_ctx.mock_query_context_test_default();
1✔
856

857
        let query = query_processor
1✔
858
            .query(query_rectangle, &query_context)
1✔
859
            .await
1✔
860
            .unwrap();
1✔
861

862
        let result = query
1✔
863
            .map(Result::unwrap)
1✔
864
            .collect::<Vec<MultiPointCollection>>()
1✔
865
            .await;
1✔
866

867
        assert_eq!(result.len(), 1);
1✔
868
        assert!(result[0].chunks_equal_ignoring_cache_hint(&MultiPointCollection::empty()));
1✔
869
    }
1✔
870

871
    #[tokio::test]
872
    async fn it_checks_sref() {
1✔
873
        let point_collection = MultiPointCollection::from_data(
1✔
874
            vec![],
1✔
875
            vec![],
1✔
876
            Default::default(),
1✔
877
            CacheHint::default(),
1✔
878
        )
879
        .unwrap();
1✔
880

881
        let polygon_collection = MultiPolygonCollection::from_data(
1✔
882
            vec![
1✔
883
                MultiPolygon::new(vec![vec![vec![
1✔
884
                    (0.0, 0.0).into(),
1✔
885
                    (10.0, 0.0).into(),
1✔
886
                    (10.0, 10.0).into(),
1✔
887
                    (0.0, 10.0).into(),
1✔
888
                    (0.0, 0.0).into(),
1✔
889
                ]]])
890
                .unwrap(),
1✔
891
            ],
892
            vec![TimeInterval::default()],
1✔
893
            Default::default(),
1✔
894
            CacheHint::default(),
1✔
895
        )
896
        .unwrap();
1✔
897

898
        let operator = PointInPolygonFilter {
1✔
899
            params: PointInPolygonFilterParams {},
1✔
900
            sources: PointInPolygonFilterSource {
1✔
901
                points: MockFeatureCollectionSource::with_collections_and_sref(
1✔
902
                    vec![point_collection],
1✔
903
                    SpatialReference::epsg_4326(),
1✔
904
                )
1✔
905
                .boxed(),
1✔
906
                polygons: MockFeatureCollectionSource::with_collections_and_sref(
1✔
907
                    vec![polygon_collection],
1✔
908
                    SpatialReference::from_str("EPSG:3857").unwrap(),
1✔
909
                )
1✔
910
                .boxed(),
1✔
911
            },
1✔
912
        }
1✔
913
        .boxed()
1✔
914
        .initialize(
1✔
915
            WorkflowOperatorPath::initialize_root(),
1✔
916
            &MockExecutionContext::test_default(),
1✔
917
        )
1✔
918
        .await;
1✔
919

920
        assert!(matches!(
1✔
921
            operator,
1✔
922
            Err(Error::InvalidSpatialReference {
1✔
923
                expected: _,
1✔
924
                found: _,
1✔
925
            })
1✔
926
        ));
1✔
927
    }
1✔
928
}
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