• 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

97.21
/operators/src/processing/raster_vector_join/non_aggregated.rs
1
use crate::adapters::FeatureCollectionStreamExt;
2
use crate::processing::raster_vector_join::create_feature_aggregator;
3
use futures::stream::BoxStream;
4
use futures::{StreamExt, TryStreamExt};
5
use geoengine_datatypes::primitives::{BoundingBox2D, Geometry, VectorQueryRectangle};
6
use geoengine_datatypes::util::arrow::ArrowTyped;
7
use std::marker::PhantomData;
8
use std::sync::Arc;
9

10
use geoengine_datatypes::raster::{GridIndexAccess, RasterTile2D};
11
use geoengine_datatypes::{
12
    collections::FeatureCollectionModifications, primitives::TimeInterval, raster::Pixel,
13
};
14

15
use super::util::{CoveredPixels, PixelCoverCreator};
16
use crate::engine::{
17
    QueryContext, QueryProcessor, RasterQueryProcessor, TypedRasterQueryProcessor,
18
    VectorQueryProcessor,
19
};
20
use crate::util::Result;
21
use crate::{adapters::RasterStreamExt, error::Error};
22
use async_trait::async_trait;
23
use geoengine_datatypes::collections::GeometryCollection;
24
use geoengine_datatypes::collections::{FeatureCollection, FeatureCollectionInfos};
25

26
use super::aggregator::TypedAggregator;
27
use super::FeatureAggregationMethod;
28

29
pub struct RasterVectorJoinProcessor<G> {
30
    collection: Box<dyn VectorQueryProcessor<VectorType = FeatureCollection<G>>>,
31
    raster_processors: Vec<TypedRasterQueryProcessor>,
32
    column_names: Vec<String>,
33
    aggregation_method: FeatureAggregationMethod,
34
}
35

36
impl<G> RasterVectorJoinProcessor<G>
37
where
38
    G: Geometry + ArrowTyped + 'static,
39
    FeatureCollection<G>: GeometryCollection + PixelCoverCreator<G>,
40
{
41
    pub fn new(
6✔
42
        collection: Box<dyn VectorQueryProcessor<VectorType = FeatureCollection<G>>>,
6✔
43
        raster_processors: Vec<TypedRasterQueryProcessor>,
6✔
44
        column_names: Vec<String>,
6✔
45
        aggregation_method: FeatureAggregationMethod,
6✔
46
    ) -> Self {
6✔
47
        Self {
6✔
48
            collection,
6✔
49
            raster_processors,
6✔
50
            column_names,
6✔
51
            aggregation_method,
6✔
52
        }
6✔
53
    }
6✔
54

55
    fn process_collections<'a>(
6✔
56
        collection: BoxStream<'a, Result<FeatureCollection<G>>>,
6✔
57
        raster_processor: &'a TypedRasterQueryProcessor,
6✔
58
        new_column_name: &'a str,
6✔
59
        query: VectorQueryRectangle,
6✔
60
        ctx: &'a dyn QueryContext,
6✔
61
        aggregation_method: FeatureAggregationMethod,
6✔
62
    ) -> BoxStream<'a, Result<FeatureCollection<G>>> {
6✔
63
        let stream = collection.and_then(move |collection| {
6✔
64
            Self::process_collection_chunk(
6✔
65
                collection,
6✔
66
                raster_processor,
6✔
67
                new_column_name,
6✔
68
                query,
6✔
69
                ctx,
6✔
70
                aggregation_method,
6✔
71
            )
6✔
72
        });
6✔
73

6✔
74
        stream
6✔
75
            .try_flatten()
6✔
76
            .merge_chunks(ctx.chunk_byte_size().into())
6✔
77
            .boxed()
6✔
78
    }
6✔
79

80
    async fn process_collection_chunk<'a>(
6✔
81
        collection: FeatureCollection<G>,
6✔
82
        raster_processor: &'a TypedRasterQueryProcessor,
6✔
83
        new_column_name: &'a str,
6✔
84
        query: VectorQueryRectangle,
6✔
85
        ctx: &'a dyn QueryContext,
6✔
86
        aggregation_method: FeatureAggregationMethod,
6✔
87
    ) -> Result<BoxStream<'a, Result<FeatureCollection<G>>>> {
6✔
88
        call_on_generic_raster_processor!(raster_processor, raster_processor => {
6✔
89
            Self::process_typed_collection_chunk(collection, raster_processor, new_column_name, query, ctx, aggregation_method).await
6✔
90
        })
91
    }
6✔
92

93
    async fn process_typed_collection_chunk<'a, P: Pixel>(
6✔
94
        collection: FeatureCollection<G>,
6✔
95
        raster_processor: &'a dyn RasterQueryProcessor<RasterType = P>,
6✔
96
        new_column_name: &'a str,
6✔
97
        query: VectorQueryRectangle,
6✔
98
        ctx: &'a dyn QueryContext,
6✔
99
        aggregation_method: FeatureAggregationMethod,
6✔
100
    ) -> Result<BoxStream<'a, Result<FeatureCollection<G>>>> {
6✔
101
        // make qrect smaller wrt. points
6✔
102
        let query = VectorQueryRectangle {
6✔
103
            spatial_bounds: collection
6✔
104
                .bbox()
6✔
105
                .and_then(|bbox| bbox.intersection(&query.spatial_bounds))
6✔
106
                .unwrap_or(query.spatial_bounds),
6✔
107
            time_interval: collection
6✔
108
                .time_bounds()
6✔
109
                .and_then(|time| time.intersect(&query.time_interval))
6✔
110
                .unwrap_or(query.time_interval),
6✔
111
            spatial_resolution: query.spatial_resolution,
6✔
112
        };
6✔
113

114
        let raster_query = raster_processor.raster_query(query.into(), ctx).await?;
6✔
115

116
        let collection = Arc::new(collection);
6✔
117

6✔
118
        let collection_stream = raster_query
6✔
119
            .time_multi_fold(
6✔
120
                move || Ok(VectorRasterJoiner::new(aggregation_method)),
9✔
121
                move |accum, raster| {
13✔
122
                    let collection = collection.clone();
13✔
123
                    async move {
13✔
124
                        let accum = accum?;
13✔
125
                        let raster = raster?;
13✔
126
                        accum.extract_raster_values(&collection, &raster)
13✔
127
                    }
13✔
128
                },
13✔
129
            )
6✔
130
            .map(move |accum| accum?.into_collection(new_column_name));
9✔
131

6✔
132
        Ok(collection_stream.boxed())
6✔
133
    }
6✔
134
}
135

136
struct JoinerState<G, C> {
137
    covered_pixels: C,
138
    aggregator: TypedAggregator,
139
    g: PhantomData<G>,
140
}
141

142
struct VectorRasterJoiner<G, C> {
143
    state: Option<JoinerState<G, C>>,
144
    aggregation_method: FeatureAggregationMethod,
145
}
146

147
impl<G, C> VectorRasterJoiner<G, C>
148
where
149
    G: Geometry + ArrowTyped + 'static,
150
    C: CoveredPixels<G>,
151
    FeatureCollection<G>: PixelCoverCreator<G, C = C>,
152
{
153
    fn new(aggregation_method: FeatureAggregationMethod) -> Self {
9✔
154
        // TODO: is it possible to do the initialization here?
9✔
155

9✔
156
        Self {
9✔
157
            state: None,
9✔
158
            aggregation_method,
9✔
159
        }
9✔
160
    }
9✔
161

162
    fn initialize<P: Pixel>(
9✔
163
        &mut self,
9✔
164
        collection: &FeatureCollection<G>,
9✔
165
        raster_time: &TimeInterval,
9✔
166
    ) -> Result<()> {
9✔
167
        // TODO: could be paralellized
9✔
168

9✔
169
        let (indexes, time_intervals): (Vec<_>, Vec<_>) = collection
9✔
170
            .time_intervals()
9✔
171
            .iter()
9✔
172
            .enumerate()
9✔
173
            .filter_map(|(i, time)| {
27✔
174
                time.intersect(raster_time)
27✔
175
                    .map(|time_intersection| (i, time_intersection))
27✔
176
            })
27✔
177
            .unzip();
9✔
178

9✔
179
        let mut valid = vec![false; collection.len()];
9✔
180
        for i in indexes {
36✔
181
            valid[i] = true;
27✔
182
        }
27✔
183

184
        let collection = collection.filter(valid)?;
9✔
185
        let collection = collection.replace_time(&time_intervals)?;
9✔
186

187
        self.state = Some(JoinerState::<G, C> {
9✔
188
            aggregator: create_feature_aggregator::<P>(collection.len(), self.aggregation_method),
9✔
189
            covered_pixels: collection.create_covered_pixels(),
9✔
190
            g: Default::default(),
9✔
191
        });
9✔
192

9✔
193
        Ok(())
9✔
194
    }
9✔
195

196
    fn extract_raster_values<P: Pixel>(
13✔
197
        mut self,
13✔
198
        initial_collection: &FeatureCollection<G>,
13✔
199
        raster: &RasterTile2D<P>,
13✔
200
    ) -> Result<Self> {
13✔
201
        let state = loop {
13✔
202
            if let Some(state) = &mut self.state {
22✔
203
                break state;
13✔
204
            }
9✔
205

9✔
206
            self.initialize::<P>(initial_collection, &raster.time)?;
9✔
207
        };
208
        let collection = &state.covered_pixels.collection_ref();
13✔
209
        let aggregator = &mut state.aggregator;
13✔
210
        let covered_pixels = &state.covered_pixels;
13✔
211

212
        for feature_index in 0..collection.len() {
33✔
213
            for grid_idx in covered_pixels.covered_pixels(feature_index, raster) {
38✔
214
                let Ok(value) = raster.get_at_grid_index(grid_idx) else {
38✔
215
                    continue; // not found in this raster tile
×
216
                };
217

218
                if let Some(data) = value {
38✔
219
                    aggregator.add_value(feature_index, data, 1);
38✔
220
                } else {
38✔
221
                    aggregator.add_null(feature_index);
×
222
                }
×
223
            }
224
        }
225

226
        Ok(self)
13✔
227
    }
13✔
228

229
    fn into_collection(self, new_column_name: &str) -> Result<FeatureCollection<G>> {
9✔
230
        let Some(state) = self.state else {
9✔
231
            return Err(Error::EmptyInput); // TODO: maybe output empty dataset or just nulls
×
232
        };
233
        Ok(state
9✔
234
            .covered_pixels
9✔
235
            .collection()
9✔
236
            .add_column(new_column_name, state.aggregator.into_data())?)
9✔
237
    }
9✔
238
}
239

240
#[async_trait]
241
impl<G> QueryProcessor for RasterVectorJoinProcessor<G>
242
where
243
    G: Geometry + ArrowTyped + 'static,
244
    FeatureCollection<G>: GeometryCollection + PixelCoverCreator<G>,
245
{
246
    type Output = FeatureCollection<G>;
247
    type SpatialBounds = BoundingBox2D;
248

249
    async fn _query<'a>(
6✔
250
        &'a self,
6✔
251
        query: VectorQueryRectangle,
6✔
252
        ctx: &'a dyn QueryContext,
6✔
253
    ) -> Result<BoxStream<'a, Result<Self::Output>>> {
6✔
254
        let mut stream = self.collection.query(query, ctx).await?;
6✔
255

256
        for (raster_processor, new_column_name) in
6✔
257
            self.raster_processors.iter().zip(&self.column_names)
6✔
258
        {
6✔
259
            // TODO: spawn task
6✔
260
            stream = Self::process_collections(
6✔
261
                stream,
6✔
262
                raster_processor,
6✔
263
                new_column_name,
6✔
264
                query,
6✔
265
                ctx,
6✔
266
                self.aggregation_method,
6✔
267
            );
6✔
268
        }
6✔
269

270
        Ok(stream)
6✔
271
    }
12✔
272
}
273

274
#[cfg(test)]
275
mod tests {
276
    use super::*;
277

278
    use crate::engine::{
279
        ChunkByteSize, MockExecutionContext, MockQueryContext, QueryProcessor, RasterOperator,
280
        RasterResultDescriptor, VectorOperator,
281
    };
282
    use crate::mock::{MockFeatureCollectionSource, MockRasterSource, MockRasterSourceParams};
283
    use crate::source::{GdalSource, GdalSourceParameters};
284
    use crate::util::gdal::add_ndvi_dataset;
285
    use geoengine_datatypes::collections::{MultiPointCollection, MultiPolygonCollection};
286
    use geoengine_datatypes::primitives::{BoundingBox2D, DateTime, FeatureData, MultiPolygon};
287
    use geoengine_datatypes::primitives::{Measurement, SpatialResolution};
288
    use geoengine_datatypes::primitives::{MultiPoint, TimeInterval};
289
    use geoengine_datatypes::raster::{
290
        Grid2D, RasterDataType, TileInformation, TilingSpecification,
291
    };
292
    use geoengine_datatypes::spatial_reference::SpatialReference;
293
    use geoengine_datatypes::util::test::TestDefault;
294

295
    #[tokio::test]
1✔
296
    async fn both_instant() {
1✔
297
        let time_instant =
1✔
298
            TimeInterval::new_instant(DateTime::new_utc(2014, 1, 1, 0, 0, 0)).unwrap();
1✔
299

1✔
300
        let points = MockFeatureCollectionSource::single(
1✔
301
            MultiPointCollection::from_data(
1✔
302
                MultiPoint::many(vec![
1✔
303
                    vec![(-13.95, 20.05)],
1✔
304
                    vec![(-14.05, 20.05)],
1✔
305
                    vec![(-13.95, 19.95)],
1✔
306
                    vec![(-14.05, 19.95)],
1✔
307
                    vec![(-13.95, 19.95), (-14.05, 19.95)],
1✔
308
                ])
1✔
309
                .unwrap(),
1✔
310
                vec![time_instant; 5],
1✔
311
                Default::default(),
1✔
312
            )
1✔
313
            .unwrap(),
1✔
314
        )
1✔
315
        .boxed();
1✔
316

1✔
317
        let mut execution_context = MockExecutionContext::test_default();
1✔
318

1✔
319
        let raster_source = GdalSource {
1✔
320
            params: GdalSourceParameters {
1✔
321
                data: add_ndvi_dataset(&mut execution_context),
1✔
322
            },
1✔
323
        }
1✔
324
        .boxed();
1✔
325

326
        let points = points
1✔
327
            .initialize(&execution_context)
1✔
328
            .await
×
329
            .unwrap()
1✔
330
            .query_processor()
1✔
331
            .unwrap()
1✔
332
            .multi_point()
1✔
333
            .unwrap();
1✔
334

335
        let rasters = raster_source
1✔
336
            .initialize(&execution_context)
1✔
337
            .await
×
338
            .unwrap()
1✔
339
            .query_processor()
1✔
340
            .unwrap();
1✔
341

1✔
342
        let processor = RasterVectorJoinProcessor::new(
1✔
343
            points,
1✔
344
            vec![rasters],
1✔
345
            vec!["ndvi".to_owned()],
1✔
346
            FeatureAggregationMethod::First,
1✔
347
        );
1✔
348

349
        let mut result = processor
1✔
350
            .query(
1✔
351
                VectorQueryRectangle {
1✔
352
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
353
                        .unwrap(),
1✔
354
                    time_interval: time_instant,
1✔
355
                    spatial_resolution: SpatialResolution::new(0.1, 0.1).unwrap(),
1✔
356
                },
1✔
357
                &MockQueryContext::new(ChunkByteSize::MAX),
1✔
358
            )
1✔
359
            .await
×
360
            .unwrap()
1✔
361
            .map(Result::unwrap)
1✔
362
            .collect::<Vec<MultiPointCollection>>()
1✔
363
            .await;
2✔
364

365
        assert_eq!(result.len(), 1);
1✔
366

367
        let result = result.remove(0);
1✔
368

1✔
369
        assert_eq!(
1✔
370
            result,
1✔
371
            MultiPointCollection::from_slices(
1✔
372
                &MultiPoint::many(vec![
1✔
373
                    vec![(-13.95, 20.05)],
1✔
374
                    vec![(-14.05, 20.05)],
1✔
375
                    vec![(-13.95, 19.95)],
1✔
376
                    vec![(-14.05, 19.95)],
1✔
377
                    vec![(-13.95, 19.95), (-14.05, 19.95)],
1✔
378
                ])
1✔
379
                .unwrap(),
1✔
380
                &[time_instant; 5],
1✔
381
                // these values are taken from loading the tiff in QGIS
1✔
382
                &[("ndvi", FeatureData::Int(vec![54, 55, 51, 55, 51]))],
1✔
383
            )
1✔
384
            .unwrap()
1✔
385
        );
1✔
386
    }
387

388
    #[tokio::test]
1✔
389
    async fn points_instant() {
1✔
390
        let points = MockFeatureCollectionSource::single(
1✔
391
            MultiPointCollection::from_data(
1✔
392
                MultiPoint::many(vec![
1✔
393
                    (-13.95, 20.05),
1✔
394
                    (-14.05, 20.05),
1✔
395
                    (-13.95, 19.95),
1✔
396
                    (-14.05, 19.95),
1✔
397
                ])
1✔
398
                .unwrap(),
1✔
399
                vec![TimeInterval::new_instant(DateTime::new_utc(2014, 1, 1, 0, 0, 0)).unwrap(); 4],
1✔
400
                Default::default(),
1✔
401
            )
1✔
402
            .unwrap(),
1✔
403
        )
1✔
404
        .boxed();
1✔
405

1✔
406
        let mut execution_context = MockExecutionContext::test_default();
1✔
407

1✔
408
        let raster_source = GdalSource {
1✔
409
            params: GdalSourceParameters {
1✔
410
                data: add_ndvi_dataset(&mut execution_context),
1✔
411
            },
1✔
412
        }
1✔
413
        .boxed();
1✔
414

415
        let points = points
1✔
416
            .initialize(&execution_context)
1✔
417
            .await
×
418
            .unwrap()
1✔
419
            .query_processor()
1✔
420
            .unwrap()
1✔
421
            .multi_point()
1✔
422
            .unwrap();
1✔
423

424
        let rasters = raster_source
1✔
425
            .initialize(&execution_context)
1✔
426
            .await
×
427
            .unwrap()
1✔
428
            .query_processor()
1✔
429
            .unwrap();
1✔
430

1✔
431
        let processor = RasterVectorJoinProcessor::new(
1✔
432
            points,
1✔
433
            vec![rasters],
1✔
434
            vec!["ndvi".to_owned()],
1✔
435
            FeatureAggregationMethod::First,
1✔
436
        );
1✔
437

438
        let mut result = processor
1✔
439
            .query(
1✔
440
                VectorQueryRectangle {
1✔
441
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
442
                        .unwrap(),
1✔
443
                    time_interval: TimeInterval::new(
1✔
444
                        DateTime::new_utc(2014, 1, 1, 0, 0, 0),
1✔
445
                        DateTime::new_utc(2014, 3, 1, 0, 0, 0),
1✔
446
                    )
1✔
447
                    .unwrap(),
1✔
448
                    spatial_resolution: SpatialResolution::new(0.1, 0.1).unwrap(),
1✔
449
                },
1✔
450
                &MockQueryContext::new(ChunkByteSize::MAX),
1✔
451
            )
1✔
452
            .await
×
453
            .unwrap()
1✔
454
            .map(Result::unwrap)
1✔
455
            .collect::<Vec<MultiPointCollection>>()
1✔
456
            .await;
2✔
457

458
        assert_eq!(result.len(), 1);
1✔
459

460
        let result = result.remove(0);
1✔
461

1✔
462
        assert_eq!(
1✔
463
            result,
1✔
464
            MultiPointCollection::from_slices(
1✔
465
                &MultiPoint::many(vec![
1✔
466
                    (-13.95, 20.05),
1✔
467
                    (-14.05, 20.05),
1✔
468
                    (-13.95, 19.95),
1✔
469
                    (-14.05, 19.95),
1✔
470
                ])
1✔
471
                .unwrap(),
1✔
472
                &[TimeInterval::new_instant(DateTime::new_utc(2014, 1, 1, 0, 0, 0)).unwrap(); 4],
1✔
473
                // these values are taken from loading the tiff in QGIS
1✔
474
                &[("ndvi", FeatureData::Int(vec![54, 55, 51, 55]))],
1✔
475
            )
1✔
476
            .unwrap()
1✔
477
        );
1✔
478
    }
479

480
    #[tokio::test]
1✔
481
    async fn raster_instant() {
1✔
482
        let points = MockFeatureCollectionSource::single(
1✔
483
            MultiPointCollection::from_data(
1✔
484
                MultiPoint::many(vec![
1✔
485
                    (-13.95, 20.05),
1✔
486
                    (-14.05, 20.05),
1✔
487
                    (-13.95, 19.95),
1✔
488
                    (-14.05, 19.95),
1✔
489
                ])
1✔
490
                .unwrap(),
1✔
491
                vec![
1✔
492
                    TimeInterval::new(
1✔
493
                        DateTime::new_utc(2014, 1, 1, 0, 0, 0),
1✔
494
                        DateTime::new_utc(2014, 3, 1, 0, 0, 0),
1✔
495
                    )
1✔
496
                    .unwrap();
1✔
497
                    4
1✔
498
                ],
1✔
499
                Default::default(),
1✔
500
            )
1✔
501
            .unwrap(),
1✔
502
        )
1✔
503
        .boxed();
1✔
504

1✔
505
        let mut execution_context = MockExecutionContext::test_default();
1✔
506

1✔
507
        let raster_source = GdalSource {
1✔
508
            params: GdalSourceParameters {
1✔
509
                data: add_ndvi_dataset(&mut execution_context),
1✔
510
            },
1✔
511
        }
1✔
512
        .boxed();
1✔
513

514
        let points = points
1✔
515
            .initialize(&execution_context)
1✔
516
            .await
×
517
            .unwrap()
1✔
518
            .query_processor()
1✔
519
            .unwrap()
1✔
520
            .multi_point()
1✔
521
            .unwrap();
1✔
522

523
        let rasters = raster_source
1✔
524
            .initialize(&execution_context)
1✔
525
            .await
×
526
            .unwrap()
1✔
527
            .query_processor()
1✔
528
            .unwrap();
1✔
529

1✔
530
        let processor = RasterVectorJoinProcessor::new(
1✔
531
            points,
1✔
532
            vec![rasters],
1✔
533
            vec!["ndvi".to_owned()],
1✔
534
            FeatureAggregationMethod::First,
1✔
535
        );
1✔
536

537
        let mut result = processor
1✔
538
            .query(
1✔
539
                VectorQueryRectangle {
1✔
540
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
541
                        .unwrap(),
1✔
542
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
543
                        2014, 1, 1, 0, 0, 0,
1✔
544
                    ))
1✔
545
                    .unwrap(),
1✔
546
                    spatial_resolution: SpatialResolution::new(0.1, 0.1).unwrap(),
1✔
547
                },
1✔
548
                &MockQueryContext::new(ChunkByteSize::MAX),
1✔
549
            )
1✔
550
            .await
×
551
            .unwrap()
1✔
552
            .map(Result::unwrap)
1✔
553
            .collect::<Vec<MultiPointCollection>>()
1✔
554
            .await;
2✔
555

556
        assert_eq!(result.len(), 1);
1✔
557

558
        let result = result.remove(0);
1✔
559

1✔
560
        assert_eq!(
1✔
561
            result,
1✔
562
            MultiPointCollection::from_slices(
1✔
563
                &MultiPoint::many(vec![
1✔
564
                    (-13.95, 20.05),
1✔
565
                    (-14.05, 20.05),
1✔
566
                    (-13.95, 19.95),
1✔
567
                    (-14.05, 19.95),
1✔
568
                ])
1✔
569
                .unwrap(),
1✔
570
                &[TimeInterval::new(
1✔
571
                    DateTime::new_utc(2014, 1, 1, 0, 0, 0),
1✔
572
                    DateTime::new_utc(2014, 2, 1, 0, 0, 0),
1✔
573
                )
1✔
574
                .unwrap(); 4],
1✔
575
                // these values are taken from loading the tiff in QGIS
1✔
576
                &[("ndvi", FeatureData::Int(vec![54, 55, 51, 55]))],
1✔
577
            )
1✔
578
            .unwrap()
1✔
579
        );
1✔
580
    }
581

582
    #[allow(clippy::too_many_lines)]
583
    #[tokio::test]
1✔
584
    async fn both_ranges() {
1✔
585
        let points = MockFeatureCollectionSource::single(
1✔
586
            MultiPointCollection::from_data(
1✔
587
                MultiPoint::many(vec![
1✔
588
                    (-13.95, 20.05),
1✔
589
                    (-14.05, 20.05),
1✔
590
                    (-13.95, 19.95),
1✔
591
                    (-14.05, 19.95),
1✔
592
                ])
1✔
593
                .unwrap(),
1✔
594
                vec![
1✔
595
                    TimeInterval::new(
1✔
596
                        DateTime::new_utc(2014, 1, 1, 0, 0, 0),
1✔
597
                        DateTime::new_utc(2014, 3, 1, 0, 0, 0),
1✔
598
                    )
1✔
599
                    .unwrap();
1✔
600
                    4
1✔
601
                ],
1✔
602
                Default::default(),
1✔
603
            )
1✔
604
            .unwrap(),
1✔
605
        )
1✔
606
        .boxed();
1✔
607

1✔
608
        let mut execution_context = MockExecutionContext::test_default();
1✔
609

1✔
610
        let raster_source = GdalSource {
1✔
611
            params: GdalSourceParameters {
1✔
612
                data: add_ndvi_dataset(&mut execution_context),
1✔
613
            },
1✔
614
        }
1✔
615
        .boxed();
1✔
616

617
        let points = points
1✔
618
            .initialize(&execution_context)
1✔
619
            .await
×
620
            .unwrap()
1✔
621
            .query_processor()
1✔
622
            .unwrap()
1✔
623
            .multi_point()
1✔
624
            .unwrap();
1✔
625

626
        let rasters = raster_source
1✔
627
            .initialize(&execution_context)
1✔
628
            .await
×
629
            .unwrap()
1✔
630
            .query_processor()
1✔
631
            .unwrap();
1✔
632

1✔
633
        let processor = RasterVectorJoinProcessor::new(
1✔
634
            points,
1✔
635
            vec![rasters],
1✔
636
            vec!["ndvi".to_owned()],
1✔
637
            FeatureAggregationMethod::First,
1✔
638
        );
1✔
639

640
        let mut result = processor
1✔
641
            .query(
1✔
642
                VectorQueryRectangle {
1✔
643
                    spatial_bounds: BoundingBox2D::new((-180., -90.).into(), (180., 90.).into())
1✔
644
                        .unwrap(),
1✔
645
                    time_interval: TimeInterval::new(
1✔
646
                        DateTime::new_utc(2014, 1, 1, 0, 0, 0),
1✔
647
                        DateTime::new_utc(2014, 3, 1, 0, 0, 0),
1✔
648
                    )
1✔
649
                    .unwrap(),
1✔
650
                    spatial_resolution: SpatialResolution::new(0.1, 0.1).unwrap(),
1✔
651
                },
1✔
652
                &MockQueryContext::new(ChunkByteSize::MAX),
1✔
653
            )
1✔
654
            .await
×
655
            .unwrap()
1✔
656
            .map(Result::unwrap)
1✔
657
            .collect::<Vec<MultiPointCollection>>()
1✔
658
            .await;
3✔
659

660
        assert_eq!(result.len(), 1);
1✔
661

662
        let result = result.remove(0);
1✔
663

1✔
664
        let t1 = TimeInterval::new(
1✔
665
            DateTime::new_utc(2014, 1, 1, 0, 0, 0),
1✔
666
            DateTime::new_utc(2014, 2, 1, 0, 0, 0),
1✔
667
        )
1✔
668
        .unwrap();
1✔
669
        let t2 = TimeInterval::new(
1✔
670
            DateTime::new_utc(2014, 2, 1, 0, 0, 0),
1✔
671
            DateTime::new_utc(2014, 3, 1, 0, 0, 0),
1✔
672
        )
1✔
673
        .unwrap();
1✔
674
        assert_eq!(
1✔
675
            result,
1✔
676
            MultiPointCollection::from_slices(
1✔
677
                &MultiPoint::many(vec![
1✔
678
                    (-13.95, 20.05),
1✔
679
                    (-14.05, 20.05),
1✔
680
                    (-13.95, 19.95),
1✔
681
                    (-14.05, 19.95),
1✔
682
                    (-13.95, 20.05),
1✔
683
                    (-14.05, 20.05),
1✔
684
                    (-13.95, 19.95),
1✔
685
                    (-14.05, 19.95),
1✔
686
                ])
1✔
687
                .unwrap(),
1✔
688
                &[t1, t1, t1, t1, t2, t2, t2, t2],
1✔
689
                // these values are taken from loading the tiff in QGIS
1✔
690
                &[(
1✔
691
                    "ndvi",
1✔
692
                    FeatureData::Int(vec![54, 55, 51, 55, 52, 55, 50, 53])
1✔
693
                )],
1✔
694
            )
1✔
695
            .unwrap()
1✔
696
        );
1✔
697
    }
698

699
    #[tokio::test]
1✔
700
    #[allow(clippy::float_cmp)]
701
    #[allow(clippy::too_many_lines)]
702
    async fn extract_raster_values_two_spatial_tiles_per_time_step_mean() {
1✔
703
        let raster_tile_a_0 = RasterTile2D::new_with_tile_info(
1✔
704
            TimeInterval::new(0, 10).unwrap(),
1✔
705
            TileInformation {
1✔
706
                global_geo_transform: TestDefault::test_default(),
1✔
707
                global_tile_position: [0, 0].into(),
1✔
708
                tile_size_in_pixels: [3, 2].into(),
1✔
709
            },
1✔
710
            Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1])
1✔
711
                .unwrap()
1✔
712
                .into(),
1✔
713
        );
1✔
714
        let raster_tile_a_1 = RasterTile2D::new_with_tile_info(
1✔
715
            TimeInterval::new(0, 10).unwrap(),
1✔
716
            TileInformation {
1✔
717
                global_geo_transform: TestDefault::test_default(),
1✔
718
                global_tile_position: [0, 1].into(),
1✔
719
                tile_size_in_pixels: [3, 2].into(),
1✔
720
            },
1✔
721
            Grid2D::new([3, 2].into(), vec![60, 50, 40, 30, 20, 10])
1✔
722
                .unwrap()
1✔
723
                .into(),
1✔
724
        );
1✔
725
        let raster_tile_b_0 = RasterTile2D::new_with_tile_info(
1✔
726
            TimeInterval::new(10, 20).unwrap(),
1✔
727
            TileInformation {
1✔
728
                global_geo_transform: TestDefault::test_default(),
1✔
729
                global_tile_position: [0, 0].into(),
1✔
730
                tile_size_in_pixels: [3, 2].into(),
1✔
731
            },
1✔
732
            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
1✔
733
                .unwrap()
1✔
734
                .into(),
1✔
735
        );
1✔
736
        let raster_tile_b_1 = RasterTile2D::new_with_tile_info(
1✔
737
            TimeInterval::new(10, 20).unwrap(),
1✔
738
            TileInformation {
1✔
739
                global_geo_transform: TestDefault::test_default(),
1✔
740
                global_tile_position: [0, 1].into(),
1✔
741
                tile_size_in_pixels: [3, 2].into(),
1✔
742
            },
1✔
743
            Grid2D::new([3, 2].into(), vec![10, 20, 30, 40, 50, 60])
1✔
744
                .unwrap()
1✔
745
                .into(),
1✔
746
        );
1✔
747

1✔
748
        let raster_source = MockRasterSource {
1✔
749
            params: MockRasterSourceParams {
1✔
750
                data: vec![
1✔
751
                    raster_tile_a_0,
1✔
752
                    raster_tile_a_1,
1✔
753
                    raster_tile_b_0,
1✔
754
                    raster_tile_b_1,
1✔
755
                ],
1✔
756
                result_descriptor: RasterResultDescriptor {
1✔
757
                    data_type: RasterDataType::U8,
1✔
758
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
759
                    measurement: Measurement::Unitless,
1✔
760
                    time: None,
1✔
761
                    bbox: None,
1✔
762
                    resolution: None,
1✔
763
                },
1✔
764
            },
1✔
765
        }
1✔
766
        .boxed();
1✔
767

1✔
768
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
769
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
1✔
770
        );
1✔
771

772
        let raster = raster_source
1✔
773
            .initialize(&execution_context)
1✔
774
            .await
×
775
            .unwrap()
1✔
776
            .query_processor()
1✔
777
            .unwrap();
1✔
778

1✔
779
        let points = MultiPointCollection::from_data(
1✔
780
            MultiPoint::many(vec![
1✔
781
                vec![(0.0, 0.0), (2.0, 0.0)],
1✔
782
                vec![(1.0, 0.0), (3.0, 0.0)],
1✔
783
            ])
1✔
784
            .unwrap(),
1✔
785
            vec![TimeInterval::default(); 2],
1✔
786
            Default::default(),
1✔
787
        )
1✔
788
        .unwrap();
1✔
789

1✔
790
        let points = MockFeatureCollectionSource::single(points).boxed();
1✔
791

792
        let points = points
1✔
793
            .initialize(&execution_context)
1✔
794
            .await
×
795
            .unwrap()
1✔
796
            .query_processor()
1✔
797
            .unwrap()
1✔
798
            .multi_point()
1✔
799
            .unwrap();
1✔
800

1✔
801
        let processor = RasterVectorJoinProcessor::new(
1✔
802
            points,
1✔
803
            vec![raster],
1✔
804
            vec!["foo".to_owned()],
1✔
805
            FeatureAggregationMethod::Mean,
1✔
806
        );
1✔
807

808
        let mut result = processor
1✔
809
            .query(
1✔
810
                VectorQueryRectangle {
1✔
811
                    spatial_bounds: BoundingBox2D::new((0.0, -3.0).into(), (4.0, 0.0).into())
1✔
812
                        .unwrap(),
1✔
813
                    time_interval: TimeInterval::new_unchecked(0, 20),
1✔
814
                    spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
815
                },
1✔
816
                &MockQueryContext::new(ChunkByteSize::MAX),
1✔
817
            )
1✔
818
            .await
×
819
            .unwrap()
1✔
820
            .map(Result::unwrap)
1✔
821
            .collect::<Vec<MultiPointCollection>>()
1✔
822
            .await;
×
823

824
        assert_eq!(result.len(), 1);
1✔
825

826
        let result = result.remove(0);
1✔
827

1✔
828
        let t1 = TimeInterval::new(0, 10).unwrap();
1✔
829
        let t2 = TimeInterval::new(10, 20).unwrap();
1✔
830

1✔
831
        assert_eq!(
1✔
832
            result,
1✔
833
            MultiPointCollection::from_slices(
1✔
834
                &MultiPoint::many(vec![
1✔
835
                    vec![(0.0, 0.0), (2.0, 0.0)],
1✔
836
                    vec![(1.0, 0.0), (3.0, 0.0)],
1✔
837
                    vec![(0.0, 0.0), (2.0, 0.0)],
1✔
838
                    vec![(1.0, 0.0), (3.0, 0.0)],
1✔
839
                ])
1✔
840
                .unwrap(),
1✔
841
                &[t1, t1, t2, t2],
1✔
842
                &[(
1✔
843
                    "foo",
1✔
844
                    FeatureData::Float(vec![
1✔
845
                        (6. + 60.) / 2.,
1✔
846
                        (5. + 50.) / 2.,
1✔
847
                        (1. + 10.) / 2.,
1✔
848
                        (2. + 20.) / 2.
1✔
849
                    ])
1✔
850
                )],
1✔
851
            )
1✔
852
            .unwrap()
1✔
853
        );
1✔
854
    }
855

856
    #[tokio::test]
1✔
857
    #[allow(clippy::float_cmp)]
858
    #[allow(clippy::too_many_lines)]
859
    async fn polygons() {
1✔
860
        let raster_tile_a_0 = RasterTile2D::new_with_tile_info(
1✔
861
            TimeInterval::new(0, 10).unwrap(),
1✔
862
            TileInformation {
1✔
863
                global_geo_transform: TestDefault::test_default(),
1✔
864
                global_tile_position: [0, 0].into(),
1✔
865
                tile_size_in_pixels: [3, 2].into(),
1✔
866
            },
1✔
867
            Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1])
1✔
868
                .unwrap()
1✔
869
                .into(),
1✔
870
        );
1✔
871
        let raster_tile_a_1 = RasterTile2D::new_with_tile_info(
1✔
872
            TimeInterval::new(0, 10).unwrap(),
1✔
873
            TileInformation {
1✔
874
                global_geo_transform: TestDefault::test_default(),
1✔
875
                global_tile_position: [0, 1].into(),
1✔
876
                tile_size_in_pixels: [3, 2].into(),
1✔
877
            },
1✔
878
            Grid2D::new([3, 2].into(), vec![60, 50, 40, 30, 20, 10])
1✔
879
                .unwrap()
1✔
880
                .into(),
1✔
881
        );
1✔
882
        let raster_tile_b_0 = RasterTile2D::new_with_tile_info(
1✔
883
            TimeInterval::new(10, 20).unwrap(),
1✔
884
            TileInformation {
1✔
885
                global_geo_transform: TestDefault::test_default(),
1✔
886
                global_tile_position: [0, 0].into(),
1✔
887
                tile_size_in_pixels: [3, 2].into(),
1✔
888
            },
1✔
889
            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
1✔
890
                .unwrap()
1✔
891
                .into(),
1✔
892
        );
1✔
893
        let raster_tile_b_1 = RasterTile2D::new_with_tile_info(
1✔
894
            TimeInterval::new(10, 20).unwrap(),
1✔
895
            TileInformation {
1✔
896
                global_geo_transform: TestDefault::test_default(),
1✔
897
                global_tile_position: [0, 1].into(),
1✔
898
                tile_size_in_pixels: [3, 2].into(),
1✔
899
            },
1✔
900
            Grid2D::new([3, 2].into(), vec![10, 20, 30, 40, 50, 60])
1✔
901
                .unwrap()
1✔
902
                .into(),
1✔
903
        );
1✔
904

1✔
905
        let raster_source = MockRasterSource {
1✔
906
            params: MockRasterSourceParams {
1✔
907
                data: vec![
1✔
908
                    raster_tile_a_0,
1✔
909
                    raster_tile_a_1,
1✔
910
                    raster_tile_b_0,
1✔
911
                    raster_tile_b_1,
1✔
912
                ],
1✔
913
                result_descriptor: RasterResultDescriptor {
1✔
914
                    data_type: RasterDataType::U8,
1✔
915
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
916
                    measurement: Measurement::Unitless,
1✔
917
                    time: None,
1✔
918
                    bbox: None,
1✔
919
                    resolution: None,
1✔
920
                },
1✔
921
            },
1✔
922
        }
1✔
923
        .boxed();
1✔
924

1✔
925
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
926
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
1✔
927
        );
1✔
928

929
        let raster = raster_source
1✔
930
            .initialize(&execution_context)
1✔
931
            .await
×
932
            .unwrap()
1✔
933
            .query_processor()
1✔
934
            .unwrap();
1✔
935

1✔
936
        let polygons = MultiPolygonCollection::from_data(
1✔
937
            vec![MultiPolygon::new(vec![vec![vec![
1✔
938
                (0.5, -0.5).into(),
1✔
939
                (4., -1.).into(),
1✔
940
                (0.5, -2.5).into(),
1✔
941
                (0.5, -0.5).into(),
1✔
942
            ]]])
1✔
943
            .unwrap()],
1✔
944
            vec![TimeInterval::default(); 1],
1✔
945
            Default::default(),
1✔
946
        )
1✔
947
        .unwrap();
1✔
948

1✔
949
        let polygons = MockFeatureCollectionSource::single(polygons).boxed();
1✔
950

951
        let points = polygons
1✔
952
            .initialize(&execution_context)
1✔
953
            .await
×
954
            .unwrap()
1✔
955
            .query_processor()
1✔
956
            .unwrap()
1✔
957
            .multi_polygon()
1✔
958
            .unwrap();
1✔
959

1✔
960
        let processor = RasterVectorJoinProcessor::new(
1✔
961
            points,
1✔
962
            vec![raster],
1✔
963
            vec!["foo".to_owned()],
1✔
964
            FeatureAggregationMethod::Mean,
1✔
965
        );
1✔
966

967
        let mut result = processor
1✔
968
            .query(
1✔
969
                VectorQueryRectangle {
1✔
970
                    spatial_bounds: BoundingBox2D::new((0.0, -3.0).into(), (4.0, 0.0).into())
1✔
971
                        .unwrap(),
1✔
972
                    time_interval: TimeInterval::new_unchecked(0, 20),
1✔
973
                    spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
974
                },
1✔
975
                &MockQueryContext::new(ChunkByteSize::MAX),
1✔
976
            )
1✔
977
            .await
×
978
            .unwrap()
1✔
979
            .map(Result::unwrap)
1✔
980
            .collect::<Vec<MultiPolygonCollection>>()
1✔
981
            .await;
×
982

983
        assert_eq!(result.len(), 1);
1✔
984

985
        let result = result.remove(0);
1✔
986

1✔
987
        let t1 = TimeInterval::new(0, 10).unwrap();
1✔
988
        let t2 = TimeInterval::new(10, 20).unwrap();
1✔
989

1✔
990
        assert_eq!(
1✔
991
            result,
1✔
992
            MultiPolygonCollection::from_slices(
1✔
993
                &[
1✔
994
                    MultiPolygon::new(vec![vec![vec![
1✔
995
                        (0.5, -0.5).into(),
1✔
996
                        (4., -1.).into(),
1✔
997
                        (0.5, -2.5).into(),
1✔
998
                        (0.5, -0.5).into(),
1✔
999
                    ]]])
1✔
1000
                    .unwrap(),
1✔
1001
                    MultiPolygon::new(vec![vec![vec![
1✔
1002
                        (0.5, -0.5).into(),
1✔
1003
                        (4., -1.).into(),
1✔
1004
                        (0.5, -2.5).into(),
1✔
1005
                        (0.5, -0.5).into(),
1✔
1006
                    ]]])
1✔
1007
                    .unwrap()
1✔
1008
                ],
1✔
1009
                &[t1, t2],
1✔
1010
                &[(
1✔
1011
                    "foo",
1✔
1012
                    FeatureData::Float(vec![
1✔
1013
                        (3. + 1. + 40. + 30.) / 4.,
1✔
1014
                        (4. + 6. + 30. + 40.) / 4.
1✔
1015
                    ])
1✔
1016
                )],
1✔
1017
            )
1✔
1018
            .unwrap()
1✔
1019
        );
1✔
1020
    }
1021
}
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

© 2025 Coveralls, Inc