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

geo-engine / geoengine / 7006568925

27 Nov 2023 02:07PM UTC coverage: 89.651% (+0.2%) from 89.498%
7006568925

push

github

web-flow
Merge pull request #888 from geo-engine/raster_stacks

raster stacking

4032 of 4274 new or added lines in 107 files covered. (94.34%)

12 existing lines in 8 files now uncovered.

113020 of 126066 relevant lines covered (89.65%)

59901.79 hits per line

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

97.28
/operators/src/processing/raster_vector_join/aggregated.rs
1
use futures::stream::BoxStream;
2
use futures::{StreamExt, TryStreamExt};
3

4
use geoengine_datatypes::collections::{
5
    FeatureCollection, FeatureCollectionInfos, FeatureCollectionModifications,
6
};
7
use geoengine_datatypes::primitives::{
8
    BandSelection, CacheHint, ColumnSelection, RasterQueryRectangle,
9
};
10
use geoengine_datatypes::raster::{GridIndexAccess, Pixel, RasterDataType};
11
use geoengine_datatypes::util::arrow::ArrowTyped;
12

13
use crate::engine::{
14
    QueryContext, QueryProcessor, RasterQueryProcessor, TypedRasterQueryProcessor,
15
    VectorQueryProcessor,
16
};
17
use crate::processing::raster_vector_join::aggregator::{
18
    Aggregator, FirstValueFloatAggregator, FirstValueIntAggregator, MeanValueAggregator,
19
    TypedAggregator,
20
};
21
use crate::processing::raster_vector_join::TemporalAggregationMethod;
22
use crate::util::Result;
23
use async_trait::async_trait;
24
use geoengine_datatypes::primitives::{BoundingBox2D, Geometry, VectorQueryRectangle};
25

26
use super::util::{CoveredPixels, FeatureTimeSpanIter, PixelCoverCreator};
27
use super::{create_feature_aggregator, FeatureAggregationMethod};
28

29
pub struct RasterVectorAggregateJoinProcessor<G> {
30
    collection: Box<dyn VectorQueryProcessor<VectorType = FeatureCollection<G>>>,
31
    raster_processors: Vec<TypedRasterQueryProcessor>,
32
    column_names: Vec<String>,
33
    feature_aggregation: FeatureAggregationMethod,
34
    feature_aggregation_ignore_no_data: bool,
35
    temporal_aggregation: TemporalAggregationMethod,
36
    temporal_aggregation_ignore_no_data: bool,
37
}
38

39
impl<G> RasterVectorAggregateJoinProcessor<G>
40
where
41
    G: Geometry + ArrowTyped,
42
    FeatureCollection<G>: PixelCoverCreator<G>,
43
{
44
    pub fn new(
4✔
45
        collection: Box<dyn VectorQueryProcessor<VectorType = FeatureCollection<G>>>,
4✔
46
        raster_processors: Vec<TypedRasterQueryProcessor>,
4✔
47
        column_names: Vec<String>,
4✔
48
        feature_aggregation: FeatureAggregationMethod,
4✔
49
        feature_aggregation_ignore_no_data: bool,
4✔
50
        temporal_aggregation: TemporalAggregationMethod,
4✔
51
        temporal_aggregation_ignore_no_data: bool,
4✔
52
    ) -> Self {
4✔
53
        Self {
4✔
54
            collection,
4✔
55
            raster_processors,
4✔
56
            column_names,
4✔
57
            feature_aggregation,
4✔
58
            feature_aggregation_ignore_no_data,
4✔
59
            temporal_aggregation,
4✔
60
            temporal_aggregation_ignore_no_data,
4✔
61
        }
4✔
62
    }
4✔
63

64
    #[allow(clippy::too_many_arguments)] // TODO: refactor to reduce arguments
65
    async fn extract_raster_values<P: Pixel>(
8✔
66
        collection: &FeatureCollection<G>,
8✔
67
        raster_processor: &dyn RasterQueryProcessor<RasterType = P>,
8✔
68
        new_column_name: &str,
8✔
69
        feature_aggreation: FeatureAggregationMethod,
8✔
70
        feature_aggregation_ignore_no_data: bool,
8✔
71
        temporal_aggregation: TemporalAggregationMethod,
8✔
72
        temporal_aggregation_ignore_no_data: bool,
8✔
73
        query: VectorQueryRectangle,
8✔
74
        ctx: &dyn QueryContext,
8✔
75
    ) -> Result<FeatureCollection<G>> {
8✔
76
        let mut temporal_aggregator = Self::create_aggregator::<P>(
8✔
77
            collection.len(),
8✔
78
            temporal_aggregation,
8✔
79
            temporal_aggregation_ignore_no_data,
8✔
80
        );
8✔
81

82
        let collection = collection.sort_by_time_asc()?;
8✔
83

84
        let covered_pixels = collection.create_covered_pixels();
8✔
85

8✔
86
        let collection = covered_pixels.collection_ref();
8✔
87

8✔
88
        let mut cache_hint = CacheHint::max_duration();
8✔
89

90
        for time_span in FeatureTimeSpanIter::new(collection.time_intervals()) {
8✔
91
            let query = VectorQueryRectangle {
8✔
92
                spatial_bounds: query.spatial_bounds,
8✔
93
                time_interval: time_span.time_interval,
8✔
94
                spatial_resolution: query.spatial_resolution,
8✔
95
                attributes: ColumnSelection::all(),
8✔
96
            };
8✔
97

98
            let mut rasters = raster_processor
8✔
99
                .raster_query(
8✔
100
                    RasterQueryRectangle::from_qrect_and_bands(&query, BandSelection::first()),
8✔
101
                    ctx,
8✔
102
                )
8✔
NEW
103
                .await?;
×
104

105
            // TODO: optimize geo access (only specific tiles, etc.)
106

107
            let mut feature_aggregator = create_feature_aggregator::<P>(
8✔
108
                collection.len(),
8✔
109
                feature_aggreation,
8✔
110
                feature_aggregation_ignore_no_data,
8✔
111
            );
8✔
112

8✔
113
            let mut time_end = None;
8✔
114

115
            while let Some(raster) = rasters.next().await {
200✔
116
                let raster = raster?;
194✔
117

118
                if let Some(end) = time_end {
194✔
119
                    if end != raster.time.end() {
186✔
120
                        // new time slice => consume old aggregator and create new one
121
                        temporal_aggregator.add_feature_data(
6✔
122
                            feature_aggregator.into_data(),
6✔
123
                            time_span.time_interval.duration_ms(), // TODO: use individual feature duration?
6✔
124
                        )?;
6✔
125

126
                        feature_aggregator = create_feature_aggregator::<P>(
6✔
127
                            collection.len(),
6✔
128
                            feature_aggreation,
6✔
129
                            feature_aggregation_ignore_no_data,
6✔
130
                        );
6✔
131

6✔
132
                        if temporal_aggregator.is_satisfied() {
6✔
133
                            break;
2✔
134
                        }
4✔
135
                    }
180✔
136
                }
8✔
137
                time_end = Some(raster.time.end());
192✔
138

139
                for feature_index in time_span.feature_index_start..=time_span.feature_index_end {
648✔
140
                    // TODO: don't do random access but use a single iterator
141
                    let mut satisfied = false;
648✔
142
                    for grid_idx in covered_pixels.covered_pixels(feature_index, &raster) {
648✔
143
                        // try to get the pixel if the coordinate is within the current tile
144
                        if let Ok(pixel) = raster.get_at_grid_index(grid_idx) {
53✔
145
                            // finally, attach value to feature
146
                            if let Some(data) = pixel {
53✔
147
                                feature_aggregator.add_value(feature_index, data, 1);
49✔
148
                            } else {
49✔
149
                                // TODO: weigh by area?
4✔
150
                                feature_aggregator.add_null(feature_index);
4✔
151
                            }
4✔
152

153
                            if feature_aggregator.is_satisfied() {
53✔
154
                                satisfied = true;
8✔
155
                                break;
8✔
156
                            }
45✔
157
                        }
×
158
                    }
159

160
                    if satisfied {
648✔
161
                        break;
8✔
162
                    }
640✔
163
                }
164

165
                cache_hint.merge_with(&raster.cache_hint);
192✔
166
            }
167

168
            temporal_aggregator.add_feature_data(
8✔
169
                feature_aggregator.into_data(),
8✔
170
                time_span.time_interval.duration_ms(), // TODO: use individual feature duration?
8✔
171
            )?;
8✔
172

173
            if temporal_aggregator.is_satisfied() {
8✔
174
                break;
4✔
175
            }
4✔
176
        }
177

178
        let mut new_collection = collection
8✔
179
            .add_column(new_column_name, temporal_aggregator.into_data())
8✔
180
            .map_err(Into::<crate::error::Error>::into)?;
8✔
181

182
        new_collection.cache_hint = cache_hint;
8✔
183

8✔
184
        Ok(new_collection)
8✔
185
    }
8✔
186

187
    fn create_aggregator<P: Pixel>(
8✔
188
        number_of_features: usize,
8✔
189
        aggregation: TemporalAggregationMethod,
8✔
190
        ignore_no_data: bool,
8✔
191
    ) -> TypedAggregator {
8✔
192
        match aggregation {
8✔
193
            TemporalAggregationMethod::First => match P::TYPE {
3✔
194
                RasterDataType::U8
195
                | RasterDataType::U16
196
                | RasterDataType::U32
197
                | RasterDataType::U64
198
                | RasterDataType::I8
199
                | RasterDataType::I16
200
                | RasterDataType::I32
201
                | RasterDataType::I64 => {
202
                    FirstValueIntAggregator::new(number_of_features, ignore_no_data).into_typed()
3✔
203
                }
204
                RasterDataType::F32 | RasterDataType::F64 => {
205
                    FirstValueFloatAggregator::new(number_of_features, ignore_no_data).into_typed()
×
206
                }
207
            },
208
            TemporalAggregationMethod::Mean => {
209
                MeanValueAggregator::new(number_of_features, ignore_no_data).into_typed()
5✔
210
            }
211
            TemporalAggregationMethod::None => {
212
                unreachable!("this type of aggregator does not lead to this kind of processor")
×
213
            }
214
        }
215
    }
8✔
216
}
217

218
#[async_trait]
219
impl<G> QueryProcessor for RasterVectorAggregateJoinProcessor<G>
220
where
221
    G: Geometry + ArrowTyped + 'static,
222
    FeatureCollection<G>: PixelCoverCreator<G>,
223
{
224
    type Output = FeatureCollection<G>;
225
    type SpatialBounds = BoundingBox2D;
226
    type Selection = ColumnSelection;
227

228
    async fn _query<'a>(
4✔
229
        &'a self,
4✔
230
        query: VectorQueryRectangle,
4✔
231
        ctx: &'a dyn QueryContext,
4✔
232
    ) -> Result<BoxStream<'a, Result<Self::Output>>> {
4✔
233
        let stream = self
4✔
234
            .collection
4✔
235
            .query(query.clone(), ctx)
4✔
236
            .await?
1✔
237
            .and_then(move |mut collection| {
4✔
238
                let query = query.clone();
4✔
239
                async move {
4✔
240
                    for (raster, new_column_name) in
4✔
241
                        self.raster_processors.iter().zip(&self.column_names)
4✔
242
                    {
4✔
243
                        collection = call_on_generic_raster_processor!(raster, raster => {
4✔
244
                            Self::extract_raster_values(
245
                                &collection,
4✔
246
                                raster,
4✔
247
                                new_column_name,
4✔
248
                                self.feature_aggregation,
4✔
249
                                self.feature_aggregation_ignore_no_data,
4✔
250
                                self.temporal_aggregation,
4✔
251
                                self.temporal_aggregation_ignore_no_data,
4✔
252
                                query.clone(),
4✔
253
                                ctx
4✔
254
                            ).await?
113✔
255
                        });
256
                    }
257

258
                    Ok(collection)
4✔
259
                }
4✔
260
            })
4✔
261
            .boxed();
4✔
262

4✔
263
        Ok(stream)
4✔
264
    }
8✔
265
}
266

267
#[cfg(test)]
268
mod tests {
269
    use super::*;
270

271
    use crate::engine::{
272
        ChunkByteSize, MockExecutionContext, RasterBandDescriptors, RasterResultDescriptor,
273
        WorkflowOperatorPath,
274
    };
275
    use crate::engine::{MockQueryContext, RasterOperator};
276
    use crate::mock::{MockRasterSource, MockRasterSourceParams};
277
    use geoengine_datatypes::collections::{MultiPointCollection, MultiPolygonCollection};
278
    use geoengine_datatypes::primitives::CacheHint;
279
    use geoengine_datatypes::primitives::MultiPolygon;
280
    use geoengine_datatypes::raster::{Grid2D, RasterTile2D, TileInformation};
281
    use geoengine_datatypes::spatial_reference::SpatialReference;
282
    use geoengine_datatypes::util::test::TestDefault;
283
    use geoengine_datatypes::{
284
        primitives::{BoundingBox2D, FeatureDataRef, MultiPoint, SpatialResolution, TimeInterval},
285
        raster::TilingSpecification,
286
    };
287

288
    #[tokio::test]
1✔
289
    async fn extract_raster_values_single_raster() {
1✔
290
        let raster_tile = RasterTile2D::<u8>::new_with_tile_info(
1✔
291
            TimeInterval::default(),
1✔
292
            TileInformation {
1✔
293
                global_geo_transform: TestDefault::test_default(),
1✔
294
                global_tile_position: [0, 0].into(),
1✔
295
                tile_size_in_pixels: [3, 2].into(),
1✔
296
            },
1✔
297
            0,
1✔
298
            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
1✔
299
                .unwrap()
1✔
300
                .into(),
1✔
301
            CacheHint::default(),
1✔
302
        );
1✔
303

1✔
304
        let raster_source = MockRasterSource {
1✔
305
            params: MockRasterSourceParams {
1✔
306
                data: vec![raster_tile],
1✔
307
                result_descriptor: RasterResultDescriptor {
1✔
308
                    data_type: RasterDataType::U8,
1✔
309
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
310
                    time: None,
1✔
311
                    bbox: None,
1✔
312
                    resolution: None,
1✔
313
                    bands: RasterBandDescriptors::new_single_band(),
1✔
314
                },
1✔
315
            },
1✔
316
        }
1✔
317
        .boxed();
1✔
318

1✔
319
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
320
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
1✔
321
        );
1✔
322

323
        let raster_source = raster_source
1✔
324
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
325
            .await
×
326
            .unwrap();
1✔
327

1✔
328
        let points = MultiPointCollection::from_data(
1✔
329
            MultiPoint::many(vec![
1✔
330
                (0.0, 0.0),
1✔
331
                (1.0, 0.0),
1✔
332
                (0.0, -1.0),
1✔
333
                (1.0, -1.0),
1✔
334
                (0.0, -2.0),
1✔
335
                (1.0, -2.0),
1✔
336
            ])
1✔
337
            .unwrap(),
1✔
338
            vec![TimeInterval::default(); 6],
1✔
339
            Default::default(),
1✔
340
            CacheHint::default(),
1✔
341
        )
1✔
342
        .unwrap();
1✔
343

344
        let result = RasterVectorAggregateJoinProcessor::extract_raster_values(
1✔
345
            &points,
1✔
346
            &raster_source.query_processor().unwrap().get_u8().unwrap(),
1✔
347
            "foo",
1✔
348
            FeatureAggregationMethod::First,
1✔
349
            false,
1✔
350
            TemporalAggregationMethod::First,
1✔
351
            false,
1✔
352
            VectorQueryRectangle {
1✔
353
                spatial_bounds: BoundingBox2D::new((0.0, -3.0).into(), (2.0, 0.).into()).unwrap(),
1✔
354
                time_interval: Default::default(),
1✔
355
                spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
356
                attributes: ColumnSelection::all(),
1✔
357
            },
1✔
358
            &MockQueryContext::new(ChunkByteSize::MIN),
1✔
359
        )
1✔
360
        .await
×
361
        .unwrap();
1✔
362

363
        if let FeatureDataRef::Int(extracted_data) = result.data("foo").unwrap() {
1✔
364
            assert_eq!(extracted_data.as_ref(), &[1, 2, 3, 4, 5, 6]);
1✔
365
        } else {
366
            unreachable!();
×
367
        }
368
    }
369

370
    #[tokio::test]
1✔
371
    #[allow(clippy::float_cmp)]
372
    async fn extract_raster_values_two_raster_timesteps() {
1✔
373
        let raster_tile_a = RasterTile2D::<u8>::new_with_tile_info(
1✔
374
            TimeInterval::new(0, 10).unwrap(),
1✔
375
            TileInformation {
1✔
376
                global_geo_transform: TestDefault::test_default(),
1✔
377
                global_tile_position: [0, 0].into(),
1✔
378
                tile_size_in_pixels: [3, 2].into(),
1✔
379
            },
1✔
380
            0,
1✔
381
            Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1])
1✔
382
                .unwrap()
1✔
383
                .into(),
1✔
384
            CacheHint::default(),
1✔
385
        );
1✔
386
        let raster_tile_b = RasterTile2D::new_with_tile_info(
1✔
387
            TimeInterval::new(10, 20).unwrap(),
1✔
388
            TileInformation {
1✔
389
                global_geo_transform: TestDefault::test_default(),
1✔
390
                global_tile_position: [0, 0].into(),
1✔
391
                tile_size_in_pixels: [3, 2].into(),
1✔
392
            },
1✔
393
            0,
1✔
394
            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
1✔
395
                .unwrap()
1✔
396
                .into(),
1✔
397
            CacheHint::default(),
1✔
398
        );
1✔
399

1✔
400
        let raster_source = MockRasterSource {
1✔
401
            params: MockRasterSourceParams {
1✔
402
                data: vec![raster_tile_a, raster_tile_b],
1✔
403
                result_descriptor: RasterResultDescriptor {
1✔
404
                    data_type: RasterDataType::U8,
1✔
405
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
406
                    time: None,
1✔
407
                    bbox: None,
1✔
408
                    resolution: None,
1✔
409
                    bands: RasterBandDescriptors::new_single_band(),
1✔
410
                },
1✔
411
            },
1✔
412
        }
1✔
413
        .boxed();
1✔
414

1✔
415
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
416
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
1✔
417
        );
1✔
418

419
        let raster_source = raster_source
1✔
420
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
421
            .await
×
422
            .unwrap();
1✔
423

1✔
424
        let points = MultiPointCollection::from_data(
1✔
425
            MultiPoint::many(vec![
1✔
426
                (0.0, 0.0),
1✔
427
                (1.0, 0.0),
1✔
428
                (0.0, -1.0),
1✔
429
                (1.0, -1.0),
1✔
430
                (0.0, -2.0),
1✔
431
                (1.0, -2.0),
1✔
432
            ])
1✔
433
            .unwrap(),
1✔
434
            vec![TimeInterval::default(); 6],
1✔
435
            Default::default(),
1✔
436
            CacheHint::default(),
1✔
437
        )
1✔
438
        .unwrap();
1✔
439

440
        let result = RasterVectorAggregateJoinProcessor::extract_raster_values(
1✔
441
            &points,
1✔
442
            &raster_source.query_processor().unwrap().get_u8().unwrap(),
1✔
443
            "foo",
1✔
444
            FeatureAggregationMethod::First,
1✔
445
            false,
1✔
446
            TemporalAggregationMethod::Mean,
1✔
447
            false,
1✔
448
            VectorQueryRectangle {
1✔
449
                spatial_bounds: BoundingBox2D::new((0.0, -3.0).into(), (2.0, 0.0).into()).unwrap(),
1✔
450
                time_interval: Default::default(),
1✔
451
                spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
452
                attributes: ColumnSelection::all(),
1✔
453
            },
1✔
454
            &MockQueryContext::new(ChunkByteSize::MIN),
1✔
455
        )
1✔
456
        .await
×
457
        .unwrap();
1✔
458

459
        if let FeatureDataRef::Float(extracted_data) = result.data("foo").unwrap() {
1✔
460
            assert_eq!(extracted_data.as_ref(), &[3.5, 3.5, 3.5, 3.5, 3.5, 3.5]);
1✔
461
        } else {
462
            unreachable!();
×
463
        }
464
    }
465

466
    #[tokio::test]
1✔
467
    #[allow(clippy::float_cmp, clippy::too_many_lines)]
468
    async fn extract_raster_values_two_spatial_tiles_per_time_step() {
1✔
469
        let raster_tile_a_0 = RasterTile2D::<u8>::new_with_tile_info(
1✔
470
            TimeInterval::new(0, 10).unwrap(),
1✔
471
            TileInformation {
1✔
472
                global_geo_transform: TestDefault::test_default(),
1✔
473
                global_tile_position: [0, 0].into(),
1✔
474
                tile_size_in_pixels: [3, 2].into(),
1✔
475
            },
1✔
476
            0,
1✔
477
            Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1])
1✔
478
                .unwrap()
1✔
479
                .into(),
1✔
480
            CacheHint::default(),
1✔
481
        );
1✔
482
        let raster_tile_a_1 = RasterTile2D::new_with_tile_info(
1✔
483
            TimeInterval::new(0, 10).unwrap(),
1✔
484
            TileInformation {
1✔
485
                global_geo_transform: TestDefault::test_default(),
1✔
486
                global_tile_position: [0, 1].into(),
1✔
487
                tile_size_in_pixels: [3, 2].into(),
1✔
488
            },
1✔
489
            0,
1✔
490
            Grid2D::new([3, 2].into(), vec![60, 50, 40, 30, 20, 10])
1✔
491
                .unwrap()
1✔
492
                .into(),
1✔
493
            CacheHint::default(),
1✔
494
        );
1✔
495
        let raster_tile_b_0 = RasterTile2D::new_with_tile_info(
1✔
496
            TimeInterval::new(10, 20).unwrap(),
1✔
497
            TileInformation {
1✔
498
                global_geo_transform: TestDefault::test_default(),
1✔
499
                global_tile_position: [0, 0].into(),
1✔
500
                tile_size_in_pixels: [3, 2].into(),
1✔
501
            },
1✔
502
            0,
1✔
503
            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
1✔
504
                .unwrap()
1✔
505
                .into(),
1✔
506
            CacheHint::default(),
1✔
507
        );
1✔
508
        let raster_tile_b_1 = RasterTile2D::new_with_tile_info(
1✔
509
            TimeInterval::new(10, 20).unwrap(),
1✔
510
            TileInformation {
1✔
511
                global_geo_transform: TestDefault::test_default(),
1✔
512
                global_tile_position: [0, 1].into(),
1✔
513
                tile_size_in_pixels: [3, 2].into(),
1✔
514
            },
1✔
515
            0,
1✔
516
            Grid2D::new([3, 2].into(), vec![10, 20, 30, 40, 50, 60])
1✔
517
                .unwrap()
1✔
518
                .into(),
1✔
519
            CacheHint::default(),
1✔
520
        );
1✔
521

1✔
522
        let raster_source = MockRasterSource {
1✔
523
            params: MockRasterSourceParams {
1✔
524
                data: vec![
1✔
525
                    raster_tile_a_0,
1✔
526
                    raster_tile_a_1,
1✔
527
                    raster_tile_b_0,
1✔
528
                    raster_tile_b_1,
1✔
529
                ],
1✔
530
                result_descriptor: RasterResultDescriptor {
1✔
531
                    data_type: RasterDataType::U8,
1✔
532
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
533
                    time: None,
1✔
534
                    bbox: None,
1✔
535
                    resolution: None,
1✔
536
                    bands: RasterBandDescriptors::new_single_band(),
1✔
537
                },
1✔
538
            },
1✔
539
        }
1✔
540
        .boxed();
1✔
541

1✔
542
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
543
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
1✔
544
        );
1✔
545

546
        let raster_source = raster_source
1✔
547
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
548
            .await
×
549
            .unwrap();
1✔
550

1✔
551
        let points = MultiPointCollection::from_data(
1✔
552
            MultiPoint::many(vec![
1✔
553
                vec![(0.0, 0.0), (2.0, 0.0)],
1✔
554
                vec![(1.0, 0.0), (3.0, 0.0)],
1✔
555
            ])
1✔
556
            .unwrap(),
1✔
557
            vec![TimeInterval::default(); 2],
1✔
558
            Default::default(),
1✔
559
            CacheHint::default(),
1✔
560
        )
1✔
561
        .unwrap();
1✔
562

563
        let result = RasterVectorAggregateJoinProcessor::extract_raster_values(
1✔
564
            &points,
1✔
565
            &raster_source.query_processor().unwrap().get_u8().unwrap(),
1✔
566
            "foo",
1✔
567
            FeatureAggregationMethod::Mean,
1✔
568
            false,
1✔
569
            TemporalAggregationMethod::Mean,
1✔
570
            false,
1✔
571
            VectorQueryRectangle {
1✔
572
                spatial_bounds: BoundingBox2D::new((0.0, -3.0).into(), (4.0, 0.0).into()).unwrap(),
1✔
573
                time_interval: Default::default(),
1✔
574
                spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
575
                attributes: ColumnSelection::all(),
1✔
576
            },
1✔
577
            &MockQueryContext::new(ChunkByteSize::MIN),
1✔
578
        )
1✔
579
        .await
×
580
        .unwrap();
1✔
581

582
        if let FeatureDataRef::Float(extracted_data) = result.data("foo").unwrap() {
1✔
583
            assert_eq!(
1✔
584
                extracted_data.as_ref(),
1✔
585
                &[(6. + 60. + 1. + 10.) / 4., (5. + 50. + 2. + 20.) / 4.]
1✔
586
            );
1✔
587
        } else {
588
            unreachable!();
×
589
        }
590
    }
591

592
    #[tokio::test]
1✔
593
    #[allow(clippy::too_many_lines)]
594
    #[allow(clippy::float_cmp)]
595
    async fn polygons() {
1✔
596
        let raster_tile_a_0 = RasterTile2D::<u8>::new_with_tile_info(
1✔
597
            TimeInterval::new(0, 10).unwrap(),
1✔
598
            TileInformation {
1✔
599
                global_geo_transform: TestDefault::test_default(),
1✔
600
                global_tile_position: [0, 0].into(),
1✔
601
                tile_size_in_pixels: [3, 2].into(),
1✔
602
            },
1✔
603
            0,
1✔
604
            Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1])
1✔
605
                .unwrap()
1✔
606
                .into(),
1✔
607
            CacheHint::default(),
1✔
608
        );
1✔
609
        let raster_tile_a_1 = RasterTile2D::new_with_tile_info(
1✔
610
            TimeInterval::new(0, 10).unwrap(),
1✔
611
            TileInformation {
1✔
612
                global_geo_transform: TestDefault::test_default(),
1✔
613
                global_tile_position: [0, 1].into(),
1✔
614
                tile_size_in_pixels: [3, 2].into(),
1✔
615
            },
1✔
616
            0,
1✔
617
            Grid2D::new([3, 2].into(), vec![60, 50, 40, 30, 20, 10])
1✔
618
                .unwrap()
1✔
619
                .into(),
1✔
620
            CacheHint::default(),
1✔
621
        );
1✔
622
        let raster_tile_a_2 = RasterTile2D::new_with_tile_info(
1✔
623
            TimeInterval::new(0, 10).unwrap(),
1✔
624
            TileInformation {
1✔
625
                global_geo_transform: TestDefault::test_default(),
1✔
626
                global_tile_position: [0, 2].into(),
1✔
627
                tile_size_in_pixels: [3, 2].into(),
1✔
628
            },
1✔
629
            0,
1✔
630
            Grid2D::new([3, 2].into(), vec![160, 150, 140, 130, 120, 110])
1✔
631
                .unwrap()
1✔
632
                .into(),
1✔
633
            CacheHint::default(),
1✔
634
        );
1✔
635
        let raster_tile_b_0 = RasterTile2D::new_with_tile_info(
1✔
636
            TimeInterval::new(10, 20).unwrap(),
1✔
637
            TileInformation {
1✔
638
                global_geo_transform: TestDefault::test_default(),
1✔
639
                global_tile_position: [0, 0].into(),
1✔
640
                tile_size_in_pixels: [3, 2].into(),
1✔
641
            },
1✔
642
            0,
1✔
643
            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
1✔
644
                .unwrap()
1✔
645
                .into(),
1✔
646
            CacheHint::default(),
1✔
647
        );
1✔
648
        let raster_tile_b_1 = RasterTile2D::new_with_tile_info(
1✔
649
            TimeInterval::new(10, 20).unwrap(),
1✔
650
            TileInformation {
1✔
651
                global_geo_transform: TestDefault::test_default(),
1✔
652
                global_tile_position: [0, 1].into(),
1✔
653
                tile_size_in_pixels: [3, 2].into(),
1✔
654
            },
1✔
655
            0,
1✔
656
            Grid2D::new([3, 2].into(), vec![10, 20, 30, 40, 50, 60])
1✔
657
                .unwrap()
1✔
658
                .into(),
1✔
659
            CacheHint::default(),
1✔
660
        );
1✔
661
        let raster_tile_b_2 = RasterTile2D::new_with_tile_info(
1✔
662
            TimeInterval::new(10, 20).unwrap(),
1✔
663
            TileInformation {
1✔
664
                global_geo_transform: TestDefault::test_default(),
1✔
665
                global_tile_position: [0, 2].into(),
1✔
666
                tile_size_in_pixels: [3, 2].into(),
1✔
667
            },
1✔
668
            0,
1✔
669
            Grid2D::new([3, 2].into(), vec![110, 120, 130, 140, 150, 160])
1✔
670
                .unwrap()
1✔
671
                .into(),
1✔
672
            CacheHint::default(),
1✔
673
        );
1✔
674

1✔
675
        let raster_source = MockRasterSource {
1✔
676
            params: MockRasterSourceParams {
1✔
677
                data: vec![
1✔
678
                    raster_tile_a_0,
1✔
679
                    raster_tile_a_1,
1✔
680
                    raster_tile_a_2,
1✔
681
                    raster_tile_b_0,
1✔
682
                    raster_tile_b_1,
1✔
683
                    raster_tile_b_2,
1✔
684
                ],
1✔
685
                result_descriptor: RasterResultDescriptor {
1✔
686
                    data_type: RasterDataType::U8,
1✔
687
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
688
                    time: None,
1✔
689
                    bbox: None,
1✔
690
                    resolution: None,
1✔
691
                    bands: RasterBandDescriptors::new_single_band(),
1✔
692
                },
1✔
693
            },
1✔
694
        }
1✔
695
        .boxed();
1✔
696

1✔
697
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
698
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
1✔
699
        );
1✔
700

701
        let raster_source = raster_source
1✔
702
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
703
            .await
×
704
            .unwrap();
1✔
705

1✔
706
        let polygons = MultiPolygonCollection::from_data(
1✔
707
            vec![MultiPolygon::new(vec![vec![vec![
1✔
708
                (0.5, -0.5).into(),
1✔
709
                (4., -1.).into(),
1✔
710
                (0.5, -2.5).into(),
1✔
711
                (0.5, -0.5).into(),
1✔
712
            ]]])
1✔
713
            .unwrap()],
1✔
714
            vec![TimeInterval::default(); 1],
1✔
715
            Default::default(),
1✔
716
            CacheHint::default(),
1✔
717
        )
1✔
718
        .unwrap();
1✔
719

720
        let result = RasterVectorAggregateJoinProcessor::extract_raster_values(
1✔
721
            &polygons,
1✔
722
            &raster_source.query_processor().unwrap().get_u8().unwrap(),
1✔
723
            "foo",
1✔
724
            FeatureAggregationMethod::Mean,
1✔
725
            false,
1✔
726
            TemporalAggregationMethod::Mean,
1✔
727
            false,
1✔
728
            VectorQueryRectangle {
1✔
729
                spatial_bounds: BoundingBox2D::new((0.0, -3.0).into(), (4.0, 0.0).into()).unwrap(),
1✔
730
                time_interval: Default::default(),
1✔
731
                spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
732
                attributes: ColumnSelection::all(),
1✔
733
            },
1✔
734
            &MockQueryContext::new(ChunkByteSize::MIN),
1✔
735
        )
1✔
736
        .await
×
737
        .unwrap();
1✔
738

739
        if let FeatureDataRef::Float(extracted_data) = result.data("foo").unwrap() {
1✔
740
            assert_eq!(
1✔
741
                extracted_data.as_ref(),
1✔
742
                &[(3. + 1. + 40. + 30. + 140. + 4. + 6. + 30. + 40. + 130.) / 10.]
1✔
743
            );
1✔
744
        } else {
745
            unreachable!();
×
746
        }
747
    }
748
}
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