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

geo-engine / geoengine / 5821755405

10 Aug 2023 12:30PM UTC coverage: 89.599% (+0.2%) from 89.424%
5821755405

push

github

web-flow
Merge pull request #850 from geo-engine/layer_workflows

change type of workflow column in layers table

60 of 60 new or added lines in 2 files covered. (100.0%)

103887 of 115947 relevant lines covered (89.6%)

62444.42 hits per line

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

97.15
/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::CacheHint;
8
use geoengine_datatypes::raster::{GridIndexAccess, Pixel, RasterDataType};
9
use geoengine_datatypes::util::arrow::ArrowTyped;
10

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

24
use super::util::{CoveredPixels, FeatureTimeSpanIter, PixelCoverCreator};
25
use super::{create_feature_aggregator, FeatureAggregationMethod};
26

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

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

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

80
        let collection = collection.sort_by_time_asc()?;
8✔
81

82
        let covered_pixels = collection.create_covered_pixels();
8✔
83

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

8✔
86
        let mut cache_hint = CacheHint::max_duration();
8✔
87

88
        for time_span in FeatureTimeSpanIter::new(collection.time_intervals()) {
8✔
89
            let query = VectorQueryRectangle {
8✔
90
                spatial_bounds: query.spatial_bounds,
8✔
91
                time_interval: time_span.time_interval,
8✔
92
                spatial_resolution: query.spatial_resolution,
8✔
93
            };
8✔
94

95
            let mut rasters = raster_processor.raster_query(query.into(), ctx).await?;
8✔
96

97
            // TODO: optimize geo access (only specific tiles, etc.)
98

99
            let mut feature_aggregator = create_feature_aggregator::<P>(
8✔
100
                collection.len(),
8✔
101
                feature_aggreation,
8✔
102
                feature_aggregation_ignore_no_data,
8✔
103
            );
8✔
104

8✔
105
            let mut time_end = None;
8✔
106

107
            while let Some(raster) = rasters.next().await {
200✔
108
                let raster = raster?;
194✔
109

110
                if let Some(end) = time_end {
194✔
111
                    if end != raster.time.end() {
186✔
112
                        // new time slice => consume old aggregator and create new one
113
                        temporal_aggregator.add_feature_data(
6✔
114
                            feature_aggregator.into_data(),
6✔
115
                            time_span.time_interval.duration_ms(), // TODO: use individual feature duration?
6✔
116
                        )?;
6✔
117

118
                        feature_aggregator = create_feature_aggregator::<P>(
6✔
119
                            collection.len(),
6✔
120
                            feature_aggreation,
6✔
121
                            feature_aggregation_ignore_no_data,
6✔
122
                        );
6✔
123

6✔
124
                        if temporal_aggregator.is_satisfied() {
6✔
125
                            break;
2✔
126
                        }
4✔
127
                    }
180✔
128
                }
8✔
129
                time_end = Some(raster.time.end());
192✔
130

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

145
                            if feature_aggregator.is_satisfied() {
53✔
146
                                satisfied = true;
8✔
147
                                break;
8✔
148
                            }
45✔
149
                        }
×
150
                    }
151

152
                    if satisfied {
648✔
153
                        break;
8✔
154
                    }
640✔
155
                }
156

157
                cache_hint.merge_with(&raster.cache_hint);
192✔
158
            }
159

160
            temporal_aggregator.add_feature_data(
8✔
161
                feature_aggregator.into_data(),
8✔
162
                time_span.time_interval.duration_ms(), // TODO: use individual feature duration?
8✔
163
            )?;
8✔
164

165
            if temporal_aggregator.is_satisfied() {
8✔
166
                break;
4✔
167
            }
4✔
168
        }
169

170
        let mut new_collection = collection
8✔
171
            .add_column(new_column_name, temporal_aggregator.into_data())
8✔
172
            .map_err(Into::<crate::error::Error>::into)?;
8✔
173

174
        new_collection.cache_hint = cache_hint;
8✔
175

8✔
176
        Ok(new_collection)
8✔
177
    }
8✔
178

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

210
#[async_trait]
211
impl<G> QueryProcessor for RasterVectorAggregateJoinProcessor<G>
212
where
213
    G: Geometry + ArrowTyped + 'static,
214
    FeatureCollection<G>: PixelCoverCreator<G>,
215
{
216
    type Output = FeatureCollection<G>;
217
    type SpatialBounds = BoundingBox2D;
218

219
    async fn _query<'a>(
4✔
220
        &'a self,
4✔
221
        query: VectorQueryRectangle,
4✔
222
        ctx: &'a dyn QueryContext,
4✔
223
    ) -> Result<BoxStream<'a, Result<Self::Output>>> {
4✔
224
        let stream = self
4✔
225
            .collection
4✔
226
            .query(query, ctx)
4✔
227
            .await?
×
228
            .and_then(move |mut collection| async move {
4✔
229
                for (raster, new_column_name) in
4✔
230
                    self.raster_processors.iter().zip(&self.column_names)
4✔
231
                {
4✔
232
                    collection = call_on_generic_raster_processor!(raster, raster => {
4✔
233
                        Self::extract_raster_values(
234
                            &collection,
4✔
235
                            raster,
4✔
236
                            new_column_name,
4✔
237
                            self.feature_aggregation,
4✔
238
                            self.feature_aggregation_ignore_no_data,
4✔
239
                            self.temporal_aggregation,
4✔
240
                            self.temporal_aggregation_ignore_no_data,
4✔
241
                            query,
4✔
242
                            ctx
4✔
243
                        ).await?
116✔
244
                    });
245
                }
246

247
                Ok(collection)
4✔
248
            })
4✔
249
            .boxed();
4✔
250

4✔
251
        Ok(stream)
4✔
252
    }
8✔
253
}
254

255
#[cfg(test)]
256
mod tests {
257
    use super::*;
258

259
    use crate::engine::{
260
        ChunkByteSize, MockExecutionContext, RasterResultDescriptor, WorkflowOperatorPath,
261
    };
262
    use crate::engine::{MockQueryContext, RasterOperator};
263
    use crate::mock::{MockRasterSource, MockRasterSourceParams};
264
    use geoengine_datatypes::collections::{MultiPointCollection, MultiPolygonCollection};
265
    use geoengine_datatypes::primitives::CacheHint;
266
    use geoengine_datatypes::primitives::MultiPolygon;
267
    use geoengine_datatypes::raster::{Grid2D, RasterTile2D, TileInformation};
268
    use geoengine_datatypes::spatial_reference::SpatialReference;
269
    use geoengine_datatypes::util::test::TestDefault;
270
    use geoengine_datatypes::{
271
        primitives::{
272
            BoundingBox2D, FeatureDataRef, Measurement, MultiPoint, SpatialResolution, TimeInterval,
273
        },
274
        raster::TilingSpecification,
275
    };
276

277
    #[tokio::test]
1✔
278
    async fn extract_raster_values_single_raster() {
1✔
279
        let raster_tile = RasterTile2D::<u8>::new_with_tile_info(
1✔
280
            TimeInterval::default(),
1✔
281
            TileInformation {
1✔
282
                global_geo_transform: TestDefault::test_default(),
1✔
283
                global_tile_position: [0, 0].into(),
1✔
284
                tile_size_in_pixels: [3, 2].into(),
1✔
285
            },
1✔
286
            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
1✔
287
                .unwrap()
1✔
288
                .into(),
1✔
289
            CacheHint::default(),
1✔
290
        );
1✔
291

1✔
292
        let raster_source = MockRasterSource {
1✔
293
            params: MockRasterSourceParams {
1✔
294
                data: vec![raster_tile],
1✔
295
                result_descriptor: RasterResultDescriptor {
1✔
296
                    data_type: RasterDataType::U8,
1✔
297
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
298
                    measurement: Measurement::Unitless,
1✔
299
                    time: None,
1✔
300
                    bbox: None,
1✔
301
                    resolution: None,
1✔
302
                },
1✔
303
            },
1✔
304
        }
1✔
305
        .boxed();
1✔
306

1✔
307
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
308
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
1✔
309
        );
1✔
310

311
        let raster_source = raster_source
1✔
312
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
313
            .await
×
314
            .unwrap();
1✔
315

1✔
316
        let points = MultiPointCollection::from_data(
1✔
317
            MultiPoint::many(vec![
1✔
318
                (0.0, 0.0),
1✔
319
                (1.0, 0.0),
1✔
320
                (0.0, -1.0),
1✔
321
                (1.0, -1.0),
1✔
322
                (0.0, -2.0),
1✔
323
                (1.0, -2.0),
1✔
324
            ])
1✔
325
            .unwrap(),
1✔
326
            vec![TimeInterval::default(); 6],
1✔
327
            Default::default(),
1✔
328
            CacheHint::default(),
1✔
329
        )
1✔
330
        .unwrap();
1✔
331

332
        let result = RasterVectorAggregateJoinProcessor::extract_raster_values(
1✔
333
            &points,
1✔
334
            &raster_source.query_processor().unwrap().get_u8().unwrap(),
1✔
335
            "foo",
1✔
336
            FeatureAggregationMethod::First,
1✔
337
            false,
1✔
338
            TemporalAggregationMethod::First,
1✔
339
            false,
1✔
340
            VectorQueryRectangle {
1✔
341
                spatial_bounds: BoundingBox2D::new((0.0, -3.0).into(), (2.0, 0.).into()).unwrap(),
1✔
342
                time_interval: Default::default(),
1✔
343
                spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
344
            },
1✔
345
            &MockQueryContext::new(ChunkByteSize::MIN),
1✔
346
        )
1✔
347
        .await
×
348
        .unwrap();
1✔
349

350
        if let FeatureDataRef::Int(extracted_data) = result.data("foo").unwrap() {
1✔
351
            assert_eq!(extracted_data.as_ref(), &[1, 2, 3, 4, 5, 6]);
1✔
352
        } else {
353
            unreachable!();
×
354
        }
355
    }
356

357
    #[tokio::test]
1✔
358
    #[allow(clippy::float_cmp)]
359
    async fn extract_raster_values_two_raster_timesteps() {
1✔
360
        let raster_tile_a = RasterTile2D::<u8>::new_with_tile_info(
1✔
361
            TimeInterval::new(0, 10).unwrap(),
1✔
362
            TileInformation {
1✔
363
                global_geo_transform: TestDefault::test_default(),
1✔
364
                global_tile_position: [0, 0].into(),
1✔
365
                tile_size_in_pixels: [3, 2].into(),
1✔
366
            },
1✔
367
            Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1])
1✔
368
                .unwrap()
1✔
369
                .into(),
1✔
370
            CacheHint::default(),
1✔
371
        );
1✔
372
        let raster_tile_b = RasterTile2D::new_with_tile_info(
1✔
373
            TimeInterval::new(10, 20).unwrap(),
1✔
374
            TileInformation {
1✔
375
                global_geo_transform: TestDefault::test_default(),
1✔
376
                global_tile_position: [0, 0].into(),
1✔
377
                tile_size_in_pixels: [3, 2].into(),
1✔
378
            },
1✔
379
            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
1✔
380
                .unwrap()
1✔
381
                .into(),
1✔
382
            CacheHint::default(),
1✔
383
        );
1✔
384

1✔
385
        let raster_source = MockRasterSource {
1✔
386
            params: MockRasterSourceParams {
1✔
387
                data: vec![raster_tile_a, raster_tile_b],
1✔
388
                result_descriptor: RasterResultDescriptor {
1✔
389
                    data_type: RasterDataType::U8,
1✔
390
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
391
                    measurement: Measurement::Unitless,
1✔
392
                    time: None,
1✔
393
                    bbox: None,
1✔
394
                    resolution: None,
1✔
395
                },
1✔
396
            },
1✔
397
        }
1✔
398
        .boxed();
1✔
399

1✔
400
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
401
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
1✔
402
        );
1✔
403

404
        let raster_source = raster_source
1✔
405
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
406
            .await
×
407
            .unwrap();
1✔
408

1✔
409
        let points = MultiPointCollection::from_data(
1✔
410
            MultiPoint::many(vec![
1✔
411
                (0.0, 0.0),
1✔
412
                (1.0, 0.0),
1✔
413
                (0.0, -1.0),
1✔
414
                (1.0, -1.0),
1✔
415
                (0.0, -2.0),
1✔
416
                (1.0, -2.0),
1✔
417
            ])
1✔
418
            .unwrap(),
1✔
419
            vec![TimeInterval::default(); 6],
1✔
420
            Default::default(),
1✔
421
            CacheHint::default(),
1✔
422
        )
1✔
423
        .unwrap();
1✔
424

425
        let result = RasterVectorAggregateJoinProcessor::extract_raster_values(
1✔
426
            &points,
1✔
427
            &raster_source.query_processor().unwrap().get_u8().unwrap(),
1✔
428
            "foo",
1✔
429
            FeatureAggregationMethod::First,
1✔
430
            false,
1✔
431
            TemporalAggregationMethod::Mean,
1✔
432
            false,
1✔
433
            VectorQueryRectangle {
1✔
434
                spatial_bounds: BoundingBox2D::new((0.0, -3.0).into(), (2.0, 0.0).into()).unwrap(),
1✔
435
                time_interval: Default::default(),
1✔
436
                spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
437
            },
1✔
438
            &MockQueryContext::new(ChunkByteSize::MIN),
1✔
439
        )
1✔
440
        .await
×
441
        .unwrap();
1✔
442

443
        if let FeatureDataRef::Float(extracted_data) = result.data("foo").unwrap() {
1✔
444
            assert_eq!(extracted_data.as_ref(), &[3.5, 3.5, 3.5, 3.5, 3.5, 3.5]);
1✔
445
        } else {
446
            unreachable!();
×
447
        }
448
    }
449

450
    #[tokio::test]
1✔
451
    #[allow(clippy::float_cmp, clippy::too_many_lines)]
452
    async fn extract_raster_values_two_spatial_tiles_per_time_step() {
1✔
453
        let raster_tile_a_0 = RasterTile2D::<u8>::new_with_tile_info(
1✔
454
            TimeInterval::new(0, 10).unwrap(),
1✔
455
            TileInformation {
1✔
456
                global_geo_transform: TestDefault::test_default(),
1✔
457
                global_tile_position: [0, 0].into(),
1✔
458
                tile_size_in_pixels: [3, 2].into(),
1✔
459
            },
1✔
460
            Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1])
1✔
461
                .unwrap()
1✔
462
                .into(),
1✔
463
            CacheHint::default(),
1✔
464
        );
1✔
465
        let raster_tile_a_1 = RasterTile2D::new_with_tile_info(
1✔
466
            TimeInterval::new(0, 10).unwrap(),
1✔
467
            TileInformation {
1✔
468
                global_geo_transform: TestDefault::test_default(),
1✔
469
                global_tile_position: [0, 1].into(),
1✔
470
                tile_size_in_pixels: [3, 2].into(),
1✔
471
            },
1✔
472
            Grid2D::new([3, 2].into(), vec![60, 50, 40, 30, 20, 10])
1✔
473
                .unwrap()
1✔
474
                .into(),
1✔
475
            CacheHint::default(),
1✔
476
        );
1✔
477
        let raster_tile_b_0 = RasterTile2D::new_with_tile_info(
1✔
478
            TimeInterval::new(10, 20).unwrap(),
1✔
479
            TileInformation {
1✔
480
                global_geo_transform: TestDefault::test_default(),
1✔
481
                global_tile_position: [0, 0].into(),
1✔
482
                tile_size_in_pixels: [3, 2].into(),
1✔
483
            },
1✔
484
            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
1✔
485
                .unwrap()
1✔
486
                .into(),
1✔
487
            CacheHint::default(),
1✔
488
        );
1✔
489
        let raster_tile_b_1 = RasterTile2D::new_with_tile_info(
1✔
490
            TimeInterval::new(10, 20).unwrap(),
1✔
491
            TileInformation {
1✔
492
                global_geo_transform: TestDefault::test_default(),
1✔
493
                global_tile_position: [0, 1].into(),
1✔
494
                tile_size_in_pixels: [3, 2].into(),
1✔
495
            },
1✔
496
            Grid2D::new([3, 2].into(), vec![10, 20, 30, 40, 50, 60])
1✔
497
                .unwrap()
1✔
498
                .into(),
1✔
499
            CacheHint::default(),
1✔
500
        );
1✔
501

1✔
502
        let raster_source = MockRasterSource {
1✔
503
            params: MockRasterSourceParams {
1✔
504
                data: vec![
1✔
505
                    raster_tile_a_0,
1✔
506
                    raster_tile_a_1,
1✔
507
                    raster_tile_b_0,
1✔
508
                    raster_tile_b_1,
1✔
509
                ],
1✔
510
                result_descriptor: RasterResultDescriptor {
1✔
511
                    data_type: RasterDataType::U8,
1✔
512
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
513
                    measurement: Measurement::Unitless,
1✔
514
                    time: None,
1✔
515
                    bbox: None,
1✔
516
                    resolution: None,
1✔
517
                },
1✔
518
            },
1✔
519
        }
1✔
520
        .boxed();
1✔
521

1✔
522
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
523
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
1✔
524
        );
1✔
525

526
        let raster_source = raster_source
1✔
527
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
528
            .await
×
529
            .unwrap();
1✔
530

1✔
531
        let points = MultiPointCollection::from_data(
1✔
532
            MultiPoint::many(vec![
1✔
533
                vec![(0.0, 0.0), (2.0, 0.0)],
1✔
534
                vec![(1.0, 0.0), (3.0, 0.0)],
1✔
535
            ])
1✔
536
            .unwrap(),
1✔
537
            vec![TimeInterval::default(); 2],
1✔
538
            Default::default(),
1✔
539
            CacheHint::default(),
1✔
540
        )
1✔
541
        .unwrap();
1✔
542

543
        let result = RasterVectorAggregateJoinProcessor::extract_raster_values(
1✔
544
            &points,
1✔
545
            &raster_source.query_processor().unwrap().get_u8().unwrap(),
1✔
546
            "foo",
1✔
547
            FeatureAggregationMethod::Mean,
1✔
548
            false,
1✔
549
            TemporalAggregationMethod::Mean,
1✔
550
            false,
1✔
551
            VectorQueryRectangle {
1✔
552
                spatial_bounds: BoundingBox2D::new((0.0, -3.0).into(), (4.0, 0.0).into()).unwrap(),
1✔
553
                time_interval: Default::default(),
1✔
554
                spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
555
            },
1✔
556
            &MockQueryContext::new(ChunkByteSize::MIN),
1✔
557
        )
1✔
558
        .await
×
559
        .unwrap();
1✔
560

561
        if let FeatureDataRef::Float(extracted_data) = result.data("foo").unwrap() {
1✔
562
            assert_eq!(
1✔
563
                extracted_data.as_ref(),
1✔
564
                &[(6. + 60. + 1. + 10.) / 4., (5. + 50. + 2. + 20.) / 4.]
1✔
565
            );
1✔
566
        } else {
567
            unreachable!();
×
568
        }
569
    }
570

571
    #[tokio::test]
1✔
572
    #[allow(clippy::too_many_lines)]
573
    #[allow(clippy::float_cmp)]
574
    async fn polygons() {
1✔
575
        let raster_tile_a_0 = RasterTile2D::<u8>::new_with_tile_info(
1✔
576
            TimeInterval::new(0, 10).unwrap(),
1✔
577
            TileInformation {
1✔
578
                global_geo_transform: TestDefault::test_default(),
1✔
579
                global_tile_position: [0, 0].into(),
1✔
580
                tile_size_in_pixels: [3, 2].into(),
1✔
581
            },
1✔
582
            Grid2D::new([3, 2].into(), vec![6, 5, 4, 3, 2, 1])
1✔
583
                .unwrap()
1✔
584
                .into(),
1✔
585
            CacheHint::default(),
1✔
586
        );
1✔
587
        let raster_tile_a_1 = RasterTile2D::new_with_tile_info(
1✔
588
            TimeInterval::new(0, 10).unwrap(),
1✔
589
            TileInformation {
1✔
590
                global_geo_transform: TestDefault::test_default(),
1✔
591
                global_tile_position: [0, 1].into(),
1✔
592
                tile_size_in_pixels: [3, 2].into(),
1✔
593
            },
1✔
594
            Grid2D::new([3, 2].into(), vec![60, 50, 40, 30, 20, 10])
1✔
595
                .unwrap()
1✔
596
                .into(),
1✔
597
            CacheHint::default(),
1✔
598
        );
1✔
599
        let raster_tile_a_2 = RasterTile2D::new_with_tile_info(
1✔
600
            TimeInterval::new(0, 10).unwrap(),
1✔
601
            TileInformation {
1✔
602
                global_geo_transform: TestDefault::test_default(),
1✔
603
                global_tile_position: [0, 2].into(),
1✔
604
                tile_size_in_pixels: [3, 2].into(),
1✔
605
            },
1✔
606
            Grid2D::new([3, 2].into(), vec![160, 150, 140, 130, 120, 110])
1✔
607
                .unwrap()
1✔
608
                .into(),
1✔
609
            CacheHint::default(),
1✔
610
        );
1✔
611
        let raster_tile_b_0 = RasterTile2D::new_with_tile_info(
1✔
612
            TimeInterval::new(10, 20).unwrap(),
1✔
613
            TileInformation {
1✔
614
                global_geo_transform: TestDefault::test_default(),
1✔
615
                global_tile_position: [0, 0].into(),
1✔
616
                tile_size_in_pixels: [3, 2].into(),
1✔
617
            },
1✔
618
            Grid2D::new([3, 2].into(), vec![1, 2, 3, 4, 5, 6])
1✔
619
                .unwrap()
1✔
620
                .into(),
1✔
621
            CacheHint::default(),
1✔
622
        );
1✔
623
        let raster_tile_b_1 = RasterTile2D::new_with_tile_info(
1✔
624
            TimeInterval::new(10, 20).unwrap(),
1✔
625
            TileInformation {
1✔
626
                global_geo_transform: TestDefault::test_default(),
1✔
627
                global_tile_position: [0, 1].into(),
1✔
628
                tile_size_in_pixels: [3, 2].into(),
1✔
629
            },
1✔
630
            Grid2D::new([3, 2].into(), vec![10, 20, 30, 40, 50, 60])
1✔
631
                .unwrap()
1✔
632
                .into(),
1✔
633
            CacheHint::default(),
1✔
634
        );
1✔
635
        let raster_tile_b_2 = 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, 2].into(),
1✔
640
                tile_size_in_pixels: [3, 2].into(),
1✔
641
            },
1✔
642
            Grid2D::new([3, 2].into(), vec![110, 120, 130, 140, 150, 160])
1✔
643
                .unwrap()
1✔
644
                .into(),
1✔
645
            CacheHint::default(),
1✔
646
        );
1✔
647

1✔
648
        let raster_source = MockRasterSource {
1✔
649
            params: MockRasterSourceParams {
1✔
650
                data: vec![
1✔
651
                    raster_tile_a_0,
1✔
652
                    raster_tile_a_1,
1✔
653
                    raster_tile_a_2,
1✔
654
                    raster_tile_b_0,
1✔
655
                    raster_tile_b_1,
1✔
656
                    raster_tile_b_2,
1✔
657
                ],
1✔
658
                result_descriptor: RasterResultDescriptor {
1✔
659
                    data_type: RasterDataType::U8,
1✔
660
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
661
                    measurement: Measurement::Unitless,
1✔
662
                    time: None,
1✔
663
                    bbox: None,
1✔
664
                    resolution: None,
1✔
665
                },
1✔
666
            },
1✔
667
        }
1✔
668
        .boxed();
1✔
669

1✔
670
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1✔
671
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
1✔
672
        );
1✔
673

674
        let raster_source = raster_source
1✔
675
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
676
            .await
×
677
            .unwrap();
1✔
678

1✔
679
        let polygons = MultiPolygonCollection::from_data(
1✔
680
            vec![MultiPolygon::new(vec![vec![vec![
1✔
681
                (0.5, -0.5).into(),
1✔
682
                (4., -1.).into(),
1✔
683
                (0.5, -2.5).into(),
1✔
684
                (0.5, -0.5).into(),
1✔
685
            ]]])
1✔
686
            .unwrap()],
1✔
687
            vec![TimeInterval::default(); 1],
1✔
688
            Default::default(),
1✔
689
            CacheHint::default(),
1✔
690
        )
1✔
691
        .unwrap();
1✔
692

693
        let result = RasterVectorAggregateJoinProcessor::extract_raster_values(
1✔
694
            &polygons,
1✔
695
            &raster_source.query_processor().unwrap().get_u8().unwrap(),
1✔
696
            "foo",
1✔
697
            FeatureAggregationMethod::Mean,
1✔
698
            false,
1✔
699
            TemporalAggregationMethod::Mean,
1✔
700
            false,
1✔
701
            VectorQueryRectangle {
1✔
702
                spatial_bounds: BoundingBox2D::new((0.0, -3.0).into(), (4.0, 0.0).into()).unwrap(),
1✔
703
                time_interval: Default::default(),
1✔
704
                spatial_resolution: SpatialResolution::new(1., 1.).unwrap(),
1✔
705
            },
1✔
706
            &MockQueryContext::new(ChunkByteSize::MIN),
1✔
707
        )
1✔
708
        .await
×
709
        .unwrap();
1✔
710

711
        if let FeatureDataRef::Float(extracted_data) = result.data("foo").unwrap() {
1✔
712
            assert_eq!(
1✔
713
                extracted_data.as_ref(),
1✔
714
                &[(3. + 1. + 40. + 30. + 140. + 4. + 6. + 30. + 40. + 130.) / 10.]
1✔
715
            );
1✔
716
        } else {
717
            unreachable!();
×
718
        }
719
    }
720
}
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