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

geo-engine / geoengine / 19438548963

17 Nov 2025 05:27PM UTC coverage: 88.512%. First build
19438548963

Pull #1083

github

web-flow
Merge c3ef76798 into f0e6b6470
Pull Request #1083: feat: new gdal source workflow optimization

6329 of 7650 new or added lines in 78 files covered. (82.73%)

116305 of 131400 relevant lines covered (88.51%)

494085.33 hits per line

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

95.16
/operators/src/processing/time_shift.rs
1
use crate::engine::{
2
    CanonicOperatorName, ExecutionContext, InitializedRasterOperator,
3
    InitializedSingleRasterOrVectorOperator, InitializedSources, InitializedVectorOperator,
4
    Operator, OperatorName, QueryContext, QueryProcessor, RasterOperator, RasterQueryProcessor,
5
    RasterResultDescriptor, ResultDescriptor, SingleRasterOrVectorSource,
6
    TypedRasterQueryProcessor, TypedVectorQueryProcessor, VectorOperator, VectorQueryProcessor,
7
    VectorResultDescriptor, WorkflowOperatorPath,
8
};
9
use crate::optimization::OptimizationError;
10
use crate::util::Result;
11
use crate::util::input::RasterOrVectorOperator;
12
use async_trait::async_trait;
13
use futures::StreamExt;
14
use futures::stream::BoxStream;
15
use geoengine_datatypes::collections::{
16
    FeatureCollection, FeatureCollectionInfos, FeatureCollectionModifications,
17
};
18
use geoengine_datatypes::error::{BoxedResultExt, ErrorSource};
19
use geoengine_datatypes::primitives::{
20
    ColumnSelection, Duration, Geometry, RasterQueryRectangle, SpatialResolution, TimeGranularity,
21
    TimeInstance, TimeInterval,
22
};
23
use geoengine_datatypes::primitives::{TimeStep, VectorQueryRectangle};
24
use geoengine_datatypes::raster::{Pixel, RasterTile2D};
25
use geoengine_datatypes::util::arrow::ArrowTyped;
26
use serde::{Deserialize, Serialize};
27
use snafu::Snafu;
28

29
/// Project the query rectangle to a new time interval.
30
pub type TimeShift = Operator<TimeShiftParams, SingleRasterOrVectorSource>;
31

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

36
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37
#[serde(tag = "type", rename_all = "camelCase")]
38
pub enum TimeShiftParams {
39
    /// Shift the query rectangle relative with a time step
40
    #[serde(rename_all = "camelCase")]
41
    Relative {
42
        granularity: TimeGranularity,
43
        value: i32,
44
    },
45
    /// Set the time interval to a fixed value
46
    #[serde(rename_all = "camelCase")]
47
    Absolute { time_interval: TimeInterval },
48
}
49

50
pub trait TimeShiftOperation: Send + Sync + Copy {
51
    type State: Send + Sync + Copy;
52

53
    fn shift(
54
        &self,
55
        time_interval: TimeInterval,
56
    ) -> Result<(TimeInterval, Self::State), TimeShiftError>;
57

58
    fn reverse_shift(
59
        &self,
60
        time_interval: TimeInterval,
61
        state: Self::State,
62
    ) -> Result<TimeInterval, TimeShiftError>;
63
}
64

65
#[derive(Debug, Clone, Copy)]
66
pub struct RelativeForwardShift {
67
    step: TimeStep,
68
}
69

70
impl TimeShiftOperation for RelativeForwardShift {
71
    type State = ();
72

73
    fn shift(
9✔
74
        &self,
9✔
75
        time_interval: TimeInterval,
9✔
76
    ) -> Result<(TimeInterval, Self::State), TimeShiftError> {
9✔
77
        let time_interval = time_interval + self.step;
9✔
78
        let time_interval = time_interval.boxed_context(error::TimeOverflow)?;
9✔
79
        Ok((time_interval, ()))
9✔
80
    }
9✔
81

82
    fn reverse_shift(
19✔
83
        &self,
19✔
84
        time_interval: TimeInterval,
19✔
85
        _state: Self::State,
19✔
86
    ) -> Result<TimeInterval, TimeShiftError> {
19✔
87
        let reversed_time_interval = time_interval - self.step;
19✔
88
        reversed_time_interval.boxed_context(error::TimeOverflow)
19✔
89
    }
19✔
90
}
91

92
#[derive(Debug, Clone, Copy)]
93
pub struct RelativeBackwardShift {
94
    step: TimeStep,
95
}
96

97
impl TimeShiftOperation for RelativeBackwardShift {
98
    type State = ();
99

100
    fn shift(
6✔
101
        &self,
6✔
102
        time_interval: TimeInterval,
6✔
103
    ) -> Result<(TimeInterval, Self::State), TimeShiftError> {
6✔
104
        let time_interval = time_interval - self.step;
6✔
105
        let time_interval = time_interval.boxed_context(error::TimeOverflow)?;
6✔
106
        Ok((time_interval, ()))
6✔
107
    }
6✔
108

109
    fn reverse_shift(
9✔
110
        &self,
9✔
111
        time_interval: TimeInterval,
9✔
112
        _state: Self::State,
9✔
113
    ) -> Result<TimeInterval, TimeShiftError> {
9✔
114
        let reversed_time_interval = time_interval + self.step;
9✔
115
        reversed_time_interval.boxed_context(error::TimeOverflow)
9✔
116
    }
9✔
117
}
118

119
#[derive(Debug, Clone, Copy)]
120
pub struct AbsoluteShift {
121
    time_interval: TimeInterval,
122
}
123

124
impl TimeShiftOperation for AbsoluteShift {
125
    type State = (Duration, Duration);
126

127
    fn shift(
5✔
128
        &self,
5✔
129
        time_interval: TimeInterval,
5✔
130
    ) -> Result<(TimeInterval, Self::State), TimeShiftError> {
5✔
131
        let time_start_difference = time_interval.start() - self.time_interval.start();
5✔
132
        let time_end_difference = time_interval.end() - self.time_interval.end();
5✔
133

134
        Ok((
5✔
135
            self.time_interval,
5✔
136
            (time_start_difference, time_end_difference),
5✔
137
        ))
5✔
138
    }
5✔
139

140
    fn reverse_shift(
7✔
141
        &self,
7✔
142
        time_interval: TimeInterval,
7✔
143
        (time_start_difference, time_end_difference): Self::State,
7✔
144
    ) -> Result<TimeInterval, TimeShiftError> {
7✔
145
        let t1 = time_interval.start() + time_start_difference.num_milliseconds();
7✔
146
        let t2 = time_interval.end() + time_end_difference.num_milliseconds();
7✔
147
        TimeInterval::new(t1, t2).boxed_context(error::FaultyTimeInterval { t1, t2 })
7✔
148
    }
7✔
149
}
150

151
#[derive(Debug, Snafu)]
152
#[snafu(visibility(pub(crate)), context(suffix(false)), module(error))]
153
pub enum TimeShiftError {
154
    #[snafu(display("Output type must match the type of the source"))]
155
    UnmatchedOutput,
156
    #[snafu(display("Shifting the time led to an overflowing time interval"))]
157
    TimeOverflow { source: Box<dyn ErrorSource> },
158
    #[snafu(display("Shifting the time to a faulty time interval: {t1} / {t2}"))]
159
    FaultyTimeInterval {
160
        source: Box<dyn ErrorSource>,
161
        t1: TimeInstance,
162
        t2: TimeInstance,
163
    },
164
    #[snafu(display("Modifying the timestamps of the feature collection failed"))]
165
    FeatureCollectionTimeModification { source: Box<dyn ErrorSource> },
166
}
167

168
#[typetag::serde]
×
169
#[async_trait]
170
impl VectorOperator for TimeShift {
171
    async fn _initialize(
172
        self: Box<Self>,
173
        path: WorkflowOperatorPath,
174
        context: &dyn ExecutionContext,
175
    ) -> Result<Box<dyn InitializedVectorOperator>> {
2✔
176
        let name = CanonicOperatorName::from(&self);
177

178
        let init_sources = self
179
            .sources
180
            .initialize_sources(path.clone(), context)
181
            .await?;
182

183
        match (init_sources.source, self.params.clone()) {
184
            (
185
                InitializedSingleRasterOrVectorOperator::Vector(source),
186
                TimeShiftParams::Relative { granularity, value },
187
            ) if value.is_positive() => {
188
                let shift = RelativeForwardShift {
189
                    step: TimeStep {
190
                        granularity,
191
                        step: value.unsigned_abs(),
192
                    },
193
                };
194

195
                let result_descriptor = shift_result_descriptor(source.result_descriptor(), shift);
196

197
                Ok(Box::new(InitializedVectorTimeShift {
198
                    name,
199
                    path,
200
                    params: self.params.clone(),
201
                    source,
202
                    result_descriptor,
203
                    shift,
204
                }))
205
            }
206
            (
207
                InitializedSingleRasterOrVectorOperator::Vector(source),
208
                TimeShiftParams::Relative { granularity, value },
209
            ) => {
210
                let shift = RelativeBackwardShift {
211
                    step: TimeStep {
212
                        granularity,
213
                        step: value.unsigned_abs(),
214
                    },
215
                };
216

217
                let result_descriptor = shift_result_descriptor(source.result_descriptor(), shift);
218

219
                Ok(Box::new(InitializedVectorTimeShift {
220
                    name,
221
                    path,
222
                    params: self.params.clone(),
223
                    source,
224
                    result_descriptor,
225
                    shift,
226
                }))
227
            }
228
            (
229
                InitializedSingleRasterOrVectorOperator::Vector(source),
230
                TimeShiftParams::Absolute { time_interval },
231
            ) => {
232
                let shift = AbsoluteShift { time_interval };
233

234
                let result_descriptor = shift_result_descriptor(source.result_descriptor(), shift);
235

236
                Ok(Box::new(InitializedVectorTimeShift {
237
                    name,
238
                    path,
239
                    params: self.params.clone(),
240
                    source,
241
                    result_descriptor,
242
                    shift,
243
                }))
244
            }
245
            (InitializedSingleRasterOrVectorOperator::Raster(_), _) => {
246
                Err(TimeShiftError::UnmatchedOutput.into())
247
            }
248
        }
249
    }
2✔
250

251
    span_fn!(TimeShift);
252
}
253

254
#[typetag::serde]
×
255
#[async_trait]
256
impl RasterOperator for TimeShift {
257
    async fn _initialize(
258
        self: Box<Self>,
259
        path: WorkflowOperatorPath,
260
        context: &dyn ExecutionContext,
261
    ) -> Result<Box<dyn InitializedRasterOperator>> {
6✔
262
        let name = CanonicOperatorName::from(&self);
263

264
        let init_sources = self
265
            .sources
266
            .initialize_sources(path.clone(), context)
267
            .await?;
268

269
        match (init_sources.source, self.params.clone()) {
270
            (
271
                InitializedSingleRasterOrVectorOperator::Raster(source),
272
                TimeShiftParams::Relative { granularity, value },
273
            ) if value.is_positive() => {
274
                let shift = RelativeForwardShift {
275
                    step: TimeStep {
276
                        granularity,
277
                        step: value.unsigned_abs(),
278
                    },
279
                };
280

281
                let result_descriptor = shift_result_descriptor(source.result_descriptor(), shift);
282

283
                Ok(Box::new(InitializedRasterTimeShift {
284
                    name,
285
                    path,
286
                    params: self.params.clone(),
287
                    source,
288
                    result_descriptor,
289
                    shift,
290
                }))
291
            }
292
            (
293
                InitializedSingleRasterOrVectorOperator::Raster(source),
294
                TimeShiftParams::Relative { granularity, value },
295
            ) => {
296
                let shift = RelativeBackwardShift {
297
                    step: TimeStep {
298
                        granularity,
299
                        step: value.unsigned_abs(),
300
                    },
301
                };
302

303
                let result_descriptor = shift_result_descriptor(source.result_descriptor(), shift);
304

305
                Ok(Box::new(InitializedRasterTimeShift {
306
                    name,
307
                    path,
308
                    params: self.params.clone(),
309
                    source,
310
                    result_descriptor,
311
                    shift,
312
                }))
313
            }
314
            (
315
                InitializedSingleRasterOrVectorOperator::Raster(source),
316
                TimeShiftParams::Absolute { time_interval },
317
            ) => {
318
                let shift = AbsoluteShift { time_interval };
319

320
                let result_descriptor = shift_result_descriptor(source.result_descriptor(), shift);
321

322
                Ok(Box::new(InitializedRasterTimeShift {
323
                    name,
324
                    path,
325
                    params: self.params.clone(),
326
                    source,
327
                    result_descriptor,
328
                    shift,
329
                }))
330
            }
331
            (InitializedSingleRasterOrVectorOperator::Vector(_), _) => {
332
                Err(TimeShiftError::UnmatchedOutput.into())
333
            }
334
        }
335
    }
6✔
336

337
    span_fn!(TimeShift);
338
}
339

340
fn shift_result_descriptor<R: ResultDescriptor, S: TimeShiftOperation>(
8✔
341
    result_descriptor: &R,
8✔
342
    shift: S,
8✔
343
) -> R {
8✔
344
    result_descriptor.map_time(|time| {
8✔
345
        if let Some(time) = time {
8✔
346
            shift.shift(*time).map(|r| r.0).ok()
6✔
347
        } else {
348
            None
2✔
349
        }
350
    })
8✔
351
}
8✔
352

353
pub struct InitializedVectorTimeShift<Shift: TimeShiftOperation> {
354
    name: CanonicOperatorName,
355
    path: WorkflowOperatorPath,
356
    params: TimeShiftParams,
357
    source: Box<dyn InitializedVectorOperator>,
358
    result_descriptor: VectorResultDescriptor,
359
    shift: Shift,
360
}
361

362
pub struct InitializedRasterTimeShift<Shift: TimeShiftOperation> {
363
    name: CanonicOperatorName,
364
    path: WorkflowOperatorPath,
365
    params: TimeShiftParams,
366
    source: Box<dyn InitializedRasterOperator>,
367
    result_descriptor: RasterResultDescriptor,
368
    shift: Shift,
369
}
370

371
impl<Shift: TimeShiftOperation + 'static> InitializedVectorOperator
372
    for InitializedVectorTimeShift<Shift>
373
{
374
    fn result_descriptor(&self) -> &VectorResultDescriptor {
×
375
        &self.result_descriptor
×
376
    }
×
377

378
    fn query_processor(&self) -> Result<TypedVectorQueryProcessor> {
2✔
379
        let source_processor = self.source.query_processor()?;
2✔
380

381
        Ok(
382
            call_on_generic_vector_processor!(source_processor, processor => VectorTimeShiftProcessor {
2✔
383
                processor,
×
384
                result_descriptor: self.result_descriptor.clone(),
×
385
                shift: self.shift,
×
386
            }.boxed().into()),
×
387
        )
388
    }
2✔
389

390
    fn canonic_name(&self) -> CanonicOperatorName {
×
391
        self.name.clone()
×
392
    }
×
393

394
    fn name(&self) -> &'static str {
×
395
        TimeShift::TYPE_NAME
×
396
    }
×
397

398
    fn path(&self) -> WorkflowOperatorPath {
×
399
        self.path.clone()
×
400
    }
×
401

NEW
402
    fn optimize(
×
NEW
403
        &self,
×
NEW
404
        target_resolution: SpatialResolution,
×
NEW
405
    ) -> Result<Box<dyn VectorOperator>, OptimizationError> {
×
NEW
406
        Ok(Box::new(TimeShift {
×
NEW
407
            params: self.params.clone(),
×
408
            sources: SingleRasterOrVectorSource {
NEW
409
                source: RasterOrVectorOperator::Vector(self.source.optimize(target_resolution)?),
×
410
            },
411
        }))
NEW
412
    }
×
413
}
414

415
impl<Shift: TimeShiftOperation + 'static> InitializedRasterOperator
416
    for InitializedRasterTimeShift<Shift>
417
{
418
    fn result_descriptor(&self) -> &RasterResultDescriptor {
2✔
419
        &self.result_descriptor
2✔
420
    }
2✔
421

422
    fn query_processor(&self) -> Result<TypedRasterQueryProcessor> {
6✔
423
        let source_processor = self.source.query_processor()?;
6✔
424

425
        Ok(
426
            call_on_generic_raster_processor!(source_processor, processor => RasterTimeShiftProcessor {
6✔
427
                processor,
6✔
428
                result_descriptor: self.result_descriptor.clone(),
6✔
429
                shift: self.shift,
6✔
430
            }.boxed().into()),
6✔
431
        )
432
    }
6✔
433

434
    fn canonic_name(&self) -> CanonicOperatorName {
×
435
        self.name.clone()
×
436
    }
×
437

438
    fn name(&self) -> &'static str {
2✔
439
        TimeShift::TYPE_NAME
2✔
440
    }
2✔
441

442
    fn path(&self) -> WorkflowOperatorPath {
2✔
443
        self.path.clone()
2✔
444
    }
2✔
445

NEW
446
    fn optimize(
×
NEW
447
        &self,
×
NEW
448
        target_resolution: SpatialResolution,
×
NEW
449
    ) -> Result<Box<dyn RasterOperator>, OptimizationError> {
×
NEW
450
        Ok(Box::new(TimeShift {
×
NEW
451
            params: self.params.clone(),
×
452
            sources: SingleRasterOrVectorSource {
NEW
453
                source: RasterOrVectorOperator::Raster(self.source.optimize(target_resolution)?),
×
454
            },
455
        }))
NEW
456
    }
×
457
}
458

459
pub struct RasterTimeShiftProcessor<Q, P, Shift: TimeShiftOperation>
460
where
461
    Q: RasterQueryProcessor<RasterType = P>,
462
{
463
    processor: Q,
464
    result_descriptor: RasterResultDescriptor,
465
    shift: Shift,
466
}
467

468
pub struct VectorTimeShiftProcessor<Q, G, Shift: TimeShiftOperation>
469
where
470
    G: Geometry,
471
    Q: VectorQueryProcessor<VectorType = FeatureCollection<G>>,
472
{
473
    processor: Q,
474
    result_descriptor: VectorResultDescriptor,
475
    shift: Shift,
476
}
477

478
#[async_trait]
479
impl<Q, G, Shift> VectorQueryProcessor for VectorTimeShiftProcessor<Q, G, Shift>
480
where
481
    G: Geometry + ArrowTyped + 'static,
482
    Q: VectorQueryProcessor<VectorType = FeatureCollection<G>>,
483
    Shift: TimeShiftOperation + 'static,
484
{
485
    type VectorType = FeatureCollection<G>;
486

487
    async fn vector_query<'a>(
488
        &'a self,
489
        query: VectorQueryRectangle,
490
        ctx: &'a dyn QueryContext,
491
    ) -> Result<BoxStream<'a, Result<Self::VectorType>>> {
2✔
492
        let (time_interval, state) = self.shift.shift(query.time_interval())?;
493

494
        let query = VectorQueryRectangle::new(
495
            query.spatial_bounds(),
496
            time_interval,
497
            ColumnSelection::all(),
498
        );
499
        let stream = self.processor.vector_query(query, ctx).await?;
500

501
        let stream = stream.then(move |collection| async move {
2✔
502
            let collection = collection?;
2✔
503
            let shift = self.shift;
2✔
504

505
            crate::util::spawn_blocking(move || {
2✔
506
                let time_intervals = collection
2✔
507
                    .time_intervals()
2✔
508
                    .iter()
2✔
509
                    .map(move |time| shift.reverse_shift(*time, state))
3✔
510
                    .collect::<Result<Vec<TimeInterval>, TimeShiftError>>()?;
2✔
511

512
                collection
2✔
513
                    .replace_time(&time_intervals)
2✔
514
                    .boxed_context(error::FeatureCollectionTimeModification)
2✔
515
                    .map_err(Into::into)
2✔
516
            })
2✔
517
            .await?
2✔
518
        });
4✔
519

520
        Ok(stream.boxed())
521
    }
2✔
522

523
    fn vector_result_descriptor(&self) -> &VectorResultDescriptor {
2✔
524
        &self.result_descriptor
2✔
525
    }
2✔
526
}
527

528
#[async_trait]
529
impl<Q, P, Shift> QueryProcessor for RasterTimeShiftProcessor<Q, P, Shift>
530
where
531
    Q: RasterQueryProcessor<RasterType = P>,
532
    P: Pixel,
533
    Shift: TimeShiftOperation,
534
{
535
    type Output = RasterTile2D<P>;
536
    type SpatialBounds = Q::SpatialBounds;
537
    type ResultDescription = RasterResultDescriptor;
538
    type Selection = Q::Selection;
539

540
    async fn _query<'a>(
541
        &'a self,
542
        query: RasterQueryRectangle,
543
        ctx: &'a dyn QueryContext,
544
    ) -> Result<BoxStream<'a, Result<Self::Output>>> {
6✔
545
        let (time_interval, state) = self.shift.shift(query.time_interval())?;
546
        let query = query.select_time_interval(time_interval);
547
        let stream = self.processor.raster_query(query, ctx).await?;
548

549
        let stream = stream.map(move |raster| {
26✔
550
            // reverse time shift for results
551
            let mut raster = raster?;
26✔
552

553
            raster.time = self.shift.reverse_shift(raster.time, state)?;
26✔
554

555
            Ok(raster)
26✔
556
        });
26✔
557

558
        Ok(Box::pin(stream))
559
    }
6✔
560

561
    fn result_descriptor(&self) -> &RasterResultDescriptor {
16✔
562
        &self.result_descriptor
16✔
563
    }
16✔
564
}
565

566
#[async_trait]
567
impl<Q, P, Shift> RasterQueryProcessor for RasterTimeShiftProcessor<Q, P, Shift>
568
where
569
    P: Pixel,
570
    Q: RasterQueryProcessor<RasterType = P>,
571
    Shift: TimeShiftOperation,
572
{
573
    type RasterType = P;
574

575
    async fn _time_query<'a>(
576
        &'a self,
577
        query: TimeInterval,
578
        ctx: &'a dyn QueryContext,
579
    ) -> Result<BoxStream<'a, Result<TimeInterval>>> {
×
580
        let (time_interval, state) = self.shift.shift(query)?;
581
        let stream = self.processor.time_query(time_interval, ctx).await?;
582

583
        let stream = stream
584
            .map(move |ti| {
×
585
                // reverse time shift for results
586
                ti.and_then(|t| self.shift.reverse_shift(t, state).map_err(Into::into)) // TODO: maybe we need a time_query error...
×
587
            })
×
588
            .boxed();
589

590
        Ok(stream)
591
    }
×
592
}
593

594
#[cfg(test)]
595
mod tests {
596
    use super::*;
597

598
    use crate::{
599
        engine::{
600
            MockExecutionContext, MultipleRasterSources, RasterBandDescriptors, SingleRasterSource,
601
            SpatialGridDescriptor, TimeDescriptor,
602
        },
603
        mock::{MockFeatureCollectionSource, MockRasterSource, MockRasterSourceParams},
604
        processing::{Expression, ExpressionParams, RasterStacker, RasterStackerParams},
605
        source::{GdalSource, GdalSourceParameters},
606
        util::{gdal::add_ndvi_dataset, input::RasterOrVectorOperator},
607
    };
608
    use futures::StreamExt;
609
    use geoengine_datatypes::{
610
        collections::{ChunksEqualIgnoringCacheHint, MultiPointCollection},
611
        dataset::NamedData,
612
        primitives::{
613
            BandSelection, BoundingBox2D, CacheHint, Coordinate2D, DateTime, MultiPoint,
614
            TimeGranularity,
615
        },
616
        raster::{
617
            BoundedGrid, EmptyGrid2D, GeoTransform, GridBoundingBox2D, GridOrEmpty, GridShape2D,
618
            RasterDataType, RenameBands, TileInformation, TilingSpecification,
619
        },
620
        spatial_reference::SpatialReference,
621
        util::test::TestDefault,
622
    };
623

624
    #[test]
625
    fn test_ser_de_absolute() {
1✔
626
        let time_shift = TimeShift {
1✔
627
            sources: SingleRasterOrVectorSource {
1✔
628
                source: RasterOrVectorOperator::Raster(
1✔
629
                    GdalSource {
1✔
630
                        params: GdalSourceParameters::new(NamedData::with_system_name(
1✔
631
                            "test-raster",
1✔
632
                        )),
1✔
633
                    }
1✔
634
                    .boxed(),
1✔
635
                ),
1✔
636
            },
1✔
637
            params: TimeShiftParams::Absolute {
1✔
638
                time_interval: TimeInterval::new_unchecked(
1✔
639
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
640
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
641
                ),
1✔
642
            },
1✔
643
        };
1✔
644

645
        let serialized = serde_json::to_value(&time_shift).unwrap();
1✔
646

647
        assert_eq!(
1✔
648
            serialized,
649
            serde_json::json!({
1✔
650
                "params": {
1✔
651
                    "type": "absolute",
1✔
652
                    "timeInterval": {
1✔
653
                        "start": 1_293_840_000_000_i64,
1✔
654
                        "end": 1_325_376_000_000_i64
1✔
655
                    }
656
                },
657
                "sources": {
1✔
658
                    "source": {
1✔
659
                        "type": "GdalSource",
1✔
660
                        "params": {
1✔
661
                            "data": "test-raster",
1✔
662
                            "overviewLevel": null
1✔
663
                        }
664
                    }
665
                }
666
            })
667
        );
668

669
        let deserialized: TimeShift = serde_json::from_value(serialized).unwrap();
1✔
670

671
        assert_eq!(time_shift.params, deserialized.params);
1✔
672
    }
1✔
673

674
    #[test]
675
    fn test_ser_de_relative() {
1✔
676
        let time_shift = TimeShift {
1✔
677
            sources: SingleRasterOrVectorSource {
1✔
678
                source: RasterOrVectorOperator::Raster(
1✔
679
                    GdalSource {
1✔
680
                        params: GdalSourceParameters::new(NamedData::with_system_name(
1✔
681
                            "test-raster",
1✔
682
                        )),
1✔
683
                    }
1✔
684
                    .boxed(),
1✔
685
                ),
1✔
686
            },
1✔
687
            params: TimeShiftParams::Relative {
1✔
688
                granularity: TimeGranularity::Years,
1✔
689
                value: 1,
1✔
690
            },
1✔
691
        };
1✔
692

693
        let serialized = serde_json::to_value(&time_shift).unwrap();
1✔
694

695
        assert_eq!(
1✔
696
            serialized,
697
            serde_json::json!({
1✔
698
                "params": {
1✔
699
                    "type": "relative",
1✔
700
                    "granularity": "years",
1✔
701
                    "value": 1
1✔
702
                },
703
                "sources": {
1✔
704
                    "source": {
1✔
705
                        "type": "GdalSource",
1✔
706
                        "params": {
1✔
707
                            "data": "test-raster",
1✔
708
                            "overviewLevel": null
1✔
709
                        }
710
                    }
711
                }
712
            })
713
        );
714

715
        let deserialized: TimeShift = serde_json::from_value(serialized).unwrap();
1✔
716

717
        assert_eq!(time_shift.params, deserialized.params);
1✔
718
    }
1✔
719

720
    #[tokio::test]
721
    async fn test_absolute_vector_shift() {
1✔
722
        let execution_context = MockExecutionContext::test_default();
1✔
723
        let query_context = execution_context.mock_query_context_test_default();
1✔
724

725
        let source = MockFeatureCollectionSource::single(
1✔
726
            MultiPointCollection::from_data(
1✔
727
                MultiPoint::many(vec![(0., 0.), (1., 1.), (2., 2.)]).unwrap(),
1✔
728
                vec![
1✔
729
                    TimeInterval::new(
1✔
730
                        DateTime::new_utc(2009, 1, 1, 0, 0, 0),
1✔
731
                        DateTime::new_utc_with_millis(2010, 12, 31, 23, 59, 59, 999),
1✔
732
                    )
733
                    .unwrap(),
1✔
734
                    TimeInterval::new(
1✔
735
                        DateTime::new_utc(2009, 6, 3, 0, 0, 0),
1✔
736
                        DateTime::new_utc(2010, 7, 14, 0, 0, 0),
1✔
737
                    )
738
                    .unwrap(),
1✔
739
                    TimeInterval::new(
1✔
740
                        DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
741
                        DateTime::new_utc_with_millis(2011, 3, 31, 23, 59, 59, 999),
1✔
742
                    )
743
                    .unwrap(),
1✔
744
                ],
745
                Default::default(),
1✔
746
                CacheHint::default(),
1✔
747
            )
748
            .unwrap(),
1✔
749
        );
750

751
        let time_shift = TimeShift {
1✔
752
            sources: SingleRasterOrVectorSource {
1✔
753
                source: RasterOrVectorOperator::Vector(source.boxed()),
1✔
754
            },
1✔
755
            params: TimeShiftParams::Absolute {
1✔
756
                time_interval: TimeInterval::new(
1✔
757
                    DateTime::new_utc(2009, 1, 1, 0, 0, 0),
1✔
758
                    DateTime::new_utc(2009, 6, 1, 0, 0, 0),
1✔
759
                )
1✔
760
                .unwrap(),
1✔
761
            },
1✔
762
        };
1✔
763

764
        let query_processor = VectorOperator::boxed(time_shift)
1✔
765
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
766
            .await
1✔
767
            .unwrap()
1✔
768
            .query_processor()
1✔
769
            .unwrap()
1✔
770
            .multi_point()
1✔
771
            .unwrap();
1✔
772

773
        let mut stream = query_processor
1✔
774
            .vector_query(
1✔
775
                VectorQueryRectangle::new(
1✔
776
                    BoundingBox2D::new((0., 0.).into(), (2., 2.).into()).unwrap(),
1✔
777
                    TimeInterval::new(
1✔
778
                        DateTime::new_utc(2009, 1, 1, 0, 0, 0),
1✔
779
                        DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
780
                    )
1✔
781
                    .unwrap(),
1✔
782
                    ColumnSelection::all(),
1✔
783
                ),
1✔
784
                &query_context,
1✔
785
            )
1✔
786
            .await
1✔
787
            .unwrap();
1✔
788

789
        let mut result = Vec::new();
1✔
790
        while let Some(collection) = stream.next().await {
2✔
791
            result.push(collection.unwrap());
1✔
792
        }
1✔
793

794
        assert_eq!(result.len(), 1);
1✔
795

796
        let expected = MultiPointCollection::from_data(
1✔
797
            MultiPoint::many(vec![(0., 0.)]).unwrap(),
1✔
798
            vec![
1✔
799
                TimeInterval::new(
1✔
800
                    DateTime::new_utc(2009, 1, 1, 0, 0, 0),
1✔
801
                    DateTime::new_utc_with_millis(2013, 8, 1, 23, 59, 59, 999),
1✔
802
                )
803
                .unwrap(),
1✔
804
            ],
805
            Default::default(),
1✔
806
            CacheHint::default(),
1✔
807
        )
808
        .unwrap();
1✔
809

810
        assert!(result[0].chunks_equal_ignoring_cache_hint(&expected));
1✔
811
    }
1✔
812

813
    #[tokio::test]
814
    async fn test_relative_vector_shift() {
1✔
815
        let execution_context = MockExecutionContext::test_default();
1✔
816
        let query_context = execution_context.mock_query_context_test_default();
1✔
817

818
        let source = MockFeatureCollectionSource::single(
1✔
819
            MultiPointCollection::from_data(
1✔
820
                MultiPoint::many(vec![(0., 0.), (1., 1.), (2., 2.)]).unwrap(),
1✔
821
                vec![
1✔
822
                    TimeInterval::new(
1✔
823
                        DateTime::new_utc(2009, 1, 1, 0, 0, 0),
1✔
824
                        DateTime::new_utc_with_millis(2010, 12, 31, 23, 59, 59, 999),
1✔
825
                    )
826
                    .unwrap(),
1✔
827
                    TimeInterval::new(
1✔
828
                        DateTime::new_utc(2009, 6, 3, 0, 0, 0),
1✔
829
                        DateTime::new_utc(2010, 7, 14, 0, 0, 0),
1✔
830
                    )
831
                    .unwrap(),
1✔
832
                    TimeInterval::new(
1✔
833
                        DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
834
                        DateTime::new_utc_with_millis(2011, 3, 31, 23, 59, 59, 999),
1✔
835
                    )
836
                    .unwrap(),
1✔
837
                ],
838
                Default::default(),
1✔
839
                CacheHint::default(),
1✔
840
            )
841
            .unwrap(),
1✔
842
        );
843

844
        let time_shift = TimeShift {
1✔
845
            sources: SingleRasterOrVectorSource {
1✔
846
                source: RasterOrVectorOperator::Vector(source.boxed()),
1✔
847
            },
1✔
848
            params: TimeShiftParams::Relative {
1✔
849
                granularity: TimeGranularity::Years,
1✔
850
                value: -1,
1✔
851
            },
1✔
852
        };
1✔
853

854
        let query_processor = VectorOperator::boxed(time_shift)
1✔
855
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
856
            .await
1✔
857
            .unwrap()
1✔
858
            .query_processor()
1✔
859
            .unwrap()
1✔
860
            .multi_point()
1✔
861
            .unwrap();
1✔
862

863
        let mut stream = query_processor
1✔
864
            .vector_query(
1✔
865
                VectorQueryRectangle::new(
1✔
866
                    BoundingBox2D::new((0., 0.).into(), (2., 2.).into()).unwrap(),
1✔
867
                    TimeInterval::new(
1✔
868
                        DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
869
                        DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
870
                    )
1✔
871
                    .unwrap(),
1✔
872
                    ColumnSelection::all(),
1✔
873
                ),
1✔
874
                &query_context,
1✔
875
            )
1✔
876
            .await
1✔
877
            .unwrap();
1✔
878

879
        let mut result = Vec::new();
1✔
880
        while let Some(collection) = stream.next().await {
2✔
881
            result.push(collection.unwrap());
1✔
882
        }
1✔
883

884
        assert_eq!(result.len(), 1);
1✔
885

886
        let expected = MultiPointCollection::from_data(
1✔
887
            MultiPoint::many(vec![(0., 0.), (1., 1.)]).unwrap(),
1✔
888
            vec![
1✔
889
                TimeInterval::new(
1✔
890
                    DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
891
                    DateTime::new_utc_with_millis(2011, 12, 31, 23, 59, 59, 999),
1✔
892
                )
893
                .unwrap(),
1✔
894
                TimeInterval::new(
1✔
895
                    DateTime::new_utc(2010, 6, 3, 0, 0, 0),
1✔
896
                    DateTime::new_utc(2011, 7, 14, 0, 0, 0),
1✔
897
                )
898
                .unwrap(),
1✔
899
            ],
900
            Default::default(),
1✔
901
            CacheHint::default(),
1✔
902
        )
903
        .unwrap();
1✔
904

905
        assert!(result[0].chunks_equal_ignoring_cache_hint(&expected));
1✔
906
    }
1✔
907

908
    #[tokio::test]
909
    #[allow(clippy::too_many_lines)]
910
    async fn test_absolute_raster_shift() {
1✔
911
        let tile_size_in_pixels = GridShape2D::new_2d(3, 2);
1✔
912
        let result_descriptor = RasterResultDescriptor {
1✔
913
            data_type: RasterDataType::U8,
1✔
914
            spatial_reference: SpatialReference::epsg_4326().into(),
1✔
915
            time: TimeDescriptor::new_regular_with_epoch(
1✔
916
                Some(TimeInterval::new_unchecked(
1✔
917
                    DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
918
                    DateTime::new_utc(2013, 1, 1, 0, 0, 0),
1✔
919
                )),
1✔
920
                TimeStep::years(1).unwrap(),
1✔
921
            ),
1✔
922
            spatial_grid: SpatialGridDescriptor::source_from_parts(
1✔
923
                GeoTransform::new(Coordinate2D::new(0., -3.), 1., -1.),
1✔
924
                GridShape2D::new_2d(3, 4).bounding_box(),
1✔
925
            ),
1✔
926
            bands: RasterBandDescriptors::new_single_band(),
1✔
927
        };
1✔
928
        let tiling_specification = TilingSpecification::new(tile_size_in_pixels);
1✔
929

930
        let empty_grid = GridOrEmpty::Empty(EmptyGrid2D::<u8>::new(tile_size_in_pixels));
1✔
931
        let raster_tiles = vec![
1✔
932
            RasterTile2D::new_with_tile_info(
1✔
933
                TimeInterval::new_unchecked(
1✔
934
                    DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
935
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
936
                ),
937
                TileInformation {
1✔
938
                    global_tile_position: [-1, 0].into(),
1✔
939
                    tile_size_in_pixels: [3, 2].into(),
1✔
940
                    global_geo_transform: TestDefault::test_default(),
1✔
941
                },
1✔
942
                0,
943
                empty_grid.clone(),
1✔
944
                CacheHint::default(),
1✔
945
            ),
946
            RasterTile2D::new_with_tile_info(
1✔
947
                TimeInterval::new_unchecked(
1✔
948
                    DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
949
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
950
                ),
951
                TileInformation {
1✔
952
                    global_tile_position: [-1, 1].into(),
1✔
953
                    tile_size_in_pixels: [3, 2].into(),
1✔
954
                    global_geo_transform: TestDefault::test_default(),
1✔
955
                },
1✔
956
                0,
957
                empty_grid.clone(),
1✔
958
                CacheHint::default(),
1✔
959
            ),
960
            RasterTile2D::new_with_tile_info(
1✔
961
                TimeInterval::new_unchecked(
1✔
962
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
963
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
964
                ),
965
                TileInformation {
1✔
966
                    global_tile_position: [-1, 0].into(),
1✔
967
                    tile_size_in_pixels: [3, 2].into(),
1✔
968
                    global_geo_transform: TestDefault::test_default(),
1✔
969
                },
1✔
970
                0,
971
                empty_grid.clone(),
1✔
972
                CacheHint::default(),
1✔
973
            ),
974
            RasterTile2D::new_with_tile_info(
1✔
975
                TimeInterval::new_unchecked(
1✔
976
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
977
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
978
                ),
979
                TileInformation {
1✔
980
                    global_tile_position: [-1, 1].into(),
1✔
981
                    tile_size_in_pixels: [3, 2].into(),
1✔
982
                    global_geo_transform: TestDefault::test_default(),
1✔
983
                },
1✔
984
                0,
985
                empty_grid.clone(),
1✔
986
                CacheHint::default(),
1✔
987
            ),
988
            RasterTile2D::new_with_tile_info(
1✔
989
                TimeInterval::new_unchecked(
1✔
990
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
991
                    DateTime::new_utc(2013, 1, 1, 0, 0, 0),
1✔
992
                ),
993
                TileInformation {
1✔
994
                    global_tile_position: [-1, 0].into(),
1✔
995
                    tile_size_in_pixels: [3, 2].into(),
1✔
996
                    global_geo_transform: TestDefault::test_default(),
1✔
997
                },
1✔
998
                0,
999
                empty_grid.clone(),
1✔
1000
                CacheHint::default(),
1✔
1001
            ),
1002
            RasterTile2D::new_with_tile_info(
1✔
1003
                TimeInterval::new_unchecked(
1✔
1004
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
1005
                    DateTime::new_utc(2013, 1, 1, 0, 0, 0),
1✔
1006
                ),
1007
                TileInformation {
1✔
1008
                    global_tile_position: [-1, 1].into(),
1✔
1009
                    tile_size_in_pixels: [3, 2].into(),
1✔
1010
                    global_geo_transform: TestDefault::test_default(),
1✔
1011
                },
1✔
1012
                0,
1013
                empty_grid.clone(),
1✔
1014
                CacheHint::default(),
1✔
1015
            ),
1016
        ];
1017

1018
        let mrs = MockRasterSource {
1✔
1019
            params: MockRasterSourceParams {
1✔
1020
                data: raster_tiles,
1✔
1021
                result_descriptor,
1✔
1022
            },
1✔
1023
        }
1✔
1024
        .boxed();
1✔
1025

1026
        let time_shift = TimeShift {
1✔
1027
            sources: SingleRasterOrVectorSource {
1✔
1028
                source: RasterOrVectorOperator::Raster(mrs),
1✔
1029
            },
1✔
1030
            params: TimeShiftParams::Absolute {
1✔
1031
                time_interval: TimeInterval::new_unchecked(
1✔
1032
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
1033
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
1034
                ),
1✔
1035
            },
1✔
1036
        };
1✔
1037

1038
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1039

1040
        let query_context = execution_context.mock_query_context_test_default();
1✔
1041

1042
        let query_processor = RasterOperator::boxed(time_shift)
1✔
1043
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1044
            .await
1✔
1045
            .unwrap()
1✔
1046
            .query_processor()
1✔
1047
            .unwrap()
1✔
1048
            .get_u8()
1✔
1049
            .unwrap();
1✔
1050

1051
        let mut stream = query_processor
1✔
1052
            .raster_query(
1✔
1053
                RasterQueryRectangle::new(
1✔
1054
                    GridBoundingBox2D::new([-3, 0], [-1, 3]).unwrap(),
1✔
1055
                    TimeInterval::new(
1✔
1056
                        DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
1057
                        DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
1058
                    )
1✔
1059
                    .unwrap(),
1✔
1060
                    BandSelection::first(),
1✔
1061
                ),
1✔
1062
                &query_context,
1✔
1063
            )
1✔
1064
            .await
1✔
1065
            .unwrap();
1✔
1066

1067
        let mut result = Vec::new();
1✔
1068
        while let Some(tile) = stream.next().await {
3✔
1069
            result.push(tile.unwrap());
2✔
1070
        }
2✔
1071

1072
        assert_eq!(result.len(), 2);
1✔
1073

1074
        assert_eq!(
1✔
1075
            result[0].time,
1✔
1076
            TimeInterval::new_unchecked(
1✔
1077
                DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
1078
                DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
1079
            ),
1080
        );
1081
        assert_eq!(
1✔
1082
            result[1].time,
1✔
1083
            TimeInterval::new_unchecked(
1✔
1084
                DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
1085
                DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
1086
            ),
1✔
1087
        );
1✔
1088
    }
1✔
1089

1090
    #[tokio::test]
1091
    #[allow(clippy::too_many_lines)]
1092
    async fn test_relative_raster_shift() {
1✔
1093
        let tile_size_in_pixels = GridShape2D::new_2d(3, 2);
1✔
1094
        let result_descriptor = RasterResultDescriptor {
1✔
1095
            data_type: RasterDataType::U8,
1✔
1096
            spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1097
            time: TimeDescriptor::new_regular_with_epoch(
1✔
1098
                Some(TimeInterval::new_unchecked(
1✔
1099
                    DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
1100
                    DateTime::new_utc(2013, 1, 1, 0, 0, 0),
1✔
1101
                )),
1✔
1102
                TimeStep::years(1).unwrap(),
1✔
1103
            ),
1✔
1104
            spatial_grid: SpatialGridDescriptor::source_from_parts(
1✔
1105
                GeoTransform::new(Coordinate2D::new(0., 0.), 1., -1.),
1✔
1106
                GridBoundingBox2D::new([-3, 0], [0, 4]).unwrap(),
1✔
1107
            ),
1✔
1108
            bands: RasterBandDescriptors::new_single_band(),
1✔
1109
        };
1✔
1110
        let tiling_specification = TilingSpecification::new(tile_size_in_pixels);
1✔
1111

1112
        let empty_grid = GridOrEmpty::Empty(EmptyGrid2D::<u8>::new(tile_size_in_pixels));
1✔
1113
        let raster_tiles = vec![
1✔
1114
            RasterTile2D::new_with_tile_info(
1✔
1115
                TimeInterval::new_unchecked(
1✔
1116
                    DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
1117
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
1118
                ),
1119
                TileInformation {
1✔
1120
                    global_tile_position: [-1, 0].into(),
1✔
1121
                    tile_size_in_pixels: [3, 2].into(),
1✔
1122
                    global_geo_transform: TestDefault::test_default(),
1✔
1123
                },
1✔
1124
                0,
1125
                empty_grid.clone(),
1✔
1126
                CacheHint::default(),
1✔
1127
            ),
1128
            RasterTile2D::new_with_tile_info(
1✔
1129
                TimeInterval::new_unchecked(
1✔
1130
                    DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
1131
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
1132
                ),
1133
                TileInformation {
1✔
1134
                    global_tile_position: [-1, 1].into(),
1✔
1135
                    tile_size_in_pixels: [3, 2].into(),
1✔
1136
                    global_geo_transform: TestDefault::test_default(),
1✔
1137
                },
1✔
1138
                0,
1139
                empty_grid.clone(),
1✔
1140
                CacheHint::default(),
1✔
1141
            ),
1142
            RasterTile2D::new_with_tile_info(
1✔
1143
                TimeInterval::new_unchecked(
1✔
1144
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
1145
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
1146
                ),
1147
                TileInformation {
1✔
1148
                    global_tile_position: [-1, 0].into(),
1✔
1149
                    tile_size_in_pixels: [3, 2].into(),
1✔
1150
                    global_geo_transform: TestDefault::test_default(),
1✔
1151
                },
1✔
1152
                0,
1153
                empty_grid.clone(),
1✔
1154
                CacheHint::default(),
1✔
1155
            ),
1156
            RasterTile2D::new_with_tile_info(
1✔
1157
                TimeInterval::new_unchecked(
1✔
1158
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
1159
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
1160
                ),
1161
                TileInformation {
1✔
1162
                    global_tile_position: [-1, 1].into(),
1✔
1163
                    tile_size_in_pixels: [3, 2].into(),
1✔
1164
                    global_geo_transform: TestDefault::test_default(),
1✔
1165
                },
1✔
1166
                0,
1167
                empty_grid.clone(),
1✔
1168
                CacheHint::default(),
1✔
1169
            ),
1170
            RasterTile2D::new_with_tile_info(
1✔
1171
                TimeInterval::new_unchecked(
1✔
1172
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
1173
                    DateTime::new_utc(2013, 1, 1, 0, 0, 0),
1✔
1174
                ),
1175
                TileInformation {
1✔
1176
                    global_tile_position: [-1, 0].into(),
1✔
1177
                    tile_size_in_pixels: [3, 2].into(),
1✔
1178
                    global_geo_transform: TestDefault::test_default(),
1✔
1179
                },
1✔
1180
                0,
1181
                empty_grid.clone(),
1✔
1182
                CacheHint::default(),
1✔
1183
            ),
1184
            RasterTile2D::new_with_tile_info(
1✔
1185
                TimeInterval::new_unchecked(
1✔
1186
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
1187
                    DateTime::new_utc(2013, 1, 1, 0, 0, 0),
1✔
1188
                ),
1189
                TileInformation {
1✔
1190
                    global_tile_position: [-1, 1].into(),
1✔
1191
                    tile_size_in_pixels: [3, 2].into(),
1✔
1192
                    global_geo_transform: TestDefault::test_default(),
1✔
1193
                },
1✔
1194
                0,
1195
                empty_grid.clone(),
1✔
1196
                CacheHint::default(),
1✔
1197
            ),
1198
        ];
1199

1200
        let mrs = MockRasterSource {
1✔
1201
            params: MockRasterSourceParams {
1✔
1202
                data: raster_tiles,
1✔
1203
                result_descriptor,
1✔
1204
            },
1✔
1205
        }
1✔
1206
        .boxed();
1✔
1207

1208
        let time_shift = TimeShift {
1✔
1209
            sources: SingleRasterOrVectorSource {
1✔
1210
                source: RasterOrVectorOperator::Raster(mrs),
1✔
1211
            },
1✔
1212
            params: TimeShiftParams::Relative {
1✔
1213
                granularity: TimeGranularity::Years,
1✔
1214
                value: 1,
1✔
1215
            },
1✔
1216
        };
1✔
1217

1218
        let execution_context = MockExecutionContext::new_with_tiling_spec(tiling_specification);
1✔
1219
        let query_context = execution_context.mock_query_context_test_default();
1✔
1220

1221
        let query_processor = RasterOperator::boxed(time_shift)
1✔
1222
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1223
            .await
1✔
1224
            .unwrap()
1✔
1225
            .query_processor()
1✔
1226
            .unwrap()
1✔
1227
            .get_u8()
1✔
1228
            .unwrap();
1✔
1229

1230
        let mut stream = query_processor
1✔
1231
            .raster_query(
1✔
1232
                RasterQueryRectangle::new(
1✔
1233
                    GridBoundingBox2D::new([-3, 0], [-1, 3]).unwrap(),
1✔
1234
                    TimeInterval::new(
1✔
1235
                        DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
1236
                        DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
1237
                    )
1✔
1238
                    .unwrap(),
1✔
1239
                    BandSelection::first(),
1✔
1240
                ),
1✔
1241
                &query_context,
1✔
1242
            )
1✔
1243
            .await
1✔
1244
            .unwrap();
1✔
1245

1246
        let mut result = Vec::new();
1✔
1247
        while let Some(tile) = stream.next().await {
3✔
1248
            result.push(tile.unwrap());
2✔
1249
        }
2✔
1250

1251
        assert_eq!(result.len(), 2);
1✔
1252

1253
        assert_eq!(
1✔
1254
            result[0].time,
1✔
1255
            TimeInterval::new_unchecked(
1✔
1256
                DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
1257
                DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
1258
            ),
1259
        );
1260
        assert_eq!(
1✔
1261
            result[1].time,
1✔
1262
            TimeInterval::new_unchecked(
1✔
1263
                DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
1264
                DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
1265
            ),
1✔
1266
        );
1✔
1267
    }
1✔
1268

1269
    #[tokio::test]
1270
    async fn test_expression_on_shifted_raster() {
1✔
1271
        let mut execution_context = MockExecutionContext::test_default();
1✔
1272

1273
        let ndvi_source = GdalSource {
1✔
1274
            params: GdalSourceParameters::new(add_ndvi_dataset(&mut execution_context)),
1✔
1275
        }
1✔
1276
        .boxed();
1✔
1277

1278
        let shifted_ndvi_source = RasterOperator::boxed(TimeShift {
1✔
1279
            params: TimeShiftParams::Relative {
1✔
1280
                granularity: TimeGranularity::Months,
1✔
1281
                value: -1,
1✔
1282
            },
1✔
1283
            sources: SingleRasterOrVectorSource {
1✔
1284
                source: RasterOrVectorOperator::Raster(ndvi_source.clone()),
1✔
1285
            },
1✔
1286
        });
1✔
1287

1288
        let expression = Expression {
1✔
1289
            params: ExpressionParams {
1✔
1290
                expression: "A - B".to_string(),
1✔
1291
                output_type: RasterDataType::F64,
1✔
1292
                output_band: None,
1✔
1293
                map_no_data: false,
1✔
1294
            },
1✔
1295
            sources: SingleRasterSource {
1✔
1296
                raster: RasterStacker {
1✔
1297
                    params: RasterStackerParams {
1✔
1298
                        rename_bands: RenameBands::Default,
1✔
1299
                    },
1✔
1300
                    sources: MultipleRasterSources {
1✔
1301
                        rasters: vec![ndvi_source, shifted_ndvi_source],
1✔
1302
                    },
1✔
1303
                }
1✔
1304
                .boxed(),
1✔
1305
            },
1✔
1306
        }
1✔
1307
        .boxed();
1✔
1308

1309
        let query_processor = expression
1✔
1310
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1311
            .await
1✔
1312
            .unwrap()
1✔
1313
            .query_processor()
1✔
1314
            .unwrap()
1✔
1315
            .get_f64()
1✔
1316
            .unwrap();
1✔
1317

1318
        let query_context = execution_context.mock_query_context_test_default();
1✔
1319

1320
        let mut stream = query_processor
1✔
1321
            .raster_query(
1✔
1322
                RasterQueryRectangle::new(
1✔
1323
                    GridBoundingBox2D::new_min_max(-90, 89, -180, 179).unwrap(), // Note: this is not the actual bounding box of the NDVI dataset. The pixel size is 0.1!
1✔
1324
                    TimeInterval::new_instant(DateTime::new_utc(2014, 3, 1, 0, 0, 0)).unwrap(),
1✔
1325
                    BandSelection::first(),
1✔
1326
                ),
1✔
1327
                &query_context,
1✔
1328
            )
1✔
1329
            .await
1✔
1330
            .unwrap();
1✔
1331

1332
        let mut result = Vec::new();
1✔
1333
        while let Some(tile) = stream.next().await {
5✔
1334
            result.push(tile.unwrap());
4✔
1335
        }
4✔
1336

1337
        assert_eq!(result.len(), 4);
1✔
1338
        assert_eq!(
1✔
1339
            result[0].time,
1✔
1340
            TimeInterval::new(
1✔
1341
                DateTime::new_utc(2014, 3, 1, 0, 0, 0),
1✔
1342
                DateTime::new_utc(2014, 4, 1, 0, 0, 0)
1✔
1343
            )
1✔
1344
            .unwrap()
1✔
1345
        );
1✔
1346
    }
1✔
1347

1348
    #[tokio::test]
1349
    async fn test_expression_on_absolute_shifted_raster() {
1✔
1350
        let mut execution_context = MockExecutionContext::test_default();
1✔
1351

1352
        let ndvi_source = GdalSource {
1✔
1353
            params: GdalSourceParameters::new(add_ndvi_dataset(&mut execution_context)),
1✔
1354
        }
1✔
1355
        .boxed();
1✔
1356

1357
        let shifted_ndvi_source = RasterOperator::boxed(TimeShift {
1✔
1358
            params: TimeShiftParams::Absolute {
1✔
1359
                time_interval: TimeInterval::new_instant(DateTime::new_utc(2014, 5, 1, 0, 0, 0))
1✔
1360
                    .unwrap(),
1✔
1361
            },
1✔
1362
            sources: SingleRasterOrVectorSource {
1✔
1363
                source: RasterOrVectorOperator::Raster(ndvi_source),
1✔
1364
            },
1✔
1365
        });
1✔
1366

1367
        let query_processor = shifted_ndvi_source
1✔
1368
            .initialize(WorkflowOperatorPath::initialize_root(), &execution_context)
1✔
1369
            .await
1✔
1370
            .unwrap()
1✔
1371
            .query_processor()
1✔
1372
            .unwrap()
1✔
1373
            .get_u8()
1✔
1374
            .unwrap();
1✔
1375

1376
        let query_context = execution_context.mock_query_context_test_default();
1✔
1377

1378
        let mut stream = query_processor
1✔
1379
            .raster_query(
1✔
1380
                RasterQueryRectangle::new(
1✔
1381
                    GridBoundingBox2D::new_min_max(-90, 89, -180, 179).unwrap(), // Note: this is not the actual bounding box of the NDVI dataset. The pixel size is 0.1!
1✔
1382
                    TimeInterval::new_instant(DateTime::new_utc(2014, 3, 1, 0, 0, 0)).unwrap(),
1✔
1383
                    BandSelection::first(),
1✔
1384
                ),
1✔
1385
                &query_context,
1✔
1386
            )
1✔
1387
            .await
1✔
1388
            .unwrap();
1✔
1389

1390
        let mut result = Vec::new();
1✔
1391
        while let Some(tile) = stream.next().await {
5✔
1392
            result.push(tile.unwrap());
4✔
1393
        }
4✔
1394

1395
        assert_eq!(result.len(), 4);
1✔
1396
        assert_eq!(
1✔
1397
            result[0].time,
1✔
1398
            TimeInterval::new(
1✔
1399
                DateTime::new_utc(2014, 3, 1, 0, 0, 0),
1✔
1400
                DateTime::new_utc(2014, 4, 1, 0, 0, 0)
1✔
1401
            )
1✔
1402
            .unwrap()
1✔
1403
        );
1✔
1404
    }
1✔
1405

1406
    #[test]
1407
    fn shift_eternal() {
1✔
1408
        let eternal = TimeInterval::default();
1✔
1409

1410
        let f_shift = RelativeForwardShift {
1✔
1411
            step: TimeStep {
1✔
1412
                granularity: TimeGranularity::Seconds,
1✔
1413
                step: 1,
1✔
1414
            },
1✔
1415
        };
1✔
1416

1417
        let (shifted, state) = f_shift.shift(eternal).unwrap();
1✔
1418
        assert_eq!(shifted, eternal);
1✔
1419
        let reverse_shifted = f_shift.reverse_shift(eternal, state).unwrap();
1✔
1420
        assert_eq!(reverse_shifted, eternal);
1✔
1421

1422
        let b_shift = RelativeBackwardShift {
1✔
1423
            step: TimeStep {
1✔
1424
                granularity: TimeGranularity::Seconds,
1✔
1425
                step: 1,
1✔
1426
            },
1✔
1427
        };
1✔
1428

1429
        let (shifted, state) = b_shift.shift(eternal).unwrap();
1✔
1430
        assert_eq!(shifted, eternal);
1✔
1431

1432
        let reverse_shifted = b_shift.reverse_shift(eternal, state).unwrap();
1✔
1433
        assert_eq!(reverse_shifted, eternal);
1✔
1434
    }
1✔
1435

1436
    #[test]
1437
    fn shift_begin_of_time() {
1✔
1438
        let time = TimeInterval::new_unchecked(TimeInstance::MIN, TimeInstance::EPOCH_START);
1✔
1439

1440
        let f_shift = RelativeForwardShift {
1✔
1441
            step: TimeStep {
1✔
1442
                granularity: TimeGranularity::Seconds,
1✔
1443
                step: 1,
1✔
1444
            },
1✔
1445
        };
1✔
1446

1447
        let expected_shift =
1✔
1448
            TimeInterval::new_unchecked(TimeInstance::MIN, TimeInstance::EPOCH_START + 1_000);
1✔
1449

1450
        let (shifted, state) = f_shift.shift(time).unwrap();
1✔
1451
        assert_eq!(shifted, expected_shift);
1✔
1452

1453
        let reverse_shifted = f_shift.reverse_shift(expected_shift, state).unwrap();
1✔
1454
        assert_eq!(reverse_shifted, time);
1✔
1455

1456
        let f_shift = RelativeBackwardShift {
1✔
1457
            step: TimeStep {
1✔
1458
                granularity: TimeGranularity::Seconds,
1✔
1459
                step: 1,
1✔
1460
            },
1✔
1461
        };
1✔
1462

1463
        let expected_shift =
1✔
1464
            TimeInterval::new_unchecked(TimeInstance::MIN, TimeInstance::EPOCH_START - 1_000);
1✔
1465

1466
        let (shifted, state) = f_shift.shift(time).unwrap();
1✔
1467
        assert_eq!(shifted, expected_shift);
1✔
1468

1469
        let reverse_shifted = f_shift.reverse_shift(expected_shift, state).unwrap();
1✔
1470
        assert_eq!(reverse_shifted, time);
1✔
1471
    }
1✔
1472

1473
    #[test]
1474
    fn shift_end_of_time() {
1✔
1475
        let time = TimeInterval::new_unchecked(TimeInstance::EPOCH_START, TimeInstance::MAX);
1✔
1476

1477
        let f_shift = RelativeForwardShift {
1✔
1478
            step: TimeStep {
1✔
1479
                granularity: TimeGranularity::Seconds,
1✔
1480
                step: 1,
1✔
1481
            },
1✔
1482
        };
1✔
1483

1484
        let expected_shift =
1✔
1485
            TimeInterval::new_unchecked(TimeInstance::EPOCH_START + 1_000, TimeInstance::MAX);
1✔
1486

1487
        let (shifted, state) = f_shift.shift(time).unwrap();
1✔
1488
        assert_eq!(shifted, expected_shift);
1✔
1489

1490
        let reverse_shifted = f_shift.reverse_shift(expected_shift, state).unwrap();
1✔
1491
        assert_eq!(reverse_shifted, time);
1✔
1492

1493
        let f_shift = RelativeBackwardShift {
1✔
1494
            step: TimeStep {
1✔
1495
                granularity: TimeGranularity::Seconds,
1✔
1496
                step: 1,
1✔
1497
            },
1✔
1498
        };
1✔
1499

1500
        let expected_shift =
1✔
1501
            TimeInterval::new_unchecked(TimeInstance::EPOCH_START - 1_000, TimeInstance::MAX);
1✔
1502

1503
        let (shifted, state) = f_shift.shift(time).unwrap();
1✔
1504
        assert_eq!(shifted, expected_shift);
1✔
1505

1506
        let reverse_shifted = f_shift.reverse_shift(expected_shift, state).unwrap();
1✔
1507
        assert_eq!(reverse_shifted, time);
1✔
1508
    }
1✔
1509
}
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