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

geo-engine / geoengine / 7006568925

27 Nov 2023 02:07PM UTC coverage: 89.651% (+0.2%) from 89.498%
7006568925

push

github

web-flow
Merge pull request #888 from geo-engine/raster_stacks

raster stacking

4032 of 4274 new or added lines in 107 files covered. (94.34%)

12 existing lines in 8 files now uncovered.

113020 of 126066 relevant lines covered (89.65%)

59901.79 hits per line

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

92.33
/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✔
NEW
83
            crate::error::OperatorDoesNotSupportMultiBandsSourcesYet {
×
NEW
84
                operator: Interpolation::TYPE_NAME
×
NEW
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.input_resolution,
2✔
142
                        self.tiling_specification,
2✔
143
                    ).boxed()
2✔
144
                    .into(),
2✔
145
                InterpolationMethod::BiLinear =>InterploationProcessor::<_,_, Bilinear>::new(
146
                        p,
×
147
                        self.input_resolution,
×
148
                        self.tiling_specification,
×
149
                    ).boxed()
×
150
                    .into(),
×
151
            }
152
        );
153

154
        Ok(res)
2✔
155
    }
2✔
156

157
    fn result_descriptor(&self) -> &RasterResultDescriptor {
×
158
        &self.result_descriptor
×
159
    }
×
160

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

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

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

198
#[async_trait]
199
impl<Q, P, I> QueryProcessor for InterploationProcessor<Q, P, I>
200
where
201
    Q: QueryProcessor<
202
        Output = RasterTile2D<P>,
203
        SpatialBounds = SpatialPartition2D,
204
        Selection = BandSelection,
205
    >,
206
    P: Pixel,
207
    I: InterpolationAlgorithm<P>,
208
{
209
    type Output = RasterTile2D<P>;
210
    type SpatialBounds = SpatialPartition2D;
211
    type Selection = BandSelection;
212

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

2✔
226
        let sub_query = InterpolationSubQuery::<_, P, I> {
2✔
227
            input_resolution: self.input_resolution,
2✔
228
            fold_fn: fold_future,
2✔
229
            tiling_specification: self.tiling_specification,
2✔
230
            phantom: PhantomData,
2✔
231
            _phantom_pixel_type: PhantomData,
2✔
232
        };
2✔
233

2✔
234
        Ok(RasterSubQueryAdapter::<'a, P, _, _>::new(
2✔
235
            &self.source,
2✔
236
            query,
2✔
237
            self.tiling_specification,
2✔
238
            ctx,
2✔
239
            sub_query,
2✔
240
        )
2✔
241
        .filter_and_fill(
2✔
242
            crate::adapters::FillerTileCacheExpirationStrategy::DerivedFromSurroundingTiles,
2✔
243
        ))
2✔
244
    }
4✔
245
}
246

247
#[derive(Debug, Clone)]
×
248
pub struct InterpolationSubQuery<F, T, I> {
249
    input_resolution: SpatialResolution,
250
    fold_fn: F,
251
    tiling_specification: TilingSpecification,
252
    phantom: PhantomData<I>,
253
    _phantom_pixel_type: PhantomData<T>,
254
}
255

256
impl<'a, T, FoldM, FoldF, I> SubQueryTileAggregator<'a, T> for InterpolationSubQuery<FoldM, T, I>
257
where
258
    T: Pixel,
259
    FoldM: Send + Sync + 'a + Clone + Fn(InterpolationAccu<T, I>, RasterTile2D<T>) -> FoldF,
260
    FoldF: Send + TryFuture<Ok = InterpolationAccu<T, I>, Error = crate::error::Error>,
261
    I: InterpolationAlgorithm<T>,
262
{
263
    type FoldFuture = FoldF;
264

265
    type FoldMethod = FoldM;
266

267
    type TileAccu = InterpolationAccu<T, I>;
268
    type TileAccuFuture = BoxFuture<'a, Result<Self::TileAccu>>;
269

270
    fn new_fold_accu(
32✔
271
        &self,
32✔
272
        tile_info: TileInformation,
32✔
273
        query_rect: RasterQueryRectangle,
32✔
274
        pool: &Arc<ThreadPool>,
32✔
275
    ) -> Self::TileAccuFuture {
32✔
276
        create_accu(
32✔
277
            tile_info,
32✔
278
            &query_rect,
32✔
279
            pool.clone(),
32✔
280
            self.tiling_specification,
32✔
281
        )
32✔
282
        .boxed()
32✔
283
    }
32✔
284

285
    fn tile_query_rectangle(
32✔
286
        &self,
32✔
287
        tile_info: TileInformation,
32✔
288
        _query_rect: RasterQueryRectangle,
32✔
289
        start_time: TimeInstance,
32✔
290
        band: usize,
32✔
291
    ) -> Result<Option<RasterQueryRectangle>> {
32✔
292
        // enlarge the spatial bounds in order to have the neighbor pixels for the interpolation
32✔
293
        let spatial_bounds = tile_info.spatial_partition();
32✔
294
        let enlarge: Coordinate2D = (self.input_resolution.x, -self.input_resolution.y).into();
32✔
295
        let spatial_bounds = SpatialPartition2D::new(
32✔
296
            spatial_bounds.upper_left(),
32✔
297
            spatial_bounds.lower_right() + enlarge,
32✔
298
        )?;
32✔
299

300
        Ok(Some(RasterQueryRectangle {
301
            spatial_bounds,
32✔
302
            time_interval: TimeInterval::new_instant(start_time)?,
32✔
303
            spatial_resolution: self.input_resolution,
32✔
304
            attributes: band.into(),
32✔
305
        }))
306
    }
32✔
307

308
    fn fold_method(&self) -> Self::FoldMethod {
32✔
309
        self.fold_fn.clone()
32✔
310
    }
32✔
311
}
312

313
#[derive(Clone, Debug)]
×
314
pub struct InterpolationAccu<T: Pixel, I: InterpolationAlgorithm<T>> {
315
    pub output_info: TileInformation,
316
    pub input_tile: RasterTile2D<T>,
317
    pub pool: Arc<ThreadPool>,
318
    phantom: PhantomData<I>,
319
}
320

321
impl<T: Pixel, I: InterpolationAlgorithm<T>> InterpolationAccu<T, I> {
322
    pub fn new(
104✔
323
        input_tile: RasterTile2D<T>,
104✔
324
        output_info: TileInformation,
104✔
325
        pool: Arc<ThreadPool>,
104✔
326
    ) -> Self {
104✔
327
        InterpolationAccu {
104✔
328
            input_tile,
104✔
329
            output_info,
104✔
330
            pool,
104✔
331
            phantom: Default::default(),
104✔
332
        }
104✔
333
    }
104✔
334
}
335

336
#[async_trait]
337
impl<T: Pixel, I: InterpolationAlgorithm<T>> FoldTileAccu for InterpolationAccu<T, I> {
338
    type RasterType = T;
339

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

343
        let output_tile = crate::util::spawn_blocking_with_thread_pool(self.pool, move || {
32✔
344
            I::interpolate(&self.input_tile, &self.output_info)
32✔
345
        })
32✔
346
        .await??;
32✔
347

348
        Ok(output_tile)
32✔
349
    }
64✔
350

351
    fn thread_pool(&self) -> &Arc<ThreadPool> {
×
352
        &self.pool
×
353
    }
×
354
}
355

356
impl<T: Pixel, I: InterpolationAlgorithm<T>> FoldTileAccuMut for InterpolationAccu<T, I> {
357
    fn tile_mut(&mut self) -> &mut RasterTile2D<T> {
×
358
        &mut self.input_tile
×
359
    }
×
360
}
361

362
pub fn create_accu<T: Pixel, I: InterpolationAlgorithm<T>>(
32✔
363
    tile_info: TileInformation,
32✔
364
    query_rect: &RasterQueryRectangle,
32✔
365
    pool: Arc<ThreadPool>,
32✔
366
    tiling_specification: TilingSpecification,
32✔
367
) -> impl Future<Output = Result<InterpolationAccu<T, I>>> {
32✔
368
    // create an accumulator as a single tile that fits all the input tiles
32✔
369
    let spatial_bounds = query_rect.spatial_bounds;
32✔
370
    let spatial_resolution = query_rect.spatial_resolution;
32✔
371
    let time_interval = query_rect.time_interval;
32✔
372

32✔
373
    crate::util::spawn_blocking(move || {
32✔
374
        let tiling = tiling_specification.strategy(spatial_resolution.x, -spatial_resolution.y);
32✔
375

32✔
376
        let origin_coordinate = tiling
32✔
377
            .tile_information_iterator(spatial_bounds)
32✔
378
            .next()
32✔
379
            .expect("a query contains at least one tile")
32✔
380
            .spatial_partition()
32✔
381
            .upper_left();
32✔
382

32✔
383
        let geo_transform = GeoTransform::new(
32✔
384
            origin_coordinate,
32✔
385
            spatial_resolution.x,
32✔
386
            -spatial_resolution.y,
32✔
387
        );
32✔
388

32✔
389
        let bbox = tiling.tile_grid_box(spatial_bounds);
32✔
390

32✔
391
        let shape = [
32✔
392
            bbox.axis_size_y() * tiling.tile_size_in_pixels.axis_size_y(),
32✔
393
            bbox.axis_size_x() * tiling.tile_size_in_pixels.axis_size_x(),
32✔
394
        ];
32✔
395

32✔
396
        // 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✔
397
        let grid = EmptyGrid2D::new(shape.into());
32✔
398

32✔
399
        let input_tile = RasterTile2D::new(
32✔
400
            time_interval,
32✔
401
            [0, 0].into(),
32✔
402
            0,
32✔
403
            geo_transform,
32✔
404
            GridOrEmpty::from(grid),
32✔
405
            CacheHint::max_duration(),
32✔
406
        );
32✔
407

32✔
408
        InterpolationAccu::new(input_tile, tile_info, pool)
32✔
409
    })
32✔
410
    .map_err(From::from)
32✔
411
}
32✔
412

413
pub fn fold_future<T, I>(
72✔
414
    accu: InterpolationAccu<T, I>,
72✔
415
    tile: RasterTile2D<T>,
72✔
416
) -> impl Future<Output = Result<InterpolationAccu<T, I>>>
72✔
417
where
72✔
418
    T: Pixel,
72✔
419
    I: InterpolationAlgorithm<T>,
72✔
420
{
72✔
421
    crate::util::spawn_blocking(|| fold_impl(accu, tile)).then(|x| async move {
72✔
422
        match x {
72✔
423
            Ok(r) => r,
72✔
424
            Err(e) => Err(e.into()),
×
425
        }
426
    })
72✔
427
}
72✔
428

429
pub fn fold_impl<T, I>(
72✔
430
    mut accu: InterpolationAccu<T, I>,
72✔
431
    tile: RasterTile2D<T>,
72✔
432
) -> Result<InterpolationAccu<T, I>>
72✔
433
where
72✔
434
    T: Pixel,
72✔
435
    I: InterpolationAlgorithm<T>,
72✔
436
{
72✔
437
    // get the time now because it is not known when the accu was created
72✔
438
    accu.input_tile.time = tile.time;
72✔
439

72✔
440
    // TODO: add a skip if both tiles are empty?
72✔
441

72✔
442
    // copy all input tiles into the accu to have all data for interpolation
72✔
443
    let mut accu_input_tile = accu.input_tile.into_materialized_tile();
72✔
444
    accu_input_tile.blit(tile)?;
72✔
445

446
    Ok(InterpolationAccu::new(
72✔
447
        accu_input_tile.into(),
72✔
448
        accu.output_info,
72✔
449
        accu.pool,
72✔
450
    ))
72✔
451
}
72✔
452

453
#[cfg(test)]
454
mod tests {
455
    use super::*;
456
    use futures::StreamExt;
457
    use geoengine_datatypes::{
458
        primitives::{RasterQueryRectangle, SpatialPartition2D, SpatialResolution, TimeInterval},
459
        raster::{
460
            Grid2D, GridOrEmpty, RasterDataType, RasterTile2D, TileInformation, TilingSpecification,
461
        },
462
        spatial_reference::SpatialReference,
463
        util::test::TestDefault,
464
    };
465

466
    use crate::{
467
        engine::{
468
            MockExecutionContext, MockQueryContext, RasterBandDescriptors, RasterOperator,
469
            RasterResultDescriptor,
470
        },
471
        mock::{MockRasterSource, MockRasterSourceParams},
472
    };
473

474
    #[tokio::test]
1✔
475
    async fn nearest_neighbor_operator() -> Result<()> {
1✔
476
        let exe_ctx = MockExecutionContext::new_with_tiling_spec(TilingSpecification::new(
1✔
477
            (0., 0.).into(),
1✔
478
            [2, 2].into(),
1✔
479
        ));
1✔
480

1✔
481
        let raster = make_raster(CacheHint::max_duration());
1✔
482

483
        let operator = Interpolation {
1✔
484
            params: InterpolationParams {
1✔
485
                interpolation: InterpolationMethod::NearestNeighbor,
1✔
486
                input_resolution: InputResolution::Value(SpatialResolution::one()),
1✔
487
            },
1✔
488
            sources: SingleRasterSource { raster },
1✔
489
        }
1✔
490
        .boxed()
1✔
491
        .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
492
        .await?;
×
493

494
        let processor = operator.query_processor()?.get_i8().unwrap();
1✔
495

1✔
496
        let query_rect = RasterQueryRectangle {
1✔
497
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 2.).into(), (4., 0.).into()),
1✔
498
            time_interval: TimeInterval::new_unchecked(0, 20),
1✔
499
            spatial_resolution: SpatialResolution::zero_point_five(),
1✔
500
            attributes: BandSelection::first(),
1✔
501
        };
1✔
502
        let query_ctx = MockQueryContext::test_default();
1✔
503

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

506
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
68✔
507
        let result = result.into_iter().collect::<Result<Vec<_>>>()?;
1✔
508

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

1✔
512
        let data = vec![
1✔
513
            vec![1, 2, 5, 6],
1✔
514
            vec![2, 3, 6, 7],
1✔
515
            vec![3, 4, 7, 8],
1✔
516
            vec![4, 0, 8, 0],
1✔
517
            vec![5, 6, 0, 0],
1✔
518
            vec![6, 7, 0, 0],
1✔
519
            vec![7, 8, 0, 0],
1✔
520
            vec![8, 0, 0, 0],
1✔
521
            vec![8, 7, 4, 3],
1✔
522
            vec![7, 6, 3, 2],
1✔
523
            vec![6, 5, 2, 1],
1✔
524
            vec![5, 0, 1, 0],
1✔
525
            vec![4, 3, 0, 0],
1✔
526
            vec![3, 2, 0, 0],
1✔
527
            vec![2, 1, 0, 0],
1✔
528
            vec![1, 0, 0, 0],
1✔
529
        ];
1✔
530

1✔
531
        let valid = vec![
1✔
532
            vec![true; 4],
1✔
533
            vec![true; 4],
1✔
534
            vec![true; 4],
1✔
535
            vec![true, false, true, false],
1✔
536
            vec![true, true, false, false],
1✔
537
            vec![true, true, false, false],
1✔
538
            vec![true, true, false, false],
1✔
539
            vec![true, false, false, false],
1✔
540
            vec![true; 4],
1✔
541
            vec![true; 4],
1✔
542
            vec![true; 4],
1✔
543
            vec![true, false, true, false],
1✔
544
            vec![true, true, false, false],
1✔
545
            vec![true, true, false, false],
1✔
546
            vec![true, true, false, false],
1✔
547
            vec![true, false, false, false],
1✔
548
        ];
1✔
549

550
        for (i, tile) in result.into_iter().enumerate() {
16✔
551
            let tile = tile.into_materialized_tile();
16✔
552
            assert_eq!(tile.time, times[i]);
16✔
553
            assert_eq!(tile.grid_array.inner_grid.data, data[i]);
16✔
554
            assert_eq!(tile.grid_array.validity_mask.data, valid[i]);
16✔
555
        }
556

557
        Ok(())
1✔
558
    }
559

560
    fn make_raster(cache_hint: CacheHint) -> Box<dyn RasterOperator> {
2✔
561
        // test raster:
2✔
562
        // [0, 10)
2✔
563
        // || 1 | 2 || 3 | 4 ||
2✔
564
        // || 5 | 6 || 7 | 8 ||
2✔
565
        //
2✔
566
        // [10, 20)
2✔
567
        // || 8 | 7 || 6 | 5 ||
2✔
568
        // || 4 | 3 || 2 | 1 ||
2✔
569
        let raster_tiles = vec![
2✔
570
            RasterTile2D::<i8>::new_with_tile_info(
2✔
571
                TimeInterval::new_unchecked(0, 10),
2✔
572
                TileInformation {
2✔
573
                    global_tile_position: [-1, 0].into(),
2✔
574
                    tile_size_in_pixels: [2, 2].into(),
2✔
575
                    global_geo_transform: TestDefault::test_default(),
2✔
576
                },
2✔
577
                0,
2✔
578
                GridOrEmpty::from(Grid2D::new([2, 2].into(), vec![1, 2, 5, 6]).unwrap()),
2✔
579
                cache_hint,
2✔
580
            ),
2✔
581
            RasterTile2D::new_with_tile_info(
2✔
582
                TimeInterval::new_unchecked(0, 10),
2✔
583
                TileInformation {
2✔
584
                    global_tile_position: [-1, 1].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![3, 4, 7, 8]).unwrap()),
2✔
590
                cache_hint,
2✔
591
            ),
2✔
592
            RasterTile2D::new_with_tile_info(
2✔
593
                TimeInterval::new_unchecked(10, 20),
2✔
594
                TileInformation {
2✔
595
                    global_tile_position: [-1, 0].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![8, 7, 4, 3]).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, 1].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![6, 5, 2, 1]).unwrap()),
2✔
612
                cache_hint,
2✔
613
            ),
2✔
614
        ];
2✔
615

2✔
616
        MockRasterSource {
2✔
617
            params: MockRasterSourceParams {
2✔
618
                data: raster_tiles,
2✔
619
                result_descriptor: RasterResultDescriptor {
2✔
620
                    data_type: RasterDataType::I8,
2✔
621
                    spatial_reference: SpatialReference::epsg_4326().into(),
2✔
622
                    time: None,
2✔
623
                    bbox: None,
2✔
624
                    resolution: None,
2✔
625
                    bands: RasterBandDescriptors::new_single_band(),
2✔
626
                },
2✔
627
            },
2✔
628
        }
2✔
629
        .boxed()
2✔
630
    }
2✔
631

632
    #[tokio::test]
1✔
633
    async fn it_attaches_cache_hint() -> Result<()> {
1✔
634
        let exe_ctx = MockExecutionContext::new_with_tiling_spec(TilingSpecification::new(
1✔
635
            (0., 0.).into(),
1✔
636
            [2, 2].into(),
1✔
637
        ));
1✔
638

1✔
639
        let cache_hint = CacheHint::seconds(1234);
1✔
640
        let raster = make_raster(cache_hint);
1✔
641

642
        let operator = Interpolation {
1✔
643
            params: InterpolationParams {
1✔
644
                interpolation: InterpolationMethod::NearestNeighbor,
1✔
645
                input_resolution: InputResolution::Value(SpatialResolution::one()),
1✔
646
            },
1✔
647
            sources: SingleRasterSource { raster },
1✔
648
        }
1✔
649
        .boxed()
1✔
650
        .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
651
        .await?;
×
652

653
        let processor = operator.query_processor()?.get_i8().unwrap();
1✔
654

1✔
655
        let query_rect = RasterQueryRectangle {
1✔
656
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 2.).into(), (4., 0.).into()),
1✔
657
            time_interval: TimeInterval::new_unchecked(0, 20),
1✔
658
            spatial_resolution: SpatialResolution::zero_point_five(),
1✔
659
            attributes: BandSelection::first(),
1✔
660
        };
1✔
661
        let query_ctx = MockQueryContext::test_default();
1✔
662

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

665
        let result: Vec<Result<RasterTile2D<i8>>> = result_stream.collect().await;
68✔
666
        let result = result.into_iter().collect::<Result<Vec<_>>>()?;
1✔
667

668
        for tile in result {
17✔
669
            // dbg!(tile.time, tile.grid_array);
670
            assert_eq!(tile.cache_hint.expires(), cache_hint.expires());
16✔
671
        }
672

673
        Ok(())
1✔
674
    }
675
}
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