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

geo-engine / geoengine / 7208011886

14 Dec 2023 10:33AM UTC coverage: 89.82% (+0.008%) from 89.812%
7208011886

push

github

web-flow
Merge pull request #904 from geo-engine/fix_stacker

fix stacker adapter when streams are not immediately ready on poll

115 of 119 new or added lines in 2 files covered. (96.64%)

8 existing lines in 5 files now uncovered.

115350 of 128423 relevant lines covered (89.82%)

58993.2 hits per line

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

94.55
/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(inputs, bands_per_source);
5✔
141
                TypedRasterQueryProcessor::U8(Box::new(p))
5✔
142
            }
143
            geoengine_datatypes::raster::RasterDataType::U16 => {
144
                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();
×
145
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
146
                TypedRasterQueryProcessor::U16(Box::new(p))
×
147
            }
148
            geoengine_datatypes::raster::RasterDataType::U32 => {
149
                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();
×
150
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
151
                TypedRasterQueryProcessor::U32(Box::new(p))
×
152
            }
153
            geoengine_datatypes::raster::RasterDataType::U64 => {
154
                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();
×
155
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
156
                TypedRasterQueryProcessor::U64(Box::new(p))
×
157
            }
158
            geoengine_datatypes::raster::RasterDataType::I8 => {
159
                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();
×
160
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
161
                TypedRasterQueryProcessor::I8(Box::new(p))
×
162
            }
163
            geoengine_datatypes::raster::RasterDataType::I16 => {
164
                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();
×
165
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
166
                TypedRasterQueryProcessor::I16(Box::new(p))
×
167
            }
168
            geoengine_datatypes::raster::RasterDataType::I32 => {
169
                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();
×
170
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
171
                TypedRasterQueryProcessor::I32(Box::new(p))
×
172
            }
173
            geoengine_datatypes::raster::RasterDataType::I64 => {
174
                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();
×
175
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
176
                TypedRasterQueryProcessor::I64(Box::new(p))
×
177
            }
178
            geoengine_datatypes::raster::RasterDataType::F32 => {
179
                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();
×
180
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
181
                TypedRasterQueryProcessor::F32(Box::new(p))
×
182
            }
183
            geoengine_datatypes::raster::RasterDataType::F64 => {
184
                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();
×
185
                let p = RasterStackerProcessor::new(inputs, bands_per_source);
×
186
                TypedRasterQueryProcessor::F64(Box::new(p))
×
187
            }
188
        })
189
    }
5✔
190

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

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

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

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

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

33✔
230
    if bands.is_empty() {
33✔
231
        return None;
12✔
232
    }
21✔
233

21✔
234
    Some(BandSelection::new_unchecked(bands))
21✔
235
}
33✔
236

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

250
        for (idx, source) in self.sources.iter().enumerate() {
28✔
251
            let Some(bands) =
17✔
252
                map_query_bands_to_source_bands(&query.attributes, &self.bands_per_source, idx)
28✔
253
            else {
254
                continue;
11✔
255
            };
256

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

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

14✔
267
        Ok(Box::pin(output))
14✔
268
    }
28✔
269
}
270

271
#[cfg(test)]
272
mod tests {
273
    use std::str::FromStr;
274

275
    use futures::StreamExt;
276
    use geoengine_datatypes::{
277
        primitives::{CacheHint, SpatialPartition2D, TimeInstance, TimeInterval},
278
        raster::{Grid, GridShape, RasterDataType, TilesEqualIgnoringCacheHint},
279
        spatial_reference::SpatialReference,
280
        util::test::TestDefault,
281
    };
282

283
    use crate::{
284
        engine::{
285
            MockExecutionContext, MockQueryContext, RasterBandDescriptor, RasterBandDescriptors,
286
        },
287
        mock::{MockRasterSource, MockRasterSourceParams},
288
        processing::{Expression, ExpressionParams, ExpressionSources},
289
        source::{GdalSource, GdalSourceParameters},
290
        util::gdal::add_ndvi_dataset,
291
    };
292

293
    use super::*;
294

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

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

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

1✔
361
        let data2: Vec<RasterTile2D<u8>> = vec![
1✔
362
            RasterTile2D {
1✔
363
                time: TimeInterval::new_unchecked(0, 5),
1✔
364
                tile_position: [-1, 0].into(),
1✔
365
                band: 0,
1✔
366
                global_geo_transform: TestDefault::test_default(),
1✔
367
                grid_array: Grid::new([2, 2].into(), vec![16, 17, 18, 19])
1✔
368
                    .unwrap()
1✔
369
                    .into(),
1✔
370
                properties: Default::default(),
1✔
371
                cache_hint: CacheHint::default(),
1✔
372
            },
1✔
373
            RasterTile2D {
1✔
374
                time: TimeInterval::new_unchecked(0, 5),
1✔
375
                tile_position: [-1, 1].into(),
1✔
376
                band: 0,
1✔
377
                global_geo_transform: TestDefault::test_default(),
1✔
378
                grid_array: Grid::new([2, 2].into(), vec![20, 21, 22, 23])
1✔
379
                    .unwrap()
1✔
380
                    .into(),
1✔
381
                properties: Default::default(),
1✔
382
                cache_hint: CacheHint::default(),
1✔
383
            },
1✔
384
            RasterTile2D {
1✔
385
                time: TimeInterval::new_unchecked(5, 10),
1✔
386
                tile_position: [-1, 0].into(),
1✔
387
                band: 0,
1✔
388
                global_geo_transform: TestDefault::test_default(),
1✔
389
                grid_array: Grid::new([2, 2].into(), vec![24, 25, 26, 27])
1✔
390
                    .unwrap()
1✔
391
                    .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![28, 29, 30, 31])
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 mrs1 = MockRasterSource {
1✔
409
            params: MockRasterSourceParams {
1✔
410
                data: data.clone(),
1✔
411
                result_descriptor: RasterResultDescriptor {
1✔
412
                    data_type: RasterDataType::U8,
1✔
413
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
414
                    time: None,
1✔
415
                    bbox: None,
1✔
416
                    resolution: None,
1✔
417
                    bands: RasterBandDescriptors::new_single_band(),
1✔
418
                },
1✔
419
            },
1✔
420
        }
1✔
421
        .boxed();
1✔
422

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

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

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

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

1✔
458
        let query_ctx = MockQueryContext::test_default();
1✔
459

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

1✔
465
        let qp = op.query_processor().unwrap().get_u8().unwrap();
1✔
466

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

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

1✔
484
        assert!(expected.tiles_equal_ignoring_cache_hint(&result));
1✔
485
    }
486

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

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

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

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

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

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

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

1✔
718
        let query_ctx = MockQueryContext::test_default();
1✔
719

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

1✔
725
        let qp = op.query_processor().unwrap().get_u8().unwrap();
1✔
726

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

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

1✔
751
        assert!(expected.tiles_equal_ignoring_cache_hint(&result));
1✔
752
    }
753

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

1✔
798
        let data2: Vec<RasterTile2D<u8>> = vec![
1✔
799
            RasterTile2D {
1✔
800
                time: TimeInterval::new_unchecked(0, 5),
1✔
801
                tile_position: [-1, 0].into(),
1✔
802
                band: 0,
1✔
803
                global_geo_transform: TestDefault::test_default(),
1✔
804
                grid_array: Grid::new([2, 2].into(), vec![16, 17, 18, 19])
1✔
805
                    .unwrap()
1✔
806
                    .into(),
1✔
807
                properties: Default::default(),
1✔
808
                cache_hint: CacheHint::default(),
1✔
809
            },
1✔
810
            RasterTile2D {
1✔
811
                time: TimeInterval::new_unchecked(0, 5),
1✔
812
                tile_position: [-1, 1].into(),
1✔
813
                band: 0,
1✔
814
                global_geo_transform: TestDefault::test_default(),
1✔
815
                grid_array: Grid::new([2, 2].into(), vec![20, 21, 22, 23])
1✔
816
                    .unwrap()
1✔
817
                    .into(),
1✔
818
                properties: Default::default(),
1✔
819
                cache_hint: CacheHint::default(),
1✔
820
            },
1✔
821
            RasterTile2D {
1✔
822
                time: TimeInterval::new_unchecked(5, 10),
1✔
823
                tile_position: [-1, 0].into(),
1✔
824
                band: 0,
1✔
825
                global_geo_transform: TestDefault::test_default(),
1✔
826
                grid_array: Grid::new([2, 2].into(), vec![24, 25, 26, 27])
1✔
827
                    .unwrap()
1✔
828
                    .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![28, 29, 30, 31])
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 mrs1 = MockRasterSource {
1✔
846
            params: MockRasterSourceParams {
1✔
847
                data: data.clone(),
1✔
848
                result_descriptor: RasterResultDescriptor {
1✔
849
                    data_type: RasterDataType::U8,
1✔
850
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
851
                    time: None,
1✔
852
                    bbox: None,
1✔
853
                    resolution: None,
1✔
854
                    bands: RasterBandDescriptors::new_single_band(),
1✔
855
                },
1✔
856
            },
1✔
857
        }
1✔
858
        .boxed();
1✔
859

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

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

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

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

1✔
895
        let query_ctx = MockQueryContext::test_default();
1✔
896

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

1✔
902
        let qp = op.query_processor().unwrap().get_u8().unwrap();
1✔
903

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

1✔
912
        assert!(data2.tiles_equal_ignoring_cache_hint(&result));
1✔
913
    }
914

915
    #[tokio::test]
1✔
916
    #[allow(clippy::too_many_lines)]
917
    async fn it_stacks_ndvi() {
1✔
918
        let mut exe_ctx = MockExecutionContext::test_default();
1✔
919

1✔
920
        let ndvi_id = add_ndvi_dataset(&mut exe_ctx);
1✔
921

1✔
922
        let expression = Expression {
1✔
923
            params: ExpressionParams {
1✔
924
                expression: "if A > 100 { A } else { 0 }".into(),
1✔
925
                output_type: RasterDataType::U8,
1✔
926
                output_measurement: None,
1✔
927
                map_no_data: false,
1✔
928
            },
1✔
929
            sources: ExpressionSources::new_a(
1✔
930
                GdalSource {
1✔
931
                    params: GdalSourceParameters {
1✔
932
                        data: ndvi_id.clone(),
1✔
933
                    },
1✔
934
                }
1✔
935
                .boxed(),
1✔
936
            ),
1✔
937
        }
1✔
938
        .boxed();
1✔
939

1✔
940
        let operator = RasterStacker {
1✔
941
            params: RasterStackerParams {},
1✔
942
            sources: MultipleRasterSources {
1✔
943
                rasters: vec![
1✔
944
                    GdalSource {
1✔
945
                        params: GdalSourceParameters { data: ndvi_id },
1✔
946
                    }
1✔
947
                    .boxed(),
1✔
948
                    expression,
1✔
949
                ],
1✔
950
            },
1✔
951
        }
1✔
952
        .boxed();
1✔
953

954
        let operator = operator
1✔
955
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
NEW
956
            .await
×
957
            .unwrap();
1✔
958

1✔
959
        let processor = operator.query_processor().unwrap().get_u8().unwrap();
1✔
960

1✔
961
        let mut exe_ctx = MockExecutionContext::test_default();
1✔
962
        exe_ctx.tiling_specification.tile_size_in_pixels = GridShape {
1✔
963
            shape_array: [2, 2],
1✔
964
        };
1✔
965

1✔
966
        let query_ctx = MockQueryContext::test_default();
1✔
967

1✔
968
        // query both bands
1✔
969
        let query_rect = RasterQueryRectangle {
1✔
970
            spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
971
                (-180., 90.).into(),
1✔
972
                (180., -90.).into(),
1✔
973
            ),
1✔
974
            time_interval: TimeInterval::new_unchecked(
1✔
975
                TimeInstance::from_str("2014-01-01T00:00:00.000Z").unwrap(),
1✔
976
                TimeInstance::from_str("2014-01-01T00:00:00.000Z").unwrap(),
1✔
977
            ),
1✔
978
            spatial_resolution: SpatialResolution::one(),
1✔
979
            attributes: [0, 1].try_into().unwrap(),
1✔
980
        };
1✔
981

982
        let result = processor
1✔
983
            .raster_query(query_rect, &query_ctx)
1✔
NEW
984
            .await
×
985
            .unwrap()
1✔
986
            .collect::<Vec<_>>()
1✔
987
            .await;
16✔
988
        let result = result.into_iter().collect::<Result<Vec<_>>>().unwrap();
1✔
989

1✔
990
        assert!(!result.is_empty());
1✔
991

992
        // query only first band
993
        let query_rect = RasterQueryRectangle {
1✔
994
            spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
995
                (-180., 90.).into(),
1✔
996
                (180., -90.).into(),
1✔
997
            ),
1✔
998
            time_interval: TimeInterval::new_unchecked(
1✔
999
                TimeInstance::from_str("2014-01-01T00:00:00.000Z").unwrap(),
1✔
1000
                TimeInstance::from_str("2014-01-01T00:00:00.000Z").unwrap(),
1✔
1001
            ),
1✔
1002
            spatial_resolution: SpatialResolution::one(),
1✔
1003
            attributes: [0].try_into().unwrap(),
1✔
1004
        };
1✔
1005

1006
        let result = processor
1✔
1007
            .raster_query(query_rect, &query_ctx)
1✔
NEW
1008
            .await
×
1009
            .unwrap()
1✔
1010
            .collect::<Vec<_>>()
1✔
1011
            .await;
5✔
1012
        let result = result.into_iter().collect::<Result<Vec<_>>>().unwrap();
1✔
1013

1✔
1014
        assert!(!result.is_empty());
1✔
1015

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

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

1✔
1038
        assert!(!result.is_empty());
1✔
1039
    }
1040
}
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