• 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

94.03
/operators/src/processing/raster_stacker.rs
1
use crate::adapters::{RasterStackerAdapter, StreamBundle};
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::RasterInputsMustHaveSameSpatialReferenceAndDatatype;
9
use crate::util::Result;
10
use async_trait::async_trait;
11
use futures::future::join_all;
12
use futures::stream::BoxStream;
13
use geoengine_datatypes::primitives::{
14
    partitions_extent, time_interval_extent, BandSelection, RasterQueryRectangle, SpatialResolution,
15
};
16
use geoengine_datatypes::raster::{DynamicRasterDataType, Pixel, RasterTile2D};
17
use serde::{Deserialize, Serialize};
18
use snafu::ensure;
19

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

24
/// This `QueryProcessor` stacks all of it's inputs into a single raster time-series.
25
/// It does so by querying all of it's inputs outputting them by band, space and then time.
26
///
27
/// All inputs must have the same data type and spatial reference.
28
// TODO: temporal alignment or do that beforehand?
29
//     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
30
//      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
31
pub type RasterStacker = Operator<RasterStackerParams, MultipleRasterSources>;
32

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

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

47
        // TODO: ensure at least two inputs
48

49
        // TODO: verify all inputs have same data type and spatial reference
50

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

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

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

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

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

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

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

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

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

NEW
110
    span_fn!(RasterStacker);
×
111
}
112

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

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

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

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

4✔
135
        let bands_per_source = self.bands_per_source.clone();
4✔
136

4✔
137
        // TODO: use a macro to unpack all the input processor to the same datatype?
4✔
138
        Ok(match datatype {
4✔
139
            geoengine_datatypes::raster::RasterDataType::U8 => {
140
                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✔
141
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
4✔
142
                TypedRasterQueryProcessor::U8(Box::new(p))
4✔
143
            }
144
            geoengine_datatypes::raster::RasterDataType::U16 => {
NEW
145
                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();
×
NEW
146
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
NEW
147
                TypedRasterQueryProcessor::U16(Box::new(p))
×
148
            }
149
            geoengine_datatypes::raster::RasterDataType::U32 => {
NEW
150
                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();
×
NEW
151
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
NEW
152
                TypedRasterQueryProcessor::U32(Box::new(p))
×
153
            }
154
            geoengine_datatypes::raster::RasterDataType::U64 => {
NEW
155
                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();
×
NEW
156
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
NEW
157
                TypedRasterQueryProcessor::U64(Box::new(p))
×
158
            }
159
            geoengine_datatypes::raster::RasterDataType::I8 => {
NEW
160
                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();
×
NEW
161
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
NEW
162
                TypedRasterQueryProcessor::I8(Box::new(p))
×
163
            }
164
            geoengine_datatypes::raster::RasterDataType::I16 => {
NEW
165
                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();
×
NEW
166
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
NEW
167
                TypedRasterQueryProcessor::I16(Box::new(p))
×
168
            }
169
            geoengine_datatypes::raster::RasterDataType::I32 => {
NEW
170
                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();
×
NEW
171
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
NEW
172
                TypedRasterQueryProcessor::I32(Box::new(p))
×
173
            }
174
            geoengine_datatypes::raster::RasterDataType::I64 => {
NEW
175
                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();
×
NEW
176
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
NEW
177
                TypedRasterQueryProcessor::I64(Box::new(p))
×
178
            }
179
            geoengine_datatypes::raster::RasterDataType::F32 => {
NEW
180
                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();
×
NEW
181
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
NEW
182
                TypedRasterQueryProcessor::F32(Box::new(p))
×
183
            }
184
            geoengine_datatypes::raster::RasterDataType::F64 => {
NEW
185
                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();
×
NEW
186
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
NEW
187
                TypedRasterQueryProcessor::F64(Box::new(p))
×
188
            }
189
        })
190
    }
4✔
191

NEW
192
    fn canonic_name(&self) -> CanonicOperatorName {
×
NEW
193
        self.name.clone()
×
NEW
194
    }
×
195
}
196

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

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

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

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

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

17✔
235
    Some(BandSelection::new(bands))
17✔
236
}
27✔
237

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

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

259
            let mut source_query = query.clone();
13✔
260
            source_query.attributes = bands.clone();
13✔
261
            source_stream_futures.push(async move { source.raster_query(source_query, ctx).await });
13✔
262
            selected_bands_per_source.push(bands.count());
13✔
263
        }
264

265
        let source_streams = join_all(source_stream_futures)
11✔
NEW
266
            .await
×
267
            .into_iter()
11✔
268
            .collect::<Result<Vec<_>>>()?;
11✔
269

270
        let stream_bundles = source_streams
11✔
271
            .into_iter()
11✔
272
            .zip(selected_bands_per_source)
11✔
273
            .map(Into::into)
11✔
274
            .collect::<Vec<StreamBundle<_>>>();
11✔
275

276
        let output = RasterStackerAdapter::new(stream_bundles)?;
11✔
277

278
        Ok(Box::pin(output))
11✔
279
    }
22✔
280
}
281

282
#[cfg(test)]
283
mod tests {
284
    use futures::StreamExt;
285
    use geoengine_datatypes::{
286
        primitives::{CacheHint, SpatialPartition2D, TimeInterval},
287
        raster::{Grid, GridShape, RasterDataType, TilesEqualIgnoringCacheHint},
288
        spatial_reference::SpatialReference,
289
        util::test::TestDefault,
290
    };
291

292
    use crate::{
293
        engine::{
294
            MockExecutionContext, MockQueryContext, RasterBandDescriptor, RasterBandDescriptors,
295
        },
296
        mock::{MockRasterSource, MockRasterSourceParams},
297
    };
298

299
    use super::*;
300

301
    #[test]
1✔
302
    fn it_maps_query_bands_to_source_bands() {
1✔
303
        assert_eq!(
1✔
304
            map_query_bands_to_source_bands(&0.into(), &[2, 1], 0),
1✔
305
            Some(0.into())
1✔
306
        );
1✔
307
        assert_eq!(map_query_bands_to_source_bands(&0.into(), &[2, 1], 1), None);
1✔
308
        assert_eq!(
1✔
309
            map_query_bands_to_source_bands(&2.into(), &[2, 1], 1),
1✔
310
            Some(0.into())
1✔
311
        );
1✔
312

313
        assert_eq!(
1✔
314
            map_query_bands_to_source_bands(&[1, 2].into(), &[2, 2], 0),
1✔
315
            Some(1.into())
1✔
316
        );
1✔
317
        assert_eq!(
1✔
318
            map_query_bands_to_source_bands(&[1, 2, 3].into(), &[2, 2], 1),
1✔
319
            Some([0, 1].into())
1✔
320
        );
1✔
321
    }
1✔
322

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

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

1✔
414
        let mrs1 = MockRasterSource {
1✔
415
            params: MockRasterSourceParams {
1✔
416
                data: data.clone(),
1✔
417
                result_descriptor: RasterResultDescriptor {
1✔
418
                    data_type: RasterDataType::U8,
1✔
419
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
420
                    time: None,
1✔
421
                    bbox: None,
1✔
422
                    resolution: None,
1✔
423
                    bands: RasterBandDescriptors::new_single_band(),
1✔
424
                },
1✔
425
            },
1✔
426
        }
1✔
427
        .boxed();
1✔
428

1✔
429
        let mrs2 = MockRasterSource {
1✔
430
            params: MockRasterSourceParams {
1✔
431
                data: data2.clone(),
1✔
432
                result_descriptor: RasterResultDescriptor {
1✔
433
                    data_type: RasterDataType::U8,
1✔
434
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
435
                    time: None,
1✔
436
                    bbox: None,
1✔
437
                    resolution: None,
1✔
438
                    bands: RasterBandDescriptors::new_single_band(),
1✔
439
                },
1✔
440
            },
1✔
441
        }
1✔
442
        .boxed();
1✔
443

1✔
444
        let stacker = RasterStacker {
1✔
445
            params: RasterStackerParams {},
1✔
446
            sources: MultipleRasterSources {
1✔
447
                rasters: vec![mrs1, mrs2],
1✔
448
            },
1✔
449
        }
1✔
450
        .boxed();
1✔
451

1✔
452
        let mut exe_ctx = MockExecutionContext::test_default();
1✔
453
        exe_ctx.tiling_specification.tile_size_in_pixels = GridShape {
1✔
454
            shape_array: [2, 2],
1✔
455
        };
1✔
456

1✔
457
        let query_rect = RasterQueryRectangle {
1✔
458
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 1.).into(), (3., 0.).into()),
1✔
459
            time_interval: TimeInterval::new_unchecked(0, 10),
1✔
460
            spatial_resolution: SpatialResolution::one(),
1✔
461
            attributes: [0, 1].into(),
1✔
462
        };
1✔
463

1✔
464
        let query_ctx = MockQueryContext::test_default();
1✔
465

466
        let op = stacker
1✔
467
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
NEW
468
            .await
×
469
            .unwrap();
1✔
470

1✔
471
        let qp = op.query_processor().unwrap().get_u8().unwrap();
1✔
472

473
        let result = qp
1✔
474
            .raster_query(query_rect, &query_ctx)
1✔
NEW
475
            .await
×
476
            .unwrap()
1✔
477
            .collect::<Vec<_>>()
1✔
NEW
478
            .await;
×
479
        let result = result.into_iter().collect::<Result<Vec<_>>>().unwrap();
1✔
480

1✔
481
        let expected: Vec<_> = data
1✔
482
            .into_iter()
1✔
483
            .zip(data2.into_iter().map(|mut tile| {
4✔
484
                tile.band = 1;
4✔
485
                tile
4✔
486
            }))
4✔
487
            .flat_map(|(a, b)| vec![a.clone(), b.clone()])
4✔
488
            .collect();
1✔
489

1✔
490
        assert!(expected.tiles_equal_ignoring_cache_hint(&result));
1✔
491
    }
492

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

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

1✔
666
        let mrs1 = MockRasterSource {
1✔
667
            params: MockRasterSourceParams {
1✔
668
                data: data.clone(),
1✔
669
                result_descriptor: RasterResultDescriptor {
1✔
670
                    data_type: RasterDataType::U8,
1✔
671
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
672
                    time: None,
1✔
673
                    bbox: None,
1✔
674
                    resolution: None,
1✔
675
                    bands: RasterBandDescriptors::new(vec![
1✔
676
                        RasterBandDescriptor::new_unitless("band_0".into()),
1✔
677
                        RasterBandDescriptor::new_unitless("band_1".into()),
1✔
678
                    ])
1✔
679
                    .unwrap(),
1✔
680
                },
1✔
681
            },
1✔
682
        }
1✔
683
        .boxed();
1✔
684

1✔
685
        let mrs2 = MockRasterSource {
1✔
686
            params: MockRasterSourceParams {
1✔
687
                data: data2.clone(),
1✔
688
                result_descriptor: RasterResultDescriptor {
1✔
689
                    data_type: RasterDataType::U8,
1✔
690
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
691
                    time: None,
1✔
692
                    bbox: None,
1✔
693
                    resolution: None,
1✔
694
                    bands: RasterBandDescriptors::new(vec![
1✔
695
                        RasterBandDescriptor::new_unitless("band_0".into()),
1✔
696
                        RasterBandDescriptor::new_unitless("band_1".into()),
1✔
697
                    ])
1✔
698
                    .unwrap(),
1✔
699
                },
1✔
700
            },
1✔
701
        }
1✔
702
        .boxed();
1✔
703

1✔
704
        let stacker = RasterStacker {
1✔
705
            params: RasterStackerParams {},
1✔
706
            sources: MultipleRasterSources {
1✔
707
                rasters: vec![mrs1, mrs2],
1✔
708
            },
1✔
709
        }
1✔
710
        .boxed();
1✔
711

1✔
712
        let mut exe_ctx = MockExecutionContext::test_default();
1✔
713
        exe_ctx.tiling_specification.tile_size_in_pixels = GridShape {
1✔
714
            shape_array: [2, 2],
1✔
715
        };
1✔
716

1✔
717
        let query_rect = RasterQueryRectangle {
1✔
718
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 1.).into(), (3., 0.).into()),
1✔
719
            time_interval: TimeInterval::new_unchecked(0, 10),
1✔
720
            spatial_resolution: SpatialResolution::one(),
1✔
721
            attributes: [0, 1, 2, 3].into(),
1✔
722
        };
1✔
723

1✔
724
        let query_ctx = MockQueryContext::test_default();
1✔
725

726
        let op = stacker
1✔
727
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
NEW
728
            .await
×
729
            .unwrap();
1✔
730

1✔
731
        let qp = op.query_processor().unwrap().get_u8().unwrap();
1✔
732

733
        let result = qp
1✔
734
            .raster_query(query_rect, &query_ctx)
1✔
NEW
735
            .await
×
736
            .unwrap()
1✔
737
            .collect::<Vec<_>>()
1✔
NEW
738
            .await;
×
739
        let result = result.into_iter().collect::<Result<Vec<_>>>().unwrap();
1✔
740

1✔
741
        let expected: Vec<_> = data
1✔
742
            .chunks(2)
1✔
743
            .zip(
1✔
744
                data2
1✔
745
                    .into_iter()
1✔
746
                    .map(|mut tile| {
8✔
747
                        tile.band += 2;
8✔
748
                        tile
8✔
749
                    })
8✔
750
                    .collect::<Vec<_>>()
1✔
751
                    .chunks(2),
1✔
752
            )
1✔
753
            .flat_map(|(chunk1, chunk2)| chunk1.iter().chain(chunk2.iter()))
4✔
754
            .cloned()
1✔
755
            .collect();
1✔
756

1✔
757
        assert!(expected.tiles_equal_ignoring_cache_hint(&result));
1✔
758
    }
759

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

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

1✔
851
        let mrs1 = MockRasterSource {
1✔
852
            params: MockRasterSourceParams {
1✔
853
                data: data.clone(),
1✔
854
                result_descriptor: RasterResultDescriptor {
1✔
855
                    data_type: RasterDataType::U8,
1✔
856
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
857
                    time: None,
1✔
858
                    bbox: None,
1✔
859
                    resolution: None,
1✔
860
                    bands: RasterBandDescriptors::new_single_band(),
1✔
861
                },
1✔
862
            },
1✔
863
        }
1✔
864
        .boxed();
1✔
865

1✔
866
        let mrs2 = MockRasterSource {
1✔
867
            params: MockRasterSourceParams {
1✔
868
                data: data2.clone(),
1✔
869
                result_descriptor: RasterResultDescriptor {
1✔
870
                    data_type: RasterDataType::U8,
1✔
871
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
872
                    time: None,
1✔
873
                    bbox: None,
1✔
874
                    resolution: None,
1✔
875
                    bands: RasterBandDescriptors::new_single_band(),
1✔
876
                },
1✔
877
            },
1✔
878
        }
1✔
879
        .boxed();
1✔
880

1✔
881
        let stacker = RasterStacker {
1✔
882
            params: RasterStackerParams {},
1✔
883
            sources: MultipleRasterSources {
1✔
884
                rasters: vec![mrs1, mrs2],
1✔
885
            },
1✔
886
        }
1✔
887
        .boxed();
1✔
888

1✔
889
        let mut exe_ctx = MockExecutionContext::test_default();
1✔
890
        exe_ctx.tiling_specification.tile_size_in_pixels = GridShape {
1✔
891
            shape_array: [2, 2],
1✔
892
        };
1✔
893

1✔
894
        let query_rect = RasterQueryRectangle {
1✔
895
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 1.).into(), (3., 0.).into()),
1✔
896
            time_interval: TimeInterval::new_unchecked(0, 10),
1✔
897
            spatial_resolution: SpatialResolution::one(),
1✔
898
            attributes: 1.into(),
1✔
899
        };
1✔
900

1✔
901
        let query_ctx = MockQueryContext::test_default();
1✔
902

903
        let op = stacker
1✔
904
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
NEW
905
            .await
×
906
            .unwrap();
1✔
907

1✔
908
        let qp = op.query_processor().unwrap().get_u8().unwrap();
1✔
909

910
        let result = qp
1✔
911
            .raster_query(query_rect, &query_ctx)
1✔
NEW
912
            .await
×
913
            .unwrap()
1✔
914
            .collect::<Vec<_>>()
1✔
NEW
915
            .await;
×
916
        let result = result.into_iter().collect::<Result<Vec<_>>>().unwrap();
1✔
917

1✔
918
        assert!(data2.tiles_equal_ignoring_cache_hint(&result));
1✔
919
    }
920
}
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