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

geo-engine / geoengine / 3929938005

pending completion
3929938005

push

github

GitHub
Merge #713

84930 of 96741 relevant lines covered (87.79%)

79640.1 hits per line

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

97.78
/operators/src/source/gdal_source/loading_info.rs
1
use std::collections::HashMap;
2

3
use async_trait::async_trait;
4
use geoengine_datatypes::primitives::{
5
    RasterQueryRectangle, TimeInstance, TimeInterval, TimeStep, TimeStepIter,
6
};
7
use serde::{Deserialize, Serialize};
8

9
use crate::{
10
    engine::{MetaData, RasterResultDescriptor},
11
    error::Error,
12
    util::Result,
13
};
14

15
use super::{GdalDatasetParameters, GdalSourceTimePlaceholder};
16

17
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)]
14✔
18
#[serde(rename_all = "camelCase")]
19
pub struct GdalMetaDataStatic {
20
    pub time: Option<TimeInterval>,
21
    pub params: GdalDatasetParameters,
22
    pub result_descriptor: RasterResultDescriptor,
23
}
24

25
#[async_trait]
26
impl MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>
27
    for GdalMetaDataStatic
28
{
29
    async fn loading_info(&self, query: RasterQueryRectangle) -> Result<GdalLoadingInfo> {
12✔
30
        let valid = self.time.unwrap_or_default();
12✔
31

32
        let parts = if valid.intersects(&query.time_interval) {
12✔
33
            vec![GdalLoadingInfoTemporalSlice {
12✔
34
                time: valid,
12✔
35
                params: Some(self.params.clone()),
12✔
36
            }]
12✔
37
            .into_iter()
12✔
38
        } else {
39
            vec![].into_iter()
×
40
        };
41

42
        Ok(GdalLoadingInfo {
12✔
43
            info: GdalLoadingInfoTemporalSliceIterator::Static { parts },
12✔
44
        })
12✔
45
    }
24✔
46

47
    async fn result_descriptor(&self) -> Result<RasterResultDescriptor> {
15✔
48
        Ok(self.result_descriptor.clone())
15✔
49
    }
15✔
50

51
    fn box_clone(
11✔
52
        &self,
11✔
53
    ) -> Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>> {
11✔
54
        Box::new(self.clone())
11✔
55
    }
11✔
56
}
57

58
/// Meta data for a regular time series that begins and ends at the given `data_time` interval with multiple gdal data
59
/// sets `step` time apart. The `time_placeholders` in the file path of the dataset are replaced with the
60
/// specified time `reference` in specified time `format`. Inside the `data_time` the gdal source will load the data
61
/// from the files and outside it will create nodata.
62
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)]
105✔
63
#[serde(rename_all = "camelCase")]
64
pub struct GdalMetaDataRegular {
65
    pub result_descriptor: RasterResultDescriptor,
66
    pub params: GdalDatasetParameters,
67
    pub time_placeholders: HashMap<String, GdalSourceTimePlaceholder>,
68
    pub data_time: TimeInterval,
69
    pub step: TimeStep,
70
}
71

72
#[async_trait]
73
impl MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>
74
    for GdalMetaDataRegular
75
{
76
    async fn loading_info(&self, query: RasterQueryRectangle) -> Result<GdalLoadingInfo> {
97✔
77
        Ok(GdalLoadingInfo {
78
            info: GdalLoadingInfoTemporalSliceIterator::Dynamic(
79
                DynamicGdalLoadingInfoPartIterator::new(
97✔
80
                    self.params.clone(),
97✔
81
                    self.time_placeholders.clone(),
97✔
82
                    self.step,
97✔
83
                    query.time_interval,
97✔
84
                    self.data_time,
97✔
85
                )?,
97✔
86
            ),
87
        })
88
    }
194✔
89

90
    async fn result_descriptor(&self) -> Result<RasterResultDescriptor> {
125✔
91
        Ok(self.result_descriptor.clone())
125✔
92
    }
125✔
93

94
    fn box_clone(
60✔
95
        &self,
60✔
96
    ) -> Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>> {
60✔
97
        Box::new(self.clone())
60✔
98
    }
60✔
99
}
100

101
/// Meta data for 4D `NetCDF` CF datasets
102
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)]
×
103
#[serde(rename_all = "camelCase")]
104
pub struct GdalMetadataNetCdfCf {
105
    pub result_descriptor: RasterResultDescriptor,
106
    pub params: GdalDatasetParameters,
107
    pub start: TimeInstance,
108
    /// We use the end to specify the last, non-inclusive valid time point.
109
    /// Queries behind this point return no data.
110
    /// TODO: Alternatively, we could think about using the number of possible time steps in the future.
111
    pub end: TimeInstance,
112
    pub step: TimeStep,
113
    /// A band offset specifies the first band index to use for the first point in time.
114
    /// All other time steps are added to this offset.
115
    pub band_offset: usize,
116
}
117

118
#[async_trait]
119
// TODO: handle queries before and after valid time like in `GdalMetaDataRegular`
120
impl MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>
121
    for GdalMetadataNetCdfCf
122
{
123
    async fn loading_info(&self, query: RasterQueryRectangle) -> Result<GdalLoadingInfo> {
4✔
124
        // special case: single step
125
        if self.start == self.end || self.step.step == 0 {
4✔
126
            let time = TimeInterval::new(self.start, self.end)?;
2✔
127

128
            let mut params = self.params.clone();
2✔
129
            params.rasterband_channel = 1 /* GDAL starts at 1 */ + self.band_offset;
2✔
130

2✔
131
            return GdalMetaDataStatic {
2✔
132
                time: Some(time),
2✔
133
                params,
2✔
134
                result_descriptor: self.result_descriptor.clone(),
2✔
135
            }
2✔
136
            .loading_info(query)
2✔
137
            .await;
×
138
        }
2✔
139

140
        let snapped_start = self
2✔
141
            .step
2✔
142
            .snap_relative(self.start, query.time_interval.start())?;
2✔
143

144
        let snapped_interval =
2✔
145
            TimeInterval::new_unchecked(snapped_start, query.time_interval.end()); // TODO: snap end?
2✔
146

147
        let time_iterator = TimeStepIter::new_with_interval(snapped_interval, self.step)?;
2✔
148

149
        Ok(GdalLoadingInfo {
2✔
150
            info: GdalLoadingInfoTemporalSliceIterator::NetCdfCf(
2✔
151
                NetCdfCfGdalLoadingInfoPartIterator::new(
2✔
152
                    time_iterator,
2✔
153
                    self.params.clone(),
2✔
154
                    self.step,
2✔
155
                    self.start,
2✔
156
                    self.end,
2✔
157
                    self.band_offset,
2✔
158
                ),
2✔
159
            ),
2✔
160
        })
2✔
161
    }
8✔
162

163
    async fn result_descriptor(&self) -> Result<RasterResultDescriptor> {
1✔
164
        Ok(self.result_descriptor.clone())
1✔
165
    }
1✔
166

167
    fn box_clone(
×
168
        &self,
×
169
    ) -> Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>> {
×
170
        Box::new(self.clone())
×
171
    }
×
172
}
173

174
// TODO: custom deserializer that checks that that params are sorted and do not overlap
175
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)]
6✔
176
#[serde(rename_all = "camelCase")]
177
pub struct GdalMetaDataList {
178
    pub result_descriptor: RasterResultDescriptor,
179
    pub params: Vec<GdalLoadingInfoTemporalSlice>,
180
}
181

182
#[async_trait]
183
impl MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle> for GdalMetaDataList {
184
    async fn loading_info(&self, query: RasterQueryRectangle) -> Result<GdalLoadingInfo> {
6✔
185
        #[allow(clippy::needless_collect)]
186
        let parts = self
6✔
187
            .params
6✔
188
            .iter()
6✔
189
            .filter(|item| item.time.intersects(&query.time_interval))
18✔
190
            .cloned()
6✔
191
            .collect::<Vec<_>>();
6✔
192

6✔
193
        Ok(GdalLoadingInfo {
6✔
194
            info: GdalLoadingInfoTemporalSliceIterator::Static {
6✔
195
                parts: parts.into_iter(),
6✔
196
            },
6✔
197
        })
6✔
198
    }
6✔
199

200
    async fn result_descriptor(&self) -> Result<RasterResultDescriptor> {
7✔
201
        Ok(self.result_descriptor.clone())
7✔
202
    }
7✔
203

204
    fn box_clone(
1✔
205
        &self,
1✔
206
    ) -> Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>> {
1✔
207
        Box::new(self.clone())
1✔
208
    }
1✔
209
}
210

211
#[derive(Debug, Clone)]
×
212
/// An iterator for gdal loading infos based on time placeholders that generates
213
/// a new loading info for each time step within `data_time` and an empty loading info
214
/// for the time before and after `date_time`.
215
pub struct DynamicGdalLoadingInfoPartIterator {
216
    time_step_iter: TimeStepIter,
217
    params: GdalDatasetParameters,
218
    time_placeholders: HashMap<String, GdalSourceTimePlaceholder>,
219
    step: TimeStep,
220
    query_time: TimeInterval,
221
    data_time: TimeInterval,
222
    state: DynamicGdalLoadingInfoPartIteratorState,
223
}
224

225
#[derive(Debug, Clone)]
×
226
enum DynamicGdalLoadingInfoPartIteratorState {
227
    BeforeDataTime,
228
    WithinDataTime,
229
    AfterDataTime,
230
    Finished,
231
}
232

233
impl DynamicGdalLoadingInfoPartIterator {
234
    fn new(
97✔
235
        params: GdalDatasetParameters,
97✔
236
        time_placeholders: HashMap<String, GdalSourceTimePlaceholder>,
97✔
237
        step: TimeStep,
97✔
238
        query_time: TimeInterval,
97✔
239
        data_time: TimeInterval,
97✔
240
    ) -> Result<Self> {
97✔
241
        // TODO: maybe fail on deserialization
97✔
242
        if time_placeholders.is_empty()
97✔
243
            || time_placeholders.keys().any(String::is_empty)
97✔
244
            || time_placeholders
97✔
245
                .values()
97✔
246
                .any(|value| value.format.is_empty())
97✔
247
        {
248
            return Err(Error::DynamicGdalSourceSpecHasEmptyTimePlaceholders);
×
249
        }
97✔
250

251
        // depending on whether the query starts before, within or after the data, the time step iterator has to begin at a different point in time
252
        // because it only produces time steps within the data time. Before and after are handled separately.
253
        let (snapped_start, state) = if query_time.start() < data_time.start() {
97✔
254
            (
5✔
255
                data_time.start(),
5✔
256
                DynamicGdalLoadingInfoPartIteratorState::BeforeDataTime,
5✔
257
            )
5✔
258
        } else if query_time.start() < data_time.end() {
92✔
259
            (
260
                step.snap_relative(data_time.start(), query_time.start())?,
90✔
261
                DynamicGdalLoadingInfoPartIteratorState::WithinDataTime,
90✔
262
            )
263
        } else {
264
            (
2✔
265
                data_time.end(),
2✔
266
                DynamicGdalLoadingInfoPartIteratorState::AfterDataTime,
2✔
267
            )
2✔
268
        };
269

270
        // cap at end of data
271
        let mut end = query_time.end().min(data_time.end());
97✔
272

273
        // snap the end time to the _next_ step within in the `data_time`
274
        let snapped_end = step.snap_relative(data_time.start(), end)?;
97✔
275
        if snapped_end < end {
97✔
276
            end = (snapped_end + step)?;
17✔
277
        }
80✔
278

279
        // ensure start <= end
280
        end = end.max(snapped_start);
97✔
281

97✔
282
        let snapped_interval = TimeInterval::new_unchecked(snapped_start, end);
97✔
283

284
        let time_step_iter = TimeStepIter::new_with_interval(snapped_interval, step)?;
97✔
285

286
        Ok(Self {
97✔
287
            time_step_iter,
97✔
288
            params,
97✔
289
            time_placeholders,
97✔
290
            step,
97✔
291
            query_time,
97✔
292
            data_time,
97✔
293
            state,
97✔
294
        })
97✔
295
    }
97✔
296
}
297

298
impl Iterator for DynamicGdalLoadingInfoPartIterator {
299
    type Item = Result<GdalLoadingInfoTemporalSlice>;
300

301
    fn next(&mut self) -> Option<Self::Item> {
209✔
302
        match self.state {
209✔
303
            DynamicGdalLoadingInfoPartIteratorState::BeforeDataTime => {
304
                if self.query_time.end() > self.data_time.start() {
5✔
305
                    self.state = DynamicGdalLoadingInfoPartIteratorState::WithinDataTime;
2✔
306
                } else {
3✔
307
                    self.state = DynamicGdalLoadingInfoPartIteratorState::Finished;
3✔
308
                }
3✔
309
                Some(Ok(GdalLoadingInfoTemporalSlice {
5✔
310
                    time: TimeInterval::new_unchecked(TimeInstance::MIN, self.data_time.start()),
5✔
311
                    params: None,
5✔
312
                }))
5✔
313
            }
314
            DynamicGdalLoadingInfoPartIteratorState::WithinDataTime => {
315
                if let Some(t) = self.time_step_iter.next() {
196✔
316
                    let t2 = t + self.step;
106✔
317
                    let t2 = t2.unwrap_or_else(|_| self.data_time.end());
106✔
318
                    let time_interval = TimeInterval::new_unchecked(t, t2);
106✔
319

106✔
320
                    let loading_info_part = self
106✔
321
                        .params
106✔
322
                        .replace_time_placeholders(&self.time_placeholders, time_interval)
106✔
323
                        .map(|loading_info_part_params| GdalLoadingInfoTemporalSlice {
106✔
324
                            time: time_interval,
106✔
325
                            params: Some(loading_info_part_params),
106✔
326
                        })
106✔
327
                        .map_err(Into::into);
106✔
328

106✔
329
                    Some(loading_info_part)
106✔
330
                } else {
331
                    self.state = DynamicGdalLoadingInfoPartIteratorState::Finished;
90✔
332

90✔
333
                    if self.query_time.end() > self.data_time.end() {
90✔
334
                        Some(Ok(GdalLoadingInfoTemporalSlice {
1✔
335
                            time: TimeInterval::new_unchecked(
1✔
336
                                self.data_time.end(),
1✔
337
                                TimeInstance::MAX,
1✔
338
                            ),
1✔
339
                            params: None,
1✔
340
                        }))
1✔
341
                    } else {
342
                        None
89✔
343
                    }
344
                }
345
            }
346
            DynamicGdalLoadingInfoPartIteratorState::AfterDataTime => {
347
                self.state = DynamicGdalLoadingInfoPartIteratorState::Finished;
2✔
348

2✔
349
                Some(Ok(GdalLoadingInfoTemporalSlice {
2✔
350
                    time: TimeInterval::new_unchecked(self.data_time.end(), TimeInstance::MAX),
2✔
351
                    params: None,
2✔
352
                }))
2✔
353
            }
354
            DynamicGdalLoadingInfoPartIteratorState::Finished => None,
6✔
355
        }
356
    }
209✔
357
}
358

359
#[derive(Debug, Clone)]
×
360
pub struct NetCdfCfGdalLoadingInfoPartIterator {
361
    time_step_iter: TimeStepIter,
362
    params: GdalDatasetParameters,
363
    step: TimeStep,
364
    dataset_time_start: TimeInstance,
365
    max_t2: TimeInstance,
366
    band_offset: usize,
367
}
368

369
impl NetCdfCfGdalLoadingInfoPartIterator {
370
    fn new(
2✔
371
        time_step_iter: TimeStepIter,
2✔
372
        params: GdalDatasetParameters,
2✔
373
        step: TimeStep,
2✔
374
        dataset_time_start: TimeInstance,
2✔
375
        max_t2: TimeInstance,
2✔
376
        band_offset: usize,
2✔
377
    ) -> Self {
2✔
378
        Self {
2✔
379
            time_step_iter,
2✔
380
            params,
2✔
381
            step,
2✔
382
            dataset_time_start,
2✔
383
            max_t2,
2✔
384
            band_offset,
2✔
385
        }
2✔
386
    }
2✔
387
}
388

389
impl Iterator for NetCdfCfGdalLoadingInfoPartIterator {
390
    type Item = Result<GdalLoadingInfoTemporalSlice>;
391

392
    fn next(&mut self) -> Option<Self::Item> {
27✔
393
        let t1 = self.time_step_iter.next()?;
27✔
394

395
        // snap t1 relative to reference time
396
        let t1 = self.step.snap_relative(self.dataset_time_start, t1).ok()?;
22✔
397

398
        let t2 = t1 + self.step;
22✔
399
        let t2 = t2.unwrap_or(self.max_t2);
22✔
400

22✔
401
        let time_interval = TimeInterval::new_unchecked(t1, t2);
22✔
402

22✔
403
        // TODO: how to prevent generating loads of empty time intervals for a very small t1?
22✔
404
        if t1 < self.dataset_time_start {
22✔
405
            return Some(Ok(GdalLoadingInfoTemporalSlice {
2✔
406
                time: time_interval,
2✔
407
                params: None,
2✔
408
            }));
2✔
409
        }
20✔
410

20✔
411
        if t1 >= self.max_t2 {
20✔
412
            return None;
2✔
413
        }
18✔
414

415
        let steps_between = match self
18✔
416
            .step
18✔
417
            .num_steps_in_interval(TimeInterval::new_unchecked(self.dataset_time_start, t1))
18✔
418
        {
419
            Ok(num_steps) => num_steps,
18✔
420
            // should only happen when time intervals are faulty
421
            Err(error) => return Some(Err(error.into())),
×
422
        };
423

424
        // our first band is the reference time
425
        let mut params = self.params.clone();
18✔
426
        params.rasterband_channel =
18✔
427
            1 /* GDAL starts at 1 */ + self.band_offset + steps_between as usize;
18✔
428

18✔
429
        Some(Ok(GdalLoadingInfoTemporalSlice {
18✔
430
            time: time_interval,
18✔
431
            params: Some(params),
18✔
432
        }))
18✔
433
    }
27✔
434
}
435

436
#[derive(Debug, Clone)]
×
437
pub struct GdalLoadingInfo {
438
    /// partitions of dataset sorted by time
439
    pub info: GdalLoadingInfoTemporalSliceIterator,
440
}
441

442
#[allow(clippy::large_enum_variant)]
443
#[derive(Debug, Clone)]
×
444
pub enum GdalLoadingInfoTemporalSliceIterator {
445
    Static {
446
        parts: std::vec::IntoIter<GdalLoadingInfoTemporalSlice>,
447
    },
448
    Dynamic(DynamicGdalLoadingInfoPartIterator),
449
    NetCdfCf(NetCdfCfGdalLoadingInfoPartIterator),
450
}
451

452
impl Iterator for GdalLoadingInfoTemporalSliceIterator {
453
    type Item = Result<GdalLoadingInfoTemporalSlice>;
454

455
    fn next(&mut self) -> Option<Self::Item> {
254✔
456
        match self {
254✔
457
            GdalLoadingInfoTemporalSliceIterator::Static { parts } => parts.next().map(Result::Ok),
39✔
458
            GdalLoadingInfoTemporalSliceIterator::Dynamic(iter) => iter.next(),
209✔
459
            GdalLoadingInfoTemporalSliceIterator::NetCdfCf(iter) => iter.next(),
6✔
460
        }
461
    }
254✔
462
}
463

464
/// one temporal slice of the dataset that requires reading from exactly one Gdal dataset
465
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)]
28✔
466
#[serde(rename_all = "camelCase")]
467
pub struct GdalLoadingInfoTemporalSlice {
468
    pub time: TimeInterval,
469
    pub params: Option<GdalDatasetParameters>,
470
}
471

472
#[cfg(test)]
473
mod tests {
474
    use geoengine_datatypes::{
475
        hashmap,
476
        primitives::{
477
            DateTime, DateTimeParseFormat, Measurement, SpatialPartition2D, SpatialResolution,
478
            TimeGranularity,
479
        },
480
        raster::RasterDataType,
481
        spatial_reference::SpatialReference,
482
        util::test::TestDefault,
483
    };
484

485
    use crate::source::{FileNotFoundHandling, GdalDatasetGeoTransform, TimeReference};
486

487
    use super::*;
488

489
    fn create_regular_metadata() -> GdalMetaDataRegular {
7✔
490
        let no_data_value = Some(0.);
7✔
491

7✔
492
        GdalMetaDataRegular {
7✔
493
            result_descriptor: RasterResultDescriptor {
7✔
494
                data_type: RasterDataType::U8,
7✔
495
                spatial_reference: SpatialReference::epsg_4326().into(),
7✔
496
                measurement: Measurement::Unitless,
7✔
497
                time: None,
7✔
498
                bbox: None,
7✔
499
                resolution: None,
7✔
500
            },
7✔
501
            params: GdalDatasetParameters {
7✔
502
                file_path: "/foo/bar_%TIME%.tiff".into(),
7✔
503
                rasterband_channel: 0,
7✔
504
                geo_transform: TestDefault::test_default(),
7✔
505
                width: 360,
7✔
506
                height: 180,
7✔
507
                file_not_found_handling: FileNotFoundHandling::NoData,
7✔
508
                no_data_value,
7✔
509
                properties_mapping: None,
7✔
510
                gdal_open_options: None,
7✔
511
                gdal_config_options: None,
7✔
512
                allow_alphaband_as_mask: true,
7✔
513
            },
7✔
514
            time_placeholders: hashmap! {
7✔
515
                "%TIME%".to_string() => GdalSourceTimePlaceholder {
7✔
516
                    format: DateTimeParseFormat::custom("%f".to_string()),
7✔
517
                    reference: TimeReference::Start,
7✔
518
                },
7✔
519
            },
7✔
520
            data_time: TimeInterval::new_unchecked(
7✔
521
                TimeInstance::from_millis_unchecked(0),
7✔
522
                TimeInstance::from_millis_unchecked(33),
7✔
523
            ),
7✔
524
            step: TimeStep {
7✔
525
                granularity: TimeGranularity::Millis,
7✔
526
                step: 11,
7✔
527
            },
7✔
528
        }
7✔
529
    }
7✔
530

531
    #[tokio::test]
1✔
532
    async fn test_regular_meta_data_result_descriptor() {
1✔
533
        let meta_data = create_regular_metadata();
1✔
534

535
        assert_eq!(
1✔
536
            meta_data.result_descriptor().await.unwrap(),
1✔
537
            RasterResultDescriptor {
1✔
538
                data_type: RasterDataType::U8,
1✔
539
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
540
                measurement: Measurement::Unitless,
1✔
541
                time: None,
1✔
542
                bbox: None,
1✔
543
                resolution: None,
1✔
544
            }
1✔
545
        );
546
    }
547

548
    #[tokio::test]
1✔
549
    async fn test_regular_meta_data_0_30() {
1✔
550
        let meta_data = create_regular_metadata();
1✔
551

552
        assert_eq!(
1✔
553
            meta_data
1✔
554
                .loading_info(RasterQueryRectangle {
1✔
555
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
556
                        (0., 1.).into(),
1✔
557
                        (1., 0.).into()
1✔
558
                    ),
1✔
559
                    time_interval: TimeInterval::new_unchecked(0, 30),
1✔
560
                    spatial_resolution: SpatialResolution::one(),
1✔
561
                })
1✔
562
                .await
×
563
                .unwrap()
1✔
564
                .info
1✔
565
                .map(|p| {
3✔
566
                    let p = p.unwrap();
3✔
567
                    (
3✔
568
                        p.time,
3✔
569
                        p.params.map(|p| p.file_path.to_str().unwrap().to_owned()),
3✔
570
                    )
3✔
571
                })
3✔
572
                .collect::<Vec<_>>(),
1✔
573
            &[
1✔
574
                (
1✔
575
                    TimeInterval::new_unchecked(0, 11),
1✔
576
                    Some("/foo/bar_000000000.tiff".to_owned())
1✔
577
                ),
1✔
578
                (
1✔
579
                    TimeInterval::new_unchecked(11, 22),
1✔
580
                    Some("/foo/bar_011000000.tiff".to_owned())
1✔
581
                ),
1✔
582
                (
1✔
583
                    TimeInterval::new_unchecked(22, 33),
1✔
584
                    Some("/foo/bar_022000000.tiff".to_owned())
1✔
585
                ),
1✔
586
            ]
1✔
587
        );
588
    }
589

590
    #[tokio::test]
1✔
591
    async fn test_regular_meta_data_default_time() {
1✔
592
        let meta_data = create_regular_metadata();
1✔
593

594
        assert_eq!(
1✔
595
            meta_data
1✔
596
                .loading_info(RasterQueryRectangle {
1✔
597
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
598
                        (0., 1.).into(),
1✔
599
                        (1., 0.).into()
1✔
600
                    ),
1✔
601
                    time_interval: TimeInterval::default(),
1✔
602
                    spatial_resolution: SpatialResolution::one(),
1✔
603
                })
1✔
604
                .await
×
605
                .unwrap()
1✔
606
                .info
1✔
607
                .map(|p| {
5✔
608
                    let p = p.unwrap();
5✔
609
                    (
5✔
610
                        p.time,
5✔
611
                        p.params.map(|p| p.file_path.to_str().unwrap().to_owned()),
5✔
612
                    )
5✔
613
                })
5✔
614
                .collect::<Vec<_>>(),
1✔
615
            &[
1✔
616
                (TimeInterval::new_unchecked(TimeInstance::MIN, 0), None),
1✔
617
                (
1✔
618
                    TimeInterval::new_unchecked(0, 11),
1✔
619
                    Some("/foo/bar_000000000.tiff".to_owned())
1✔
620
                ),
1✔
621
                (
1✔
622
                    TimeInterval::new_unchecked(11, 22),
1✔
623
                    Some("/foo/bar_011000000.tiff".to_owned())
1✔
624
                ),
1✔
625
                (
1✔
626
                    TimeInterval::new_unchecked(22, 33),
1✔
627
                    Some("/foo/bar_022000000.tiff".to_owned())
1✔
628
                ),
1✔
629
                (TimeInterval::new_unchecked(33, TimeInstance::MAX), None)
1✔
630
            ]
1✔
631
        );
632
    }
633

634
    #[tokio::test]
1✔
635
    async fn test_regular_meta_data_before_data() {
1✔
636
        let meta_data = create_regular_metadata();
1✔
637

638
        assert_eq!(
1✔
639
            meta_data
1✔
640
                .loading_info(RasterQueryRectangle {
1✔
641
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
642
                        (0., 1.).into(),
1✔
643
                        (1., 0.).into()
1✔
644
                    ),
1✔
645
                    time_interval: TimeInterval::new_unchecked(-10, -5),
1✔
646
                    spatial_resolution: SpatialResolution::one(),
1✔
647
                })
1✔
648
                .await
×
649
                .unwrap()
1✔
650
                .info
1✔
651
                .map(|p| {
1✔
652
                    let p = p.unwrap();
1✔
653
                    (
1✔
654
                        p.time,
1✔
655
                        p.params.map(|p| p.file_path.to_str().unwrap().to_owned()),
1✔
656
                    )
1✔
657
                })
1✔
658
                .collect::<Vec<_>>(),
1✔
659
            &[(TimeInterval::new_unchecked(TimeInstance::MIN, 0), None),]
1✔
660
        );
661
    }
662

663
    #[tokio::test]
1✔
664
    async fn test_regular_meta_data_after_data() {
1✔
665
        let meta_data = create_regular_metadata();
1✔
666

667
        assert_eq!(
1✔
668
            meta_data
1✔
669
                .loading_info(RasterQueryRectangle {
1✔
670
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
671
                        (0., 1.).into(),
1✔
672
                        (1., 0.).into()
1✔
673
                    ),
1✔
674
                    time_interval: TimeInterval::new_unchecked(50, 55),
1✔
675
                    spatial_resolution: SpatialResolution::one(),
1✔
676
                })
1✔
677
                .await
×
678
                .unwrap()
1✔
679
                .info
1✔
680
                .map(|p| {
1✔
681
                    let p = p.unwrap();
1✔
682
                    (
1✔
683
                        p.time,
1✔
684
                        p.params.map(|p| p.file_path.to_str().unwrap().to_owned()),
1✔
685
                    )
1✔
686
                })
1✔
687
                .collect::<Vec<_>>(),
1✔
688
            &[(TimeInterval::new_unchecked(33, TimeInstance::MAX), None)]
1✔
689
        );
690
    }
691

692
    #[tokio::test]
1✔
693
    async fn test_regular_meta_data_0_22() {
1✔
694
        let meta_data = create_regular_metadata();
1✔
695

696
        assert_eq!(
1✔
697
            meta_data
1✔
698
                .loading_info(RasterQueryRectangle {
1✔
699
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
700
                        (0., 1.).into(),
1✔
701
                        (1., 0.).into()
1✔
702
                    ),
1✔
703
                    time_interval: TimeInterval::new_unchecked(0, 22),
1✔
704
                    spatial_resolution: SpatialResolution::one(),
1✔
705
                })
1✔
706
                .await
×
707
                .unwrap()
1✔
708
                .info
1✔
709
                .map(|p| {
2✔
710
                    let p = p.unwrap();
2✔
711
                    (
2✔
712
                        p.time,
2✔
713
                        p.params.map(|p| p.file_path.to_str().unwrap().to_owned()),
2✔
714
                    )
2✔
715
                })
2✔
716
                .collect::<Vec<_>>(),
1✔
717
            &[
1✔
718
                (
1✔
719
                    TimeInterval::new_unchecked(0, 11),
1✔
720
                    Some("/foo/bar_000000000.tiff".to_owned())
1✔
721
                ),
1✔
722
                (
1✔
723
                    TimeInterval::new_unchecked(11, 22),
1✔
724
                    Some("/foo/bar_011000000.tiff".to_owned())
1✔
725
                ),
1✔
726
            ]
1✔
727
        );
728
    }
729

730
    #[tokio::test]
1✔
731
    async fn test_regular_meta_data_0_20() {
1✔
732
        let meta_data = create_regular_metadata();
1✔
733

734
        assert_eq!(
1✔
735
            meta_data
1✔
736
                .loading_info(RasterQueryRectangle {
1✔
737
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
738
                        (0., 1.).into(),
1✔
739
                        (1., 0.).into()
1✔
740
                    ),
1✔
741
                    time_interval: TimeInterval::new_unchecked(0, 20),
1✔
742
                    spatial_resolution: SpatialResolution::one(),
1✔
743
                })
1✔
744
                .await
×
745
                .unwrap()
1✔
746
                .info
1✔
747
                .map(|p| {
2✔
748
                    let p = p.unwrap();
2✔
749
                    (
2✔
750
                        p.time,
2✔
751
                        p.params.map(|p| p.file_path.to_str().unwrap().to_owned()),
2✔
752
                    )
2✔
753
                })
2✔
754
                .collect::<Vec<_>>(),
1✔
755
            &[
1✔
756
                (
1✔
757
                    TimeInterval::new_unchecked(0, 11),
1✔
758
                    Some("/foo/bar_000000000.tiff".to_owned())
1✔
759
                ),
1✔
760
                (
1✔
761
                    TimeInterval::new_unchecked(11, 22),
1✔
762
                    Some("/foo/bar_011000000.tiff".to_owned())
1✔
763
                ),
1✔
764
            ]
1✔
765
        );
766
    }
767

768
    #[tokio::test]
1✔
769
    async fn test_meta_data_list() {
1✔
770
        let no_data_value = Some(0.);
1✔
771

1✔
772
        let meta_data = GdalMetaDataList {
1✔
773
            result_descriptor: RasterResultDescriptor {
1✔
774
                data_type: RasterDataType::U8,
1✔
775
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
776
                measurement: Measurement::Unitless,
1✔
777
                time: None,
1✔
778
                bbox: None,
1✔
779
                resolution: None,
1✔
780
            },
1✔
781
            params: vec![
1✔
782
                GdalLoadingInfoTemporalSlice {
1✔
783
                    time: TimeInterval::new_unchecked(0, 1),
1✔
784
                    params: Some(GdalDatasetParameters {
1✔
785
                        file_path: "/foo/bar_0.tiff".into(),
1✔
786
                        rasterband_channel: 0,
1✔
787
                        geo_transform: TestDefault::test_default(),
1✔
788
                        width: 360,
1✔
789
                        height: 180,
1✔
790
                        file_not_found_handling: FileNotFoundHandling::NoData,
1✔
791
                        no_data_value,
1✔
792
                        properties_mapping: None,
1✔
793
                        gdal_open_options: None,
1✔
794
                        gdal_config_options: None,
1✔
795
                        allow_alphaband_as_mask: true,
1✔
796
                    }),
1✔
797
                },
1✔
798
                GdalLoadingInfoTemporalSlice {
1✔
799
                    time: TimeInterval::new_unchecked(1, 5),
1✔
800
                    params: Some(GdalDatasetParameters {
1✔
801
                        file_path: "/foo/bar_1.tiff".into(),
1✔
802
                        rasterband_channel: 0,
1✔
803
                        geo_transform: TestDefault::test_default(),
1✔
804
                        width: 360,
1✔
805
                        height: 180,
1✔
806
                        file_not_found_handling: FileNotFoundHandling::NoData,
1✔
807
                        no_data_value,
1✔
808
                        properties_mapping: None,
1✔
809
                        gdal_open_options: None,
1✔
810
                        gdal_config_options: None,
1✔
811
                        allow_alphaband_as_mask: true,
1✔
812
                    }),
1✔
813
                },
1✔
814
                GdalLoadingInfoTemporalSlice {
1✔
815
                    time: TimeInterval::new_unchecked(5, 6),
1✔
816
                    params: Some(GdalDatasetParameters {
1✔
817
                        file_path: "/foo/bar_2.tiff".into(),
1✔
818
                        rasterband_channel: 0,
1✔
819
                        geo_transform: TestDefault::test_default(),
1✔
820
                        width: 360,
1✔
821
                        height: 180,
1✔
822
                        file_not_found_handling: FileNotFoundHandling::NoData,
1✔
823
                        no_data_value,
1✔
824
                        properties_mapping: None,
1✔
825
                        gdal_open_options: None,
1✔
826
                        gdal_config_options: None,
1✔
827
                        allow_alphaband_as_mask: true,
1✔
828
                    }),
1✔
829
                },
1✔
830
            ],
1✔
831
        };
1✔
832

833
        assert_eq!(
1✔
834
            meta_data.result_descriptor().await.unwrap(),
1✔
835
            RasterResultDescriptor {
1✔
836
                data_type: RasterDataType::U8,
1✔
837
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
838
                measurement: Measurement::Unitless,
1✔
839
                time: None,
1✔
840
                bbox: None,
1✔
841
                resolution: None,
1✔
842
            }
1✔
843
        );
844

845
        assert_eq!(
1✔
846
            meta_data
1✔
847
                .loading_info(RasterQueryRectangle {
1✔
848
                    spatial_bounds: SpatialPartition2D::new_unchecked(
1✔
849
                        (0., 1.).into(),
1✔
850
                        (1., 0.).into()
1✔
851
                    ),
1✔
852
                    time_interval: TimeInterval::new_unchecked(0, 3),
1✔
853
                    spatial_resolution: SpatialResolution::one(),
1✔
854
                })
1✔
855
                .await
×
856
                .unwrap()
1✔
857
                .info
1✔
858
                .map(|p| p
2✔
859
                    .unwrap()
2✔
860
                    .params
2✔
861
                    .unwrap()
2✔
862
                    .file_path
2✔
863
                    .to_str()
2✔
864
                    .unwrap()
2✔
865
                    .to_owned())
2✔
866
                .collect::<Vec<_>>(),
1✔
867
            &["/foo/bar_0.tiff", "/foo/bar_1.tiff",]
868
        );
869
    }
870

871
    #[tokio::test]
1✔
872
    async fn netcdf_cf_single_time_step() {
1✔
873
        let time_start = TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0));
1✔
874
        let time_end = TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0));
1✔
875
        let time_step = TimeStep {
1✔
876
            step: 0,
1✔
877
            granularity: TimeGranularity::Years,
1✔
878
        };
1✔
879

1✔
880
        let metadata = GdalMetadataNetCdfCf {
1✔
881
            result_descriptor: RasterResultDescriptor {
1✔
882
                data_type: RasterDataType::U8,
1✔
883
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
884
                measurement: Measurement::Unitless,
1✔
885
                time: None,
1✔
886
                bbox: None,
1✔
887
                resolution: None,
1✔
888
            },
1✔
889
            params: GdalDatasetParameters {
1✔
890
                file_path: "path/to/ds".into(),
1✔
891
                rasterband_channel: 0,
1✔
892
                geo_transform: GdalDatasetGeoTransform {
1✔
893
                    origin_coordinate: (0., 0.).into(),
1✔
894
                    x_pixel_size: 1.,
1✔
895
                    y_pixel_size: 1.,
1✔
896
                },
1✔
897
                width: 128,
1✔
898
                height: 128,
1✔
899
                file_not_found_handling: FileNotFoundHandling::Error,
1✔
900
                no_data_value: None,
1✔
901
                properties_mapping: None,
1✔
902
                gdal_open_options: None,
1✔
903
                gdal_config_options: None,
1✔
904
                allow_alphaband_as_mask: true,
1✔
905
            },
1✔
906
            start: time_start,
1✔
907
            end: time_end,
1✔
908
            step: time_step,
1✔
909
            band_offset: 0,
1✔
910
        };
1✔
911

1✔
912
        let query = RasterQueryRectangle {
1✔
913
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 128.).into(), (128., 0.).into()),
1✔
914
            time_interval: TimeInterval::new(time_start, time_end).unwrap(),
1✔
915
            spatial_resolution: SpatialResolution::one(),
1✔
916
        };
1✔
917

918
        let loading_info = metadata.loading_info(query).await.unwrap();
1✔
919
        let mut iter = loading_info.info;
1✔
920

1✔
921
        let step_1 = iter.next().unwrap().unwrap();
1✔
922

1✔
923
        assert_eq!(
1✔
924
            step_1.time,
1✔
925
            TimeInterval::new(
1✔
926
                TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0)),
1✔
927
                TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0))
1✔
928
            )
1✔
929
            .unwrap()
1✔
930
        );
1✔
931
        assert_eq!(step_1.params.unwrap().rasterband_channel, 1);
1✔
932

933
        assert!(iter.next().is_none());
1✔
934
    }
935

936
    #[tokio::test]
1✔
937
    async fn netcdf_cf_single_time_step_with_offset() {
1✔
938
        let time_start = TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0));
1✔
939
        let time_end = TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0));
1✔
940
        let time_step = TimeStep {
1✔
941
            step: 0,
1✔
942
            granularity: TimeGranularity::Years,
1✔
943
        };
1✔
944

1✔
945
        let metadata = GdalMetadataNetCdfCf {
1✔
946
            result_descriptor: RasterResultDescriptor {
1✔
947
                data_type: RasterDataType::U8,
1✔
948
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
949
                measurement: Measurement::Unitless,
1✔
950
                time: None,
1✔
951
                bbox: None,
1✔
952
                resolution: None,
1✔
953
            },
1✔
954
            params: GdalDatasetParameters {
1✔
955
                file_path: "path/to/ds".into(),
1✔
956
                rasterband_channel: 0,
1✔
957
                geo_transform: GdalDatasetGeoTransform {
1✔
958
                    origin_coordinate: (0., 0.).into(),
1✔
959
                    x_pixel_size: 1.,
1✔
960
                    y_pixel_size: 1.,
1✔
961
                },
1✔
962
                width: 128,
1✔
963
                height: 128,
1✔
964
                file_not_found_handling: FileNotFoundHandling::Error,
1✔
965
                no_data_value: None,
1✔
966
                properties_mapping: None,
1✔
967
                gdal_open_options: None,
1✔
968
                gdal_config_options: None,
1✔
969
                allow_alphaband_as_mask: true,
1✔
970
            },
1✔
971
            start: time_start,
1✔
972
            end: time_end,
1✔
973
            step: time_step,
1✔
974
            band_offset: 1,
1✔
975
        };
1✔
976

1✔
977
        let query = RasterQueryRectangle {
1✔
978
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 128.).into(), (128., 0.).into()),
1✔
979
            time_interval: TimeInterval::new(time_start, time_end).unwrap(),
1✔
980
            spatial_resolution: SpatialResolution::one(),
1✔
981
        };
1✔
982

983
        let loading_info = metadata.loading_info(query).await.unwrap();
1✔
984
        let mut iter = loading_info.info;
1✔
985

1✔
986
        let step_1 = iter.next().unwrap().unwrap();
1✔
987

1✔
988
        assert_eq!(
1✔
989
            step_1.time,
1✔
990
            TimeInterval::new(
1✔
991
                TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0)),
1✔
992
                TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0))
1✔
993
            )
1✔
994
            .unwrap()
1✔
995
        );
1✔
996
        assert_eq!(step_1.params.unwrap().rasterband_channel, 2);
1✔
997

998
        assert!(iter.next().is_none());
1✔
999
    }
1000

1001
    #[tokio::test]
1✔
1002
    async fn netcdf_cf_time_steps_before_after() {
1✔
1003
        let time_start = TimeInstance::from(DateTime::new_utc(2010, 1, 1, 0, 0, 0));
1✔
1004
        let time_end = TimeInstance::from(DateTime::new_utc(2012, 1, 1, 0, 0, 0));
1✔
1005
        let time_step = TimeStep {
1✔
1006
            step: 1,
1✔
1007
            granularity: TimeGranularity::Years,
1✔
1008
        };
1✔
1009

1✔
1010
        let metadata = GdalMetadataNetCdfCf {
1✔
1011
            result_descriptor: RasterResultDescriptor {
1✔
1012
                data_type: RasterDataType::U8,
1✔
1013
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1014
                measurement: Measurement::Unitless,
1✔
1015
                time: None,
1✔
1016
                bbox: None,
1✔
1017
                resolution: None,
1✔
1018
            },
1✔
1019
            params: GdalDatasetParameters {
1✔
1020
                file_path: "path/to/ds".into(),
1✔
1021
                rasterband_channel: 0,
1✔
1022
                geo_transform: GdalDatasetGeoTransform {
1✔
1023
                    origin_coordinate: (0., 0.).into(),
1✔
1024
                    x_pixel_size: 1.,
1✔
1025
                    y_pixel_size: 1.,
1✔
1026
                },
1✔
1027
                width: 128,
1✔
1028
                height: 128,
1✔
1029
                file_not_found_handling: FileNotFoundHandling::Error,
1✔
1030
                no_data_value: None,
1✔
1031
                properties_mapping: None,
1✔
1032
                gdal_open_options: None,
1✔
1033
                gdal_config_options: None,
1✔
1034
                allow_alphaband_as_mask: true,
1✔
1035
            },
1✔
1036
            start: time_start,
1✔
1037
            end: time_end,
1✔
1038
            step: time_step,
1✔
1039
            band_offset: 0,
1✔
1040
        };
1✔
1041

1✔
1042
        let query = RasterQueryRectangle {
1✔
1043
            spatial_bounds: SpatialPartition2D::new_unchecked((0., 128.).into(), (128., 0.).into()),
1✔
1044
            time_interval: TimeInterval::new_unchecked(
1✔
1045
                TimeInstance::from(DateTime::new_utc(2009, 7, 1, 0, 0, 0)),
1✔
1046
                TimeInstance::from(DateTime::new_utc(2013, 3, 1, 0, 0, 0)),
1✔
1047
            ),
1✔
1048
            spatial_resolution: SpatialResolution::one(),
1✔
1049
        };
1✔
1050

1051
        let loading_info = metadata.loading_info(query).await.unwrap();
1✔
1052
        let mut iter = loading_info.info;
1✔
1053

1✔
1054
        let step_1 = iter.next().unwrap().unwrap();
1✔
1055

1✔
1056
        assert_eq!(
1✔
1057
            step_1.time,
1✔
1058
            TimeInterval::new(
1✔
1059
                TimeInstance::from(DateTime::new_utc(2009, 1, 1, 0, 0, 0)),
1✔
1060
                TimeInstance::from(DateTime::new_utc(2010, 1, 1, 0, 0, 0))
1✔
1061
            )
1✔
1062
            .unwrap()
1✔
1063
        );
1✔
1064
        assert!(step_1.params.is_none());
1✔
1065

1066
        let step_2 = iter.next().unwrap().unwrap();
1✔
1067

1✔
1068
        assert_eq!(
1✔
1069
            step_2.time,
1✔
1070
            TimeInterval::new(
1✔
1071
                TimeInstance::from(DateTime::new_utc(2010, 1, 1, 0, 0, 0)),
1✔
1072
                TimeInstance::from(DateTime::new_utc(2011, 1, 1, 0, 0, 0))
1✔
1073
            )
1✔
1074
            .unwrap()
1✔
1075
        );
1✔
1076
        assert_eq!(step_2.params.unwrap().rasterband_channel, 1);
1✔
1077

1078
        let step_3 = iter.next().unwrap().unwrap();
1✔
1079

1✔
1080
        assert_eq!(
1✔
1081
            step_3.time,
1✔
1082
            TimeInterval::new(
1✔
1083
                TimeInstance::from(DateTime::new_utc(2011, 1, 1, 0, 0, 0)),
1✔
1084
                TimeInstance::from(DateTime::new_utc(2012, 1, 1, 0, 0, 0))
1✔
1085
            )
1✔
1086
            .unwrap()
1✔
1087
        );
1✔
1088
        assert_eq!(step_3.params.unwrap().rasterband_channel, 2);
1✔
1089

1090
        assert!(iter.next().is_none());
1✔
1091
    }
1092

1093
    #[test]
1✔
1094
    fn netcdf_cf_time_steps() {
1✔
1095
        let time_start = TimeInstance::from(DateTime::new_utc(2010, 1, 1, 0, 0, 0));
1✔
1096
        let time_end = TimeInstance::from(DateTime::new_utc(2022, 1, 1, 0, 0, 0));
1✔
1097
        let time_step = TimeStep {
1✔
1098
            step: 1,
1✔
1099
            granularity: TimeGranularity::Years,
1✔
1100
        };
1✔
1101
        let mut iter = NetCdfCfGdalLoadingInfoPartIterator {
1✔
1102
            time_step_iter: TimeStepIter::new_with_interval(
1✔
1103
                TimeInterval::new(time_start, time_end).unwrap(),
1✔
1104
                time_step,
1✔
1105
            )
1✔
1106
            .unwrap(),
1✔
1107
            params: GdalDatasetParameters {
1✔
1108
                file_path: "path/to/ds".into(),
1✔
1109
                rasterband_channel: 0,
1✔
1110
                geo_transform: GdalDatasetGeoTransform {
1✔
1111
                    origin_coordinate: (0., 0.).into(),
1✔
1112
                    x_pixel_size: 1.,
1✔
1113
                    y_pixel_size: 1.,
1✔
1114
                },
1✔
1115
                width: 128,
1✔
1116
                height: 128,
1✔
1117
                file_not_found_handling: FileNotFoundHandling::Error,
1✔
1118
                no_data_value: None,
1✔
1119
                properties_mapping: None,
1✔
1120
                gdal_open_options: None,
1✔
1121
                gdal_config_options: None,
1✔
1122
                allow_alphaband_as_mask: true,
1✔
1123
            },
1✔
1124
            step: time_step,
1✔
1125
            dataset_time_start: time_start,
1✔
1126
            max_t2: time_end,
1✔
1127
            band_offset: 0,
1✔
1128
        };
1✔
1129

1✔
1130
        let step_1 = iter.next().unwrap().unwrap();
1✔
1131

1✔
1132
        assert_eq!(
1✔
1133
            step_1.time,
1✔
1134
            TimeInterval::new(
1✔
1135
                TimeInstance::from(DateTime::new_utc(2010, 1, 1, 0, 0, 0)),
1✔
1136
                TimeInstance::from(DateTime::new_utc(2011, 1, 1, 0, 0, 0))
1✔
1137
            )
1✔
1138
            .unwrap()
1✔
1139
        );
1✔
1140
        assert_eq!(step_1.params.unwrap().rasterband_channel, 1);
1✔
1141

1142
        let step_2 = iter.next().unwrap().unwrap();
1✔
1143

1✔
1144
        assert_eq!(
1✔
1145
            step_2.time,
1✔
1146
            TimeInterval::new(
1✔
1147
                TimeInstance::from(DateTime::new_utc(2011, 1, 1, 0, 0, 0)),
1✔
1148
                TimeInstance::from(DateTime::new_utc(2012, 1, 1, 0, 0, 0))
1✔
1149
            )
1✔
1150
            .unwrap()
1✔
1151
        );
1✔
1152
        assert_eq!(step_2.params.unwrap().rasterband_channel, 2);
1✔
1153

1154
        let step_3 = iter.next().unwrap().unwrap();
1✔
1155

1✔
1156
        assert_eq!(
1✔
1157
            step_3.time,
1✔
1158
            TimeInterval::new(
1✔
1159
                TimeInstance::from(DateTime::new_utc(2012, 1, 1, 0, 0, 0)),
1✔
1160
                TimeInstance::from(DateTime::new_utc(2013, 1, 1, 0, 0, 0))
1✔
1161
            )
1✔
1162
            .unwrap()
1✔
1163
        );
1✔
1164
        assert_eq!(step_3.params.unwrap().rasterband_channel, 3);
1✔
1165

1166
        for i in 4..=12 {
10✔
1167
            let step = iter.next().unwrap().unwrap();
9✔
1168

9✔
1169
            assert_eq!(step.params.unwrap().rasterband_channel, i);
9✔
1170
        }
1171

1172
        assert!(iter.next().is_none());
1✔
1173
    }
1✔
1174

1175
    #[test]
1✔
1176
    fn netcdf_cf_time_step_instant() {
1✔
1177
        fn iter_for_instance(instance: TimeInstance) -> NetCdfCfGdalLoadingInfoPartIterator {
5✔
1178
            let time_step = TimeStep {
5✔
1179
                step: 1,
5✔
1180
                granularity: TimeGranularity::Years,
5✔
1181
            };
5✔
1182
            NetCdfCfGdalLoadingInfoPartIterator {
5✔
1183
                time_step_iter: TimeStepIter::new_with_interval(
5✔
1184
                    TimeInterval::new_instant(instance).unwrap(),
5✔
1185
                    time_step,
5✔
1186
                )
5✔
1187
                .unwrap(),
5✔
1188
                params: GdalDatasetParameters {
5✔
1189
                    file_path: "path/to/ds".into(),
5✔
1190
                    rasterband_channel: 0,
5✔
1191
                    geo_transform: GdalDatasetGeoTransform {
5✔
1192
                        origin_coordinate: (0., 0.).into(),
5✔
1193
                        x_pixel_size: 1.,
5✔
1194
                        y_pixel_size: 1.,
5✔
1195
                    },
5✔
1196
                    width: 128,
5✔
1197
                    height: 128,
5✔
1198
                    file_not_found_handling: FileNotFoundHandling::Error,
5✔
1199
                    no_data_value: None,
5✔
1200
                    properties_mapping: None,
5✔
1201
                    gdal_open_options: None,
5✔
1202
                    gdal_config_options: None,
5✔
1203
                    allow_alphaband_as_mask: true,
5✔
1204
                },
5✔
1205
                step: time_step,
5✔
1206
                dataset_time_start: TimeInstance::from(DateTime::new_utc(2010, 1, 1, 0, 0, 0)),
5✔
1207
                max_t2: TimeInstance::from(DateTime::new_utc(2022, 1, 1, 0, 0, 0)),
5✔
1208
                band_offset: 0,
5✔
1209
            }
5✔
1210
        }
5✔
1211

1✔
1212
        let mut iter =
1✔
1213
            iter_for_instance(TimeInstance::from(DateTime::new_utc(2014, 1, 1, 0, 0, 0)));
1✔
1214

1✔
1215
        let step = iter.next().unwrap().unwrap();
1✔
1216

1✔
1217
        assert_eq!(
1✔
1218
            step.time,
1✔
1219
            TimeInterval::new(
1✔
1220
                TimeInstance::from(DateTime::new_utc(2014, 1, 1, 0, 0, 0)),
1✔
1221
                TimeInstance::from(DateTime::new_utc(2015, 1, 1, 0, 0, 0))
1✔
1222
            )
1✔
1223
            .unwrap()
1✔
1224
        );
1✔
1225
        assert_eq!(step.params.unwrap().rasterband_channel, 5);
1✔
1226

1227
        assert!(iter.next().is_none());
1✔
1228

1229
        let mut iter =
1✔
1230
            iter_for_instance(TimeInstance::from(DateTime::new_utc(2010, 1, 1, 0, 0, 0)));
1✔
1231

1✔
1232
        let step = iter.next().unwrap().unwrap();
1✔
1233

1✔
1234
        assert_eq!(
1✔
1235
            step.time,
1✔
1236
            TimeInterval::new(
1✔
1237
                TimeInstance::from(DateTime::new_utc(2010, 1, 1, 0, 0, 0)),
1✔
1238
                TimeInstance::from(DateTime::new_utc(2011, 1, 1, 0, 0, 0))
1✔
1239
            )
1✔
1240
            .unwrap()
1✔
1241
        );
1✔
1242
        assert_eq!(step.params.unwrap().rasterband_channel, 1);
1✔
1243

1244
        assert!(iter.next().is_none());
1✔
1245

1246
        let mut iter =
1✔
1247
            iter_for_instance(TimeInstance::from(DateTime::new_utc(2021, 1, 1, 0, 0, 0)));
1✔
1248

1✔
1249
        let step = iter.next().unwrap().unwrap();
1✔
1250

1✔
1251
        assert_eq!(
1✔
1252
            step.time,
1✔
1253
            TimeInterval::new(
1✔
1254
                TimeInstance::from(DateTime::new_utc(2021, 1, 1, 0, 0, 0)),
1✔
1255
                TimeInstance::from(DateTime::new_utc(2022, 1, 1, 0, 0, 0))
1✔
1256
            )
1✔
1257
            .unwrap()
1✔
1258
        );
1✔
1259
        assert_eq!(step.params.unwrap().rasterband_channel, 12);
1✔
1260

1261
        assert!(iter.next().is_none());
1✔
1262

1263
        let iter = iter_for_instance(TimeInstance::from(DateTime::new_utc(2009, 1, 1, 0, 0, 0)))
1✔
1264
            .next()
1✔
1265
            .unwrap()
1✔
1266
            .unwrap();
1✔
1267
        assert_eq!(
1✔
1268
            iter.time,
1✔
1269
            TimeInterval::new(
1✔
1270
                TimeInstance::from(DateTime::new_utc(2009, 1, 1, 0, 0, 0)),
1✔
1271
                TimeInstance::from(DateTime::new_utc(2010, 1, 1, 0, 0, 0))
1✔
1272
            )
1✔
1273
            .unwrap(),
1✔
1274
        );
1✔
1275
        assert!(iter.params.is_none());
1✔
1276

1277
        assert!(
1✔
1278
            iter_for_instance(TimeInstance::from(DateTime::new_utc(2022, 1, 1, 0, 0, 0),))
1✔
1279
                .next()
1✔
1280
                .is_none()
1✔
1281
        );
1✔
1282
    }
1✔
1283
}
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