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

geo-engine / geoengine / 7006568925

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

push

github

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

raster stacking

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

12 existing lines in 8 files now uncovered.

113020 of 126066 relevant lines covered (89.65%)

59901.79 hits per line

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

56.85
/services/src/api/model/operators.rs
1
use super::datatypes::{
2
    BoundingBox2D, FeatureDataType, Measurement, RasterDataType, SpatialPartition2D,
3
    SpatialReferenceOption, TimeInterval, VectorDataType,
4
};
5
use crate::api::model::datatypes::{
6
    CacheTtlSeconds, Coordinate2D, DateTimeParseFormat, GdalConfigOption, MultiLineString,
7
    MultiPoint, MultiPolygon, NoGeometry, QueryRectangle, RasterPropertiesEntryType,
8
    RasterPropertiesKey, SpatialResolution, TimeInstance, TimeStep, VectorQueryRectangle,
9
};
10
use crate::error::{
11
    RasterBandNameMustNotBeEmpty, RasterBandNameTooLong, RasterBandNamesMustBeUnique, Result,
12
};
13
use async_trait::async_trait;
14
use geoengine_datatypes::primitives::ColumnSelection;
15
use geoengine_datatypes::util::ByteSize;
16
use geoengine_operators::{
17
    engine::{MetaData, ResultDescriptor},
18
    util::input::float_option_with_nan,
19
};
20
use serde::{Deserialize, Deserializer, Serialize};
21
use snafu::ensure;
22
use std::collections::{HashMap, HashSet};
23
use std::fmt::Debug;
24
use std::marker::PhantomData;
25
use std::path::PathBuf;
26
use utoipa::ToSchema;
27

28
/// A `ResultDescriptor` for raster queries
29
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
52✔
30
#[serde(rename_all = "camelCase")]
31
pub struct RasterResultDescriptor {
32
    pub data_type: RasterDataType,
33
    #[schema(value_type = String)]
34
    pub spatial_reference: SpatialReferenceOption,
35
    pub time: Option<TimeInterval>,
36
    pub bbox: Option<SpatialPartition2D>,
37
    pub resolution: Option<SpatialResolution>,
38
    pub bands: RasterBandDescriptors,
39
}
40

41
#[derive(Debug, Clone, PartialEq, Serialize, ToSchema)]
5✔
42
pub struct RasterBandDescriptors(Vec<RasterBandDescriptor>);
43

44
impl RasterBandDescriptors {
45
    pub fn new(bands: Vec<RasterBandDescriptor>) -> Result<Self> {
7✔
46
        let mut names = HashSet::new();
7✔
47
        for value in &bands {
16✔
48
            ensure!(!value.name.is_empty(), RasterBandNameMustNotBeEmpty);
10✔
49
            ensure!(value.name.byte_size() <= 256, RasterBandNameTooLong);
10✔
50
            ensure!(
10✔
51
                names.insert(&value.name),
10✔
52
                RasterBandNamesMustBeUnique {
1✔
53
                    duplicate_key: value.name.clone()
1✔
54
                }
1✔
55
            );
56
        }
57

58
        Ok(Self(bands))
6✔
59
    }
7✔
60
}
61

62
impl<'de> Deserialize<'de> for RasterBandDescriptors {
63
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
6✔
64
    where
6✔
65
        D: Deserializer<'de>,
6✔
66
    {
6✔
67
        let vec = Vec::deserialize(deserializer)?;
6✔
68
        RasterBandDescriptors::new(vec).map_err(serde::de::Error::custom)
6✔
69
    }
6✔
70
}
71

72
impl From<geoengine_operators::engine::RasterBandDescriptors> for RasterBandDescriptors {
73
    fn from(value: geoengine_operators::engine::RasterBandDescriptors) -> Self {
4✔
74
        Self(value.into_vec().into_iter().map(Into::into).collect())
4✔
75
    }
4✔
76
}
77

78
impl From<RasterBandDescriptors> for geoengine_operators::engine::RasterBandDescriptors {
79
    fn from(value: RasterBandDescriptors) -> Self {
4✔
80
        geoengine_operators::engine::RasterBandDescriptors::new(
4✔
81
            value.0.into_iter().map(Into::into).collect(),
4✔
82
        )
4✔
83
        .expect("RasterBandDescriptors should be valid, because the API descriptors are valid")
4✔
84
    }
4✔
85
}
86

87
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
40✔
88
pub struct RasterBandDescriptor {
89
    pub name: String,
90
    pub measurement: Measurement,
91
}
92

93
impl From<geoengine_operators::engine::RasterBandDescriptor> for RasterBandDescriptor {
94
    fn from(value: geoengine_operators::engine::RasterBandDescriptor) -> Self {
4✔
95
        Self {
4✔
96
            name: value.name,
4✔
97
            measurement: value.measurement.into(),
4✔
98
        }
4✔
99
    }
4✔
100
}
101

102
impl From<RasterBandDescriptor> for geoengine_operators::engine::RasterBandDescriptor {
103
    fn from(value: RasterBandDescriptor) -> Self {
4✔
104
        Self {
4✔
105
            name: value.name,
4✔
106
            measurement: value.measurement.into(),
4✔
107
        }
4✔
108
    }
4✔
109
}
110

111
impl From<geoengine_operators::engine::RasterResultDescriptor> for RasterResultDescriptor {
112
    fn from(value: geoengine_operators::engine::RasterResultDescriptor) -> Self {
4✔
113
        Self {
4✔
114
            data_type: value.data_type.into(),
4✔
115
            spatial_reference: value.spatial_reference.into(),
4✔
116
            time: value.time.map(Into::into),
4✔
117
            bbox: value.bbox.map(Into::into),
4✔
118
            resolution: value.resolution.map(Into::into),
4✔
119
            bands: value.bands.into(),
4✔
120
        }
4✔
121
    }
4✔
122
}
123

124
impl From<RasterResultDescriptor> for geoengine_operators::engine::RasterResultDescriptor {
125
    fn from(value: RasterResultDescriptor) -> Self {
4✔
126
        Self {
4✔
127
            data_type: value.data_type.into(),
4✔
128
            spatial_reference: value.spatial_reference.into(),
4✔
129
            time: value.time.map(Into::into),
4✔
130
            bbox: value.bbox.map(Into::into),
4✔
131
            resolution: value.resolution.map(Into::into),
4✔
132
            bands: value.bands.into(),
4✔
133
        }
4✔
134
    }
4✔
135
}
136

137
/// An enum to differentiate between `Operator` variants
138
#[derive(Clone, Debug, Serialize, Deserialize)]
×
139
#[serde(tag = "type", content = "operator")]
140
pub enum TypedOperator {
141
    Vector(Box<dyn geoengine_operators::engine::VectorOperator>),
142
    Raster(Box<dyn geoengine_operators::engine::RasterOperator>),
143
    Plot(Box<dyn geoengine_operators::engine::PlotOperator>),
144
}
145

146
impl<'a> ToSchema<'a> for TypedOperator {
147
    fn schema() -> (&'a str, utoipa::openapi::RefOr<utoipa::openapi::Schema>) {
2✔
148
        use utoipa::openapi::*;
2✔
149
        (
2✔
150
            "TypedOperator",
2✔
151
            ObjectBuilder::new()
2✔
152
            .property(
2✔
153
                "type",
2✔
154
                ObjectBuilder::new()
2✔
155
                    .schema_type(SchemaType::String)
2✔
156
                    .enum_values(Some(vec!["Vector", "Raster", "Plot"]))
2✔
157
            )
2✔
158
            .required("type")
2✔
159
            .property(
2✔
160
                "operator",
2✔
161
                ObjectBuilder::new()
2✔
162
                    .property(
2✔
163
                        "type",
2✔
164
                        Object::with_type(SchemaType::String)
2✔
165
                    )
2✔
166
                    .required("type")
2✔
167
                    .property(
2✔
168
                        "params",
2✔
169
                        Object::with_type(SchemaType::Object)
2✔
170
                    )
2✔
171
                    .property(
2✔
172
                        "sources",
2✔
173
                        Object::with_type(SchemaType::Object)
2✔
174
                    )
2✔
175
            )
2✔
176
            .required("operator")
2✔
177
            .example(Some(serde_json::json!(
2✔
178
                {"type": "MockPointSource", "params": {"points": [{"x": 0.0, "y": 0.1}, {"x": 1.0, "y": 1.1}]}
2✔
179
            })))
2✔
180
            .description(Some("An enum to differentiate between `Operator` variants"))
2✔
181
            .into()
2✔
182
        )
2✔
183
    }
2✔
184
}
185

186
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
42✔
187
#[serde(rename_all = "camelCase")]
188
pub struct VectorResultDescriptor {
189
    pub data_type: VectorDataType,
190
    #[schema(value_type = String)]
191
    pub spatial_reference: SpatialReferenceOption,
192
    pub columns: HashMap<String, VectorColumnInfo>,
193
    pub time: Option<TimeInterval>,
194
    pub bbox: Option<BoundingBox2D>,
195
}
196

197
impl From<geoengine_operators::engine::VectorResultDescriptor> for VectorResultDescriptor {
198
    fn from(value: geoengine_operators::engine::VectorResultDescriptor) -> Self {
1✔
199
        Self {
1✔
200
            data_type: value.data_type.into(),
1✔
201
            spatial_reference: value.spatial_reference.into(),
1✔
202
            columns: value
1✔
203
                .columns
1✔
204
                .into_iter()
1✔
205
                .map(|(key, value)| (key, value.into()))
2✔
206
                .collect(),
1✔
207
            time: value.time.map(Into::into),
1✔
208
            bbox: value.bbox.map(Into::into),
1✔
209
        }
1✔
210
    }
1✔
211
}
212

213
impl From<VectorResultDescriptor> for geoengine_operators::engine::VectorResultDescriptor {
214
    fn from(value: VectorResultDescriptor) -> Self {
4✔
215
        Self {
4✔
216
            data_type: value.data_type.into(),
4✔
217
            spatial_reference: value.spatial_reference.into(),
4✔
218
            columns: value
4✔
219
                .columns
4✔
220
                .into_iter()
4✔
221
                .map(|(key, value)| (key, value.into()))
20✔
222
                .collect(),
4✔
223
            time: value.time.map(Into::into),
4✔
224
            bbox: value.bbox.map(Into::into),
4✔
225
        }
4✔
226
    }
4✔
227
}
228

229
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ToSchema)]
105✔
230
#[serde(rename_all = "camelCase")]
231
pub struct VectorColumnInfo {
232
    pub data_type: FeatureDataType,
233
    pub measurement: Measurement,
234
}
235

236
impl From<geoengine_operators::engine::VectorColumnInfo> for VectorColumnInfo {
237
    fn from(value: geoengine_operators::engine::VectorColumnInfo) -> Self {
2✔
238
        Self {
2✔
239
            data_type: value.data_type.into(),
2✔
240
            measurement: value.measurement.into(),
2✔
241
        }
2✔
242
    }
2✔
243
}
244

245
impl From<VectorColumnInfo> for geoengine_operators::engine::VectorColumnInfo {
246
    fn from(value: VectorColumnInfo) -> Self {
20✔
247
        Self {
20✔
248
            data_type: value.data_type.into(),
20✔
249
            measurement: value.measurement.into(),
20✔
250
        }
20✔
251
    }
20✔
252
}
253

254
/// A `ResultDescriptor` for plot queries
255
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
2✔
256
#[serde(rename_all = "camelCase")]
257
pub struct PlotResultDescriptor {
258
    #[schema(value_type = String)]
259
    pub spatial_reference: SpatialReferenceOption,
260
    pub time: Option<TimeInterval>,
261
    pub bbox: Option<BoundingBox2D>,
262
}
263

264
impl From<geoengine_operators::engine::PlotResultDescriptor> for PlotResultDescriptor {
265
    fn from(value: geoengine_operators::engine::PlotResultDescriptor) -> Self {
×
266
        Self {
×
267
            spatial_reference: value.spatial_reference.into(),
×
268
            time: value.time.map(Into::into),
×
269
            bbox: value.bbox.map(Into::into),
×
270
        }
×
271
    }
×
272
}
273

274
impl From<PlotResultDescriptor> for geoengine_operators::engine::PlotResultDescriptor {
275
    fn from(value: PlotResultDescriptor) -> Self {
×
276
        Self {
×
277
            spatial_reference: value.spatial_reference.into(),
×
278
            time: value.time.map(Into::into),
×
279
            bbox: value.bbox.map(Into::into),
×
280
        }
×
281
    }
×
282
}
283

284
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
2✔
285
#[serde(rename_all = "camelCase", tag = "type")]
286
pub enum TypedResultDescriptor {
287
    Plot(PlotResultDescriptor),
288
    Raster(RasterResultDescriptor),
289
    Vector(VectorResultDescriptor),
290
}
291

292
impl From<geoengine_operators::engine::TypedResultDescriptor> for TypedResultDescriptor {
293
    fn from(value: geoengine_operators::engine::TypedResultDescriptor) -> Self {
×
294
        match value {
×
295
            geoengine_operators::engine::TypedResultDescriptor::Plot(p) => Self::Plot(p.into()),
×
296
            geoengine_operators::engine::TypedResultDescriptor::Raster(r) => Self::Raster(r.into()),
×
297
            geoengine_operators::engine::TypedResultDescriptor::Vector(v) => Self::Vector(v.into()),
×
298
        }
299
    }
×
300
}
301

302
impl From<geoengine_operators::engine::PlotResultDescriptor> for TypedResultDescriptor {
303
    fn from(value: geoengine_operators::engine::PlotResultDescriptor) -> Self {
×
304
        Self::Plot(value.into())
×
305
    }
×
306
}
307

308
impl From<PlotResultDescriptor> for TypedResultDescriptor {
309
    fn from(value: PlotResultDescriptor) -> Self {
×
310
        Self::Plot(value)
×
311
    }
×
312
}
313

314
impl From<geoengine_operators::engine::RasterResultDescriptor> for TypedResultDescriptor {
315
    fn from(value: geoengine_operators::engine::RasterResultDescriptor) -> Self {
×
316
        Self::Raster(value.into())
×
317
    }
×
318
}
319

320
impl From<RasterResultDescriptor> for TypedResultDescriptor {
321
    fn from(value: RasterResultDescriptor) -> Self {
×
322
        Self::Raster(value)
×
323
    }
×
324
}
325

326
impl From<geoengine_operators::engine::VectorResultDescriptor> for TypedResultDescriptor {
327
    fn from(value: geoengine_operators::engine::VectorResultDescriptor) -> Self {
×
328
        Self::Vector(value.into())
×
329
    }
×
330
}
331

332
impl From<VectorResultDescriptor> for TypedResultDescriptor {
333
    fn from(value: VectorResultDescriptor) -> Self {
×
334
        Self::Vector(value)
×
335
    }
×
336
}
337

338
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, ToSchema)]
2✔
339
pub struct MockDatasetDataSourceLoadingInfo {
340
    pub points: Vec<Coordinate2D>,
341
}
342

343
impl From<geoengine_operators::mock::MockDatasetDataSourceLoadingInfo>
344
    for MockDatasetDataSourceLoadingInfo
345
{
346
    fn from(value: geoengine_operators::mock::MockDatasetDataSourceLoadingInfo) -> Self {
×
347
        Self {
×
348
            points: value.points.into_iter().map(Into::into).collect(),
×
349
        }
×
350
    }
×
351
}
352

353
impl From<MockDatasetDataSourceLoadingInfo>
354
    for geoengine_operators::mock::MockDatasetDataSourceLoadingInfo
355
{
356
    fn from(value: MockDatasetDataSourceLoadingInfo) -> Self {
×
357
        Self {
×
358
            points: value.points.into_iter().map(Into::into).collect(),
×
359
        }
×
360
    }
×
361
}
362

363
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, ToSchema)]
30✔
364
#[aliases(
365
    MockMetaData = StaticMetaData<MockDatasetDataSourceLoadingInfo, VectorResultDescriptor, VectorQueryRectangle>,
366
    OgrMetaData = StaticMetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>
367
)]
368
#[serde(rename_all = "camelCase")]
369
pub struct StaticMetaData<L, R, Q> {
370
    pub loading_info: L,
371
    pub result_descriptor: R,
372
    #[serde(skip)]
373
    pub phantom: PhantomData<Q>,
374
}
375

376
impl
377
    From<
378
        geoengine_operators::engine::StaticMetaData<
379
            geoengine_operators::mock::MockDatasetDataSourceLoadingInfo,
380
            geoengine_operators::engine::VectorResultDescriptor,
381
            geoengine_datatypes::primitives::QueryRectangle<
382
                geoengine_datatypes::primitives::BoundingBox2D,
383
                ColumnSelection,
384
            >,
385
        >,
386
    >
387
    for StaticMetaData<
388
        MockDatasetDataSourceLoadingInfo,
389
        VectorResultDescriptor,
390
        QueryRectangle<BoundingBox2D>,
391
    >
392
{
393
    fn from(
×
394
        value: geoengine_operators::engine::StaticMetaData<
×
395
            geoengine_operators::mock::MockDatasetDataSourceLoadingInfo,
×
396
            geoengine_operators::engine::VectorResultDescriptor,
×
397
            geoengine_datatypes::primitives::QueryRectangle<
×
398
                geoengine_datatypes::primitives::BoundingBox2D,
×
NEW
399
                ColumnSelection,
×
400
            >,
×
401
        >,
×
402
    ) -> Self {
×
403
        Self {
×
404
            loading_info: value.loading_info.into(),
×
405
            result_descriptor: value.result_descriptor.into(),
×
406
            phantom: Default::default(),
×
407
        }
×
408
    }
×
409
}
410

411
impl
412
    From<
413
        geoengine_operators::engine::StaticMetaData<
414
            geoengine_operators::source::OgrSourceDataset,
415
            geoengine_operators::engine::VectorResultDescriptor,
416
            geoengine_datatypes::primitives::QueryRectangle<
417
                geoengine_datatypes::primitives::BoundingBox2D,
418
                ColumnSelection,
419
            >,
420
        >,
421
    > for StaticMetaData<OgrSourceDataset, VectorResultDescriptor, QueryRectangle<BoundingBox2D>>
422
{
423
    fn from(
1✔
424
        value: geoengine_operators::engine::StaticMetaData<
1✔
425
            geoengine_operators::source::OgrSourceDataset,
1✔
426
            geoengine_operators::engine::VectorResultDescriptor,
1✔
427
            geoengine_datatypes::primitives::QueryRectangle<
1✔
428
                geoengine_datatypes::primitives::BoundingBox2D,
1✔
429
                ColumnSelection,
1✔
430
            >,
1✔
431
        >,
1✔
432
    ) -> Self {
1✔
433
        Self {
1✔
434
            loading_info: value.loading_info.into(),
1✔
435
            result_descriptor: value.result_descriptor.into(),
1✔
436
            phantom: Default::default(),
1✔
437
        }
1✔
438
    }
1✔
439
}
440

441
#[async_trait]
442
impl<L, R, Q> MetaData<L, R, Q> for StaticMetaData<L, R, Q>
443
where
444
    L: Debug + Clone + Send + Sync + 'static,
445
    R: Debug + Send + Sync + 'static + ResultDescriptor,
446
    Q: Debug + Clone + Send + Sync + 'static,
447
{
448
    async fn loading_info(&self, _query: Q) -> geoengine_operators::util::Result<L> {
×
449
        Ok(self.loading_info.clone())
×
450
    }
×
451

452
    async fn result_descriptor(&self) -> geoengine_operators::util::Result<R> {
×
453
        Ok(self.result_descriptor.clone())
×
454
    }
×
455

456
    fn box_clone(&self) -> Box<dyn MetaData<L, R, Q>> {
×
457
        Box::new(self.clone())
×
458
    }
×
459
}
460

461
impl From<MockMetaData>
462
    for geoengine_operators::engine::StaticMetaData<
463
        geoengine_operators::mock::MockDatasetDataSourceLoadingInfo,
464
        geoengine_operators::engine::VectorResultDescriptor,
465
        geoengine_datatypes::primitives::QueryRectangle<
466
            geoengine_datatypes::primitives::BoundingBox2D,
467
            ColumnSelection,
468
        >,
469
    >
470
{
471
    fn from(value: MockMetaData) -> Self {
×
472
        Self {
×
473
            loading_info: value.loading_info.into(),
×
474
            result_descriptor: value.result_descriptor.into(),
×
475
            phantom: Default::default(),
×
476
        }
×
477
    }
×
478
}
479

480
impl From<OgrMetaData>
481
    for geoengine_operators::engine::StaticMetaData<
482
        geoengine_operators::source::OgrSourceDataset,
483
        geoengine_operators::engine::VectorResultDescriptor,
484
        geoengine_datatypes::primitives::QueryRectangle<
485
            geoengine_datatypes::primitives::BoundingBox2D,
486
            ColumnSelection,
487
        >,
488
    >
489
{
490
    fn from(value: OgrMetaData) -> Self {
4✔
491
        Self {
4✔
492
            loading_info: value.loading_info.into(),
4✔
493
            result_descriptor: value.result_descriptor.into(),
4✔
494
            phantom: Default::default(),
4✔
495
        }
4✔
496
    }
4✔
497
}
498

499
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, ToSchema)]
2✔
500
#[serde(rename_all = "camelCase")]
501
pub struct GdalMetaDataStatic {
502
    pub time: Option<TimeInterval>,
503
    pub params: GdalDatasetParameters,
504
    pub result_descriptor: RasterResultDescriptor,
505
    #[serde(default)]
506
    pub cache_ttl: CacheTtlSeconds,
507
}
508

509
impl From<geoengine_operators::source::GdalMetaDataStatic> for GdalMetaDataStatic {
510
    fn from(value: geoengine_operators::source::GdalMetaDataStatic) -> Self {
×
511
        Self {
×
512
            time: value.time.map(Into::into),
×
513
            params: value.params.into(),
×
514
            result_descriptor: value.result_descriptor.into(),
×
515
            cache_ttl: value.cache_ttl.into(),
×
516
        }
×
517
    }
×
518
}
519

520
impl From<GdalMetaDataStatic> for geoengine_operators::source::GdalMetaDataStatic {
521
    fn from(value: GdalMetaDataStatic) -> Self {
×
522
        Self {
×
523
            time: value.time.map(Into::into),
×
524
            params: value.params.into(),
×
525
            result_descriptor: value.result_descriptor.into(),
×
526
            cache_ttl: value.cache_ttl.into(),
×
527
        }
×
528
    }
×
529
}
530

531
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, ToSchema)]
90✔
532
#[serde(rename_all = "camelCase")]
533
pub struct OgrSourceDataset {
534
    #[schema(value_type = String)]
535
    pub file_name: PathBuf,
536
    pub layer_name: String,
537
    pub data_type: Option<VectorDataType>,
538
    #[serde(default)]
539
    pub time: OgrSourceDatasetTimeType,
540
    pub default_geometry: Option<TypedGeometry>,
541
    pub columns: Option<OgrSourceColumnSpec>,
542
    #[serde(default)]
543
    pub force_ogr_time_filter: bool,
544
    #[serde(default)]
545
    pub force_ogr_spatial_filter: bool,
546
    pub on_error: OgrSourceErrorSpec,
547
    pub sql_query: Option<String>,
548
    pub attribute_query: Option<String>,
549
    #[serde(default)]
550
    pub cache_ttl: CacheTtlSeconds,
551
}
552

553
impl From<geoengine_operators::source::OgrSourceDataset> for OgrSourceDataset {
554
    fn from(value: geoengine_operators::source::OgrSourceDataset) -> Self {
1✔
555
        Self {
1✔
556
            file_name: value.file_name,
1✔
557
            layer_name: value.layer_name,
1✔
558
            data_type: value.data_type.map(Into::into),
1✔
559
            time: value.time.into(),
1✔
560
            default_geometry: value.default_geometry.map(Into::into),
1✔
561
            columns: value.columns.map(Into::into),
1✔
562
            force_ogr_time_filter: value.force_ogr_time_filter,
1✔
563
            force_ogr_spatial_filter: value.force_ogr_spatial_filter,
1✔
564
            on_error: value.on_error.into(),
1✔
565
            sql_query: value.sql_query,
1✔
566
            attribute_query: value.attribute_query,
1✔
567
            cache_ttl: value.cache_ttl.into(),
1✔
568
        }
1✔
569
    }
1✔
570
}
571

572
impl From<OgrSourceDataset> for geoengine_operators::source::OgrSourceDataset {
573
    fn from(value: OgrSourceDataset) -> Self {
4✔
574
        Self {
4✔
575
            file_name: value.file_name,
4✔
576
            layer_name: value.layer_name,
4✔
577
            data_type: value.data_type.map(Into::into),
4✔
578
            time: value.time.into(),
4✔
579
            default_geometry: value.default_geometry.map(Into::into),
4✔
580
            columns: value.columns.map(Into::into),
4✔
581
            force_ogr_time_filter: value.force_ogr_time_filter,
4✔
582
            force_ogr_spatial_filter: value.force_ogr_spatial_filter,
4✔
583
            on_error: value.on_error.into(),
4✔
584
            sql_query: value.sql_query,
4✔
585
            attribute_query: value.attribute_query,
4✔
586
            cache_ttl: value.cache_ttl.into(),
4✔
587
        }
4✔
588
    }
4✔
589
}
590

591
#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, ToSchema)]
2✔
592
#[serde(tag = "format")]
593
#[serde(rename_all = "camelCase")]
594
pub enum OgrSourceTimeFormat {
595
    #[serde(rename_all = "camelCase")]
596
    Custom {
597
        custom_format: DateTimeParseFormat,
598
    },
599
    #[serde(rename_all = "camelCase")]
600
    UnixTimeStamp {
601
        timestamp_type: UnixTimeStampType,
602
        #[serde(skip)]
603
        #[serde(default = "DateTimeParseFormat::unix")]
604
        fmt: DateTimeParseFormat,
605
    },
606
    Auto,
607
}
608

609
impl From<geoengine_operators::source::OgrSourceTimeFormat> for OgrSourceTimeFormat {
610
    fn from(value: geoengine_operators::source::OgrSourceTimeFormat) -> Self {
×
611
        match value {
×
612
            geoengine_operators::source::OgrSourceTimeFormat::Custom { custom_format } => {
×
613
                Self::Custom {
×
614
                    custom_format: custom_format.into(),
×
615
                }
×
616
            }
617
            geoengine_operators::source::OgrSourceTimeFormat::UnixTimeStamp {
618
                timestamp_type,
×
619
                fmt,
×
620
            } => Self::UnixTimeStamp {
×
621
                timestamp_type: timestamp_type.into(),
×
622
                fmt: fmt.into(),
×
623
            },
×
624
            geoengine_operators::source::OgrSourceTimeFormat::Auto => Self::Auto,
×
625
        }
626
    }
×
627
}
628

629
impl From<OgrSourceTimeFormat> for geoengine_operators::source::OgrSourceTimeFormat {
630
    fn from(value: OgrSourceTimeFormat) -> Self {
×
631
        match value {
×
632
            OgrSourceTimeFormat::Custom { custom_format } => Self::Custom {
×
633
                custom_format: custom_format.into(),
×
634
            },
×
635
            OgrSourceTimeFormat::UnixTimeStamp {
636
                timestamp_type,
×
637
                fmt,
×
638
            } => Self::UnixTimeStamp {
×
639
                timestamp_type: timestamp_type.into(),
×
640
                fmt: fmt.into(),
×
641
            },
×
642
            OgrSourceTimeFormat::Auto => Self::Auto,
×
643
        }
644
    }
×
645
}
646

647
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
2✔
648
#[serde(rename_all = "camelCase")]
649
pub enum UnixTimeStampType {
650
    EpochSeconds,
651
    EpochMilliseconds,
652
}
653

654
impl From<geoengine_operators::source::UnixTimeStampType> for UnixTimeStampType {
655
    fn from(value: geoengine_operators::source::UnixTimeStampType) -> Self {
×
656
        match value {
×
657
            geoengine_operators::source::UnixTimeStampType::EpochSeconds => Self::EpochSeconds,
×
658
            geoengine_operators::source::UnixTimeStampType::EpochMilliseconds => {
659
                Self::EpochMilliseconds
×
660
            }
661
        }
662
    }
×
663
}
664

665
impl From<UnixTimeStampType> for geoengine_operators::source::UnixTimeStampType {
666
    fn from(value: UnixTimeStampType) -> Self {
×
667
        match value {
×
668
            UnixTimeStampType::EpochSeconds => Self::EpochSeconds,
×
669
            UnixTimeStampType::EpochMilliseconds => Self::EpochMilliseconds,
×
670
        }
671
    }
×
672
}
673

674
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
12✔
675
#[serde(rename_all = "lowercase")]
676
pub enum OgrSourceErrorSpec {
677
    Ignore,
678
    Abort,
679
}
680

681
impl From<geoengine_operators::source::OgrSourceErrorSpec> for OgrSourceErrorSpec {
682
    fn from(value: geoengine_operators::source::OgrSourceErrorSpec) -> Self {
1✔
683
        match value {
1✔
684
            geoengine_operators::source::OgrSourceErrorSpec::Ignore => Self::Ignore,
1✔
685
            geoengine_operators::source::OgrSourceErrorSpec::Abort => Self::Abort,
×
686
        }
687
    }
1✔
688
}
689

690
impl From<OgrSourceErrorSpec> for geoengine_operators::source::OgrSourceErrorSpec {
691
    fn from(value: OgrSourceErrorSpec) -> Self {
4✔
692
        match value {
4✔
693
            OgrSourceErrorSpec::Ignore => Self::Ignore,
4✔
694
            OgrSourceErrorSpec::Abort => Self::Abort,
×
695
        }
696
    }
4✔
697
}
698

699
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, ToSchema)]
12✔
700
#[serde(rename_all = "camelCase", tag = "type")]
701
pub enum OgrSourceDatasetTimeType {
702
    None,
703
    #[serde(rename_all = "camelCase")]
704
    Start {
705
        start_field: String,
706
        start_format: OgrSourceTimeFormat,
707
        duration: OgrSourceDurationSpec,
708
    },
709
    #[serde(rename_all = "camelCase")]
710
    StartEnd {
711
        start_field: String,
712
        start_format: OgrSourceTimeFormat,
713
        end_field: String,
714
        end_format: OgrSourceTimeFormat,
715
    },
716
    #[serde(rename_all = "camelCase")]
717
    StartDuration {
718
        start_field: String,
719
        start_format: OgrSourceTimeFormat,
720
        duration_field: String,
721
    },
722
}
723

724
impl From<geoengine_operators::source::OgrSourceDatasetTimeType> for OgrSourceDatasetTimeType {
725
    fn from(value: geoengine_operators::source::OgrSourceDatasetTimeType) -> Self {
1✔
726
        match value {
1✔
727
            geoengine_operators::source::OgrSourceDatasetTimeType::None => Self::None,
1✔
728
            geoengine_operators::source::OgrSourceDatasetTimeType::Start {
729
                start_field,
×
730
                start_format,
×
731
                duration,
×
732
            } => Self::Start {
×
733
                start_field,
×
734
                start_format: start_format.into(),
×
735
                duration: duration.into(),
×
736
            },
×
737
            geoengine_operators::source::OgrSourceDatasetTimeType::StartEnd {
738
                start_field,
×
739
                start_format,
×
740
                end_field,
×
741
                end_format,
×
742
            } => Self::StartEnd {
×
743
                start_field,
×
744
                start_format: start_format.into(),
×
745
                end_field,
×
746
                end_format: end_format.into(),
×
747
            },
×
748
            geoengine_operators::source::OgrSourceDatasetTimeType::StartDuration {
749
                start_field,
×
750
                start_format,
×
751
                duration_field,
×
752
            } => Self::StartDuration {
×
753
                start_field,
×
754
                start_format: start_format.into(),
×
755
                duration_field,
×
756
            },
×
757
        }
758
    }
1✔
759
}
760

761
impl From<OgrSourceDatasetTimeType> for geoengine_operators::source::OgrSourceDatasetTimeType {
762
    fn from(value: OgrSourceDatasetTimeType) -> Self {
4✔
763
        match value {
4✔
764
            OgrSourceDatasetTimeType::None => Self::None,
4✔
765
            OgrSourceDatasetTimeType::Start {
766
                start_field,
×
767
                start_format,
×
768
                duration,
×
769
            } => Self::Start {
×
770
                start_field,
×
771
                start_format: start_format.into(),
×
772
                duration: duration.into(),
×
773
            },
×
774
            OgrSourceDatasetTimeType::StartEnd {
775
                start_field,
×
776
                start_format,
×
777
                end_field,
×
778
                end_format,
×
779
            } => Self::StartEnd {
×
780
                start_field,
×
781
                start_format: start_format.into(),
×
782
                end_field,
×
783
                end_format: end_format.into(),
×
784
            },
×
785
            OgrSourceDatasetTimeType::StartDuration {
786
                start_field,
×
787
                start_format,
×
788
                duration_field,
×
789
            } => Self::StartDuration {
×
790
                start_field,
×
791
                start_format: start_format.into(),
×
792
                duration_field,
×
793
            },
×
794
        }
795
    }
4✔
796
}
797

798
/// If no time is specified, expect to parse none
799
impl Default for OgrSourceDatasetTimeType {
800
    fn default() -> Self {
×
801
        Self::None
×
802
    }
×
803
}
804

805
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
2✔
806
#[serde(rename_all = "camelCase", tag = "type")]
807
pub enum OgrSourceDurationSpec {
808
    Infinite,
809
    Zero,
810
    Value(TimeStep),
811
}
812

813
impl From<geoengine_operators::source::OgrSourceDurationSpec> for OgrSourceDurationSpec {
814
    fn from(value: geoengine_operators::source::OgrSourceDurationSpec) -> Self {
×
815
        match value {
×
816
            geoengine_operators::source::OgrSourceDurationSpec::Infinite => Self::Infinite,
×
817
            geoengine_operators::source::OgrSourceDurationSpec::Zero => Self::Zero,
×
818
            geoengine_operators::source::OgrSourceDurationSpec::Value(v) => Self::Value(v.into()),
×
819
        }
820
    }
×
821
}
822

823
impl From<OgrSourceDurationSpec> for geoengine_operators::source::OgrSourceDurationSpec {
824
    fn from(value: OgrSourceDurationSpec) -> Self {
×
825
        match value {
×
826
            OgrSourceDurationSpec::Infinite => Self::Infinite,
×
827
            OgrSourceDurationSpec::Zero => Self::Zero,
×
828
            OgrSourceDurationSpec::Value(v) => Self::Value(v.into()),
×
829
        }
830
    }
×
831
}
832

833
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
2✔
834
pub enum TypedGeometry {
835
    Data(NoGeometry),
836
    MultiPoint(MultiPoint),
837
    MultiLineString(MultiLineString),
838
    MultiPolygon(MultiPolygon),
839
}
840

841
impl From<geoengine_datatypes::primitives::TypedGeometry> for TypedGeometry {
842
    fn from(value: geoengine_datatypes::primitives::TypedGeometry) -> Self {
×
843
        match value {
×
844
            geoengine_datatypes::primitives::TypedGeometry::Data(x) => Self::Data(x.into()),
×
845
            geoengine_datatypes::primitives::TypedGeometry::MultiPoint(x) => {
×
846
                Self::MultiPoint(x.into())
×
847
            }
848
            geoengine_datatypes::primitives::TypedGeometry::MultiLineString(x) => {
×
849
                Self::MultiLineString(x.into())
×
850
            }
851
            geoengine_datatypes::primitives::TypedGeometry::MultiPolygon(x) => {
×
852
                Self::MultiPolygon(x.into())
×
853
            }
854
        }
855
    }
×
856
}
857

858
impl From<TypedGeometry> for geoengine_datatypes::primitives::TypedGeometry {
859
    fn from(value: TypedGeometry) -> Self {
×
860
        match value {
×
861
            TypedGeometry::Data(x) => Self::Data(x.into()),
×
862
            TypedGeometry::MultiPoint(x) => Self::MultiPoint(x.into()),
×
863
            TypedGeometry::MultiLineString(x) => Self::MultiLineString(x.into()),
×
864
            TypedGeometry::MultiPolygon(x) => Self::MultiPolygon(x.into()),
×
865
        }
866
    }
×
867
}
868

869
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
86✔
870
#[serde(rename_all = "camelCase")]
871
pub struct OgrSourceColumnSpec {
872
    pub format_specifics: Option<FormatSpecifics>,
873
    pub x: String,
874
    pub y: Option<String>,
875
    #[serde(default)]
876
    pub int: Vec<String>,
877
    #[serde(default)]
878
    pub float: Vec<String>,
879
    #[serde(default)]
880
    pub text: Vec<String>,
881
    #[serde(default)]
882
    pub bool: Vec<String>,
883
    #[serde(default)]
884
    pub datetime: Vec<String>,
885
    pub rename: Option<HashMap<String, String>>,
886
}
887

888
impl From<geoengine_operators::source::OgrSourceColumnSpec> for OgrSourceColumnSpec {
889
    fn from(value: geoengine_operators::source::OgrSourceColumnSpec) -> Self {
1✔
890
        Self {
1✔
891
            format_specifics: value.format_specifics.map(Into::into),
1✔
892
            x: value.x,
1✔
893
            y: value.y,
1✔
894
            int: value.int,
1✔
895
            float: value.float,
1✔
896
            text: value.text,
1✔
897
            bool: value.bool,
1✔
898
            datetime: value.datetime,
1✔
899
            rename: value.rename,
1✔
900
        }
1✔
901
    }
1✔
902
}
903

904
impl From<OgrSourceColumnSpec> for geoengine_operators::source::OgrSourceColumnSpec {
905
    fn from(value: OgrSourceColumnSpec) -> Self {
4✔
906
        Self {
4✔
907
            format_specifics: value.format_specifics.map(Into::into),
4✔
908
            x: value.x,
4✔
909
            y: value.y,
4✔
910
            int: value.int,
4✔
911
            float: value.float,
4✔
912
            text: value.text,
4✔
913
            bool: value.bool,
4✔
914
            datetime: value.datetime,
4✔
915
            rename: value.rename,
4✔
916
        }
4✔
917
    }
4✔
918
}
919

920
#[derive(Serialize, Deserialize, Debug, Clone, ToSchema, PartialEq)]
52✔
921
#[serde(rename_all = "camelCase")]
922
pub struct GdalMetaDataRegular {
923
    pub result_descriptor: RasterResultDescriptor,
924
    pub params: GdalDatasetParameters,
925
    pub time_placeholders: HashMap<String, GdalSourceTimePlaceholder>,
926
    pub data_time: TimeInterval,
927
    pub step: TimeStep,
928
    #[serde(default)]
929
    pub cache_ttl: CacheTtlSeconds,
930
}
931

932
impl From<geoengine_operators::source::GdalMetaDataRegular> for GdalMetaDataRegular {
933
    fn from(value: geoengine_operators::source::GdalMetaDataRegular) -> Self {
4✔
934
        Self {
4✔
935
            result_descriptor: value.result_descriptor.into(),
4✔
936
            params: value.params.into(),
4✔
937
            time_placeholders: value
4✔
938
                .time_placeholders
4✔
939
                .into_iter()
4✔
940
                .map(|(k, v)| (k, v.into()))
4✔
941
                .collect(),
4✔
942
            data_time: value.data_time.into(),
4✔
943
            step: value.step.into(),
4✔
944
            cache_ttl: value.cache_ttl.into(),
4✔
945
        }
4✔
946
    }
4✔
947
}
948

949
impl From<GdalMetaDataRegular> for geoengine_operators::source::GdalMetaDataRegular {
950
    fn from(value: GdalMetaDataRegular) -> Self {
4✔
951
        Self {
4✔
952
            result_descriptor: value.result_descriptor.into(),
4✔
953
            params: value.params.into(),
4✔
954
            time_placeholders: value
4✔
955
                .time_placeholders
4✔
956
                .into_iter()
4✔
957
                .map(|(k, v)| (k, v.into()))
4✔
958
                .collect(),
4✔
959
            data_time: value.data_time.into(),
4✔
960
            step: value.step.into(),
4✔
961
            cache_ttl: value.cache_ttl.into(),
4✔
962
        }
4✔
963
    }
4✔
964
}
965

966
/// Parameters for loading data using Gdal
967
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, ToSchema)]
96✔
968
#[serde(rename_all = "camelCase")]
969
pub struct GdalDatasetParameters {
970
    #[schema(value_type = String)]
971
    pub file_path: PathBuf,
972
    pub rasterband_channel: usize,
973
    pub geo_transform: GdalDatasetGeoTransform, // TODO: discuss if we need this at all
974
    pub width: usize,
975
    pub height: usize,
976
    pub file_not_found_handling: FileNotFoundHandling,
977
    #[serde(with = "float_option_with_nan")]
978
    #[serde(default)]
979
    pub no_data_value: Option<f64>,
980
    pub properties_mapping: Option<Vec<GdalMetadataMapping>>,
981
    // Dataset open option as strings, e.g. `vec!["UserPwd=geoengine:pwd".to_owned(), "HttpAuth=BASIC".to_owned()]`
982
    pub gdal_open_options: Option<Vec<String>>,
983
    // Configs as key, value pairs that will be set as thread local config options, e.g.
984
    // `vec!["AWS_REGION".to_owned(), "eu-central-1".to_owned()]` and unset afterwards
985
    // TODO: validate the config options: only allow specific keys and specific values
986
    pub gdal_config_options: Option<Vec<GdalConfigOption>>,
987
    #[serde(default)]
988
    pub allow_alphaband_as_mask: bool,
989
}
990

991
impl From<geoengine_operators::source::GdalDatasetParameters> for GdalDatasetParameters {
992
    fn from(value: geoengine_operators::source::GdalDatasetParameters) -> Self {
4✔
993
        Self {
4✔
994
            file_path: value.file_path,
4✔
995
            rasterband_channel: value.rasterband_channel,
4✔
996
            geo_transform: value.geo_transform.into(),
4✔
997
            width: value.width,
4✔
998
            height: value.height,
4✔
999
            file_not_found_handling: value.file_not_found_handling.into(),
4✔
1000
            no_data_value: value.no_data_value,
4✔
1001
            properties_mapping: value
4✔
1002
                .properties_mapping
4✔
1003
                .map(|x| x.into_iter().map(Into::into).collect()),
4✔
1004
            gdal_open_options: value.gdal_open_options,
4✔
1005
            gdal_config_options: value
4✔
1006
                .gdal_config_options
4✔
1007
                .map(|x| x.into_iter().map(Into::into).collect()),
4✔
1008
            allow_alphaband_as_mask: value.allow_alphaband_as_mask,
4✔
1009
        }
4✔
1010
    }
4✔
1011
}
1012

1013
impl From<GdalDatasetParameters> for geoengine_operators::source::GdalDatasetParameters {
1014
    fn from(value: GdalDatasetParameters) -> Self {
4✔
1015
        Self {
4✔
1016
            file_path: value.file_path,
4✔
1017
            rasterband_channel: value.rasterband_channel,
4✔
1018
            geo_transform: value.geo_transform.into(),
4✔
1019
            width: value.width,
4✔
1020
            height: value.height,
4✔
1021
            file_not_found_handling: value.file_not_found_handling.into(),
4✔
1022
            no_data_value: value.no_data_value,
4✔
1023
            properties_mapping: value
4✔
1024
                .properties_mapping
4✔
1025
                .map(|x| x.into_iter().map(Into::into).collect()),
4✔
1026
            gdal_open_options: value.gdal_open_options,
4✔
1027
            gdal_config_options: value
4✔
1028
                .gdal_config_options
4✔
1029
                .map(|x| x.into_iter().map(Into::into).collect()),
4✔
1030
            allow_alphaband_as_mask: value.allow_alphaband_as_mask,
4✔
1031
            retry: None,
4✔
1032
        }
4✔
1033
    }
4✔
1034
}
1035

1036
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, ToSchema)]
20✔
1037
pub struct GdalSourceTimePlaceholder {
1038
    pub format: DateTimeParseFormat,
1039
    pub reference: TimeReference,
1040
}
1041

1042
impl From<geoengine_operators::source::GdalSourceTimePlaceholder> for GdalSourceTimePlaceholder {
1043
    fn from(value: geoengine_operators::source::GdalSourceTimePlaceholder) -> Self {
4✔
1044
        Self {
4✔
1045
            format: value.format.into(),
4✔
1046
            reference: value.reference.into(),
4✔
1047
        }
4✔
1048
    }
4✔
1049
}
1050

1051
impl From<GdalSourceTimePlaceholder> for geoengine_operators::source::GdalSourceTimePlaceholder {
1052
    fn from(value: GdalSourceTimePlaceholder) -> Self {
4✔
1053
        Self {
4✔
1054
            format: value.format.into(),
4✔
1055
            reference: value.reference.into(),
4✔
1056
        }
4✔
1057
    }
4✔
1058
}
1059

1060
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, ToSchema)]
28✔
1061
#[serde(rename_all = "camelCase")]
1062
pub struct GdalDatasetGeoTransform {
1063
    pub origin_coordinate: Coordinate2D,
1064
    pub x_pixel_size: f64,
1065
    pub y_pixel_size: f64,
1066
}
1067

1068
impl From<geoengine_operators::source::GdalDatasetGeoTransform> for GdalDatasetGeoTransform {
1069
    fn from(value: geoengine_operators::source::GdalDatasetGeoTransform) -> Self {
4✔
1070
        Self {
4✔
1071
            origin_coordinate: value.origin_coordinate.into(),
4✔
1072
            x_pixel_size: value.x_pixel_size,
4✔
1073
            y_pixel_size: value.y_pixel_size,
4✔
1074
        }
4✔
1075
    }
4✔
1076
}
1077

1078
impl From<GdalDatasetGeoTransform> for geoengine_operators::source::GdalDatasetGeoTransform {
1079
    fn from(value: GdalDatasetGeoTransform) -> Self {
4✔
1080
        Self {
4✔
1081
            origin_coordinate: value.origin_coordinate.into(),
4✔
1082
            x_pixel_size: value.x_pixel_size,
4✔
1083
            y_pixel_size: value.y_pixel_size,
4✔
1084
        }
4✔
1085
    }
4✔
1086
}
1087

1088
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, ToSchema)]
8✔
1089
pub enum FileNotFoundHandling {
1090
    NoData, // output tiles filled with nodata
1091
    Error,  // return error tile
1092
}
1093

1094
impl From<geoengine_operators::source::FileNotFoundHandling> for FileNotFoundHandling {
1095
    fn from(value: geoengine_operators::source::FileNotFoundHandling) -> Self {
4✔
1096
        match value {
4✔
1097
            geoengine_operators::source::FileNotFoundHandling::NoData => Self::NoData,
4✔
1098
            geoengine_operators::source::FileNotFoundHandling::Error => Self::Error,
×
1099
        }
1100
    }
4✔
1101
}
1102

1103
impl From<FileNotFoundHandling> for geoengine_operators::source::FileNotFoundHandling {
1104
    fn from(value: FileNotFoundHandling) -> Self {
4✔
1105
        match value {
4✔
1106
            FileNotFoundHandling::NoData => Self::NoData,
4✔
1107
            FileNotFoundHandling::Error => Self::Error,
×
1108
        }
1109
    }
4✔
1110
}
1111

1112
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ToSchema)]
2✔
1113
pub struct GdalMetadataMapping {
1114
    pub source_key: RasterPropertiesKey,
1115
    pub target_key: RasterPropertiesKey,
1116
    pub target_type: RasterPropertiesEntryType,
1117
}
1118

1119
impl From<geoengine_operators::source::GdalMetadataMapping> for GdalMetadataMapping {
1120
    fn from(value: geoengine_operators::source::GdalMetadataMapping) -> Self {
×
1121
        Self {
×
1122
            source_key: value.source_key.into(),
×
1123
            target_key: value.target_key.into(),
×
1124
            target_type: value.target_type.into(),
×
1125
        }
×
1126
    }
×
1127
}
1128

1129
impl From<GdalMetadataMapping> for geoengine_operators::source::GdalMetadataMapping {
1130
    fn from(value: GdalMetadataMapping) -> Self {
×
1131
        Self {
×
1132
            source_key: value.source_key.into(),
×
1133
            target_key: value.target_key.into(),
×
1134
            target_type: value.target_type.into(),
×
1135
        }
×
1136
    }
×
1137
}
1138

1139
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, ToSchema)]
8✔
1140
#[serde(rename_all = "camelCase")]
1141
pub enum TimeReference {
1142
    Start,
1143
    End,
1144
}
1145

1146
impl From<geoengine_operators::source::TimeReference> for TimeReference {
1147
    fn from(value: geoengine_operators::source::TimeReference) -> Self {
4✔
1148
        match value {
4✔
1149
            geoengine_operators::source::TimeReference::Start => Self::Start,
4✔
1150
            geoengine_operators::source::TimeReference::End => Self::End,
×
1151
        }
1152
    }
4✔
1153
}
1154

1155
impl From<TimeReference> for geoengine_operators::source::TimeReference {
1156
    fn from(value: TimeReference) -> Self {
4✔
1157
        match value {
4✔
1158
            TimeReference::Start => Self::Start,
4✔
1159
            TimeReference::End => Self::End,
×
1160
        }
1161
    }
4✔
1162
}
1163

1164
/// Meta data for 4D `NetCDF` CF datasets
1165
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, ToSchema)]
2✔
1166
#[serde(rename_all = "camelCase")]
1167
pub struct GdalMetadataNetCdfCf {
1168
    pub result_descriptor: RasterResultDescriptor,
1169
    pub params: GdalDatasetParameters,
1170
    pub start: TimeInstance,
1171
    /// We use the end to specify the last, non-inclusive valid time point.
1172
    /// Queries behind this point return no data.
1173
    /// TODO: Alternatively, we could think about using the number of possible time steps in the future.
1174
    pub end: TimeInstance,
1175
    pub step: TimeStep,
1176
    /// A band offset specifies the first band index to use for the first point in time.
1177
    /// All other time steps are added to this offset.
1178
    pub band_offset: usize,
1179
    #[serde(default)]
1180
    pub cache_ttl: CacheTtlSeconds,
1181
}
1182

1183
impl From<geoengine_operators::source::GdalMetadataNetCdfCf> for GdalMetadataNetCdfCf {
1184
    fn from(value: geoengine_operators::source::GdalMetadataNetCdfCf) -> Self {
×
1185
        Self {
×
1186
            result_descriptor: value.result_descriptor.into(),
×
1187
            params: value.params.into(),
×
1188
            start: value.start.into(),
×
1189
            end: value.end.into(),
×
1190
            step: value.step.into(),
×
1191
            band_offset: value.band_offset,
×
1192
            cache_ttl: value.cache_ttl.into(),
×
1193
        }
×
1194
    }
×
1195
}
1196

1197
impl From<GdalMetadataNetCdfCf> for geoengine_operators::source::GdalMetadataNetCdfCf {
1198
    fn from(value: GdalMetadataNetCdfCf) -> Self {
×
1199
        Self {
×
1200
            result_descriptor: value.result_descriptor.into(),
×
1201
            params: value.params.into(),
×
1202
            start: value.start.into(),
×
1203
            end: value.end.into(),
×
1204
            step: value.step.into(),
×
1205
            band_offset: value.band_offset,
×
1206
            cache_ttl: value.cache_ttl.into(),
×
1207
        }
×
1208
    }
×
1209
}
1210

1211
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, ToSchema)]
2✔
1212
#[serde(rename_all = "camelCase")]
1213
pub struct GdalMetaDataList {
1214
    pub result_descriptor: RasterResultDescriptor,
1215
    pub params: Vec<GdalLoadingInfoTemporalSlice>,
1216
}
1217

1218
impl From<geoengine_operators::source::GdalMetaDataList> for GdalMetaDataList {
1219
    fn from(value: geoengine_operators::source::GdalMetaDataList) -> Self {
×
1220
        Self {
×
1221
            result_descriptor: value.result_descriptor.into(),
×
1222
            params: value.params.into_iter().map(Into::into).collect(),
×
1223
        }
×
1224
    }
×
1225
}
1226

1227
impl From<GdalMetaDataList> for geoengine_operators::source::GdalMetaDataList {
1228
    fn from(value: GdalMetaDataList) -> Self {
×
1229
        Self {
×
1230
            result_descriptor: value.result_descriptor.into(),
×
1231
            params: value.params.into_iter().map(Into::into).collect(),
×
1232
        }
×
1233
    }
×
1234
}
1235

1236
/// one temporal slice of the dataset that requires reading from exactly one Gdal dataset
1237
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, ToSchema)]
2✔
1238
#[serde(rename_all = "camelCase")]
1239
pub struct GdalLoadingInfoTemporalSlice {
1240
    pub time: TimeInterval,
1241
    pub params: Option<GdalDatasetParameters>,
1242
    #[serde(default)]
1243
    pub cache_ttl: CacheTtlSeconds,
1244
}
1245

1246
impl From<geoengine_operators::source::GdalLoadingInfoTemporalSlice>
1247
    for GdalLoadingInfoTemporalSlice
1248
{
1249
    fn from(value: geoengine_operators::source::GdalLoadingInfoTemporalSlice) -> Self {
×
1250
        Self {
×
1251
            time: value.time.into(),
×
1252
            params: value.params.map(Into::into),
×
1253
            cache_ttl: value.cache_ttl.into(),
×
1254
        }
×
1255
    }
×
1256
}
1257

1258
impl From<GdalLoadingInfoTemporalSlice>
1259
    for geoengine_operators::source::GdalLoadingInfoTemporalSlice
1260
{
1261
    fn from(value: GdalLoadingInfoTemporalSlice) -> Self {
×
1262
        Self {
×
1263
            time: value.time.into(),
×
1264
            params: value.params.map(Into::into),
×
1265
            cache_ttl: value.cache_ttl.into(),
×
1266
        }
×
1267
    }
×
1268
}
1269

1270
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
2✔
1271
#[serde(rename_all = "lowercase")]
1272
pub enum CsvHeader {
1273
    Yes,
1274
    No,
1275
    Auto,
1276
}
1277

1278
impl From<geoengine_operators::source::CsvHeader> for CsvHeader {
1279
    fn from(value: geoengine_operators::source::CsvHeader) -> Self {
×
1280
        match value {
×
1281
            geoengine_operators::source::CsvHeader::Yes => Self::Yes,
×
1282
            geoengine_operators::source::CsvHeader::No => Self::No,
×
1283
            geoengine_operators::source::CsvHeader::Auto => Self::Auto,
×
1284
        }
1285
    }
×
1286
}
1287

1288
impl From<CsvHeader> for geoengine_operators::source::CsvHeader {
1289
    fn from(value: CsvHeader) -> Self {
×
1290
        match value {
×
1291
            CsvHeader::Yes => Self::Yes,
×
1292
            CsvHeader::No => Self::No,
×
1293
            CsvHeader::Auto => Self::Auto,
×
1294
        }
1295
    }
×
1296
}
1297

1298
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
2✔
1299
#[serde(rename_all = "camelCase")]
1300
pub enum FormatSpecifics {
1301
    Csv { header: CsvHeader },
1302
}
1303

1304
impl From<geoengine_operators::source::FormatSpecifics> for FormatSpecifics {
1305
    fn from(value: geoengine_operators::source::FormatSpecifics) -> Self {
×
1306
        match value {
×
1307
            geoengine_operators::source::FormatSpecifics::Csv { header } => Self::Csv {
×
1308
                header: header.into(),
×
1309
            },
×
1310
        }
×
1311
    }
×
1312
}
1313

1314
impl From<FormatSpecifics> for geoengine_operators::source::FormatSpecifics {
1315
    fn from(value: FormatSpecifics) -> Self {
×
1316
        match value {
×
1317
            FormatSpecifics::Csv { header } => Self::Csv {
×
1318
                header: header.into(),
×
1319
            },
×
1320
        }
×
1321
    }
×
1322
}
1323

1324
#[cfg(test)]
1325
mod tests {
1326
    use serde_json::json;
1327

1328
    use super::*;
1329

1330
    #[test]
1✔
1331
    fn it_checks_duplicates_while_deserializing_band_descriptors() {
1✔
1332
        assert_eq!(
1✔
1333
            serde_json::from_value::<RasterBandDescriptors>(json!([{
1✔
1334
                "name": "foo",
1✔
1335
                "measurement": {
1✔
1336
                    "type": "unitless"
1✔
1337
                }
1✔
1338
            },{
1✔
1339
                "name": "bar",
1✔
1340
                "measurement": {
1✔
1341
                    "type": "unitless"
1✔
1342
                }
1✔
1343
            }]))
1✔
1344
            .unwrap(),
1✔
1345
            RasterBandDescriptors::new(vec![
1✔
1346
                RasterBandDescriptor {
1✔
1347
                    name: "foo".into(),
1✔
1348
                    measurement: Measurement::Unitless
1✔
1349
                },
1✔
1350
                RasterBandDescriptor {
1✔
1351
                    name: "bar".into(),
1✔
1352
                    measurement: Measurement::Unitless
1✔
1353
                },
1✔
1354
            ])
1✔
1355
            .unwrap()
1✔
1356
        );
1✔
1357

1358
        assert!(serde_json::from_value::<RasterBandDescriptors>(json!([{
1✔
1359
            "name": "foo",
1✔
1360
            "measurement": {
1✔
1361
                "type": "unitless"
1✔
1362
            }
1✔
1363
        },{
1✔
1364
            "name": "foo",
1✔
1365
            "measurement": {
1✔
1366
                "type": "unitless"
1✔
1367
            }
1✔
1368
        }]))
1✔
1369
        .is_err());
1✔
1370
    }
1✔
1371
}
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