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

geo-engine / geoengine / 3676601107

pending completion
3676601107

push

github

GitHub
Merge #695

42001 of 50014 relevant lines covered (83.98%)

2.01 hits per line

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

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

26
/// Project the query rectangle to a new time interval.
27
pub type TimeShift = Operator<TimeShiftParams, SingleRasterOrVectorSource>;
28

29
impl OperatorName for TimeShift {
30
    const TYPE_NAME: &'static str = "TimeShift";
31
}
32

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

47
pub trait TimeShiftOperation: Send + Sync + Copy {
48
    type State: Send + Sync + Copy;
49

50
    fn shift(
51
        &self,
52
        time_interval: TimeInterval,
53
    ) -> Result<(TimeInterval, Self::State), TimeShiftError>;
54

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

62
#[derive(Debug, Clone, Copy)]
63
pub struct RelativeForwardShift {
64
    step: TimeStep,
65
}
66

67
impl TimeShiftOperation for RelativeForwardShift {
68
    type State = ();
69

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

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

89
#[derive(Debug, Clone, Copy)]
90
pub struct RelativeBackwardShift {
91
    step: TimeStep,
92
}
93

94
impl TimeShiftOperation for RelativeBackwardShift {
95
    type State = ();
96

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

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

116
#[derive(Debug, Clone, Copy)]
117
pub struct AbsoluteShift {
118
    time_interval: TimeInterval,
119
}
120

121
impl TimeShiftOperation for AbsoluteShift {
122
    type State = (Duration, Duration);
123

124
    fn shift(
1✔
125
        &self,
126
        time_interval: TimeInterval,
127
    ) -> Result<(TimeInterval, Self::State), TimeShiftError> {
128
        let time_start_difference = time_interval.start() - self.time_interval.start();
1✔
129
        let time_end_difference = time_interval.end() - self.time_interval.end();
1✔
130

131
        Ok((
1✔
132
            self.time_interval,
1✔
133
            (time_start_difference, time_end_difference),
1✔
134
        ))
135
    }
136

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

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

165
#[typetag::serde]
166
#[async_trait]
167
impl VectorOperator for TimeShift {
168
    async fn _initialize(
1✔
169
        self: Box<Self>,
170
        context: &dyn ExecutionContext,
171
    ) -> Result<Box<dyn InitializedVectorOperator>> {
172
        match (self.sources.source, self.params) {
2✔
173
            (
2✔
174
                RasterOrVectorOperator::Vector(source),
175
                TimeShiftParams::Relative { granularity, value },
176
            ) if value.is_positive() => {
177
                let source = source.initialize(context).await?;
×
178

179
                let shift = RelativeForwardShift {
180
                    step: TimeStep {
×
181
                        granularity,
182
                        step: value.unsigned_abs(),
183
                    },
184
                };
185

186
                let result_descriptor = shift_result_descriptor(source.result_descriptor(), shift);
×
187

188
                Ok(Box::new(InitializedVectorTimeShift {
×
189
                    source,
×
190
                    result_descriptor,
×
191
                    shift,
×
192
                }))
193
            }
194
            (
1✔
195
                RasterOrVectorOperator::Vector(source),
196
                TimeShiftParams::Relative { granularity, value },
197
            ) => {
198
                let source = source.initialize(context).await?;
3✔
199

200
                let shift = RelativeBackwardShift {
201
                    step: TimeStep {
1✔
202
                        granularity,
203
                        step: value.unsigned_abs(),
204
                    },
205
                };
206

207
                let result_descriptor = shift_result_descriptor(source.result_descriptor(), shift);
1✔
208

209
                Ok(Box::new(InitializedVectorTimeShift {
2✔
210
                    source,
1✔
211
                    result_descriptor,
1✔
212
                    shift,
1✔
213
                }))
214
            }
215
            (
1✔
216
                RasterOrVectorOperator::Vector(source),
217
                TimeShiftParams::Absolute { time_interval },
218
            ) => {
219
                let source = source.initialize(context).await?;
2✔
220

221
                let shift = AbsoluteShift { time_interval };
1✔
222

223
                let result_descriptor = shift_result_descriptor(source.result_descriptor(), shift);
2✔
224

225
                Ok(Box::new(InitializedVectorTimeShift {
2✔
226
                    source,
1✔
227
                    result_descriptor,
1✔
228
                    shift,
1✔
229
                }))
230
            }
231
            (RasterOrVectorOperator::Raster(_), _) => Err(TimeShiftError::UnmatchedOutput.into()),
×
232
        }
233
    }
234

235
    span_fn!(TimeShift);
236
}
237

238
#[typetag::serde]
239
#[async_trait]
240
impl RasterOperator for TimeShift {
241
    async fn _initialize(
1✔
242
        self: Box<Self>,
243
        context: &dyn ExecutionContext,
244
    ) -> Result<Box<dyn InitializedRasterOperator>> {
245
        match (self.sources.source, self.params) {
1✔
246
            (
2✔
247
                RasterOrVectorOperator::Raster(source),
248
                TimeShiftParams::Relative { granularity, value },
249
            ) if value.is_positive() => {
250
                let source = source.initialize(context).await?;
3✔
251

252
                let shift = RelativeForwardShift {
253
                    step: TimeStep {
1✔
254
                        granularity,
255
                        step: value.unsigned_abs(),
256
                    },
257
                };
258

259
                let result_descriptor = shift_result_descriptor(source.result_descriptor(), shift);
1✔
260

261
                Ok(Box::new(InitializedRasterTimeShift {
2✔
262
                    source,
1✔
263
                    result_descriptor,
1✔
264
                    shift,
1✔
265
                }))
266
            }
267
            (
1✔
268
                RasterOrVectorOperator::Raster(source),
269
                TimeShiftParams::Relative { granularity, value },
270
            ) => {
271
                let source = source.initialize(context).await?;
3✔
272

273
                let shift = RelativeBackwardShift {
274
                    step: TimeStep {
1✔
275
                        granularity,
276
                        step: value.unsigned_abs(),
277
                    },
278
                };
279

280
                let result_descriptor = shift_result_descriptor(source.result_descriptor(), shift);
1✔
281

282
                Ok(Box::new(InitializedRasterTimeShift {
2✔
283
                    source,
1✔
284
                    result_descriptor,
1✔
285
                    shift,
1✔
286
                }))
287
            }
288
            (
1✔
289
                RasterOrVectorOperator::Raster(source),
290
                TimeShiftParams::Absolute { time_interval },
291
            ) => {
292
                let source = source.initialize(context).await?;
2✔
293

294
                let shift = AbsoluteShift { time_interval };
1✔
295

296
                let result_descriptor = shift_result_descriptor(source.result_descriptor(), shift);
2✔
297

298
                Ok(Box::new(InitializedRasterTimeShift {
2✔
299
                    source,
1✔
300
                    result_descriptor,
1✔
301
                    shift,
1✔
302
                }))
303
            }
304
            (RasterOrVectorOperator::Vector(_), _) => Err(TimeShiftError::UnmatchedOutput.into()),
×
305
        }
306
    }
307

308
    span_fn!(TimeShift);
309
}
310

311
fn shift_result_descriptor<R: ResultDescriptor, S: TimeShiftOperation>(
5✔
312
    result_descriptor: &R,
313
    shift: S,
314
) -> R {
315
    result_descriptor.map_time(|time| {
10✔
316
        if let Some(time) = time {
5✔
317
            shift.shift(*time).map(|r| r.0).ok()
8✔
318
        } else {
319
            None
4✔
320
        }
321
    })
322
}
323

324
pub struct InitializedVectorTimeShift<Shift: TimeShiftOperation> {
325
    source: Box<dyn InitializedVectorOperator>,
326
    result_descriptor: VectorResultDescriptor,
327
    shift: Shift,
328
}
329

330
pub struct InitializedRasterTimeShift<Shift: TimeShiftOperation> {
331
    source: Box<dyn InitializedRasterOperator>,
332
    result_descriptor: RasterResultDescriptor,
333
    shift: Shift,
334
}
335

336
impl<Shift: TimeShiftOperation + 'static> InitializedVectorOperator
337
    for InitializedVectorTimeShift<Shift>
338
{
339
    fn result_descriptor(&self) -> &VectorResultDescriptor {
×
340
        &self.result_descriptor
×
341
    }
342

343
    fn query_processor(&self) -> Result<TypedVectorQueryProcessor> {
2✔
344
        let source_processor = self.source.query_processor()?;
2✔
345

346
        Ok(
347
            call_on_generic_vector_processor!(source_processor, processor => VectorTimeShiftProcessor {
4✔
348
                processor,
×
349
                shift: self.shift,
2✔
350
            }.boxed().into()),
×
351
        )
352
    }
353
}
354

355
impl<Shift: TimeShiftOperation + 'static> InitializedRasterOperator
356
    for InitializedRasterTimeShift<Shift>
357
{
358
    fn result_descriptor(&self) -> &RasterResultDescriptor {
1✔
359
        &self.result_descriptor
1✔
360
    }
361

362
    fn query_processor(&self) -> Result<TypedRasterQueryProcessor> {
3✔
363
        let source_processor = self.source.query_processor()?;
3✔
364

365
        Ok(
366
            call_on_generic_raster_processor!(source_processor, processor => RasterTimeShiftProcessor {
6✔
367
                processor,
×
368
                shift: self.shift,
3✔
369
            }.boxed().into()),
×
370
        )
371
    }
372
}
373

374
pub struct RasterTimeShiftProcessor<Q, P, Shift: TimeShiftOperation>
375
where
376
    Q: RasterQueryProcessor<RasterType = P>,
377
{
378
    processor: Q,
379
    shift: Shift,
380
}
381

382
pub struct VectorTimeShiftProcessor<Q, G, Shift: TimeShiftOperation>
383
where
384
    G: Geometry,
385
    Q: VectorQueryProcessor<VectorType = FeatureCollection<G>>,
386
{
387
    processor: Q,
388
    shift: Shift,
389
}
390

391
#[async_trait]
392
impl<Q, G, Shift> VectorQueryProcessor for VectorTimeShiftProcessor<Q, G, Shift>
393
where
394
    G: Geometry + ArrowTyped + 'static,
395
    Q: VectorQueryProcessor<VectorType = FeatureCollection<G>>,
396
    Shift: TimeShiftOperation + 'static,
397
{
398
    type VectorType = FeatureCollection<G>;
399

400
    async fn vector_query<'a>(
2✔
401
        &'a self,
402
        query: VectorQueryRectangle,
403
        ctx: &'a dyn QueryContext,
404
    ) -> Result<BoxStream<'a, Result<Self::VectorType>>> {
405
        let (time_interval, state) = self.shift.shift(query.time_interval)?;
2✔
406

407
        let query = VectorQueryRectangle {
408
            spatial_bounds: query.spatial_bounds,
2✔
409
            time_interval,
410
            spatial_resolution: query.spatial_resolution,
2✔
411
        };
412
        let stream = self.processor.vector_query(query, ctx).await?;
4✔
413

414
        let stream = stream.then(move |collection| async move {
10✔
415
            let collection = collection?;
4✔
416
            let shift = self.shift;
2✔
417

418
            crate::util::spawn_blocking(move || {
10✔
419
                let time_intervals = collection
6✔
420
                    .time_intervals()
×
421
                    .iter()
×
422
                    .map(move |time| shift.reverse_shift(*time, state))
6✔
423
                    .collect::<Result<Vec<TimeInterval>, TimeShiftError>>()?;
×
424

425
                collection
2✔
426
                    .replace_time(&time_intervals)
2✔
427
                    .boxed_context(error::FeatureCollectionTimeModification)
×
428
                    .map_err(Into::into)
×
429
            })
430
            .await?
8✔
431
        });
432

433
        Ok(stream.boxed())
2✔
434
    }
435
}
436

437
#[async_trait]
438
impl<Q, P, Shift> RasterQueryProcessor for RasterTimeShiftProcessor<Q, P, Shift>
439
where
440
    Q: RasterQueryProcessor<RasterType = P>,
441
    P: Pixel,
442
    Shift: TimeShiftOperation,
443
{
444
    type RasterType = P;
445

446
    async fn raster_query<'a>(
3✔
447
        &'a self,
448
        query: RasterQueryRectangle,
449
        ctx: &'a dyn QueryContext,
450
    ) -> Result<BoxStream<'a, Result<RasterTile2D<Self::RasterType>>>> {
451
        let (time_interval, state) = self.shift.shift(query.time_interval)?;
3✔
452
        let query = RasterQueryRectangle {
453
            spatial_bounds: query.spatial_bounds,
3✔
454
            time_interval,
455
            spatial_resolution: query.spatial_resolution,
3✔
456
        };
457
        let stream = self.processor.raster_query(query, ctx).await?;
6✔
458

459
        let stream = stream.map(move |raster| {
6✔
460
            // reverse time shift for results
461
            let mut raster = raster?;
3✔
462

463
            raster.time = self.shift.reverse_shift(raster.time, state)?;
6✔
464

465
            Ok(raster)
3✔
466
        });
467

468
        Ok(Box::pin(stream))
6✔
469
    }
470
}
471

472
#[cfg(test)]
473
mod tests {
474
    use super::*;
475

476
    use crate::{
477
        engine::{MockExecutionContext, MockQueryContext},
478
        mock::{MockFeatureCollectionSource, MockRasterSource, MockRasterSourceParams},
479
        processing::{Expression, ExpressionParams, ExpressionSources},
480
        source::{GdalSource, GdalSourceParameters},
481
        util::gdal::add_ndvi_dataset,
482
    };
483
    use futures::StreamExt;
484
    use geoengine_datatypes::{
485
        collections::MultiPointCollection,
486
        dataset::DatasetId,
487
        primitives::{
488
            BoundingBox2D, DateTime, Measurement, MultiPoint, SpatialPartition2D,
489
            SpatialResolution, TimeGranularity,
490
        },
491
        raster::{EmptyGrid2D, GridOrEmpty, RasterDataType, TileInformation, TilingSpecification},
492
        spatial_reference::SpatialReference,
493
        util::test::TestDefault,
494
    };
495

496
    #[test]
497
    fn test_ser_de_absolute() {
3✔
498
        let time_shift = TimeShift {
499
            sources: SingleRasterOrVectorSource {
1✔
500
                source: RasterOrVectorOperator::Raster(
501
                    GdalSource {
502
                        params: GdalSourceParameters {
503
                            data: DatasetId::from_u128(1337).into(),
504
                        },
505
                    }
506
                    .boxed(),
507
                ),
508
            },
509
            params: TimeShiftParams::Absolute {
1✔
510
                time_interval: TimeInterval::new_unchecked(
511
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
512
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
513
                ),
514
            },
515
        };
516

517
        let serialized = serde_json::to_value(&time_shift).unwrap();
2✔
518

519
        assert_eq!(
1✔
520
            serialized,
521
            serde_json::json!({
2✔
522
                "params": {
523
                    "type": "absolute",
524
                    "timeInterval": {
525
                        "start": 1_293_840_000_000_i64,
526
                        "end": 1_325_376_000_000_i64
527
                    }
528
                },
529
                "sources": {
530
                    "source": {
531
                        "type": "GdalSource",
532
                        "params": {
533
                            "data": {
534
                                "type": "internal",
535
                                "datasetId": "00000000-0000-0000-0000-000000000539"
536
                            }
537
                        }
538
                    }
539
                }
540
            })
541
        );
542

543
        let deserialized: TimeShift = serde_json::from_value(serialized).unwrap();
1✔
544

545
        assert_eq!(time_shift.params, deserialized.params);
2✔
546
    }
547

548
    #[test]
549
    fn test_ser_de_relative() {
3✔
550
        let time_shift = TimeShift {
551
            sources: SingleRasterOrVectorSource {
1✔
552
                source: RasterOrVectorOperator::Raster(
553
                    GdalSource {
554
                        params: GdalSourceParameters {
555
                            data: DatasetId::from_u128(1337).into(),
556
                        },
557
                    }
558
                    .boxed(),
559
                ),
560
            },
561
            params: TimeShiftParams::Relative {
1✔
562
                granularity: TimeGranularity::Years,
563
                value: 1,
564
            },
565
        };
566

567
        let serialized = serde_json::to_value(&time_shift).unwrap();
2✔
568

569
        assert_eq!(
1✔
570
            serialized,
571
            serde_json::json!({
3✔
572
                "params": {
573
                    "type": "relative",
574
                    "granularity": "years",
575
                    "value": 1
576
                },
577
                "sources": {
578
                    "source": {
579
                        "type": "GdalSource",
580
                        "params": {
581
                            "data": {
582
                                "type": "internal",
583
                                "datasetId": "00000000-0000-0000-0000-000000000539"
584
                            }
585
                        }
586
                    }
587
                }
588
            })
589
        );
590

591
        let deserialized: TimeShift = serde_json::from_value(serialized).unwrap();
1✔
592

593
        assert_eq!(time_shift.params, deserialized.params);
2✔
594
    }
595

596
    #[tokio::test]
2✔
597
    async fn test_absolute_vector_shift() {
6✔
598
        let execution_context = MockExecutionContext::test_default();
1✔
599
        let query_context = MockQueryContext::test_default();
1✔
600

601
        let source = MockFeatureCollectionSource::single(
602
            MultiPointCollection::from_data(
2✔
603
                MultiPoint::many(vec![(0., 0.), (1., 1.), (2., 2.)]).unwrap(),
2✔
604
                vec![
3✔
605
                    TimeInterval::new(
1✔
606
                        DateTime::new_utc(2009, 1, 1, 0, 0, 0),
1✔
607
                        DateTime::new_utc_with_millis(2010, 12, 31, 23, 59, 59, 999),
1✔
608
                    )
609
                    .unwrap(),
610
                    TimeInterval::new(
1✔
611
                        DateTime::new_utc(2009, 6, 3, 0, 0, 0),
1✔
612
                        DateTime::new_utc(2010, 7, 14, 0, 0, 0),
1✔
613
                    )
614
                    .unwrap(),
615
                    TimeInterval::new(
1✔
616
                        DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
617
                        DateTime::new_utc_with_millis(2011, 3, 31, 23, 59, 59, 999),
1✔
618
                    )
619
                    .unwrap(),
620
                ],
621
                Default::default(),
1✔
622
            )
623
            .unwrap(),
624
        );
625

626
        let time_shift = TimeShift {
627
            sources: SingleRasterOrVectorSource {
1✔
628
                source: RasterOrVectorOperator::Vector(source.boxed()),
629
            },
630
            params: TimeShiftParams::Absolute {
1✔
631
                time_interval: TimeInterval::new(
632
                    DateTime::new_utc(2009, 1, 1, 0, 0, 0),
633
                    DateTime::new_utc(2009, 6, 1, 0, 0, 0),
634
                )
635
                .unwrap(),
636
            },
637
        };
638

639
        let query_processor = VectorOperator::boxed(time_shift)
6✔
640
            .initialize(&execution_context)
1✔
641
            .await
4✔
642
            .unwrap()
643
            .query_processor()
644
            .unwrap()
645
            .multi_point()
646
            .unwrap();
647

648
        let mut stream = query_processor
5✔
649
            .vector_query(
650
                VectorQueryRectangle {
1✔
651
                    spatial_bounds: BoundingBox2D::new((0., 0.).into(), (2., 2.).into()).unwrap(),
1✔
652
                    time_interval: TimeInterval::new(
1✔
653
                        DateTime::new_utc(2009, 1, 1, 0, 0, 0),
1✔
654
                        DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
655
                    )
656
                    .unwrap(),
657
                    spatial_resolution: SpatialResolution::one(),
1✔
658
                },
659
                &query_context,
1✔
660
            )
661
            .await
4✔
662
            .unwrap();
663

664
        let mut result = Vec::new();
2✔
665
        while let Some(collection) = stream.next().await {
5✔
666
            result.push(collection.unwrap());
2✔
667
        }
668

669
        assert_eq!(result.len(), 1);
1✔
670

671
        let expected = MultiPointCollection::from_data(
672
            MultiPoint::many(vec![(0., 0.)]).unwrap(),
2✔
673
            vec![TimeInterval::new(
2✔
674
                DateTime::new_utc(2009, 1, 1, 0, 0, 0),
1✔
675
                DateTime::new_utc_with_millis(2013, 8, 1, 23, 59, 59, 999),
1✔
676
            )
677
            .unwrap()],
678
            Default::default(),
1✔
679
        )
680
        .unwrap();
681

682
        assert_eq!(result[0], expected);
4✔
683
    }
684

685
    #[tokio::test]
2✔
686
    async fn test_relative_vector_shift() {
6✔
687
        let execution_context = MockExecutionContext::test_default();
1✔
688
        let query_context = MockQueryContext::test_default();
1✔
689

690
        let source = MockFeatureCollectionSource::single(
691
            MultiPointCollection::from_data(
2✔
692
                MultiPoint::many(vec![(0., 0.), (1., 1.), (2., 2.)]).unwrap(),
2✔
693
                vec![
3✔
694
                    TimeInterval::new(
1✔
695
                        DateTime::new_utc(2009, 1, 1, 0, 0, 0),
1✔
696
                        DateTime::new_utc_with_millis(2010, 12, 31, 23, 59, 59, 999),
1✔
697
                    )
698
                    .unwrap(),
699
                    TimeInterval::new(
1✔
700
                        DateTime::new_utc(2009, 6, 3, 0, 0, 0),
1✔
701
                        DateTime::new_utc(2010, 7, 14, 0, 0, 0),
1✔
702
                    )
703
                    .unwrap(),
704
                    TimeInterval::new(
1✔
705
                        DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
706
                        DateTime::new_utc_with_millis(2011, 3, 31, 23, 59, 59, 999),
1✔
707
                    )
708
                    .unwrap(),
709
                ],
710
                Default::default(),
1✔
711
            )
712
            .unwrap(),
713
        );
714

715
        let time_shift = TimeShift {
716
            sources: SingleRasterOrVectorSource {
1✔
717
                source: RasterOrVectorOperator::Vector(source.boxed()),
718
            },
719
            params: TimeShiftParams::Relative {
1✔
720
                granularity: TimeGranularity::Years,
721
                value: -1,
722
            },
723
        };
724

725
        let query_processor = VectorOperator::boxed(time_shift)
6✔
726
            .initialize(&execution_context)
1✔
727
            .await
4✔
728
            .unwrap()
729
            .query_processor()
730
            .unwrap()
731
            .multi_point()
732
            .unwrap();
733

734
        let mut stream = query_processor
5✔
735
            .vector_query(
736
                VectorQueryRectangle {
1✔
737
                    spatial_bounds: BoundingBox2D::new((0., 0.).into(), (2., 2.).into()).unwrap(),
1✔
738
                    time_interval: TimeInterval::new(
1✔
739
                        DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
740
                        DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
741
                    )
742
                    .unwrap(),
743
                    spatial_resolution: SpatialResolution::one(),
1✔
744
                },
745
                &query_context,
1✔
746
            )
747
            .await
4✔
748
            .unwrap();
749

750
        let mut result = Vec::new();
2✔
751
        while let Some(collection) = stream.next().await {
5✔
752
            result.push(collection.unwrap());
2✔
753
        }
754

755
        assert_eq!(result.len(), 1);
1✔
756

757
        let expected = MultiPointCollection::from_data(
758
            MultiPoint::many(vec![(0., 0.), (1., 1.)]).unwrap(),
2✔
759
            vec![
2✔
760
                TimeInterval::new(
1✔
761
                    DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
762
                    DateTime::new_utc_with_millis(2011, 12, 31, 23, 59, 59, 999),
1✔
763
                )
764
                .unwrap(),
765
                TimeInterval::new(
1✔
766
                    DateTime::new_utc(2010, 6, 3, 0, 0, 0),
1✔
767
                    DateTime::new_utc(2011, 7, 14, 0, 0, 0),
1✔
768
                )
769
                .unwrap(),
770
            ],
771
            Default::default(),
1✔
772
        )
773
        .unwrap();
774

775
        assert_eq!(result[0], expected);
4✔
776
    }
777

778
    #[tokio::test]
2✔
779
    #[allow(clippy::too_many_lines)]
780
    async fn test_absolute_raster_shift() {
6✔
781
        let empty_grid = GridOrEmpty::Empty(EmptyGrid2D::<u8>::new([3, 2].into()));
2✔
782
        let raster_tiles = vec![
2✔
783
            RasterTile2D::new_with_tile_info(
1✔
784
                TimeInterval::new_unchecked(
1✔
785
                    DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
786
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
787
                ),
788
                TileInformation {
1✔
789
                    global_tile_position: [-1, 0].into(),
1✔
790
                    tile_size_in_pixels: [3, 2].into(),
1✔
791
                    global_geo_transform: TestDefault::test_default(),
1✔
792
                },
793
                empty_grid.clone(),
1✔
794
            ),
795
            RasterTile2D::new_with_tile_info(
1✔
796
                TimeInterval::new_unchecked(
1✔
797
                    DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
798
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
799
                ),
800
                TileInformation {
1✔
801
                    global_tile_position: [-1, 1].into(),
1✔
802
                    tile_size_in_pixels: [3, 2].into(),
1✔
803
                    global_geo_transform: TestDefault::test_default(),
1✔
804
                },
805
                empty_grid.clone(),
1✔
806
            ),
807
            RasterTile2D::new_with_tile_info(
1✔
808
                TimeInterval::new_unchecked(
1✔
809
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
810
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
811
                ),
812
                TileInformation {
1✔
813
                    global_tile_position: [-1, 0].into(),
1✔
814
                    tile_size_in_pixels: [3, 2].into(),
1✔
815
                    global_geo_transform: TestDefault::test_default(),
1✔
816
                },
817
                empty_grid.clone(),
1✔
818
            ),
819
            RasterTile2D::new_with_tile_info(
1✔
820
                TimeInterval::new_unchecked(
1✔
821
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
822
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
823
                ),
824
                TileInformation {
1✔
825
                    global_tile_position: [-1, 1].into(),
1✔
826
                    tile_size_in_pixels: [3, 2].into(),
1✔
827
                    global_geo_transform: TestDefault::test_default(),
1✔
828
                },
829
                empty_grid.clone(),
1✔
830
            ),
831
            RasterTile2D::new_with_tile_info(
1✔
832
                TimeInterval::new_unchecked(
1✔
833
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
834
                    DateTime::new_utc(2013, 1, 1, 0, 0, 0),
1✔
835
                ),
836
                TileInformation {
1✔
837
                    global_tile_position: [-1, 0].into(),
1✔
838
                    tile_size_in_pixels: [3, 2].into(),
1✔
839
                    global_geo_transform: TestDefault::test_default(),
1✔
840
                },
841
                empty_grid.clone(),
1✔
842
            ),
843
            RasterTile2D::new_with_tile_info(
1✔
844
                TimeInterval::new_unchecked(
1✔
845
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
1✔
846
                    DateTime::new_utc(2013, 1, 1, 0, 0, 0),
1✔
847
                ),
848
                TileInformation {
1✔
849
                    global_tile_position: [-1, 1].into(),
1✔
850
                    tile_size_in_pixels: [3, 2].into(),
1✔
851
                    global_geo_transform: TestDefault::test_default(),
1✔
852
                },
853
                empty_grid.clone(),
1✔
854
            ),
855
        ];
856

857
        let mrs = MockRasterSource {
858
            params: MockRasterSourceParams {
1✔
859
                data: raster_tiles,
860
                result_descriptor: RasterResultDescriptor {
861
                    data_type: RasterDataType::U8,
862
                    spatial_reference: SpatialReference::epsg_4326().into(),
863
                    measurement: Measurement::Unitless,
864
                    time: None,
865
                    bbox: None,
866
                    resolution: None,
867
                },
868
            },
869
        }
870
        .boxed();
871

872
        let time_shift = TimeShift {
873
            sources: SingleRasterOrVectorSource {
1✔
874
                source: RasterOrVectorOperator::Raster(mrs),
875
            },
876
            params: TimeShiftParams::Absolute {
1✔
877
                time_interval: TimeInterval::new_unchecked(
878
                    DateTime::new_utc(2011, 1, 1, 0, 0, 0),
879
                    DateTime::new_utc(2012, 1, 1, 0, 0, 0),
880
                ),
881
            },
882
        };
883

884
        let execution_context = MockExecutionContext::new_with_tiling_spec(
885
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
2✔
886
        );
887
        let query_context = MockQueryContext::test_default();
1✔
888

889
        let query_processor = RasterOperator::boxed(time_shift)
6✔
890
            .initialize(&execution_context)
1✔
891
            .await
4✔
892
            .unwrap()
893
            .query_processor()
894
            .unwrap()
895
            .get_u8()
896
            .unwrap();
897

898
        let mut stream = query_processor
5✔
899
            .raster_query(
900
                RasterQueryRectangle {
1✔
901
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
902
                        (0., 3.).into(),
1✔
903
                        (4., 0.).into(),
1✔
904
                    ),
905
                    time_interval: TimeInterval::new(
1✔
906
                        DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
907
                        DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
908
                    )
909
                    .unwrap(),
910
                    spatial_resolution: SpatialResolution::one(),
1✔
911
                },
912
                &query_context,
1✔
913
            )
914
            .await
4✔
915
            .unwrap();
916

917
        let mut result = Vec::new();
2✔
918
        while let Some(tile) = stream.next().await {
4✔
919
            result.push(tile.unwrap());
2✔
920
        }
921

922
        assert_eq!(result.len(), 2);
1✔
923

924
        assert_eq!(
1✔
925
            result[0].time,
1✔
926
            TimeInterval::new_unchecked(
1✔
927
                DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
928
                DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
929
            ),
930
        );
931
        assert_eq!(
3✔
932
            result[1].time,
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
        );
938
    }
939

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

1019
        let mrs = MockRasterSource {
1020
            params: MockRasterSourceParams {
1✔
1021
                data: raster_tiles,
1022
                result_descriptor: RasterResultDescriptor {
1023
                    data_type: RasterDataType::U8,
1024
                    spatial_reference: SpatialReference::epsg_4326().into(),
1025
                    measurement: Measurement::Unitless,
1026
                    time: None,
1027
                    bbox: None,
1028
                    resolution: None,
1029
                },
1030
            },
1031
        }
1032
        .boxed();
1033

1034
        let time_shift = TimeShift {
1035
            sources: SingleRasterOrVectorSource {
1✔
1036
                source: RasterOrVectorOperator::Raster(mrs),
1037
            },
1038
            params: TimeShiftParams::Relative {
1✔
1039
                granularity: TimeGranularity::Years,
1040
                value: 1,
1041
            },
1042
        };
1043

1044
        let execution_context = MockExecutionContext::new_with_tiling_spec(
1045
            TilingSpecification::new((0., 0.).into(), [3, 2].into()),
2✔
1046
        );
1047
        let query_context = MockQueryContext::test_default();
1✔
1048

1049
        let query_processor = RasterOperator::boxed(time_shift)
6✔
1050
            .initialize(&execution_context)
1✔
1051
            .await
4✔
1052
            .unwrap()
1053
            .query_processor()
1054
            .unwrap()
1055
            .get_u8()
1056
            .unwrap();
1057

1058
        let mut stream = query_processor
5✔
1059
            .raster_query(
1060
                RasterQueryRectangle {
1✔
1061
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1062
                        (0., 3.).into(),
1✔
1063
                        (4., 0.).into(),
1✔
1064
                    ),
1065
                    time_interval: TimeInterval::new(
1✔
1066
                        DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
1067
                        DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
1068
                    )
1069
                    .unwrap(),
1070
                    spatial_resolution: SpatialResolution::one(),
1✔
1071
                },
1072
                &query_context,
1✔
1073
            )
1074
            .await
4✔
1075
            .unwrap();
1076

1077
        let mut result = Vec::new();
2✔
1078
        while let Some(tile) = stream.next().await {
4✔
1079
            result.push(tile.unwrap());
2✔
1080
        }
1081

1082
        assert_eq!(result.len(), 2);
1✔
1083

1084
        assert_eq!(
1✔
1085
            result[0].time,
1✔
1086
            TimeInterval::new_unchecked(
1✔
1087
                DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
1088
                DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
1089
            ),
1090
        );
1091
        assert_eq!(
3✔
1092
            result[1].time,
1✔
1093
            TimeInterval::new_unchecked(
1✔
1094
                DateTime::new_utc(2010, 1, 1, 0, 0, 0),
1✔
1095
                DateTime::new_utc(2011, 1, 1, 0, 0, 0),
1✔
1096
            ),
1097
        );
1098
    }
1099

1100
    #[tokio::test]
2✔
1101
    async fn test_expression_on_shifted_raster() {
6✔
1102
        let mut execution_context = MockExecutionContext::test_default();
1✔
1103

1104
        let ndvi_source = GdalSource {
1105
            params: GdalSourceParameters {
1✔
1106
                data: add_ndvi_dataset(&mut execution_context),
1107
            },
1108
        }
1109
        .boxed();
1110

1111
        let shifted_ndvi_source = RasterOperator::boxed(TimeShift {
1✔
1112
            params: TimeShiftParams::Relative {
1✔
1113
                granularity: TimeGranularity::Months,
1✔
1114
                value: -1,
1115
            },
1116
            sources: SingleRasterOrVectorSource {
1✔
1117
                source: RasterOrVectorOperator::Raster(ndvi_source.clone()),
2✔
1118
            },
1119
        });
1120

1121
        let expression = Expression {
1122
            params: ExpressionParams {
1✔
1123
                expression: "A - B".to_string(),
1124
                output_type: RasterDataType::F64,
1125
                output_measurement: None,
1126
                map_no_data: false,
1127
            },
1128
            sources: ExpressionSources::new_a_b(ndvi_source, shifted_ndvi_source),
1✔
1129
        }
1130
        .boxed();
1131

1132
        let query_processor = expression
5✔
1133
            .initialize(&execution_context)
1✔
1134
            .await
4✔
1135
            .unwrap()
1136
            .query_processor()
1137
            .unwrap()
1138
            .get_f64()
1139
            .unwrap();
1140

1141
        let query_context = MockQueryContext::test_default();
1✔
1142

1143
        let mut stream = query_processor
5✔
1144
            .raster_query(
1145
                RasterQueryRectangle {
1✔
1146
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1147
                        (-180., 90.).into(),
1✔
1148
                        (180., -90.).into(),
1✔
1149
                    ),
1150
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1151
                        2014, 3, 1, 0, 0, 0,
1152
                    ))
1153
                    .unwrap(),
1154
                    spatial_resolution: SpatialResolution::one(),
1✔
1155
                },
1156
                &query_context,
1✔
1157
            )
1158
            .await
4✔
1159
            .unwrap();
1160

1161
        let mut result = Vec::new();
2✔
1162
        while let Some(tile) = stream.next().await {
5✔
1163
            result.push(tile.unwrap());
2✔
1164
        }
1165

1166
        assert_eq!(result.len(), 4);
1✔
1167
        assert_eq!(
3✔
1168
            result[0].time,
1✔
1169
            TimeInterval::new(
1✔
1170
                DateTime::new_utc(2014, 3, 1, 0, 0, 0),
1✔
1171
                DateTime::new_utc(2014, 4, 1, 0, 0, 0)
1✔
1172
            )
1173
            .unwrap()
1174
        );
1175
    }
1176

1177
    #[tokio::test]
2✔
1178
    async fn test_expression_on_absolute_shifted_raster() {
6✔
1179
        let mut execution_context = MockExecutionContext::test_default();
1✔
1180

1181
        let ndvi_source = GdalSource {
1182
            params: GdalSourceParameters {
1✔
1183
                data: add_ndvi_dataset(&mut execution_context),
1184
            },
1185
        }
1186
        .boxed();
1187

1188
        let shifted_ndvi_source = RasterOperator::boxed(TimeShift {
1✔
1189
            params: TimeShiftParams::Absolute {
1✔
1190
                time_interval: TimeInterval::new_instant(DateTime::new_utc(2014, 5, 1, 0, 0, 0))
2✔
1191
                    .unwrap(),
1192
            },
1193
            sources: SingleRasterOrVectorSource {
1✔
1194
                source: RasterOrVectorOperator::Raster(ndvi_source),
1✔
1195
            },
1196
        });
1197

1198
        let query_processor = shifted_ndvi_source
5✔
1199
            .initialize(&execution_context)
1✔
1200
            .await
4✔
1201
            .unwrap()
1202
            .query_processor()
1203
            .unwrap()
1204
            .get_u8()
1205
            .unwrap();
1206

1207
        let query_context = MockQueryContext::test_default();
1✔
1208

1209
        let mut stream = query_processor
5✔
1210
            .raster_query(
1211
                RasterQueryRectangle {
1✔
1212
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
1213
                        (-180., 90.).into(),
1✔
1214
                        (180., -90.).into(),
1✔
1215
                    ),
1216
                    time_interval: TimeInterval::new_instant(DateTime::new_utc(
1✔
1217
                        2014, 3, 1, 0, 0, 0,
1218
                    ))
1219
                    .unwrap(),
1220
                    spatial_resolution: SpatialResolution::one(),
1✔
1221
                },
1222
                &query_context,
1✔
1223
            )
1224
            .await
4✔
1225
            .unwrap();
1226

1227
        let mut result = Vec::new();
2✔
1228
        while let Some(tile) = stream.next().await {
5✔
1229
            result.push(tile.unwrap());
2✔
1230
        }
1231

1232
        assert_eq!(result.len(), 4);
1✔
1233
        assert_eq!(
3✔
1234
            result[0].time,
1✔
1235
            TimeInterval::new(
1✔
1236
                DateTime::new_utc(2014, 3, 1, 0, 0, 0),
1✔
1237
                DateTime::new_utc(2014, 4, 1, 0, 0, 0)
1✔
1238
            )
1239
            .unwrap()
1240
        );
1241
    }
1242

1243
    #[test]
1244
    fn shift_eternal() {
3✔
1245
        let eternal = TimeInterval::default();
1✔
1246

1247
        let f_shift = RelativeForwardShift {
1248
            step: TimeStep {
1✔
1249
                granularity: TimeGranularity::Seconds,
1250
                step: 1,
1251
            },
1252
        };
1253

1254
        let (shifted, state) = f_shift.shift(eternal).unwrap();
1✔
1255
        assert_eq!(shifted, eternal);
1✔
1256
        let reverse_shifted = f_shift.reverse_shift(eternal, state).unwrap();
2✔
1257
        assert_eq!(reverse_shifted, eternal);
1✔
1258

1259
        let b_shift = RelativeBackwardShift {
1260
            step: TimeStep {
1✔
1261
                granularity: TimeGranularity::Seconds,
1262
                step: 1,
1263
            },
1264
        };
1265

1266
        let (shifted, state) = b_shift.shift(eternal).unwrap();
2✔
1267
        assert_eq!(shifted, eternal);
1✔
1268

1269
        let reverse_shifted = b_shift.reverse_shift(eternal, state).unwrap();
2✔
1270
        assert_eq!(reverse_shifted, eternal);
1✔
1271
    }
1272

1273
    #[test]
1274
    fn shift_begin_of_time() {
3✔
1275
        let time = TimeInterval::new_unchecked(TimeInstance::MIN, TimeInstance::EPOCH_START);
1✔
1276

1277
        let f_shift = RelativeForwardShift {
1278
            step: TimeStep {
1✔
1279
                granularity: TimeGranularity::Seconds,
1280
                step: 1,
1281
            },
1282
        };
1283

1284
        let expected_shift =
1✔
1285
            TimeInterval::new_unchecked(TimeInstance::MIN, TimeInstance::EPOCH_START + 1_000);
1286

1287
        let (shifted, state) = f_shift.shift(time).unwrap();
1✔
1288
        assert_eq!(shifted, expected_shift);
1✔
1289

1290
        let reverse_shifted = f_shift.reverse_shift(expected_shift, state).unwrap();
2✔
1291
        assert_eq!(reverse_shifted, time);
1✔
1292

1293
        let f_shift = RelativeBackwardShift {
1294
            step: TimeStep {
1✔
1295
                granularity: TimeGranularity::Seconds,
1296
                step: 1,
1297
            },
1298
        };
1299

1300
        let expected_shift =
2✔
1301
            TimeInterval::new_unchecked(TimeInstance::MIN, TimeInstance::EPOCH_START - 1_000);
1302

1303
        let (shifted, state) = f_shift.shift(time).unwrap();
1✔
1304
        assert_eq!(shifted, expected_shift);
1✔
1305

1306
        let reverse_shifted = f_shift.reverse_shift(expected_shift, state).unwrap();
2✔
1307
        assert_eq!(reverse_shifted, time);
1✔
1308
    }
1309

1310
    #[test]
1311
    fn shift_end_of_time() {
3✔
1312
        let time = TimeInterval::new_unchecked(TimeInstance::EPOCH_START, TimeInstance::MAX);
1✔
1313

1314
        let f_shift = RelativeForwardShift {
1315
            step: TimeStep {
1✔
1316
                granularity: TimeGranularity::Seconds,
1317
                step: 1,
1318
            },
1319
        };
1320

1321
        let expected_shift =
1✔
1322
            TimeInterval::new_unchecked(TimeInstance::EPOCH_START + 1_000, TimeInstance::MAX);
1323

1324
        let (shifted, state) = f_shift.shift(time).unwrap();
1✔
1325
        assert_eq!(shifted, expected_shift);
1✔
1326

1327
        let reverse_shifted = f_shift.reverse_shift(expected_shift, state).unwrap();
2✔
1328
        assert_eq!(reverse_shifted, time);
1✔
1329

1330
        let f_shift = RelativeBackwardShift {
1331
            step: TimeStep {
1✔
1332
                granularity: TimeGranularity::Seconds,
1333
                step: 1,
1334
            },
1335
        };
1336

1337
        let expected_shift =
2✔
1338
            TimeInterval::new_unchecked(TimeInstance::EPOCH_START - 1_000, TimeInstance::MAX);
1339

1340
        let (shifted, state) = f_shift.shift(time).unwrap();
1✔
1341
        assert_eq!(shifted, expected_shift);
1✔
1342

1343
        let reverse_shifted = f_shift.reverse_shift(expected_shift, state).unwrap();
2✔
1344
        assert_eq!(reverse_shifted, time);
1✔
1345
    }
1346
}
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