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

geo-engine / geoengine / 7187305629

12 Dec 2023 08:32PM UTC coverage: 89.812% (+0.1%) from 89.68%
7187305629

push

github

web-flow
Merge pull request #901 from geo-engine/raster_stacker_time

temporal alignment for raster stacker

2118 of 2160 new or added lines in 5 files covered. (98.06%)

7 existing lines in 4 files now uncovered.

115234 of 128306 relevant lines covered (89.81%)

58857.56 hits per line

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

93.99
/operators/src/processing/raster_stacker.rs
1
use crate::adapters::{QueryWrapper, RasterStackerAdapter, RasterStackerSource};
2
use crate::engine::{
3
    CanonicOperatorName, ExecutionContext, InitializedRasterOperator, InitializedSources,
4
    MultipleRasterSources, Operator, OperatorName, QueryContext, RasterBandDescriptors,
5
    RasterOperator, RasterQueryProcessor, RasterResultDescriptor, TypedRasterQueryProcessor,
6
    WorkflowOperatorPath,
7
};
8
use crate::error::{
9
    InvalidNumberOfRasterStackerInputs, RasterInputsMustHaveSameSpatialReferenceAndDatatype,
10
};
11
use crate::util::Result;
12
use async_trait::async_trait;
13
use futures::stream::BoxStream;
14
use geoengine_datatypes::primitives::{
15
    partitions_extent, time_interval_extent, BandSelection, RasterQueryRectangle, SpatialResolution,
16
};
17
use geoengine_datatypes::raster::{DynamicRasterDataType, Pixel, RasterTile2D};
18
use serde::{Deserialize, Serialize};
19
use snafu::ensure;
20

21
// TODO: IF this operator shall perform temporal alignment automatically: specify the alignment strategy here(?)
22
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5✔
23
pub struct RasterStackerParams {}
24

25
/// This `QueryProcessor` stacks all of it's inputs into a single raster time-series.
26
/// It does so by querying all of it's inputs outputting them by band, space and then time.
27
///
28
/// All inputs must have the same data type and spatial reference.
29
// TODO: temporal alignment or do that beforehand?
30
//     if we explicitly align beforehand using custom operators we have the problem that we need to hardcode the alignment params(?) and if the dataset changes the workflow no longer works
31
//      we have no way of aligning indepentently of each other before putting them into the `RasterStacker`` as we cannot access other operators in the workflow
32
pub type RasterStacker = Operator<RasterStackerParams, MultipleRasterSources>;
33

34
impl OperatorName for RasterStacker {
35
    const TYPE_NAME: &'static str = "RasterStacker";
36
}
37

38
#[typetag::serde]
×
39
#[async_trait]
40
impl RasterOperator for RasterStacker {
41
    async fn _initialize(
4✔
42
        self: Box<Self>,
4✔
43
        path: WorkflowOperatorPath,
4✔
44
        context: &dyn ExecutionContext,
4✔
45
    ) -> Result<Box<dyn InitializedRasterOperator>> {
4✔
46
        let name = CanonicOperatorName::from(&self);
4✔
47

48
        ensure!(
4✔
49
            !self.sources.rasters.is_empty() && self.sources.rasters.len() <= 8,
4✔
NEW
50
            InvalidNumberOfRasterStackerInputs
×
51
        );
52

53
        let raster_sources = self
4✔
54
            .sources
4✔
55
            .initialize_sources(path, context)
4✔
56
            .await?
×
57
            .rasters;
58

59
        let in_descriptors = raster_sources
4✔
60
            .iter()
4✔
61
            .map(InitializedRasterOperator::result_descriptor)
4✔
62
            .collect::<Vec<_>>();
4✔
63

4✔
64
        ensure!(
4✔
65
            in_descriptors.iter().all(|d| d.spatial_reference
8✔
66
                == in_descriptors[0].spatial_reference
8✔
67
                && d.data_type == in_descriptors[0].data_type),
8✔
68
            RasterInputsMustHaveSameSpatialReferenceAndDatatype
×
69
        );
70

71
        let time = time_interval_extent(in_descriptors.iter().map(|d| d.time));
4✔
72
        let bbox = partitions_extent(in_descriptors.iter().map(|d| d.bbox));
4✔
73

4✔
74
        let resolution = in_descriptors
4✔
75
            .iter()
4✔
76
            .map(|d| d.resolution)
8✔
77
            .reduce(|a, b| match (a, b) {
4✔
78
                (Some(a), Some(b)) => {
×
79
                    Some(SpatialResolution::new_unchecked(a.x.min(b.x), a.y.min(b.y)))
×
80
                }
81
                _ => None,
4✔
82
            })
4✔
83
            .flatten();
4✔
84

4✔
85
        let bands_per_source = in_descriptors
4✔
86
            .iter()
4✔
87
            .map(|d| d.bands.count())
8✔
88
            .collect::<Vec<_>>();
4✔
89

90
        let output_band_descriptors = RasterBandDescriptors::merge_all_with_suffix(
4✔
91
            in_descriptors.iter().map(|d| &d.bands),
8✔
92
            "(duplicate)",
4✔
93
        )?; // TODO: make renaming of duplicate bands configurable
4✔
94

95
        let result_descriptor = RasterResultDescriptor {
4✔
96
            data_type: in_descriptors[0].data_type,
4✔
97
            spatial_reference: in_descriptors[0].spatial_reference,
4✔
98
            time,
4✔
99
            bbox,
4✔
100
            resolution,
4✔
101
            bands: output_band_descriptors,
4✔
102
        };
4✔
103

4✔
104
        Ok(Box::new(InitializedRasterStacker {
4✔
105
            name,
4✔
106
            result_descriptor,
4✔
107
            raster_sources,
4✔
108
            bands_per_source,
4✔
109
        }))
4✔
110
    }
8✔
111

112
    span_fn!(RasterStacker);
×
113
}
114

115
pub struct InitializedRasterStacker {
116
    name: CanonicOperatorName,
117
    result_descriptor: RasterResultDescriptor,
118
    raster_sources: Vec<Box<dyn InitializedRasterOperator>>,
119
    bands_per_source: Vec<u32>,
120
}
121

122
impl InitializedRasterOperator for InitializedRasterStacker {
123
    fn result_descriptor(&self) -> &RasterResultDescriptor {
1✔
124
        &self.result_descriptor
1✔
125
    }
1✔
126

127
    fn query_processor(&self) -> Result<TypedRasterQueryProcessor> {
4✔
128
        let typed_raster_processors = self
4✔
129
            .raster_sources
4✔
130
            .iter()
4✔
131
            .map(InitializedRasterOperator::query_processor)
4✔
132
            .collect::<Result<Vec<_>>>()?;
4✔
133

134
        // unpack all processors
135
        let datatype = typed_raster_processors[0].raster_data_type();
4✔
136

4✔
137
        let bands_per_source = self.bands_per_source.clone();
4✔
138

4✔
139
        // TODO: use a macro to unpack all the input processor to the same datatype?
4✔
140
        Ok(match datatype {
4✔
141
            geoengine_datatypes::raster::RasterDataType::U8 => {
142
                let inputs = typed_raster_processors.into_iter().map(|p| p.get_u8().expect("all inputs should have the same datatype because it was checked in the initialization of the operator")).collect();
8✔
143
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
4✔
144
                TypedRasterQueryProcessor::U8(Box::new(p))
4✔
145
            }
146
            geoengine_datatypes::raster::RasterDataType::U16 => {
147
                let inputs = typed_raster_processors.into_iter().map(|p| p.get_u16().expect("all inputs should have the same datatype because it was checked in the initialization of the operator")).collect();
×
148
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
149
                TypedRasterQueryProcessor::U16(Box::new(p))
×
150
            }
151
            geoengine_datatypes::raster::RasterDataType::U32 => {
152
                let inputs = typed_raster_processors.into_iter().map(|p| p.get_u32().expect("all inputs should have the same datatype because it was checked in the initialization of the operator")).collect();
×
153
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
154
                TypedRasterQueryProcessor::U32(Box::new(p))
×
155
            }
156
            geoengine_datatypes::raster::RasterDataType::U64 => {
157
                let inputs = typed_raster_processors.into_iter().map(|p| p.get_u64().expect("all inputs should have the same datatype because it was checked in the initialization of the operator")).collect();
×
158
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
159
                TypedRasterQueryProcessor::U64(Box::new(p))
×
160
            }
161
            geoengine_datatypes::raster::RasterDataType::I8 => {
162
                let inputs = typed_raster_processors.into_iter().map(|p| p.get_i8().expect("all inputs should have the same datatype because it was checked in the initialization of the operator")).collect();
×
163
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
164
                TypedRasterQueryProcessor::I8(Box::new(p))
×
165
            }
166
            geoengine_datatypes::raster::RasterDataType::I16 => {
167
                let inputs = typed_raster_processors.into_iter().map(|p| p.get_i16().expect("all inputs should have the same datatype because it was checked in the initialization of the operator")).collect();
×
168
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
169
                TypedRasterQueryProcessor::I16(Box::new(p))
×
170
            }
171
            geoengine_datatypes::raster::RasterDataType::I32 => {
172
                let inputs = typed_raster_processors.into_iter().map(|p| p.get_i32().expect("all inputs should have the same datatype because it was checked in the initialization of the operator")).collect();
×
173
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
174
                TypedRasterQueryProcessor::I32(Box::new(p))
×
175
            }
176
            geoengine_datatypes::raster::RasterDataType::I64 => {
177
                let inputs = typed_raster_processors.into_iter().map(|p| p.get_i64().expect("all inputs should have the same datatype because it was checked in the initialization of the operator")).collect();
×
178
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
179
                TypedRasterQueryProcessor::I64(Box::new(p))
×
180
            }
181
            geoengine_datatypes::raster::RasterDataType::F32 => {
182
                let inputs = typed_raster_processors.into_iter().map(|p| p.get_f32().expect("all inputs should have the same datatype because it was checked in the initialization of the operator")).collect();
×
183
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
184
                TypedRasterQueryProcessor::F32(Box::new(p))
×
185
            }
186
            geoengine_datatypes::raster::RasterDataType::F64 => {
187
                let inputs = typed_raster_processors.into_iter().map(|p| p.get_f64().expect("all inputs should have the same datatype because it was checked in the initialization of the operator")).collect();
×
188
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
189
                TypedRasterQueryProcessor::F64(Box::new(p))
×
190
            }
191
        })
192
    }
4✔
193

194
    fn canonic_name(&self) -> CanonicOperatorName {
×
195
        self.name.clone()
×
196
    }
×
197
}
198

199
pub(crate) struct RasterStackerProcessor<T> {
200
    sources: Vec<Box<dyn RasterQueryProcessor<RasterType = T>>>,
201
    bands_per_source: Vec<u32>,
202
}
203

204
impl<T> RasterStackerProcessor<T> {
205
    pub fn new(
4✔
206
        sources: Vec<Box<dyn RasterQueryProcessor<RasterType = T>>>,
4✔
207
        bands_per_source: Vec<u32>,
4✔
208
    ) -> Self {
4✔
209
        Self {
4✔
210
            sources,
4✔
211
            bands_per_source,
4✔
212
        }
4✔
213
    }
4✔
214
}
215

216
/// compute the bands in the input source from the bands in a query that uses multiple sources
217
fn map_query_bands_to_source_bands(
27✔
218
    query_bands: &BandSelection,
27✔
219
    bands_per_source: &[u32],
27✔
220
    source_index: usize,
27✔
221
) -> Option<BandSelection> {
27✔
222
    let source_start: u32 = bands_per_source.iter().take(source_index).sum();
27✔
223
    let source_bands = bands_per_source[source_index];
27✔
224
    let source_end = source_start + source_bands;
27✔
225

27✔
226
    let bands = query_bands
27✔
227
        .as_slice()
27✔
228
        .iter()
27✔
229
        .filter(|output_band| **output_band >= source_start && **output_band < source_end)
38✔
230
        .map(|output_band| output_band - source_start)
27✔
231
        .collect::<Vec<_>>();
27✔
232

27✔
233
    if bands.is_empty() {
27✔
234
        return None;
10✔
235
    }
17✔
236

17✔
237
    Some(BandSelection::new_unchecked(bands))
17✔
238
}
27✔
239

240
#[async_trait]
241
impl<T> RasterQueryProcessor for RasterStackerProcessor<T>
242
where
243
    T: Pixel,
244
{
245
    type RasterType = T;
246
    async fn raster_query<'a>(
11✔
247
        &'a self,
11✔
248
        query: RasterQueryRectangle,
11✔
249
        ctx: &'a dyn QueryContext,
11✔
250
    ) -> Result<BoxStream<'a, Result<RasterTile2D<T>>>> {
11✔
251
        let mut sources = vec![];
11✔
252

253
        for (idx, source) in self.sources.iter().enumerate() {
22✔
254
            let Some(bands) =
13✔
255
                map_query_bands_to_source_bands(&query.attributes, &self.bands_per_source, idx)
22✔
256
            else {
257
                continue;
9✔
258
            };
259

260
            let mut source_query = query.clone();
13✔
261
            source_query.attributes = bands.clone();
13✔
262
            sources.push(RasterStackerSource {
13✔
263
                queryable: QueryWrapper { p: source, ctx },
13✔
264
                band_idxs: bands.as_vec(),
13✔
265
            });
13✔
266
        }
267

268
        let output = RasterStackerAdapter::new(sources, query.into());
11✔
269

11✔
270
        Ok(Box::pin(output))
11✔
271
    }
22✔
272
}
273

274
#[cfg(test)]
275
mod tests {
276
    use futures::StreamExt;
277
    use geoengine_datatypes::{
278
        primitives::{CacheHint, SpatialPartition2D, TimeInterval},
279
        raster::{Grid, GridShape, RasterDataType, TilesEqualIgnoringCacheHint},
280
        spatial_reference::SpatialReference,
281
        util::test::TestDefault,
282
    };
283

284
    use crate::{
285
        engine::{
286
            MockExecutionContext, MockQueryContext, RasterBandDescriptor, RasterBandDescriptors,
287
        },
288
        mock::{MockRasterSource, MockRasterSourceParams},
289
    };
290

291
    use super::*;
292

293
    #[test]
1✔
294
    fn it_maps_query_bands_to_source_bands() {
1✔
295
        assert_eq!(
1✔
296
            map_query_bands_to_source_bands(&0.into(), &[2, 1], 0),
1✔
297
            Some(0.into())
1✔
298
        );
1✔
299
        assert_eq!(map_query_bands_to_source_bands(&0.into(), &[2, 1], 1), None);
1✔
300
        assert_eq!(
1✔
301
            map_query_bands_to_source_bands(&2.into(), &[2, 1], 1),
1✔
302
            Some(0.into())
1✔
303
        );
1✔
304

305
        assert_eq!(
1✔
306
            map_query_bands_to_source_bands(&[1, 2].try_into().unwrap(), &[2, 2], 0),
1✔
307
            Some(1.into())
1✔
308
        );
1✔
309
        assert_eq!(
1✔
310
            map_query_bands_to_source_bands(&[1, 2, 3].try_into().unwrap(), &[2, 2], 1),
1✔
311
            Some([0, 1].try_into().unwrap())
1✔
312
        );
1✔
313
    }
1✔
314

315
    #[tokio::test]
1✔
316
    #[allow(clippy::too_many_lines)]
317
    async fn it_stacks() {
1✔
318
        let data: Vec<RasterTile2D<u8>> = vec![
1✔
319
            RasterTile2D {
1✔
320
                time: TimeInterval::new_unchecked(0, 5),
1✔
321
                tile_position: [-1, 0].into(),
1✔
322
                band: 0,
1✔
323
                global_geo_transform: TestDefault::test_default(),
1✔
324
                grid_array: Grid::new([2, 2].into(), vec![0, 1, 2, 3]).unwrap().into(),
1✔
325
                properties: Default::default(),
1✔
326
                cache_hint: CacheHint::default(),
1✔
327
            },
1✔
328
            RasterTile2D {
1✔
329
                time: TimeInterval::new_unchecked(0, 5),
1✔
330
                tile_position: [-1, 1].into(),
1✔
331
                band: 0,
1✔
332
                global_geo_transform: TestDefault::test_default(),
1✔
333
                grid_array: Grid::new([2, 2].into(), vec![4, 5, 6, 7]).unwrap().into(),
1✔
334
                properties: Default::default(),
1✔
335
                cache_hint: CacheHint::default(),
1✔
336
            },
1✔
337
            RasterTile2D {
1✔
338
                time: TimeInterval::new_unchecked(5, 10),
1✔
339
                tile_position: [-1, 0].into(),
1✔
340
                band: 0,
1✔
341
                global_geo_transform: TestDefault::test_default(),
1✔
342
                grid_array: Grid::new([2, 2].into(), vec![8, 9, 10, 11]).unwrap().into(),
1✔
343
                properties: Default::default(),
1✔
344
                cache_hint: CacheHint::default(),
1✔
345
            },
1✔
346
            RasterTile2D {
1✔
347
                time: TimeInterval::new_unchecked(5, 10),
1✔
348
                tile_position: [-1, 1].into(),
1✔
349
                band: 0,
1✔
350
                global_geo_transform: TestDefault::test_default(),
1✔
351
                grid_array: Grid::new([2, 2].into(), vec![12, 13, 14, 15])
1✔
352
                    .unwrap()
1✔
353
                    .into(),
1✔
354
                properties: Default::default(),
1✔
355
                cache_hint: CacheHint::default(),
1✔
356
            },
1✔
357
        ];
1✔
358

1✔
359
        let data2: Vec<RasterTile2D<u8>> = vec![
1✔
360
            RasterTile2D {
1✔
361
                time: TimeInterval::new_unchecked(0, 5),
1✔
362
                tile_position: [-1, 0].into(),
1✔
363
                band: 0,
1✔
364
                global_geo_transform: TestDefault::test_default(),
1✔
365
                grid_array: Grid::new([2, 2].into(), vec![16, 17, 18, 19])
1✔
366
                    .unwrap()
1✔
367
                    .into(),
1✔
368
                properties: Default::default(),
1✔
369
                cache_hint: CacheHint::default(),
1✔
370
            },
1✔
371
            RasterTile2D {
1✔
372
                time: TimeInterval::new_unchecked(0, 5),
1✔
373
                tile_position: [-1, 1].into(),
1✔
374
                band: 0,
1✔
375
                global_geo_transform: TestDefault::test_default(),
1✔
376
                grid_array: Grid::new([2, 2].into(), vec![20, 21, 22, 23])
1✔
377
                    .unwrap()
1✔
378
                    .into(),
1✔
379
                properties: Default::default(),
1✔
380
                cache_hint: CacheHint::default(),
1✔
381
            },
1✔
382
            RasterTile2D {
1✔
383
                time: TimeInterval::new_unchecked(5, 10),
1✔
384
                tile_position: [-1, 0].into(),
1✔
385
                band: 0,
1✔
386
                global_geo_transform: TestDefault::test_default(),
1✔
387
                grid_array: Grid::new([2, 2].into(), vec![24, 25, 26, 27])
1✔
388
                    .unwrap()
1✔
389
                    .into(),
1✔
390
                properties: Default::default(),
1✔
391
                cache_hint: CacheHint::default(),
1✔
392
            },
1✔
393
            RasterTile2D {
1✔
394
                time: TimeInterval::new_unchecked(5, 10),
1✔
395
                tile_position: [-1, 1].into(),
1✔
396
                band: 0,
1✔
397
                global_geo_transform: TestDefault::test_default(),
1✔
398
                grid_array: Grid::new([2, 2].into(), vec![28, 29, 30, 31])
1✔
399
                    .unwrap()
1✔
400
                    .into(),
1✔
401
                properties: Default::default(),
1✔
402
                cache_hint: CacheHint::default(),
1✔
403
            },
1✔
404
        ];
1✔
405

1✔
406
        let mrs1 = MockRasterSource {
1✔
407
            params: MockRasterSourceParams {
1✔
408
                data: data.clone(),
1✔
409
                result_descriptor: RasterResultDescriptor {
1✔
410
                    data_type: RasterDataType::U8,
1✔
411
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
412
                    time: None,
1✔
413
                    bbox: None,
1✔
414
                    resolution: None,
1✔
415
                    bands: RasterBandDescriptors::new_single_band(),
1✔
416
                },
1✔
417
            },
1✔
418
        }
1✔
419
        .boxed();
1✔
420

1✔
421
        let mrs2 = MockRasterSource {
1✔
422
            params: MockRasterSourceParams {
1✔
423
                data: data2.clone(),
1✔
424
                result_descriptor: RasterResultDescriptor {
1✔
425
                    data_type: RasterDataType::U8,
1✔
426
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
427
                    time: None,
1✔
428
                    bbox: None,
1✔
429
                    resolution: None,
1✔
430
                    bands: RasterBandDescriptors::new_single_band(),
1✔
431
                },
1✔
432
            },
1✔
433
        }
1✔
434
        .boxed();
1✔
435

1✔
436
        let stacker = RasterStacker {
1✔
437
            params: RasterStackerParams {},
1✔
438
            sources: MultipleRasterSources {
1✔
439
                rasters: vec![mrs1, mrs2],
1✔
440
            },
1✔
441
        }
1✔
442
        .boxed();
1✔
443

1✔
444
        let mut exe_ctx = MockExecutionContext::test_default();
1✔
445
        exe_ctx.tiling_specification.tile_size_in_pixels = GridShape {
1✔
446
            shape_array: [2, 2],
1✔
447
        };
1✔
448

1✔
449
        let query_rect = RasterQueryRectangle {
1✔
450
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 1.).into(), (3., 0.).into()),
1✔
451
            time_interval: TimeInterval::new_unchecked(0, 10),
1✔
452
            spatial_resolution: SpatialResolution::one(),
1✔
453
            attributes: [0, 1].try_into().unwrap(),
1✔
454
        };
1✔
455

1✔
456
        let query_ctx = MockQueryContext::test_default();
1✔
457

458
        let op = stacker
1✔
459
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
460
            .await
×
461
            .unwrap();
1✔
462

1✔
463
        let qp = op.query_processor().unwrap().get_u8().unwrap();
1✔
464

465
        let result = qp
1✔
466
            .raster_query(query_rect, &query_ctx)
1✔
467
            .await
×
468
            .unwrap()
1✔
469
            .collect::<Vec<_>>()
1✔
470
            .await;
×
471
        let result = result.into_iter().collect::<Result<Vec<_>>>().unwrap();
1✔
472

1✔
473
        let expected: Vec<_> = data
1✔
474
            .into_iter()
1✔
475
            .zip(data2.into_iter().map(|mut tile| {
4✔
476
                tile.band = 1;
4✔
477
                tile
4✔
478
            }))
4✔
479
            .flat_map(|(a, b)| vec![a.clone(), b.clone()])
4✔
480
            .collect();
1✔
481

1✔
482
        assert!(expected.tiles_equal_ignoring_cache_hint(&result));
1✔
483
    }
484

485
    #[tokio::test]
1✔
486
    #[allow(clippy::too_many_lines)]
487
    async fn it_stacks_stacks() {
1✔
488
        let data: Vec<RasterTile2D<u8>> = vec![
1✔
489
            RasterTile2D {
1✔
490
                time: TimeInterval::new_unchecked(0, 5),
1✔
491
                tile_position: [-1, 0].into(),
1✔
492
                band: 0,
1✔
493
                global_geo_transform: TestDefault::test_default(),
1✔
494
                grid_array: Grid::new([2, 2].into(), vec![0, 1, 2, 3]).unwrap().into(),
1✔
495
                properties: Default::default(),
1✔
496
                cache_hint: CacheHint::default(),
1✔
497
            },
1✔
498
            RasterTile2D {
1✔
499
                time: TimeInterval::new_unchecked(0, 5),
1✔
500
                tile_position: [-1, 0].into(),
1✔
501
                band: 1,
1✔
502
                global_geo_transform: TestDefault::test_default(),
1✔
503
                grid_array: Grid::new([2, 2].into(), vec![3, 2, 1, 0]).unwrap().into(),
1✔
504
                properties: Default::default(),
1✔
505
                cache_hint: CacheHint::default(),
1✔
506
            },
1✔
507
            RasterTile2D {
1✔
508
                time: TimeInterval::new_unchecked(0, 5),
1✔
509
                tile_position: [-1, 1].into(),
1✔
510
                band: 0,
1✔
511
                global_geo_transform: TestDefault::test_default(),
1✔
512
                grid_array: Grid::new([2, 2].into(), vec![4, 5, 6, 7]).unwrap().into(),
1✔
513
                properties: Default::default(),
1✔
514
                cache_hint: CacheHint::default(),
1✔
515
            },
1✔
516
            RasterTile2D {
1✔
517
                time: TimeInterval::new_unchecked(0, 5),
1✔
518
                tile_position: [-1, 1].into(),
1✔
519
                band: 1,
1✔
520
                global_geo_transform: TestDefault::test_default(),
1✔
521
                grid_array: Grid::new([2, 2].into(), vec![7, 6, 5, 4]).unwrap().into(),
1✔
522
                properties: Default::default(),
1✔
523
                cache_hint: CacheHint::default(),
1✔
524
            },
1✔
525
            RasterTile2D {
1✔
526
                time: TimeInterval::new_unchecked(5, 10),
1✔
527
                tile_position: [-1, 0].into(),
1✔
528
                band: 0,
1✔
529
                global_geo_transform: TestDefault::test_default(),
1✔
530
                grid_array: Grid::new([2, 2].into(), vec![8, 9, 10, 11]).unwrap().into(),
1✔
531
                properties: Default::default(),
1✔
532
                cache_hint: CacheHint::default(),
1✔
533
            },
1✔
534
            RasterTile2D {
1✔
535
                time: TimeInterval::new_unchecked(5, 10),
1✔
536
                tile_position: [-1, 0].into(),
1✔
537
                band: 1,
1✔
538
                global_geo_transform: TestDefault::test_default(),
1✔
539
                grid_array: Grid::new([2, 2].into(), vec![11, 10, 9, 8]).unwrap().into(),
1✔
540
                properties: Default::default(),
1✔
541
                cache_hint: CacheHint::default(),
1✔
542
            },
1✔
543
            RasterTile2D {
1✔
544
                time: TimeInterval::new_unchecked(5, 10),
1✔
545
                tile_position: [-1, 1].into(),
1✔
546
                band: 0,
1✔
547
                global_geo_transform: TestDefault::test_default(),
1✔
548
                grid_array: Grid::new([2, 2].into(), vec![12, 13, 14, 15])
1✔
549
                    .unwrap()
1✔
550
                    .into(),
1✔
551
                properties: Default::default(),
1✔
552
                cache_hint: CacheHint::default(),
1✔
553
            },
1✔
554
            RasterTile2D {
1✔
555
                time: TimeInterval::new_unchecked(5, 10),
1✔
556
                tile_position: [-1, 1].into(),
1✔
557
                band: 1,
1✔
558
                global_geo_transform: TestDefault::test_default(),
1✔
559
                grid_array: Grid::new([2, 2].into(), vec![15, 14, 13, 12])
1✔
560
                    .unwrap()
1✔
561
                    .into(),
1✔
562
                properties: Default::default(),
1✔
563
                cache_hint: CacheHint::default(),
1✔
564
            },
1✔
565
        ];
1✔
566

1✔
567
        let data2: Vec<RasterTile2D<u8>> = vec![
1✔
568
            RasterTile2D {
1✔
569
                time: TimeInterval::new_unchecked(0, 5),
1✔
570
                tile_position: [-1, 0].into(),
1✔
571
                band: 0,
1✔
572
                global_geo_transform: TestDefault::test_default(),
1✔
573
                grid_array: Grid::new([2, 2].into(), vec![16, 17, 18, 19])
1✔
574
                    .unwrap()
1✔
575
                    .into(),
1✔
576
                properties: Default::default(),
1✔
577
                cache_hint: CacheHint::default(),
1✔
578
            },
1✔
579
            RasterTile2D {
1✔
580
                time: TimeInterval::new_unchecked(0, 5),
1✔
581
                tile_position: [-1, 0].into(),
1✔
582
                band: 1,
1✔
583
                global_geo_transform: TestDefault::test_default(),
1✔
584
                grid_array: Grid::new([2, 2].into(), vec![19, 18, 17, 16])
1✔
585
                    .unwrap()
1✔
586
                    .into(),
1✔
587
                properties: Default::default(),
1✔
588
                cache_hint: CacheHint::default(),
1✔
589
            },
1✔
590
            RasterTile2D {
1✔
591
                time: TimeInterval::new_unchecked(0, 5),
1✔
592
                tile_position: [-1, 1].into(),
1✔
593
                band: 0,
1✔
594
                global_geo_transform: TestDefault::test_default(),
1✔
595
                grid_array: Grid::new([2, 2].into(), vec![20, 21, 22, 23])
1✔
596
                    .unwrap()
1✔
597
                    .into(),
1✔
598
                properties: Default::default(),
1✔
599
                cache_hint: CacheHint::default(),
1✔
600
            },
1✔
601
            RasterTile2D {
1✔
602
                time: TimeInterval::new_unchecked(0, 5),
1✔
603
                tile_position: [-1, 1].into(),
1✔
604
                band: 1,
1✔
605
                global_geo_transform: TestDefault::test_default(),
1✔
606
                grid_array: Grid::new([2, 2].into(), vec![32, 22, 21, 20])
1✔
607
                    .unwrap()
1✔
608
                    .into(),
1✔
609
                properties: Default::default(),
1✔
610
                cache_hint: CacheHint::default(),
1✔
611
            },
1✔
612
            RasterTile2D {
1✔
613
                time: TimeInterval::new_unchecked(5, 10),
1✔
614
                tile_position: [-1, 0].into(),
1✔
615
                band: 0,
1✔
616
                global_geo_transform: TestDefault::test_default(),
1✔
617
                grid_array: Grid::new([2, 2].into(), vec![24, 25, 26, 27])
1✔
618
                    .unwrap()
1✔
619
                    .into(),
1✔
620
                properties: Default::default(),
1✔
621
                cache_hint: CacheHint::default(),
1✔
622
            },
1✔
623
            RasterTile2D {
1✔
624
                time: TimeInterval::new_unchecked(5, 10),
1✔
625
                tile_position: [-1, 0].into(),
1✔
626
                band: 1,
1✔
627
                global_geo_transform: TestDefault::test_default(),
1✔
628
                grid_array: Grid::new([2, 2].into(), vec![27, 26, 25, 24])
1✔
629
                    .unwrap()
1✔
630
                    .into(),
1✔
631
                properties: Default::default(),
1✔
632
                cache_hint: CacheHint::default(),
1✔
633
            },
1✔
634
            RasterTile2D {
1✔
635
                time: TimeInterval::new_unchecked(5, 10),
1✔
636
                tile_position: [-1, 1].into(),
1✔
637
                band: 0,
1✔
638
                global_geo_transform: TestDefault::test_default(),
1✔
639
                grid_array: Grid::new([2, 2].into(), vec![28, 29, 30, 31])
1✔
640
                    .unwrap()
1✔
641
                    .into(),
1✔
642
                properties: Default::default(),
1✔
643
                cache_hint: CacheHint::default(),
1✔
644
            },
1✔
645
            RasterTile2D {
1✔
646
                time: TimeInterval::new_unchecked(5, 10),
1✔
647
                tile_position: [-1, 1].into(),
1✔
648
                band: 1,
1✔
649
                global_geo_transform: TestDefault::test_default(),
1✔
650
                grid_array: Grid::new([2, 2].into(), vec![31, 30, 39, 28])
1✔
651
                    .unwrap()
1✔
652
                    .into(),
1✔
653
                properties: Default::default(),
1✔
654
                cache_hint: CacheHint::default(),
1✔
655
            },
1✔
656
        ];
1✔
657

1✔
658
        let mrs1 = MockRasterSource {
1✔
659
            params: MockRasterSourceParams {
1✔
660
                data: data.clone(),
1✔
661
                result_descriptor: RasterResultDescriptor {
1✔
662
                    data_type: RasterDataType::U8,
1✔
663
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
664
                    time: None,
1✔
665
                    bbox: None,
1✔
666
                    resolution: None,
1✔
667
                    bands: RasterBandDescriptors::new(vec![
1✔
668
                        RasterBandDescriptor::new_unitless("band_0".into()),
1✔
669
                        RasterBandDescriptor::new_unitless("band_1".into()),
1✔
670
                    ])
1✔
671
                    .unwrap(),
1✔
672
                },
1✔
673
            },
1✔
674
        }
1✔
675
        .boxed();
1✔
676

1✔
677
        let mrs2 = MockRasterSource {
1✔
678
            params: MockRasterSourceParams {
1✔
679
                data: data2.clone(),
1✔
680
                result_descriptor: RasterResultDescriptor {
1✔
681
                    data_type: RasterDataType::U8,
1✔
682
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
683
                    time: None,
1✔
684
                    bbox: None,
1✔
685
                    resolution: None,
1✔
686
                    bands: RasterBandDescriptors::new(vec![
1✔
687
                        RasterBandDescriptor::new_unitless("band_0".into()),
1✔
688
                        RasterBandDescriptor::new_unitless("band_1".into()),
1✔
689
                    ])
1✔
690
                    .unwrap(),
1✔
691
                },
1✔
692
            },
1✔
693
        }
1✔
694
        .boxed();
1✔
695

1✔
696
        let stacker = RasterStacker {
1✔
697
            params: RasterStackerParams {},
1✔
698
            sources: MultipleRasterSources {
1✔
699
                rasters: vec![mrs1, mrs2],
1✔
700
            },
1✔
701
        }
1✔
702
        .boxed();
1✔
703

1✔
704
        let mut exe_ctx = MockExecutionContext::test_default();
1✔
705
        exe_ctx.tiling_specification.tile_size_in_pixels = GridShape {
1✔
706
            shape_array: [2, 2],
1✔
707
        };
1✔
708

1✔
709
        let query_rect = RasterQueryRectangle {
1✔
710
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 1.).into(), (3., 0.).into()),
1✔
711
            time_interval: TimeInterval::new_unchecked(0, 10),
1✔
712
            spatial_resolution: SpatialResolution::one(),
1✔
713
            attributes: [0, 1, 2, 3].try_into().unwrap(),
1✔
714
        };
1✔
715

1✔
716
        let query_ctx = MockQueryContext::test_default();
1✔
717

718
        let op = stacker
1✔
719
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
720
            .await
×
721
            .unwrap();
1✔
722

1✔
723
        let qp = op.query_processor().unwrap().get_u8().unwrap();
1✔
724

725
        let result = qp
1✔
726
            .raster_query(query_rect, &query_ctx)
1✔
727
            .await
×
728
            .unwrap()
1✔
729
            .collect::<Vec<_>>()
1✔
730
            .await;
×
731
        let result = result.into_iter().collect::<Result<Vec<_>>>().unwrap();
1✔
732

1✔
733
        let expected: Vec<_> = data
1✔
734
            .chunks(2)
1✔
735
            .zip(
1✔
736
                data2
1✔
737
                    .into_iter()
1✔
738
                    .map(|mut tile| {
8✔
739
                        tile.band += 2;
8✔
740
                        tile
8✔
741
                    })
8✔
742
                    .collect::<Vec<_>>()
1✔
743
                    .chunks(2),
1✔
744
            )
1✔
745
            .flat_map(|(chunk1, chunk2)| chunk1.iter().chain(chunk2.iter()))
4✔
746
            .cloned()
1✔
747
            .collect();
1✔
748

1✔
749
        assert!(expected.tiles_equal_ignoring_cache_hint(&result));
1✔
750
    }
751

752
    #[tokio::test]
1✔
753
    #[allow(clippy::too_many_lines)]
754
    async fn it_selects_band_from_stack() {
1✔
755
        let data: Vec<RasterTile2D<u8>> = vec![
1✔
756
            RasterTile2D {
1✔
757
                time: TimeInterval::new_unchecked(0, 5),
1✔
758
                tile_position: [-1, 0].into(),
1✔
759
                band: 0,
1✔
760
                global_geo_transform: TestDefault::test_default(),
1✔
761
                grid_array: Grid::new([2, 2].into(), vec![0, 1, 2, 3]).unwrap().into(),
1✔
762
                properties: Default::default(),
1✔
763
                cache_hint: CacheHint::default(),
1✔
764
            },
1✔
765
            RasterTile2D {
1✔
766
                time: TimeInterval::new_unchecked(0, 5),
1✔
767
                tile_position: [-1, 1].into(),
1✔
768
                band: 0,
1✔
769
                global_geo_transform: TestDefault::test_default(),
1✔
770
                grid_array: Grid::new([2, 2].into(), vec![4, 5, 6, 7]).unwrap().into(),
1✔
771
                properties: Default::default(),
1✔
772
                cache_hint: CacheHint::default(),
1✔
773
            },
1✔
774
            RasterTile2D {
1✔
775
                time: TimeInterval::new_unchecked(5, 10),
1✔
776
                tile_position: [-1, 0].into(),
1✔
777
                band: 0,
1✔
778
                global_geo_transform: TestDefault::test_default(),
1✔
779
                grid_array: Grid::new([2, 2].into(), vec![8, 9, 10, 11]).unwrap().into(),
1✔
780
                properties: Default::default(),
1✔
781
                cache_hint: CacheHint::default(),
1✔
782
            },
1✔
783
            RasterTile2D {
1✔
784
                time: TimeInterval::new_unchecked(5, 10),
1✔
785
                tile_position: [-1, 1].into(),
1✔
786
                band: 0,
1✔
787
                global_geo_transform: TestDefault::test_default(),
1✔
788
                grid_array: Grid::new([2, 2].into(), vec![12, 13, 14, 15])
1✔
789
                    .unwrap()
1✔
790
                    .into(),
1✔
791
                properties: Default::default(),
1✔
792
                cache_hint: CacheHint::default(),
1✔
793
            },
1✔
794
        ];
1✔
795

1✔
796
        let data2: Vec<RasterTile2D<u8>> = vec![
1✔
797
            RasterTile2D {
1✔
798
                time: TimeInterval::new_unchecked(0, 5),
1✔
799
                tile_position: [-1, 0].into(),
1✔
800
                band: 0,
1✔
801
                global_geo_transform: TestDefault::test_default(),
1✔
802
                grid_array: Grid::new([2, 2].into(), vec![16, 17, 18, 19])
1✔
803
                    .unwrap()
1✔
804
                    .into(),
1✔
805
                properties: Default::default(),
1✔
806
                cache_hint: CacheHint::default(),
1✔
807
            },
1✔
808
            RasterTile2D {
1✔
809
                time: TimeInterval::new_unchecked(0, 5),
1✔
810
                tile_position: [-1, 1].into(),
1✔
811
                band: 0,
1✔
812
                global_geo_transform: TestDefault::test_default(),
1✔
813
                grid_array: Grid::new([2, 2].into(), vec![20, 21, 22, 23])
1✔
814
                    .unwrap()
1✔
815
                    .into(),
1✔
816
                properties: Default::default(),
1✔
817
                cache_hint: CacheHint::default(),
1✔
818
            },
1✔
819
            RasterTile2D {
1✔
820
                time: TimeInterval::new_unchecked(5, 10),
1✔
821
                tile_position: [-1, 0].into(),
1✔
822
                band: 0,
1✔
823
                global_geo_transform: TestDefault::test_default(),
1✔
824
                grid_array: Grid::new([2, 2].into(), vec![24, 25, 26, 27])
1✔
825
                    .unwrap()
1✔
826
                    .into(),
1✔
827
                properties: Default::default(),
1✔
828
                cache_hint: CacheHint::default(),
1✔
829
            },
1✔
830
            RasterTile2D {
1✔
831
                time: TimeInterval::new_unchecked(5, 10),
1✔
832
                tile_position: [-1, 1].into(),
1✔
833
                band: 0,
1✔
834
                global_geo_transform: TestDefault::test_default(),
1✔
835
                grid_array: Grid::new([2, 2].into(), vec![28, 29, 30, 31])
1✔
836
                    .unwrap()
1✔
837
                    .into(),
1✔
838
                properties: Default::default(),
1✔
839
                cache_hint: CacheHint::default(),
1✔
840
            },
1✔
841
        ];
1✔
842

1✔
843
        let mrs1 = MockRasterSource {
1✔
844
            params: MockRasterSourceParams {
1✔
845
                data: data.clone(),
1✔
846
                result_descriptor: RasterResultDescriptor {
1✔
847
                    data_type: RasterDataType::U8,
1✔
848
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
849
                    time: None,
1✔
850
                    bbox: None,
1✔
851
                    resolution: None,
1✔
852
                    bands: RasterBandDescriptors::new_single_band(),
1✔
853
                },
1✔
854
            },
1✔
855
        }
1✔
856
        .boxed();
1✔
857

1✔
858
        let mrs2 = MockRasterSource {
1✔
859
            params: MockRasterSourceParams {
1✔
860
                data: data2.clone(),
1✔
861
                result_descriptor: RasterResultDescriptor {
1✔
862
                    data_type: RasterDataType::U8,
1✔
863
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
864
                    time: None,
1✔
865
                    bbox: None,
1✔
866
                    resolution: None,
1✔
867
                    bands: RasterBandDescriptors::new_single_band(),
1✔
868
                },
1✔
869
            },
1✔
870
        }
1✔
871
        .boxed();
1✔
872

1✔
873
        let stacker = RasterStacker {
1✔
874
            params: RasterStackerParams {},
1✔
875
            sources: MultipleRasterSources {
1✔
876
                rasters: vec![mrs1, mrs2],
1✔
877
            },
1✔
878
        }
1✔
879
        .boxed();
1✔
880

1✔
881
        let mut exe_ctx = MockExecutionContext::test_default();
1✔
882
        exe_ctx.tiling_specification.tile_size_in_pixels = GridShape {
1✔
883
            shape_array: [2, 2],
1✔
884
        };
1✔
885

1✔
886
        let query_rect = RasterQueryRectangle {
1✔
887
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 1.).into(), (3., 0.).into()),
1✔
888
            time_interval: TimeInterval::new_unchecked(0, 10),
1✔
889
            spatial_resolution: SpatialResolution::one(),
1✔
890
            attributes: 1.into(),
1✔
891
        };
1✔
892

1✔
893
        let query_ctx = MockQueryContext::test_default();
1✔
894

895
        let op = stacker
1✔
896
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
897
            .await
×
898
            .unwrap();
1✔
899

1✔
900
        let qp = op.query_processor().unwrap().get_u8().unwrap();
1✔
901

902
        let result = qp
1✔
903
            .raster_query(query_rect, &query_ctx)
1✔
904
            .await
×
905
            .unwrap()
1✔
906
            .collect::<Vec<_>>()
1✔
907
            .await;
×
908
        let result = result.into_iter().collect::<Result<Vec<_>>>().unwrap();
1✔
909

1✔
910
        assert!(data2.tiles_equal_ignoring_cache_hint(&result));
1✔
911
    }
912
}
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