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

geo-engine / geoengine / 7276350647

20 Dec 2023 01:21PM UTC coverage: 89.798% (-0.03%) from 89.823%
7276350647

push

github

web-flow
Merge pull request #906 from geo-engine/raster_result_describer

result descriptors for query processors

1080 of 1240 new or added lines in 43 files covered. (87.1%)

11 existing lines in 3 files now uncovered.

115920 of 129090 relevant lines covered (89.8%)

58689.64 hits per line

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

90.93
/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
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6✔
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
/// The tiles are automatically temporally aligned.
27
///
28
/// All inputs must have the same data type and spatial reference.
29
pub type RasterStacker = Operator<RasterStackerParams, MultipleRasterSources>;
30

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

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

45
        ensure!(
5✔
46
            !self.sources.rasters.is_empty() && self.sources.rasters.len() <= 8,
5✔
47
            InvalidNumberOfRasterStackerInputs
×
48
        );
49

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

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

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

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

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

5✔
82
        let bands_per_source = in_descriptors
5✔
83
            .iter()
5✔
84
            .map(|d| d.bands.count())
10✔
85
            .collect::<Vec<_>>();
5✔
86

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

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

5✔
101
        Ok(Box::new(InitializedRasterStacker {
5✔
102
            name,
5✔
103
            result_descriptor,
5✔
104
            raster_sources,
5✔
105
            bands_per_source,
5✔
106
        }))
5✔
107
    }
10✔
108

109
    span_fn!(RasterStacker);
×
110
}
111

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

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

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

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

5✔
134
        let bands_per_source = self.bands_per_source.clone();
5✔
135

5✔
136
        // TODO: use a macro to unpack all the input processor to the same datatype?
5✔
137
        Ok(match datatype {
5✔
138
            geoengine_datatypes::raster::RasterDataType::U8 => {
139
                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();
10✔
140
                let p = RasterStackerProcessor::new(
5✔
141
                    inputs,
5✔
142
                    self.result_descriptor.clone(),
5✔
143
                    bands_per_source,
5✔
144
                );
5✔
145
                TypedRasterQueryProcessor::U8(Box::new(p))
5✔
146
            }
147
            geoengine_datatypes::raster::RasterDataType::U16 => {
148
                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
149
                let p = RasterStackerProcessor::new(
×
NEW
150
                    inputs,
×
NEW
151
                    self.result_descriptor.clone(),
×
NEW
152
                    bands_per_source,
×
NEW
153
                );
×
UNCOV
154
                TypedRasterQueryProcessor::U16(Box::new(p))
×
155
            }
156
            geoengine_datatypes::raster::RasterDataType::U32 => {
157
                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
158
                let p = RasterStackerProcessor::new(
×
NEW
159
                    inputs,
×
NEW
160
                    self.result_descriptor.clone(),
×
NEW
161
                    bands_per_source,
×
NEW
162
                );
×
UNCOV
163
                TypedRasterQueryProcessor::U32(Box::new(p))
×
164
            }
165
            geoengine_datatypes::raster::RasterDataType::U64 => {
166
                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
167
                let p = RasterStackerProcessor::new(
×
NEW
168
                    inputs,
×
NEW
169
                    self.result_descriptor.clone(),
×
NEW
170
                    bands_per_source,
×
NEW
171
                );
×
UNCOV
172
                TypedRasterQueryProcessor::U64(Box::new(p))
×
173
            }
174
            geoengine_datatypes::raster::RasterDataType::I8 => {
175
                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
176
                let p = RasterStackerProcessor::new(
×
NEW
177
                    inputs,
×
NEW
178
                    self.result_descriptor.clone(),
×
NEW
179
                    bands_per_source,
×
NEW
180
                );
×
UNCOV
181
                TypedRasterQueryProcessor::I8(Box::new(p))
×
182
            }
183
            geoengine_datatypes::raster::RasterDataType::I16 => {
184
                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
185
                let p = RasterStackerProcessor::new(
×
NEW
186
                    inputs,
×
NEW
187
                    self.result_descriptor.clone(),
×
NEW
188
                    bands_per_source,
×
NEW
189
                );
×
UNCOV
190
                TypedRasterQueryProcessor::I16(Box::new(p))
×
191
            }
192
            geoengine_datatypes::raster::RasterDataType::I32 => {
193
                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
194
                let p = RasterStackerProcessor::new(
×
NEW
195
                    inputs,
×
NEW
196
                    self.result_descriptor.clone(),
×
NEW
197
                    bands_per_source,
×
NEW
198
                );
×
UNCOV
199
                TypedRasterQueryProcessor::I32(Box::new(p))
×
200
            }
201
            geoengine_datatypes::raster::RasterDataType::I64 => {
202
                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
203
                let p = RasterStackerProcessor::new(
×
NEW
204
                    inputs,
×
NEW
205
                    self.result_descriptor.clone(),
×
NEW
206
                    bands_per_source,
×
NEW
207
                );
×
UNCOV
208
                TypedRasterQueryProcessor::I64(Box::new(p))
×
209
            }
210
            geoengine_datatypes::raster::RasterDataType::F32 => {
211
                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
212
                let p = RasterStackerProcessor::new(
×
NEW
213
                    inputs,
×
NEW
214
                    self.result_descriptor.clone(),
×
NEW
215
                    bands_per_source,
×
NEW
216
                );
×
UNCOV
217
                TypedRasterQueryProcessor::F32(Box::new(p))
×
218
            }
219
            geoengine_datatypes::raster::RasterDataType::F64 => {
220
                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
221
                let p = RasterStackerProcessor::new(
×
NEW
222
                    inputs,
×
NEW
223
                    self.result_descriptor.clone(),
×
NEW
224
                    bands_per_source,
×
NEW
225
                );
×
UNCOV
226
                TypedRasterQueryProcessor::F64(Box::new(p))
×
227
            }
228
        })
229
    }
5✔
230

231
    fn canonic_name(&self) -> CanonicOperatorName {
×
232
        self.name.clone()
×
233
    }
×
234
}
235

236
pub(crate) struct RasterStackerProcessor<T> {
237
    sources: Vec<Box<dyn RasterQueryProcessor<RasterType = T>>>,
238
    result_descriptor: RasterResultDescriptor,
239
    bands_per_source: Vec<u32>,
240
}
241

242
impl<T> RasterStackerProcessor<T> {
243
    pub fn new(
5✔
244
        sources: Vec<Box<dyn RasterQueryProcessor<RasterType = T>>>,
5✔
245
        result_descriptor: RasterResultDescriptor,
5✔
246
        bands_per_source: Vec<u32>,
5✔
247
    ) -> Self {
5✔
248
        Self {
5✔
249
            sources,
5✔
250
            result_descriptor,
5✔
251
            bands_per_source,
5✔
252
        }
5✔
253
    }
5✔
254
}
255

256
/// compute the bands in the input source from the bands in a query that uses multiple sources
257
fn map_query_bands_to_source_bands(
33✔
258
    query_bands: &BandSelection,
33✔
259
    bands_per_source: &[u32],
33✔
260
    source_index: usize,
33✔
261
) -> Option<BandSelection> {
33✔
262
    let source_start: u32 = bands_per_source.iter().take(source_index).sum();
33✔
263
    let source_bands = bands_per_source[source_index];
33✔
264
    let source_end = source_start + source_bands;
33✔
265

33✔
266
    let bands = query_bands
33✔
267
        .as_slice()
33✔
268
        .iter()
33✔
269
        .filter(|output_band| **output_band >= source_start && **output_band < source_end)
46✔
270
        .map(|output_band| output_band - source_start)
33✔
271
        .collect::<Vec<_>>();
33✔
272

33✔
273
    if bands.is_empty() {
33✔
274
        return None;
12✔
275
    }
21✔
276

21✔
277
    Some(BandSelection::new_unchecked(bands))
21✔
278
}
33✔
279

280
#[async_trait]
281
impl<T> RasterQueryProcessor for RasterStackerProcessor<T>
282
where
283
    T: Pixel,
284
{
285
    type RasterType = T;
286
    async fn raster_query<'a>(
14✔
287
        &'a self,
14✔
288
        query: RasterQueryRectangle,
14✔
289
        ctx: &'a dyn QueryContext,
14✔
290
    ) -> Result<BoxStream<'a, Result<RasterTile2D<T>>>> {
14✔
291
        let mut sources = vec![];
14✔
292

293
        for (idx, source) in self.sources.iter().enumerate() {
28✔
294
            let Some(bands) =
17✔
295
                map_query_bands_to_source_bands(&query.attributes, &self.bands_per_source, idx)
28✔
296
            else {
297
                continue;
11✔
298
            };
299

300
            let mut source_query = query.clone();
17✔
301
            source_query.attributes = bands.clone();
17✔
302
            sources.push(RasterStackerSource {
17✔
303
                queryable: QueryWrapper { p: source, ctx },
17✔
304
                band_idxs: bands.as_vec(),
17✔
305
            });
17✔
306
        }
307

308
        let output = RasterStackerAdapter::new(sources, query.into());
14✔
309

14✔
310
        Ok(Box::pin(output))
14✔
311
    }
28✔
312

313
    fn raster_result_descriptor(&self) -> &RasterResultDescriptor {
14✔
314
        &self.result_descriptor
14✔
315
    }
14✔
316
}
317

318
#[cfg(test)]
319
mod tests {
320
    use std::str::FromStr;
321

322
    use futures::StreamExt;
323
    use geoengine_datatypes::{
324
        primitives::{CacheHint, SpatialPartition2D, TimeInstance, TimeInterval},
325
        raster::{Grid, GridShape, RasterDataType, TilesEqualIgnoringCacheHint},
326
        spatial_reference::SpatialReference,
327
        util::test::TestDefault,
328
    };
329

330
    use crate::{
331
        engine::{
332
            MockExecutionContext, MockQueryContext, RasterBandDescriptor, RasterBandDescriptors,
333
        },
334
        mock::{MockRasterSource, MockRasterSourceParams},
335
        processing::{Expression, ExpressionParams, ExpressionSources},
336
        source::{GdalSource, GdalSourceParameters},
337
        util::gdal::add_ndvi_dataset,
338
    };
339

340
    use super::*;
341

342
    #[test]
1✔
343
    fn it_maps_query_bands_to_source_bands() {
1✔
344
        assert_eq!(
1✔
345
            map_query_bands_to_source_bands(&0.into(), &[2, 1], 0),
1✔
346
            Some(0.into())
1✔
347
        );
1✔
348
        assert_eq!(map_query_bands_to_source_bands(&0.into(), &[2, 1], 1), None);
1✔
349
        assert_eq!(
1✔
350
            map_query_bands_to_source_bands(&2.into(), &[2, 1], 1),
1✔
351
            Some(0.into())
1✔
352
        );
1✔
353

354
        assert_eq!(
1✔
355
            map_query_bands_to_source_bands(&[1, 2].try_into().unwrap(), &[2, 2], 0),
1✔
356
            Some(1.into())
1✔
357
        );
1✔
358
        assert_eq!(
1✔
359
            map_query_bands_to_source_bands(&[1, 2, 3].try_into().unwrap(), &[2, 2], 1),
1✔
360
            Some([0, 1].try_into().unwrap())
1✔
361
        );
1✔
362
    }
1✔
363

364
    #[tokio::test]
1✔
365
    #[allow(clippy::too_many_lines)]
366
    async fn it_stacks() {
1✔
367
        let data: 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![0, 1, 2, 3]).unwrap().into(),
1✔
374
                properties: Default::default(),
1✔
375
                cache_hint: CacheHint::default(),
1✔
376
            },
1✔
377
            RasterTile2D {
1✔
378
                time: TimeInterval::new_unchecked(0, 5),
1✔
379
                tile_position: [-1, 1].into(),
1✔
380
                band: 0,
1✔
381
                global_geo_transform: TestDefault::test_default(),
1✔
382
                grid_array: Grid::new([2, 2].into(), vec![4, 5, 6, 7]).unwrap().into(),
1✔
383
                properties: Default::default(),
1✔
384
                cache_hint: CacheHint::default(),
1✔
385
            },
1✔
386
            RasterTile2D {
1✔
387
                time: TimeInterval::new_unchecked(5, 10),
1✔
388
                tile_position: [-1, 0].into(),
1✔
389
                band: 0,
1✔
390
                global_geo_transform: TestDefault::test_default(),
1✔
391
                grid_array: Grid::new([2, 2].into(), vec![8, 9, 10, 11]).unwrap().into(),
1✔
392
                properties: Default::default(),
1✔
393
                cache_hint: CacheHint::default(),
1✔
394
            },
1✔
395
            RasterTile2D {
1✔
396
                time: TimeInterval::new_unchecked(5, 10),
1✔
397
                tile_position: [-1, 1].into(),
1✔
398
                band: 0,
1✔
399
                global_geo_transform: TestDefault::test_default(),
1✔
400
                grid_array: Grid::new([2, 2].into(), vec![12, 13, 14, 15])
1✔
401
                    .unwrap()
1✔
402
                    .into(),
1✔
403
                properties: Default::default(),
1✔
404
                cache_hint: CacheHint::default(),
1✔
405
            },
1✔
406
        ];
1✔
407

1✔
408
        let data2: Vec<RasterTile2D<u8>> = vec![
1✔
409
            RasterTile2D {
1✔
410
                time: TimeInterval::new_unchecked(0, 5),
1✔
411
                tile_position: [-1, 0].into(),
1✔
412
                band: 0,
1✔
413
                global_geo_transform: TestDefault::test_default(),
1✔
414
                grid_array: Grid::new([2, 2].into(), vec![16, 17, 18, 19])
1✔
415
                    .unwrap()
1✔
416
                    .into(),
1✔
417
                properties: Default::default(),
1✔
418
                cache_hint: CacheHint::default(),
1✔
419
            },
1✔
420
            RasterTile2D {
1✔
421
                time: TimeInterval::new_unchecked(0, 5),
1✔
422
                tile_position: [-1, 1].into(),
1✔
423
                band: 0,
1✔
424
                global_geo_transform: TestDefault::test_default(),
1✔
425
                grid_array: Grid::new([2, 2].into(), vec![20, 21, 22, 23])
1✔
426
                    .unwrap()
1✔
427
                    .into(),
1✔
428
                properties: Default::default(),
1✔
429
                cache_hint: CacheHint::default(),
1✔
430
            },
1✔
431
            RasterTile2D {
1✔
432
                time: TimeInterval::new_unchecked(5, 10),
1✔
433
                tile_position: [-1, 0].into(),
1✔
434
                band: 0,
1✔
435
                global_geo_transform: TestDefault::test_default(),
1✔
436
                grid_array: Grid::new([2, 2].into(), vec![24, 25, 26, 27])
1✔
437
                    .unwrap()
1✔
438
                    .into(),
1✔
439
                properties: Default::default(),
1✔
440
                cache_hint: CacheHint::default(),
1✔
441
            },
1✔
442
            RasterTile2D {
1✔
443
                time: TimeInterval::new_unchecked(5, 10),
1✔
444
                tile_position: [-1, 1].into(),
1✔
445
                band: 0,
1✔
446
                global_geo_transform: TestDefault::test_default(),
1✔
447
                grid_array: Grid::new([2, 2].into(), vec![28, 29, 30, 31])
1✔
448
                    .unwrap()
1✔
449
                    .into(),
1✔
450
                properties: Default::default(),
1✔
451
                cache_hint: CacheHint::default(),
1✔
452
            },
1✔
453
        ];
1✔
454

1✔
455
        let mrs1 = MockRasterSource {
1✔
456
            params: MockRasterSourceParams {
1✔
457
                data: data.clone(),
1✔
458
                result_descriptor: RasterResultDescriptor {
1✔
459
                    data_type: RasterDataType::U8,
1✔
460
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
461
                    time: None,
1✔
462
                    bbox: None,
1✔
463
                    resolution: None,
1✔
464
                    bands: RasterBandDescriptors::new_single_band(),
1✔
465
                },
1✔
466
            },
1✔
467
        }
1✔
468
        .boxed();
1✔
469

1✔
470
        let mrs2 = MockRasterSource {
1✔
471
            params: MockRasterSourceParams {
1✔
472
                data: data2.clone(),
1✔
473
                result_descriptor: RasterResultDescriptor {
1✔
474
                    data_type: RasterDataType::U8,
1✔
475
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
476
                    time: None,
1✔
477
                    bbox: None,
1✔
478
                    resolution: None,
1✔
479
                    bands: RasterBandDescriptors::new_single_band(),
1✔
480
                },
1✔
481
            },
1✔
482
        }
1✔
483
        .boxed();
1✔
484

1✔
485
        let stacker = RasterStacker {
1✔
486
            params: RasterStackerParams {},
1✔
487
            sources: MultipleRasterSources {
1✔
488
                rasters: vec![mrs1, mrs2],
1✔
489
            },
1✔
490
        }
1✔
491
        .boxed();
1✔
492

1✔
493
        let mut exe_ctx = MockExecutionContext::test_default();
1✔
494
        exe_ctx.tiling_specification.tile_size_in_pixels = GridShape {
1✔
495
            shape_array: [2, 2],
1✔
496
        };
1✔
497

1✔
498
        let query_rect = RasterQueryRectangle {
1✔
499
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 1.).into(), (3., 0.).into()),
1✔
500
            time_interval: TimeInterval::new_unchecked(0, 10),
1✔
501
            spatial_resolution: SpatialResolution::one(),
1✔
502
            attributes: [0, 1].try_into().unwrap(),
1✔
503
        };
1✔
504

1✔
505
        let query_ctx = MockQueryContext::test_default();
1✔
506

507
        let op = stacker
1✔
508
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
509
            .await
×
510
            .unwrap();
1✔
511

1✔
512
        let qp = op.query_processor().unwrap().get_u8().unwrap();
1✔
513

514
        let result = qp
1✔
515
            .raster_query(query_rect, &query_ctx)
1✔
516
            .await
×
517
            .unwrap()
1✔
518
            .collect::<Vec<_>>()
1✔
519
            .await;
×
520
        let result = result.into_iter().collect::<Result<Vec<_>>>().unwrap();
1✔
521

1✔
522
        let expected: Vec<_> = data
1✔
523
            .into_iter()
1✔
524
            .zip(data2.into_iter().map(|mut tile| {
4✔
525
                tile.band = 1;
4✔
526
                tile
4✔
527
            }))
4✔
528
            .flat_map(|(a, b)| vec![a.clone(), b.clone()])
4✔
529
            .collect();
1✔
530

1✔
531
        assert!(expected.tiles_equal_ignoring_cache_hint(&result));
1✔
532
    }
533

534
    #[tokio::test]
1✔
535
    #[allow(clippy::too_many_lines)]
536
    async fn it_stacks_stacks() {
1✔
537
        let data: Vec<RasterTile2D<u8>> = vec![
1✔
538
            RasterTile2D {
1✔
539
                time: TimeInterval::new_unchecked(0, 5),
1✔
540
                tile_position: [-1, 0].into(),
1✔
541
                band: 0,
1✔
542
                global_geo_transform: TestDefault::test_default(),
1✔
543
                grid_array: Grid::new([2, 2].into(), vec![0, 1, 2, 3]).unwrap().into(),
1✔
544
                properties: Default::default(),
1✔
545
                cache_hint: CacheHint::default(),
1✔
546
            },
1✔
547
            RasterTile2D {
1✔
548
                time: TimeInterval::new_unchecked(0, 5),
1✔
549
                tile_position: [-1, 0].into(),
1✔
550
                band: 1,
1✔
551
                global_geo_transform: TestDefault::test_default(),
1✔
552
                grid_array: Grid::new([2, 2].into(), vec![3, 2, 1, 0]).unwrap().into(),
1✔
553
                properties: Default::default(),
1✔
554
                cache_hint: CacheHint::default(),
1✔
555
            },
1✔
556
            RasterTile2D {
1✔
557
                time: TimeInterval::new_unchecked(0, 5),
1✔
558
                tile_position: [-1, 1].into(),
1✔
559
                band: 0,
1✔
560
                global_geo_transform: TestDefault::test_default(),
1✔
561
                grid_array: Grid::new([2, 2].into(), vec![4, 5, 6, 7]).unwrap().into(),
1✔
562
                properties: Default::default(),
1✔
563
                cache_hint: CacheHint::default(),
1✔
564
            },
1✔
565
            RasterTile2D {
1✔
566
                time: TimeInterval::new_unchecked(0, 5),
1✔
567
                tile_position: [-1, 1].into(),
1✔
568
                band: 1,
1✔
569
                global_geo_transform: TestDefault::test_default(),
1✔
570
                grid_array: Grid::new([2, 2].into(), vec![7, 6, 5, 4]).unwrap().into(),
1✔
571
                properties: Default::default(),
1✔
572
                cache_hint: CacheHint::default(),
1✔
573
            },
1✔
574
            RasterTile2D {
1✔
575
                time: TimeInterval::new_unchecked(5, 10),
1✔
576
                tile_position: [-1, 0].into(),
1✔
577
                band: 0,
1✔
578
                global_geo_transform: TestDefault::test_default(),
1✔
579
                grid_array: Grid::new([2, 2].into(), vec![8, 9, 10, 11]).unwrap().into(),
1✔
580
                properties: Default::default(),
1✔
581
                cache_hint: CacheHint::default(),
1✔
582
            },
1✔
583
            RasterTile2D {
1✔
584
                time: TimeInterval::new_unchecked(5, 10),
1✔
585
                tile_position: [-1, 0].into(),
1✔
586
                band: 1,
1✔
587
                global_geo_transform: TestDefault::test_default(),
1✔
588
                grid_array: Grid::new([2, 2].into(), vec![11, 10, 9, 8]).unwrap().into(),
1✔
589
                properties: Default::default(),
1✔
590
                cache_hint: CacheHint::default(),
1✔
591
            },
1✔
592
            RasterTile2D {
1✔
593
                time: TimeInterval::new_unchecked(5, 10),
1✔
594
                tile_position: [-1, 1].into(),
1✔
595
                band: 0,
1✔
596
                global_geo_transform: TestDefault::test_default(),
1✔
597
                grid_array: Grid::new([2, 2].into(), vec![12, 13, 14, 15])
1✔
598
                    .unwrap()
1✔
599
                    .into(),
1✔
600
                properties: Default::default(),
1✔
601
                cache_hint: CacheHint::default(),
1✔
602
            },
1✔
603
            RasterTile2D {
1✔
604
                time: TimeInterval::new_unchecked(5, 10),
1✔
605
                tile_position: [-1, 1].into(),
1✔
606
                band: 1,
1✔
607
                global_geo_transform: TestDefault::test_default(),
1✔
608
                grid_array: Grid::new([2, 2].into(), vec![15, 14, 13, 12])
1✔
609
                    .unwrap()
1✔
610
                    .into(),
1✔
611
                properties: Default::default(),
1✔
612
                cache_hint: CacheHint::default(),
1✔
613
            },
1✔
614
        ];
1✔
615

1✔
616
        let data2: Vec<RasterTile2D<u8>> = vec![
1✔
617
            RasterTile2D {
1✔
618
                time: TimeInterval::new_unchecked(0, 5),
1✔
619
                tile_position: [-1, 0].into(),
1✔
620
                band: 0,
1✔
621
                global_geo_transform: TestDefault::test_default(),
1✔
622
                grid_array: Grid::new([2, 2].into(), vec![16, 17, 18, 19])
1✔
623
                    .unwrap()
1✔
624
                    .into(),
1✔
625
                properties: Default::default(),
1✔
626
                cache_hint: CacheHint::default(),
1✔
627
            },
1✔
628
            RasterTile2D {
1✔
629
                time: TimeInterval::new_unchecked(0, 5),
1✔
630
                tile_position: [-1, 0].into(),
1✔
631
                band: 1,
1✔
632
                global_geo_transform: TestDefault::test_default(),
1✔
633
                grid_array: Grid::new([2, 2].into(), vec![19, 18, 17, 16])
1✔
634
                    .unwrap()
1✔
635
                    .into(),
1✔
636
                properties: Default::default(),
1✔
637
                cache_hint: CacheHint::default(),
1✔
638
            },
1✔
639
            RasterTile2D {
1✔
640
                time: TimeInterval::new_unchecked(0, 5),
1✔
641
                tile_position: [-1, 1].into(),
1✔
642
                band: 0,
1✔
643
                global_geo_transform: TestDefault::test_default(),
1✔
644
                grid_array: Grid::new([2, 2].into(), vec![20, 21, 22, 23])
1✔
645
                    .unwrap()
1✔
646
                    .into(),
1✔
647
                properties: Default::default(),
1✔
648
                cache_hint: CacheHint::default(),
1✔
649
            },
1✔
650
            RasterTile2D {
1✔
651
                time: TimeInterval::new_unchecked(0, 5),
1✔
652
                tile_position: [-1, 1].into(),
1✔
653
                band: 1,
1✔
654
                global_geo_transform: TestDefault::test_default(),
1✔
655
                grid_array: Grid::new([2, 2].into(), vec![32, 22, 21, 20])
1✔
656
                    .unwrap()
1✔
657
                    .into(),
1✔
658
                properties: Default::default(),
1✔
659
                cache_hint: CacheHint::default(),
1✔
660
            },
1✔
661
            RasterTile2D {
1✔
662
                time: TimeInterval::new_unchecked(5, 10),
1✔
663
                tile_position: [-1, 0].into(),
1✔
664
                band: 0,
1✔
665
                global_geo_transform: TestDefault::test_default(),
1✔
666
                grid_array: Grid::new([2, 2].into(), vec![24, 25, 26, 27])
1✔
667
                    .unwrap()
1✔
668
                    .into(),
1✔
669
                properties: Default::default(),
1✔
670
                cache_hint: CacheHint::default(),
1✔
671
            },
1✔
672
            RasterTile2D {
1✔
673
                time: TimeInterval::new_unchecked(5, 10),
1✔
674
                tile_position: [-1, 0].into(),
1✔
675
                band: 1,
1✔
676
                global_geo_transform: TestDefault::test_default(),
1✔
677
                grid_array: Grid::new([2, 2].into(), vec![27, 26, 25, 24])
1✔
678
                    .unwrap()
1✔
679
                    .into(),
1✔
680
                properties: Default::default(),
1✔
681
                cache_hint: CacheHint::default(),
1✔
682
            },
1✔
683
            RasterTile2D {
1✔
684
                time: TimeInterval::new_unchecked(5, 10),
1✔
685
                tile_position: [-1, 1].into(),
1✔
686
                band: 0,
1✔
687
                global_geo_transform: TestDefault::test_default(),
1✔
688
                grid_array: Grid::new([2, 2].into(), vec![28, 29, 30, 31])
1✔
689
                    .unwrap()
1✔
690
                    .into(),
1✔
691
                properties: Default::default(),
1✔
692
                cache_hint: CacheHint::default(),
1✔
693
            },
1✔
694
            RasterTile2D {
1✔
695
                time: TimeInterval::new_unchecked(5, 10),
1✔
696
                tile_position: [-1, 1].into(),
1✔
697
                band: 1,
1✔
698
                global_geo_transform: TestDefault::test_default(),
1✔
699
                grid_array: Grid::new([2, 2].into(), vec![31, 30, 39, 28])
1✔
700
                    .unwrap()
1✔
701
                    .into(),
1✔
702
                properties: Default::default(),
1✔
703
                cache_hint: CacheHint::default(),
1✔
704
            },
1✔
705
        ];
1✔
706

1✔
707
        let mrs1 = MockRasterSource {
1✔
708
            params: MockRasterSourceParams {
1✔
709
                data: data.clone(),
1✔
710
                result_descriptor: RasterResultDescriptor {
1✔
711
                    data_type: RasterDataType::U8,
1✔
712
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
713
                    time: None,
1✔
714
                    bbox: None,
1✔
715
                    resolution: None,
1✔
716
                    bands: RasterBandDescriptors::new(vec![
1✔
717
                        RasterBandDescriptor::new_unitless("band_0".into()),
1✔
718
                        RasterBandDescriptor::new_unitless("band_1".into()),
1✔
719
                    ])
1✔
720
                    .unwrap(),
1✔
721
                },
1✔
722
            },
1✔
723
        }
1✔
724
        .boxed();
1✔
725

1✔
726
        let mrs2 = MockRasterSource {
1✔
727
            params: MockRasterSourceParams {
1✔
728
                data: data2.clone(),
1✔
729
                result_descriptor: RasterResultDescriptor {
1✔
730
                    data_type: RasterDataType::U8,
1✔
731
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
732
                    time: None,
1✔
733
                    bbox: None,
1✔
734
                    resolution: None,
1✔
735
                    bands: RasterBandDescriptors::new(vec![
1✔
736
                        RasterBandDescriptor::new_unitless("band_0".into()),
1✔
737
                        RasterBandDescriptor::new_unitless("band_1".into()),
1✔
738
                    ])
1✔
739
                    .unwrap(),
1✔
740
                },
1✔
741
            },
1✔
742
        }
1✔
743
        .boxed();
1✔
744

1✔
745
        let stacker = RasterStacker {
1✔
746
            params: RasterStackerParams {},
1✔
747
            sources: MultipleRasterSources {
1✔
748
                rasters: vec![mrs1, mrs2],
1✔
749
            },
1✔
750
        }
1✔
751
        .boxed();
1✔
752

1✔
753
        let mut exe_ctx = MockExecutionContext::test_default();
1✔
754
        exe_ctx.tiling_specification.tile_size_in_pixels = GridShape {
1✔
755
            shape_array: [2, 2],
1✔
756
        };
1✔
757

1✔
758
        let query_rect = RasterQueryRectangle {
1✔
759
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 1.).into(), (3., 0.).into()),
1✔
760
            time_interval: TimeInterval::new_unchecked(0, 10),
1✔
761
            spatial_resolution: SpatialResolution::one(),
1✔
762
            attributes: [0, 1, 2, 3].try_into().unwrap(),
1✔
763
        };
1✔
764

1✔
765
        let query_ctx = MockQueryContext::test_default();
1✔
766

767
        let op = stacker
1✔
768
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
769
            .await
×
770
            .unwrap();
1✔
771

1✔
772
        let qp = op.query_processor().unwrap().get_u8().unwrap();
1✔
773

774
        let result = qp
1✔
775
            .raster_query(query_rect, &query_ctx)
1✔
776
            .await
×
777
            .unwrap()
1✔
778
            .collect::<Vec<_>>()
1✔
779
            .await;
×
780
        let result = result.into_iter().collect::<Result<Vec<_>>>().unwrap();
1✔
781

1✔
782
        let expected: Vec<_> = data
1✔
783
            .chunks(2)
1✔
784
            .zip(
1✔
785
                data2
1✔
786
                    .into_iter()
1✔
787
                    .map(|mut tile| {
8✔
788
                        tile.band += 2;
8✔
789
                        tile
8✔
790
                    })
8✔
791
                    .collect::<Vec<_>>()
1✔
792
                    .chunks(2),
1✔
793
            )
1✔
794
            .flat_map(|(chunk1, chunk2)| chunk1.iter().chain(chunk2.iter()))
4✔
795
            .cloned()
1✔
796
            .collect();
1✔
797

1✔
798
        assert!(expected.tiles_equal_ignoring_cache_hint(&result));
1✔
799
    }
800

801
    #[tokio::test]
1✔
802
    #[allow(clippy::too_many_lines)]
803
    async fn it_selects_band_from_stack() {
1✔
804
        let data: 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![0, 1, 2, 3]).unwrap().into(),
1✔
811
                properties: Default::default(),
1✔
812
                cache_hint: CacheHint::default(),
1✔
813
            },
1✔
814
            RasterTile2D {
1✔
815
                time: TimeInterval::new_unchecked(0, 5),
1✔
816
                tile_position: [-1, 1].into(),
1✔
817
                band: 0,
1✔
818
                global_geo_transform: TestDefault::test_default(),
1✔
819
                grid_array: Grid::new([2, 2].into(), vec![4, 5, 6, 7]).unwrap().into(),
1✔
820
                properties: Default::default(),
1✔
821
                cache_hint: CacheHint::default(),
1✔
822
            },
1✔
823
            RasterTile2D {
1✔
824
                time: TimeInterval::new_unchecked(5, 10),
1✔
825
                tile_position: [-1, 0].into(),
1✔
826
                band: 0,
1✔
827
                global_geo_transform: TestDefault::test_default(),
1✔
828
                grid_array: Grid::new([2, 2].into(), vec![8, 9, 10, 11]).unwrap().into(),
1✔
829
                properties: Default::default(),
1✔
830
                cache_hint: CacheHint::default(),
1✔
831
            },
1✔
832
            RasterTile2D {
1✔
833
                time: TimeInterval::new_unchecked(5, 10),
1✔
834
                tile_position: [-1, 1].into(),
1✔
835
                band: 0,
1✔
836
                global_geo_transform: TestDefault::test_default(),
1✔
837
                grid_array: Grid::new([2, 2].into(), vec![12, 13, 14, 15])
1✔
838
                    .unwrap()
1✔
839
                    .into(),
1✔
840
                properties: Default::default(),
1✔
841
                cache_hint: CacheHint::default(),
1✔
842
            },
1✔
843
        ];
1✔
844

1✔
845
        let data2: Vec<RasterTile2D<u8>> = vec![
1✔
846
            RasterTile2D {
1✔
847
                time: TimeInterval::new_unchecked(0, 5),
1✔
848
                tile_position: [-1, 0].into(),
1✔
849
                band: 0,
1✔
850
                global_geo_transform: TestDefault::test_default(),
1✔
851
                grid_array: Grid::new([2, 2].into(), vec![16, 17, 18, 19])
1✔
852
                    .unwrap()
1✔
853
                    .into(),
1✔
854
                properties: Default::default(),
1✔
855
                cache_hint: CacheHint::default(),
1✔
856
            },
1✔
857
            RasterTile2D {
1✔
858
                time: TimeInterval::new_unchecked(0, 5),
1✔
859
                tile_position: [-1, 1].into(),
1✔
860
                band: 0,
1✔
861
                global_geo_transform: TestDefault::test_default(),
1✔
862
                grid_array: Grid::new([2, 2].into(), vec![20, 21, 22, 23])
1✔
863
                    .unwrap()
1✔
864
                    .into(),
1✔
865
                properties: Default::default(),
1✔
866
                cache_hint: CacheHint::default(),
1✔
867
            },
1✔
868
            RasterTile2D {
1✔
869
                time: TimeInterval::new_unchecked(5, 10),
1✔
870
                tile_position: [-1, 0].into(),
1✔
871
                band: 0,
1✔
872
                global_geo_transform: TestDefault::test_default(),
1✔
873
                grid_array: Grid::new([2, 2].into(), vec![24, 25, 26, 27])
1✔
874
                    .unwrap()
1✔
875
                    .into(),
1✔
876
                properties: Default::default(),
1✔
877
                cache_hint: CacheHint::default(),
1✔
878
            },
1✔
879
            RasterTile2D {
1✔
880
                time: TimeInterval::new_unchecked(5, 10),
1✔
881
                tile_position: [-1, 1].into(),
1✔
882
                band: 0,
1✔
883
                global_geo_transform: TestDefault::test_default(),
1✔
884
                grid_array: Grid::new([2, 2].into(), vec![28, 29, 30, 31])
1✔
885
                    .unwrap()
1✔
886
                    .into(),
1✔
887
                properties: Default::default(),
1✔
888
                cache_hint: CacheHint::default(),
1✔
889
            },
1✔
890
        ];
1✔
891

1✔
892
        let mrs1 = MockRasterSource {
1✔
893
            params: MockRasterSourceParams {
1✔
894
                data: data.clone(),
1✔
895
                result_descriptor: RasterResultDescriptor {
1✔
896
                    data_type: RasterDataType::U8,
1✔
897
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
898
                    time: None,
1✔
899
                    bbox: None,
1✔
900
                    resolution: None,
1✔
901
                    bands: RasterBandDescriptors::new_single_band(),
1✔
902
                },
1✔
903
            },
1✔
904
        }
1✔
905
        .boxed();
1✔
906

1✔
907
        let mrs2 = MockRasterSource {
1✔
908
            params: MockRasterSourceParams {
1✔
909
                data: data2.clone(),
1✔
910
                result_descriptor: RasterResultDescriptor {
1✔
911
                    data_type: RasterDataType::U8,
1✔
912
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
913
                    time: None,
1✔
914
                    bbox: None,
1✔
915
                    resolution: None,
1✔
916
                    bands: RasterBandDescriptors::new_single_band(),
1✔
917
                },
1✔
918
            },
1✔
919
        }
1✔
920
        .boxed();
1✔
921

1✔
922
        let stacker = RasterStacker {
1✔
923
            params: RasterStackerParams {},
1✔
924
            sources: MultipleRasterSources {
1✔
925
                rasters: vec![mrs1, mrs2],
1✔
926
            },
1✔
927
        }
1✔
928
        .boxed();
1✔
929

1✔
930
        let mut exe_ctx = MockExecutionContext::test_default();
1✔
931
        exe_ctx.tiling_specification.tile_size_in_pixels = GridShape {
1✔
932
            shape_array: [2, 2],
1✔
933
        };
1✔
934

1✔
935
        let query_rect = RasterQueryRectangle {
1✔
936
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 1.).into(), (3., 0.).into()),
1✔
937
            time_interval: TimeInterval::new_unchecked(0, 10),
1✔
938
            spatial_resolution: SpatialResolution::one(),
1✔
939
            attributes: 1.into(),
1✔
940
        };
1✔
941

1✔
942
        let query_ctx = MockQueryContext::test_default();
1✔
943

944
        let op = stacker
1✔
945
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
946
            .await
×
947
            .unwrap();
1✔
948

1✔
949
        let qp = op.query_processor().unwrap().get_u8().unwrap();
1✔
950

951
        let result = qp
1✔
952
            .raster_query(query_rect, &query_ctx)
1✔
953
            .await
×
954
            .unwrap()
1✔
955
            .collect::<Vec<_>>()
1✔
956
            .await;
×
957
        let result = result.into_iter().collect::<Result<Vec<_>>>().unwrap();
1✔
958

1✔
959
        assert!(data2.tiles_equal_ignoring_cache_hint(&result));
1✔
960
    }
961

962
    #[tokio::test]
1✔
963
    #[allow(clippy::too_many_lines)]
964
    async fn it_stacks_ndvi() {
1✔
965
        let mut exe_ctx = MockExecutionContext::test_default();
1✔
966

1✔
967
        let ndvi_id = add_ndvi_dataset(&mut exe_ctx);
1✔
968

1✔
969
        let expression = Expression {
1✔
970
            params: ExpressionParams {
1✔
971
                expression: "if A > 100 { A } else { 0 }".into(),
1✔
972
                output_type: RasterDataType::U8,
1✔
973
                output_measurement: None,
1✔
974
                map_no_data: false,
1✔
975
            },
1✔
976
            sources: ExpressionSources::new_a(
1✔
977
                GdalSource {
1✔
978
                    params: GdalSourceParameters {
1✔
979
                        data: ndvi_id.clone(),
1✔
980
                    },
1✔
981
                }
1✔
982
                .boxed(),
1✔
983
            ),
1✔
984
        }
1✔
985
        .boxed();
1✔
986

1✔
987
        let operator = RasterStacker {
1✔
988
            params: RasterStackerParams {},
1✔
989
            sources: MultipleRasterSources {
1✔
990
                rasters: vec![
1✔
991
                    GdalSource {
1✔
992
                        params: GdalSourceParameters { data: ndvi_id },
1✔
993
                    }
1✔
994
                    .boxed(),
1✔
995
                    expression,
1✔
996
                ],
1✔
997
            },
1✔
998
        }
1✔
999
        .boxed();
1✔
1000

1001
        let operator = operator
1✔
1002
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
1003
            .await
×
1004
            .unwrap();
1✔
1005

1✔
1006
        let processor = operator.query_processor().unwrap().get_u8().unwrap();
1✔
1007

1✔
1008
        let mut exe_ctx = MockExecutionContext::test_default();
1✔
1009
        exe_ctx.tiling_specification.tile_size_in_pixels = GridShape {
1✔
1010
            shape_array: [2, 2],
1✔
1011
        };
1✔
1012

1✔
1013
        let query_ctx = MockQueryContext::test_default();
1✔
1014

1✔
1015
        // query both bands
1✔
1016
        let query_rect = RasterQueryRectangle {
1✔
1017
            spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1018
                (-180., 90.).into(),
1✔
1019
                (180., -90.).into(),
1✔
1020
            ),
1✔
1021
            time_interval: TimeInterval::new_unchecked(
1✔
1022
                TimeInstance::from_str("2014-01-01T00:00:00.000Z").unwrap(),
1✔
1023
                TimeInstance::from_str("2014-01-01T00:00:00.000Z").unwrap(),
1✔
1024
            ),
1✔
1025
            spatial_resolution: SpatialResolution::one(),
1✔
1026
            attributes: [0, 1].try_into().unwrap(),
1✔
1027
        };
1✔
1028

1029
        let result = processor
1✔
1030
            .raster_query(query_rect, &query_ctx)
1✔
1031
            .await
×
1032
            .unwrap()
1✔
1033
            .collect::<Vec<_>>()
1✔
1034
            .await;
15✔
1035
        let result = result.into_iter().collect::<Result<Vec<_>>>().unwrap();
1✔
1036

1✔
1037
        assert!(!result.is_empty());
1✔
1038

1039
        // query only first band
1040
        let query_rect = RasterQueryRectangle {
1✔
1041
            spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1042
                (-180., 90.).into(),
1✔
1043
                (180., -90.).into(),
1✔
1044
            ),
1✔
1045
            time_interval: TimeInterval::new_unchecked(
1✔
1046
                TimeInstance::from_str("2014-01-01T00:00:00.000Z").unwrap(),
1✔
1047
                TimeInstance::from_str("2014-01-01T00:00:00.000Z").unwrap(),
1✔
1048
            ),
1✔
1049
            spatial_resolution: SpatialResolution::one(),
1✔
1050
            attributes: [0].try_into().unwrap(),
1✔
1051
        };
1✔
1052

1053
        let result = processor
1✔
1054
            .raster_query(query_rect, &query_ctx)
1✔
1055
            .await
×
1056
            .unwrap()
1✔
1057
            .collect::<Vec<_>>()
1✔
1058
            .await;
5✔
1059
        let result = result.into_iter().collect::<Result<Vec<_>>>().unwrap();
1✔
1060

1✔
1061
        assert!(!result.is_empty());
1✔
1062

1063
        // query only second band
1064
        let query_rect = RasterQueryRectangle {
1✔
1065
            spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1066
                (-180., 90.).into(),
1✔
1067
                (180., -90.).into(),
1✔
1068
            ),
1✔
1069
            time_interval: TimeInterval::new_unchecked(
1✔
1070
                TimeInstance::from_str("2014-01-01T00:00:00.000Z").unwrap(),
1✔
1071
                TimeInstance::from_str("2014-01-01T00:00:00.000Z").unwrap(),
1✔
1072
            ),
1✔
1073
            spatial_resolution: SpatialResolution::one(),
1✔
1074
            attributes: [1].try_into().unwrap(),
1✔
1075
        };
1✔
1076

1077
        let result = processor
1✔
1078
            .raster_query(query_rect, &query_ctx)
1✔
1079
            .await
×
1080
            .unwrap()
1✔
1081
            .collect::<Vec<_>>()
1✔
1082
            .await;
13✔
1083
        let result = result.into_iter().collect::<Result<Vec<_>>>().unwrap();
1✔
1084

1✔
1085
        assert!(!result.is_empty());
1✔
1086
    }
1087
}
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

© 2026 Coveralls, Inc