• 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

92.22
/operators/src/processing/interpolation/mod.rs
1
use std::marker::PhantomData;
2
use std::sync::Arc;
3

4
use crate::adapters::{
5
    FoldTileAccu, FoldTileAccuMut, RasterSubQueryAdapter, SubQueryTileAggregator,
6
};
7
use crate::engine::{
8
    CanonicOperatorName, ExecutionContext, InitializedRasterOperator, InitializedSources, Operator,
9
    OperatorName, QueryContext, QueryProcessor, RasterOperator, RasterQueryProcessor,
10
    RasterResultDescriptor, SingleRasterSource, TypedRasterQueryProcessor, WorkflowOperatorPath,
11
};
12
use crate::util::Result;
13
use async_trait::async_trait;
14
use futures::future::BoxFuture;
15
use futures::stream::BoxStream;
16
use futures::{Future, FutureExt, TryFuture, TryFutureExt};
17
use geoengine_datatypes::primitives::{
18
    AxisAlignedRectangle, Coordinate2D, RasterQueryRectangle, SpatialPartition2D,
19
    SpatialPartitioned, SpatialResolution, TimeInstance, TimeInterval,
20
};
21
use geoengine_datatypes::primitives::{BandSelection, CacheHint};
22
use geoengine_datatypes::raster::{
23
    Bilinear, Blit, EmptyGrid2D, GeoTransform, GridOrEmpty, GridSize, InterpolationAlgorithm,
24
    NearestNeighbor, Pixel, RasterTile2D, TileInformation, TilingSpecification,
25
};
26
use rayon::ThreadPool;
27
use serde::{Deserialize, Serialize};
28
use snafu::{ensure, Snafu};
29

30
#[derive(Debug, Serialize, Deserialize, Clone)]
2✔
31
#[serde(rename_all = "camelCase")]
32
pub struct InterpolationParams {
33
    pub interpolation: InterpolationMethod,
34
    pub input_resolution: InputResolution,
35
}
36

37
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
2✔
38
#[serde(rename_all = "camelCase", tag = "type")]
39
pub enum InputResolution {
40
    Value(SpatialResolution),
41
    Source,
42
}
43

44
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
2✔
45
#[serde(rename_all = "camelCase")]
46
pub enum InterpolationMethod {
47
    NearestNeighbor,
48
    BiLinear,
49
}
50

51
#[derive(Debug, Snafu)]
×
52
#[snafu(visibility(pub(crate)), context(suffix(false)), module(error))]
×
53
pub enum InterpolationError {
54
    #[snafu(display(
55
        "The input resolution was defined as `source` but the source resolution is unknown.",
56
    ))]
57
    UnknownInputResolution,
58
}
59

60
pub type Interpolation = Operator<InterpolationParams, SingleRasterSource>;
61

62
impl OperatorName for Interpolation {
63
    const TYPE_NAME: &'static str = "Interpolation";
64
}
65

66
#[typetag::serde]
×
67
#[async_trait]
68
impl RasterOperator for Interpolation {
69
    async fn _initialize(
2✔
70
        self: Box<Self>,
2✔
71
        path: WorkflowOperatorPath,
2✔
72
        context: &dyn ExecutionContext,
2✔
73
    ) -> Result<Box<dyn InitializedRasterOperator>> {
2✔
74
        let name = CanonicOperatorName::from(&self);
2✔
75

76
        let initialized_sources = self.sources.initialize_sources(path, context).await?;
2✔
77
        let raster_source = initialized_sources.raster;
2✔
78
        let in_descriptor = raster_source.result_descriptor();
2✔
79

2✔
80
        // TODO: implement multi-band functionality and remove this check
2✔
81
        ensure!(
2✔
82
            in_descriptor.bands.len() == 1,
2✔
83
            crate::error::OperatorDoesNotSupportMultiBandsSourcesYet {
×
84
                operator: Interpolation::TYPE_NAME
×
85
            }
×
86
        );
87

88
        ensure!(
2✔
89
            matches!(self.params.input_resolution, InputResolution::Value(_))
2✔
90
                || in_descriptor.resolution.is_some(),
×
91
            error::UnknownInputResolution
×
92
        );
93

94
        let input_resolution = if let InputResolution::Value(res) = self.params.input_resolution {
2✔
95
            res
2✔
96
        } else {
97
            in_descriptor.resolution.expect("checked in ensure")
×
98
        };
99

100
        let out_descriptor = RasterResultDescriptor {
2✔
101
            spatial_reference: in_descriptor.spatial_reference,
2✔
102
            data_type: in_descriptor.data_type,
2✔
103
            bbox: in_descriptor.bbox,
2✔
104
            time: in_descriptor.time,
2✔
105
            resolution: None, // after interpolation the resolution is uncapped
2✔
106
            bands: in_descriptor.bands.clone(),
2✔
107
        };
2✔
108

2✔
109
        let initialized_operator = InitializedInterpolation {
2✔
110
            name,
2✔
111
            result_descriptor: out_descriptor,
2✔
112
            raster_source,
2✔
113
            interpolation_method: self.params.interpolation,
2✔
114
            input_resolution,
2✔
115
            tiling_specification: context.tiling_specification(),
2✔
116
        };
2✔
117

2✔
118
        Ok(initialized_operator.boxed())
2✔
119
    }
4✔
120

121
    span_fn!(Interpolation);
×
122
}
123

124
pub struct InitializedInterpolation {
125
    name: CanonicOperatorName,
126
    result_descriptor: RasterResultDescriptor,
127
    raster_source: Box<dyn InitializedRasterOperator>,
128
    interpolation_method: InterpolationMethod,
129
    input_resolution: SpatialResolution,
130
    tiling_specification: TilingSpecification,
131
}
132

133
impl InitializedRasterOperator for InitializedInterpolation {
134
    fn query_processor(&self) -> Result<TypedRasterQueryProcessor> {
2✔
135
        let source_processor = self.raster_source.query_processor()?;
2✔
136

137
        let res = call_on_generic_raster_processor!(
2✔
138
            source_processor, p => match self.interpolation_method  {
2✔
139
                InterpolationMethod::NearestNeighbor => InterploationProcessor::<_,_, NearestNeighbor>::new(
140
                        p,
2✔
141
                        self.result_descriptor.clone(),
2✔
142
                        self.input_resolution,
2✔
143
                        self.tiling_specification,
2✔
144
                    ).boxed()
2✔
145
                    .into(),
2✔
146
                InterpolationMethod::BiLinear =>InterploationProcessor::<_,_, Bilinear>::new(
147
                        p,
×
NEW
148
                        self.result_descriptor.clone(),
×
149
                        self.input_resolution,
×
150
                        self.tiling_specification,
×
151
                    ).boxed()
×
152
                    .into(),
×
153
            }
154
        );
155

156
        Ok(res)
2✔
157
    }
2✔
158

159
    fn result_descriptor(&self) -> &RasterResultDescriptor {
×
160
        &self.result_descriptor
×
161
    }
×
162

163
    fn canonic_name(&self) -> CanonicOperatorName {
×
164
        self.name.clone()
×
165
    }
×
166
}
167

168
pub struct InterploationProcessor<Q, P, I>
169
where
170
    Q: RasterQueryProcessor<RasterType = P>,
171
    P: Pixel,
172
    I: InterpolationAlgorithm<P>,
173
{
174
    source: Q,
175
    result_descriptor: RasterResultDescriptor,
176
    input_resolution: SpatialResolution,
177
    tiling_specification: TilingSpecification,
178
    interpolation: PhantomData<I>,
179
}
180

181
impl<Q, P, I> InterploationProcessor<Q, P, I>
182
where
183
    Q: RasterQueryProcessor<RasterType = P>,
184
    P: Pixel,
185
    I: InterpolationAlgorithm<P>,
186
{
187
    pub fn new(
2✔
188
        source: Q,
2✔
189
        result_descriptor: RasterResultDescriptor,
2✔
190
        input_resolution: SpatialResolution,
2✔
191
        tiling_specification: TilingSpecification,
2✔
192
    ) -> Self {
2✔
193
        Self {
2✔
194
            source,
2✔
195
            result_descriptor,
2✔
196
            input_resolution,
2✔
197
            tiling_specification,
2✔
198
            interpolation: PhantomData,
2✔
199
        }
2✔
200
    }
2✔
201
}
202

203
#[async_trait]
204
impl<Q, P, I> QueryProcessor for InterploationProcessor<Q, P, I>
205
where
206
    Q: QueryProcessor<
207
        Output = RasterTile2D<P>,
208
        SpatialBounds = SpatialPartition2D,
209
        Selection = BandSelection,
210
        ResultDescription = RasterResultDescriptor,
211
    >,
212
    P: Pixel,
213
    I: InterpolationAlgorithm<P>,
214
{
215
    type Output = RasterTile2D<P>;
216
    type SpatialBounds = SpatialPartition2D;
217
    type Selection = BandSelection;
218
    type ResultDescription = RasterResultDescriptor;
219

220
    async fn _query<'a>(
2✔
221
        &'a self,
2✔
222
        query: RasterQueryRectangle,
2✔
223
        ctx: &'a dyn QueryContext,
2✔
224
    ) -> Result<BoxStream<'a, Result<Self::Output>>> {
2✔
225
        // do not interpolate if the source resolution is already fine enough
226
        if query.spatial_resolution.x >= self.input_resolution.x
2✔
227
            && query.spatial_resolution.y >= self.input_resolution.y
×
228
        {
229
            // TODO: should we use the query or the input resolution here?
230
            return self.source.query(query, ctx).await;
×
231
        }
2✔
232

2✔
233
        let sub_query = InterpolationSubQuery::<_, P, I> {
2✔
234
            input_resolution: self.input_resolution,
2✔
235
            fold_fn: fold_future,
2✔
236
            tiling_specification: self.tiling_specification,
2✔
237
            phantom: PhantomData,
2✔
238
            _phantom_pixel_type: PhantomData,
2✔
239
        };
2✔
240

2✔
241
        Ok(RasterSubQueryAdapter::<'a, P, _, _>::new(
2✔
242
            &self.source,
2✔
243
            query,
2✔
244
            self.tiling_specification,
2✔
245
            ctx,
2✔
246
            sub_query,
2✔
247
        )
2✔
248
        .filter_and_fill(
2✔
249
            crate::adapters::FillerTileCacheExpirationStrategy::DerivedFromSurroundingTiles,
2✔
250
        ))
2✔
251
    }
4✔
252

253
    fn result_descriptor(&self) -> &RasterResultDescriptor {
4✔
254
        &self.result_descriptor
4✔
255
    }
4✔
256
}
257

258
#[derive(Debug, Clone)]
×
259
pub struct InterpolationSubQuery<F, T, I> {
260
    input_resolution: SpatialResolution,
261
    fold_fn: F,
262
    tiling_specification: TilingSpecification,
263
    phantom: PhantomData<I>,
264
    _phantom_pixel_type: PhantomData<T>,
265
}
266

267
impl<'a, T, FoldM, FoldF, I> SubQueryTileAggregator<'a, T> for InterpolationSubQuery<FoldM, T, I>
268
where
269
    T: Pixel,
270
    FoldM: Send + Sync + 'a + Clone + Fn(InterpolationAccu<T, I>, RasterTile2D<T>) -> FoldF,
271
    FoldF: Send + TryFuture<Ok = InterpolationAccu<T, I>, Error = crate::error::Error>,
272
    I: InterpolationAlgorithm<T>,
273
{
274
    type FoldFuture = FoldF;
275

276
    type FoldMethod = FoldM;
277

278
    type TileAccu = InterpolationAccu<T, I>;
279
    type TileAccuFuture = BoxFuture<'a, Result<Self::TileAccu>>;
280

281
    fn new_fold_accu(
32✔
282
        &self,
32✔
283
        tile_info: TileInformation,
32✔
284
        query_rect: RasterQueryRectangle,
32✔
285
        pool: &Arc<ThreadPool>,
32✔
286
    ) -> Self::TileAccuFuture {
32✔
287
        create_accu(
32✔
288
            tile_info,
32✔
289
            &query_rect,
32✔
290
            pool.clone(),
32✔
291
            self.tiling_specification,
32✔
292
        )
32✔
293
        .boxed()
32✔
294
    }
32✔
295

296
    fn tile_query_rectangle(
32✔
297
        &self,
32✔
298
        tile_info: TileInformation,
32✔
299
        _query_rect: RasterQueryRectangle,
32✔
300
        start_time: TimeInstance,
32✔
301
        band_idx: u32,
32✔
302
    ) -> Result<Option<RasterQueryRectangle>> {
32✔
303
        // enlarge the spatial bounds in order to have the neighbor pixels for the interpolation
32✔
304
        let spatial_bounds = tile_info.spatial_partition();
32✔
305
        let enlarge: Coordinate2D = (self.input_resolution.x, -self.input_resolution.y).into();
32✔
306
        let spatial_bounds = SpatialPartition2D::new(
32✔
307
            spatial_bounds.upper_left(),
32✔
308
            spatial_bounds.lower_right() + enlarge,
32✔
309
        )?;
32✔
310

311
        Ok(Some(RasterQueryRectangle {
312
            spatial_bounds,
32✔
313
            time_interval: TimeInterval::new_instant(start_time)?,
32✔
314
            spatial_resolution: self.input_resolution,
32✔
315
            attributes: band_idx.into(),
32✔
316
        }))
317
    }
32✔
318

319
    fn fold_method(&self) -> Self::FoldMethod {
32✔
320
        self.fold_fn.clone()
32✔
321
    }
32✔
322
}
323

324
#[derive(Clone, Debug)]
×
325
pub struct InterpolationAccu<T: Pixel, I: InterpolationAlgorithm<T>> {
326
    pub output_info: TileInformation,
327
    pub input_tile: RasterTile2D<T>,
328
    pub pool: Arc<ThreadPool>,
329
    phantom: PhantomData<I>,
330
}
331

332
impl<T: Pixel, I: InterpolationAlgorithm<T>> InterpolationAccu<T, I> {
333
    pub fn new(
104✔
334
        input_tile: RasterTile2D<T>,
104✔
335
        output_info: TileInformation,
104✔
336
        pool: Arc<ThreadPool>,
104✔
337
    ) -> Self {
104✔
338
        InterpolationAccu {
104✔
339
            input_tile,
104✔
340
            output_info,
104✔
341
            pool,
104✔
342
            phantom: Default::default(),
104✔
343
        }
104✔
344
    }
104✔
345
}
346

347
#[async_trait]
348
impl<T: Pixel, I: InterpolationAlgorithm<T>> FoldTileAccu for InterpolationAccu<T, I> {
349
    type RasterType = T;
350

351
    async fn into_tile(self) -> Result<RasterTile2D<Self::RasterType>> {
32✔
352
        // now that we collected all the input tile pixels we perform the actual interpolation
353

354
        let output_tile = crate::util::spawn_blocking_with_thread_pool(self.pool, move || {
32✔
355
            I::interpolate(&self.input_tile, &self.output_info)
32✔
356
        })
32✔
357
        .await??;
32✔
358

359
        Ok(output_tile)
32✔
360
    }
64✔
361

362
    fn thread_pool(&self) -> &Arc<ThreadPool> {
×
363
        &self.pool
×
364
    }
×
365
}
366

367
impl<T: Pixel, I: InterpolationAlgorithm<T>> FoldTileAccuMut for InterpolationAccu<T, I> {
368
    fn tile_mut(&mut self) -> &mut RasterTile2D<T> {
×
369
        &mut self.input_tile
×
370
    }
×
371
}
372

373
pub fn create_accu<T: Pixel, I: InterpolationAlgorithm<T>>(
32✔
374
    tile_info: TileInformation,
32✔
375
    query_rect: &RasterQueryRectangle,
32✔
376
    pool: Arc<ThreadPool>,
32✔
377
    tiling_specification: TilingSpecification,
32✔
378
) -> impl Future<Output = Result<InterpolationAccu<T, I>>> {
32✔
379
    // create an accumulator as a single tile that fits all the input tiles
32✔
380
    let spatial_bounds = query_rect.spatial_bounds;
32✔
381
    let spatial_resolution = query_rect.spatial_resolution;
32✔
382
    let time_interval = query_rect.time_interval;
32✔
383

32✔
384
    crate::util::spawn_blocking(move || {
32✔
385
        let tiling = tiling_specification.strategy(spatial_resolution.x, -spatial_resolution.y);
32✔
386

32✔
387
        let origin_coordinate = tiling
32✔
388
            .tile_information_iterator(spatial_bounds)
32✔
389
            .next()
32✔
390
            .expect("a query contains at least one tile")
32✔
391
            .spatial_partition()
32✔
392
            .upper_left();
32✔
393

32✔
394
        let geo_transform = GeoTransform::new(
32✔
395
            origin_coordinate,
32✔
396
            spatial_resolution.x,
32✔
397
            -spatial_resolution.y,
32✔
398
        );
32✔
399

32✔
400
        let bbox = tiling.tile_grid_box(spatial_bounds);
32✔
401

32✔
402
        let shape = [
32✔
403
            bbox.axis_size_y() * tiling.tile_size_in_pixels.axis_size_y(),
32✔
404
            bbox.axis_size_x() * tiling.tile_size_in_pixels.axis_size_x(),
32✔
405
        ];
32✔
406

32✔
407
        // create a non-aligned (w.r.t. the tiling specification) grid by setting the origin to the top-left of the tile and the tile-index to [0, 0]
32✔
408
        let grid = EmptyGrid2D::new(shape.into());
32✔
409

32✔
410
        let input_tile = RasterTile2D::new(
32✔
411
            time_interval,
32✔
412
            [0, 0].into(),
32✔
413
            0,
32✔
414
            geo_transform,
32✔
415
            GridOrEmpty::from(grid),
32✔
416
            CacheHint::max_duration(),
32✔
417
        );
32✔
418

32✔
419
        InterpolationAccu::new(input_tile, tile_info, pool)
32✔
420
    })
32✔
421
    .map_err(From::from)
32✔
422
}
32✔
423

424
pub fn fold_future<T, I>(
72✔
425
    accu: InterpolationAccu<T, I>,
72✔
426
    tile: RasterTile2D<T>,
72✔
427
) -> impl Future<Output = Result<InterpolationAccu<T, I>>>
72✔
428
where
72✔
429
    T: Pixel,
72✔
430
    I: InterpolationAlgorithm<T>,
72✔
431
{
72✔
432
    crate::util::spawn_blocking(|| fold_impl(accu, tile)).then(|x| async move {
72✔
433
        match x {
72✔
434
            Ok(r) => r,
72✔
435
            Err(e) => Err(e.into()),
×
436
        }
437
    })
72✔
438
}
72✔
439

440
pub fn fold_impl<T, I>(
72✔
441
    mut accu: InterpolationAccu<T, I>,
72✔
442
    tile: RasterTile2D<T>,
72✔
443
) -> Result<InterpolationAccu<T, I>>
72✔
444
where
72✔
445
    T: Pixel,
72✔
446
    I: InterpolationAlgorithm<T>,
72✔
447
{
72✔
448
    // get the time now because it is not known when the accu was created
72✔
449
    accu.input_tile.time = tile.time;
72✔
450

72✔
451
    // TODO: add a skip if both tiles are empty?
72✔
452

72✔
453
    // copy all input tiles into the accu to have all data for interpolation
72✔
454
    let mut accu_input_tile = accu.input_tile.into_materialized_tile();
72✔
455
    accu_input_tile.blit(tile)?;
72✔
456

457
    Ok(InterpolationAccu::new(
72✔
458
        accu_input_tile.into(),
72✔
459
        accu.output_info,
72✔
460
        accu.pool,
72✔
461
    ))
72✔
462
}
72✔
463

464
#[cfg(test)]
465
mod tests {
466
    use super::*;
467
    use futures::StreamExt;
468
    use geoengine_datatypes::{
469
        primitives::{RasterQueryRectangle, SpatialPartition2D, SpatialResolution, TimeInterval},
470
        raster::{
471
            Grid2D, GridOrEmpty, RasterDataType, RasterTile2D, TileInformation, TilingSpecification,
472
        },
473
        spatial_reference::SpatialReference,
474
        util::test::TestDefault,
475
    };
476

477
    use crate::{
478
        engine::{
479
            MockExecutionContext, MockQueryContext, RasterBandDescriptors, RasterOperator,
480
            RasterResultDescriptor,
481
        },
482
        mock::{MockRasterSource, MockRasterSourceParams},
483
    };
484

485
    #[tokio::test]
1✔
486
    async fn nearest_neighbor_operator() -> Result<()> {
1✔
487
        let exe_ctx = MockExecutionContext::new_with_tiling_spec(TilingSpecification::new(
1✔
488
            (0., 0.).into(),
1✔
489
            [2, 2].into(),
1✔
490
        ));
1✔
491

1✔
492
        let raster = make_raster(CacheHint::max_duration());
1✔
493

494
        let operator = Interpolation {
1✔
495
            params: InterpolationParams {
1✔
496
                interpolation: InterpolationMethod::NearestNeighbor,
1✔
497
                input_resolution: InputResolution::Value(SpatialResolution::one()),
1✔
498
            },
1✔
499
            sources: SingleRasterSource { raster },
1✔
500
        }
1✔
501
        .boxed()
1✔
502
        .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
503
        .await?;
×
504

505
        let processor = operator.query_processor()?.get_i8().unwrap();
1✔
506

1✔
507
        let query_rect = RasterQueryRectangle {
1✔
508
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 2.).into(), (4., 0.).into()),
1✔
509
            time_interval: TimeInterval::new_unchecked(0, 20),
1✔
510
            spatial_resolution: SpatialResolution::zero_point_five(),
1✔
511
            attributes: BandSelection::first(),
1✔
512
        };
1✔
513
        let query_ctx = MockQueryContext::test_default();
1✔
514

515
        let result_stream = processor.query(query_rect, &query_ctx).await?;
1✔
516

517
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
66✔
518
        let result = result.into_iter().collect::<Result<Vec<_>>>()?;
1✔
519

520
        let mut times: Vec<TimeInterval> = vec![TimeInterval::new_unchecked(0, 10); 8];
1✔
521
        times.append(&mut vec![TimeInterval::new_unchecked(10, 20); 8]);
1✔
522

1✔
523
        let data = vec![
1✔
524
            vec![1, 2, 5, 6],
1✔
525
            vec![2, 3, 6, 7],
1✔
526
            vec![3, 4, 7, 8],
1✔
527
            vec![4, 0, 8, 0],
1✔
528
            vec![5, 6, 0, 0],
1✔
529
            vec![6, 7, 0, 0],
1✔
530
            vec![7, 8, 0, 0],
1✔
531
            vec![8, 0, 0, 0],
1✔
532
            vec![8, 7, 4, 3],
1✔
533
            vec![7, 6, 3, 2],
1✔
534
            vec![6, 5, 2, 1],
1✔
535
            vec![5, 0, 1, 0],
1✔
536
            vec![4, 3, 0, 0],
1✔
537
            vec![3, 2, 0, 0],
1✔
538
            vec![2, 1, 0, 0],
1✔
539
            vec![1, 0, 0, 0],
1✔
540
        ];
1✔
541

1✔
542
        let valid = vec![
1✔
543
            vec![true; 4],
1✔
544
            vec![true; 4],
1✔
545
            vec![true; 4],
1✔
546
            vec![true, false, true, false],
1✔
547
            vec![true, true, false, false],
1✔
548
            vec![true, true, false, false],
1✔
549
            vec![true, true, false, false],
1✔
550
            vec![true, false, false, false],
1✔
551
            vec![true; 4],
1✔
552
            vec![true; 4],
1✔
553
            vec![true; 4],
1✔
554
            vec![true, false, true, false],
1✔
555
            vec![true, true, false, false],
1✔
556
            vec![true, true, false, false],
1✔
557
            vec![true, true, false, false],
1✔
558
            vec![true, false, false, false],
1✔
559
        ];
1✔
560

561
        for (i, tile) in result.into_iter().enumerate() {
16✔
562
            let tile = tile.into_materialized_tile();
16✔
563
            assert_eq!(tile.time, times[i]);
16✔
564
            assert_eq!(tile.grid_array.inner_grid.data, data[i]);
16✔
565
            assert_eq!(tile.grid_array.validity_mask.data, valid[i]);
16✔
566
        }
567

568
        Ok(())
1✔
569
    }
570

571
    fn make_raster(cache_hint: CacheHint) -> Box<dyn RasterOperator> {
2✔
572
        // test raster:
2✔
573
        // [0, 10)
2✔
574
        // || 1 | 2 || 3 | 4 ||
2✔
575
        // || 5 | 6 || 7 | 8 ||
2✔
576
        //
2✔
577
        // [10, 20)
2✔
578
        // || 8 | 7 || 6 | 5 ||
2✔
579
        // || 4 | 3 || 2 | 1 ||
2✔
580
        let raster_tiles = vec![
2✔
581
            RasterTile2D::<i8>::new_with_tile_info(
2✔
582
                TimeInterval::new_unchecked(0, 10),
2✔
583
                TileInformation {
2✔
584
                    global_tile_position: [-1, 0].into(),
2✔
585
                    tile_size_in_pixels: [2, 2].into(),
2✔
586
                    global_geo_transform: TestDefault::test_default(),
2✔
587
                },
2✔
588
                0,
2✔
589
                GridOrEmpty::from(Grid2D::new([2, 2].into(), vec![1, 2, 5, 6]).unwrap()),
2✔
590
                cache_hint,
2✔
591
            ),
2✔
592
            RasterTile2D::new_with_tile_info(
2✔
593
                TimeInterval::new_unchecked(0, 10),
2✔
594
                TileInformation {
2✔
595
                    global_tile_position: [-1, 1].into(),
2✔
596
                    tile_size_in_pixels: [2, 2].into(),
2✔
597
                    global_geo_transform: TestDefault::test_default(),
2✔
598
                },
2✔
599
                0,
2✔
600
                GridOrEmpty::from(Grid2D::new([2, 2].into(), vec![3, 4, 7, 8]).unwrap()),
2✔
601
                cache_hint,
2✔
602
            ),
2✔
603
            RasterTile2D::new_with_tile_info(
2✔
604
                TimeInterval::new_unchecked(10, 20),
2✔
605
                TileInformation {
2✔
606
                    global_tile_position: [-1, 0].into(),
2✔
607
                    tile_size_in_pixels: [2, 2].into(),
2✔
608
                    global_geo_transform: TestDefault::test_default(),
2✔
609
                },
2✔
610
                0,
2✔
611
                GridOrEmpty::from(Grid2D::new([2, 2].into(), vec![8, 7, 4, 3]).unwrap()),
2✔
612
                cache_hint,
2✔
613
            ),
2✔
614
            RasterTile2D::new_with_tile_info(
2✔
615
                TimeInterval::new_unchecked(10, 20),
2✔
616
                TileInformation {
2✔
617
                    global_tile_position: [-1, 1].into(),
2✔
618
                    tile_size_in_pixels: [2, 2].into(),
2✔
619
                    global_geo_transform: TestDefault::test_default(),
2✔
620
                },
2✔
621
                0,
2✔
622
                GridOrEmpty::from(Grid2D::new([2, 2].into(), vec![6, 5, 2, 1]).unwrap()),
2✔
623
                cache_hint,
2✔
624
            ),
2✔
625
        ];
2✔
626

2✔
627
        MockRasterSource {
2✔
628
            params: MockRasterSourceParams {
2✔
629
                data: raster_tiles,
2✔
630
                result_descriptor: RasterResultDescriptor {
2✔
631
                    data_type: RasterDataType::I8,
2✔
632
                    spatial_reference: SpatialReference::epsg_4326().into(),
2✔
633
                    time: None,
2✔
634
                    bbox: None,
2✔
635
                    resolution: None,
2✔
636
                    bands: RasterBandDescriptors::new_single_band(),
2✔
637
                },
2✔
638
            },
2✔
639
        }
2✔
640
        .boxed()
2✔
641
    }
2✔
642

643
    #[tokio::test]
1✔
644
    async fn it_attaches_cache_hint() -> Result<()> {
1✔
645
        let exe_ctx = MockExecutionContext::new_with_tiling_spec(TilingSpecification::new(
1✔
646
            (0., 0.).into(),
1✔
647
            [2, 2].into(),
1✔
648
        ));
1✔
649

1✔
650
        let cache_hint = CacheHint::seconds(1234);
1✔
651
        let raster = make_raster(cache_hint);
1✔
652

653
        let operator = Interpolation {
1✔
654
            params: InterpolationParams {
1✔
655
                interpolation: InterpolationMethod::NearestNeighbor,
1✔
656
                input_resolution: InputResolution::Value(SpatialResolution::one()),
1✔
657
            },
1✔
658
            sources: SingleRasterSource { raster },
1✔
659
        }
1✔
660
        .boxed()
1✔
661
        .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
662
        .await?;
×
663

664
        let processor = operator.query_processor()?.get_i8().unwrap();
1✔
665

1✔
666
        let query_rect = RasterQueryRectangle {
1✔
667
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 2.).into(), (4., 0.).into()),
1✔
668
            time_interval: TimeInterval::new_unchecked(0, 20),
1✔
669
            spatial_resolution: SpatialResolution::zero_point_five(),
1✔
670
            attributes: BandSelection::first(),
1✔
671
        };
1✔
672
        let query_ctx = MockQueryContext::test_default();
1✔
673

674
        let result_stream = processor.query(query_rect, &query_ctx).await?;
1✔
675

676
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
68✔
677
        let result = result.into_iter().collect::<Result<Vec<_>>>()?;
1✔
678

679
        for tile in result {
17✔
680
            // dbg!(tile.time, tile.grid_array);
681
            assert_eq!(tile.cache_hint.expires(), cache_hint.expires());
16✔
682
        }
683

684
        Ok(())
1✔
685
    }
686
}
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