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

geo-engine / geoengine / 13989818373

21 Mar 2025 10:34AM UTC coverage: 90.016% (-0.07%) from 90.081%
13989818373

push

github

web-flow
Merge pull request #1013 from geo-engine/update-utoipa

Update-utoipa

1088 of 1303 new or added lines in 47 files covered. (83.5%)

43 existing lines in 10 files now uncovered.

126708 of 140762 relevant lines covered (90.02%)

57181.28 hits per line

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

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

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

35
#[derive(Debug, Clone, PartialEq, Serialize, ToSchema)]
875✔
36
pub struct RasterBandDescriptors(Vec<RasterBandDescriptor>);
37

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

52
        Ok(Self(bands))
6✔
53
    }
7✔
54
}
55

56
impl<'de> Deserialize<'de> for RasterBandDescriptors {
57
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
6✔
58
    where
6✔
59
        D: Deserializer<'de>,
6✔
60
    {
6✔
61
        let vec = Vec::deserialize(deserializer)?;
6✔
62
        RasterBandDescriptors::new(vec).map_err(serde::de::Error::custom)
6✔
63
    }
6✔
64
}
65

66
impl From<geoengine_operators::engine::RasterBandDescriptors> for RasterBandDescriptors {
67
    fn from(value: geoengine_operators::engine::RasterBandDescriptors) -> Self {
4✔
68
        Self(value.into_vec().into_iter().map(Into::into).collect())
4✔
69
    }
4✔
70
}
71

72
impl From<RasterBandDescriptors> for geoengine_operators::engine::RasterBandDescriptors {
73
    fn from(value: RasterBandDescriptors) -> Self {
4✔
74
        geoengine_operators::engine::RasterBandDescriptors::new(
4✔
75
            value.0.into_iter().map(Into::into).collect(),
4✔
76
        )
4✔
77
        .expect("RasterBandDescriptors should be valid, because the API descriptors are valid")
4✔
78
    }
4✔
79
}
80

81
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
895✔
82
pub struct RasterBandDescriptor {
83
    pub name: String,
84
    pub measurement: Measurement,
85
}
86

87
impl From<geoengine_operators::engine::RasterBandDescriptor> for RasterBandDescriptor {
88
    fn from(value: geoengine_operators::engine::RasterBandDescriptor) -> Self {
4✔
89
        Self {
4✔
90
            name: value.name,
4✔
91
            measurement: value.measurement.into(),
4✔
92
        }
4✔
93
    }
4✔
94
}
95

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

105
impl From<geoengine_operators::engine::RasterResultDescriptor> for RasterResultDescriptor {
106
    fn from(value: geoengine_operators::engine::RasterResultDescriptor) -> Self {
4✔
107
        Self {
4✔
108
            data_type: value.data_type.into(),
4✔
109
            spatial_reference: value.spatial_reference.into(),
4✔
110
            time: value.time.map(Into::into),
4✔
111
            bbox: value.bbox.map(Into::into),
4✔
112
            resolution: value.resolution.map(Into::into),
4✔
113
            bands: value.bands.into(),
4✔
114
        }
4✔
115
    }
4✔
116
}
117

118
impl From<RasterResultDescriptor> for geoengine_operators::engine::RasterResultDescriptor {
119
    fn from(value: RasterResultDescriptor) -> Self {
4✔
120
        Self {
4✔
121
            data_type: value.data_type.into(),
4✔
122
            spatial_reference: value.spatial_reference.into(),
4✔
123
            time: value.time.map(Into::into),
4✔
124
            bbox: value.bbox.map(Into::into),
4✔
125
            resolution: value.resolution.map(Into::into),
4✔
126
            bands: value.bands.into(),
4✔
127
        }
4✔
128
    }
4✔
129
}
130

131
/// An enum to differentiate between `Operator` variants
132
#[derive(Clone, Debug, Serialize, Deserialize)]
133
#[serde(tag = "type", content = "operator")]
134
pub enum TypedOperator {
135
    Vector(Box<dyn geoengine_operators::engine::VectorOperator>),
136
    Raster(Box<dyn geoengine_operators::engine::RasterOperator>),
137
    Plot(Box<dyn geoengine_operators::engine::PlotOperator>),
138
}
139

140
impl PartialSchema for TypedOperator {
141
    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::Schema> {
50✔
142
        use utoipa::openapi::schema::{Object, ObjectBuilder, SchemaType, Type};
143
        ObjectBuilder::new()
50✔
144
            .property(
50✔
145
                "type",
50✔
146
                ObjectBuilder::new()
50✔
147
                    .schema_type(SchemaType::Type(Type::String))
50✔
148
                    .enum_values(Some(vec!["Vector", "Raster", "Plot"]))
50✔
149
            )
50✔
150
            .required("type")
50✔
151
            .property(
50✔
152
                "operator",
50✔
153
                ObjectBuilder::new()
50✔
154
                    .property(
50✔
155
                        "type",
50✔
156
                        Object::with_type(SchemaType::Type(Type::String))
50✔
157
                    )
50✔
158
                    .required("type")
50✔
159
                    .property(
50✔
160
                        "params",
50✔
161
                        Object::with_type(SchemaType::Type(Type::Object))
50✔
162
                    )
50✔
163
                    .property(
50✔
164
                        "sources",
50✔
165
                        Object::with_type(SchemaType::Type(Type::Object))
50✔
166
                    )
50✔
167
            )
50✔
168
            .required("operator")
50✔
169
            .examples(vec![serde_json::json!(
50✔
170
                {"type": "MockPointSource", "params": {"points": [{"x": 0.0, "y": 0.1}, {"x": 1.0, "y": 1.1}]}
50✔
171
            })])
50✔
172
            .description(Some("An enum to differentiate between `Operator` variants"))
50✔
173
            .into()
50✔
174
    }
50✔
175
}
176

177
impl ToSchema for TypedOperator {}
178

179
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
495✔
180
#[serde(rename_all = "camelCase")]
181
pub struct VectorResultDescriptor {
182
    pub data_type: VectorDataType,
183
    #[schema(value_type = String)]
184
    pub spatial_reference: SpatialReferenceOption,
185
    pub columns: HashMap<String, VectorColumnInfo>,
186
    pub time: Option<TimeInterval>,
187
    pub bbox: Option<BoundingBox2D>,
188
}
189

190
impl From<geoengine_operators::engine::VectorResultDescriptor> for VectorResultDescriptor {
191
    fn from(value: geoengine_operators::engine::VectorResultDescriptor) -> Self {
4✔
192
        Self {
4✔
193
            data_type: value.data_type.into(),
4✔
194
            spatial_reference: value.spatial_reference.into(),
4✔
195
            columns: value
4✔
196
                .columns
4✔
197
                .into_iter()
4✔
198
                .map(|(key, value)| (key, value.into()))
4✔
199
                .collect(),
4✔
200
            time: value.time.map(Into::into),
4✔
201
            bbox: value.bbox.map(Into::into),
4✔
202
        }
4✔
203
    }
4✔
204
}
205

206
impl From<VectorResultDescriptor> for geoengine_operators::engine::VectorResultDescriptor {
207
    fn from(value: VectorResultDescriptor) -> Self {
3✔
208
        Self {
3✔
209
            data_type: value.data_type.into(),
3✔
210
            spatial_reference: value.spatial_reference.into(),
3✔
211
            columns: value
3✔
212
                .columns
3✔
213
                .into_iter()
3✔
214
                .map(|(key, value)| (key, value.into()))
10✔
215
                .collect(),
3✔
216
            time: value.time.map(Into::into),
3✔
217
            bbox: value.bbox.map(Into::into),
3✔
218
        }
3✔
219
    }
3✔
220
}
221

222
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ToSchema)]
515✔
223
#[serde(rename_all = "camelCase")]
224
pub struct VectorColumnInfo {
225
    pub data_type: FeatureDataType,
226
    pub measurement: Measurement,
227
}
228

229
impl From<geoengine_operators::engine::VectorColumnInfo> for VectorColumnInfo {
230
    fn from(value: geoengine_operators::engine::VectorColumnInfo) -> Self {
2✔
231
        Self {
2✔
232
            data_type: value.data_type.into(),
2✔
233
            measurement: value.measurement.into(),
2✔
234
        }
2✔
235
    }
2✔
236
}
237

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

247
/// A `ResultDescriptor` for plot queries
248
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
135✔
249
#[serde(rename_all = "camelCase")]
250
pub struct PlotResultDescriptor {
251
    #[schema(value_type = String)]
252
    pub spatial_reference: SpatialReferenceOption,
253
    pub time: Option<TimeInterval>,
254
    pub bbox: Option<BoundingBox2D>,
255
}
256

257
impl From<geoengine_operators::engine::PlotResultDescriptor> for PlotResultDescriptor {
258
    fn from(value: geoengine_operators::engine::PlotResultDescriptor) -> Self {
×
259
        Self {
×
260
            spatial_reference: value.spatial_reference.into(),
×
261
            time: value.time.map(Into::into),
×
262
            bbox: value.bbox.map(Into::into),
×
263
        }
×
264
    }
×
265
}
266

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

277
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
115✔
278
#[serde(rename_all = "camelCase", untagged)]
279
#[schema(discriminator = "type")]
280
pub enum TypedResultDescriptor {
281
    Plot(TypedPlotResultDescriptor),
282
    Raster(TypedRasterResultDescriptor),
283
    Vector(TypedVectorResultDescriptor),
284
}
285

286
#[type_tag(value = "plot")]
60✔
287
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
120✔
288
#[serde(rename_all = "camelCase")]
289
pub struct TypedPlotResultDescriptor {
290
    #[serde(flatten)]
291
    pub result_descriptor: PlotResultDescriptor,
292
}
293

294
#[type_tag(value = "raster")]
60✔
295
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
120✔
296
#[serde(rename_all = "camelCase")]
297
pub struct TypedRasterResultDescriptor {
298
    #[serde(flatten)]
299
    pub result_descriptor: RasterResultDescriptor,
300
}
301

302
#[type_tag(value = "vector")]
60✔
303
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
120✔
304
#[serde(rename_all = "camelCase")]
305
pub struct TypedVectorResultDescriptor {
306
    #[serde(flatten)]
307
    pub result_descriptor: VectorResultDescriptor,
308
}
309

310
impl From<geoengine_operators::engine::TypedResultDescriptor> for TypedResultDescriptor {
311
    fn from(value: geoengine_operators::engine::TypedResultDescriptor) -> Self {
×
312
        match value {
×
NEW
313
            geoengine_operators::engine::TypedResultDescriptor::Plot(p) => {
×
NEW
314
                Self::Plot(TypedPlotResultDescriptor {
×
NEW
315
                    r#type: Default::default(),
×
NEW
316
                    result_descriptor: p.into(),
×
NEW
317
                })
×
318
            }
NEW
319
            geoengine_operators::engine::TypedResultDescriptor::Raster(r) => {
×
NEW
320
                Self::Raster(TypedRasterResultDescriptor {
×
NEW
321
                    r#type: Default::default(),
×
NEW
322
                    result_descriptor: r.into(),
×
NEW
323
                })
×
324
            }
NEW
325
            geoengine_operators::engine::TypedResultDescriptor::Vector(v) => {
×
NEW
326
                Self::Vector(TypedVectorResultDescriptor {
×
NEW
327
                    r#type: Default::default(),
×
NEW
328
                    result_descriptor: v.into(),
×
NEW
329
                })
×
330
            }
331
        }
332
    }
×
333
}
334

335
impl From<geoengine_operators::engine::PlotResultDescriptor> for TypedResultDescriptor {
336
    fn from(value: geoengine_operators::engine::PlotResultDescriptor) -> Self {
×
NEW
337
        Self::Plot(TypedPlotResultDescriptor {
×
NEW
338
            r#type: Default::default(),
×
NEW
339
            result_descriptor: value.into(),
×
NEW
340
        })
×
UNCOV
341
    }
×
342
}
343

344
impl From<PlotResultDescriptor> for TypedResultDescriptor {
345
    fn from(value: PlotResultDescriptor) -> Self {
×
NEW
346
        Self::Plot(TypedPlotResultDescriptor {
×
NEW
347
            r#type: Default::default(),
×
NEW
348
            result_descriptor: value,
×
NEW
349
        })
×
UNCOV
350
    }
×
351
}
352

353
impl From<geoengine_operators::engine::RasterResultDescriptor> for TypedResultDescriptor {
354
    fn from(value: geoengine_operators::engine::RasterResultDescriptor) -> Self {
×
NEW
355
        Self::Raster(TypedRasterResultDescriptor {
×
NEW
356
            r#type: Default::default(),
×
NEW
357
            result_descriptor: value.into(),
×
NEW
358
        })
×
UNCOV
359
    }
×
360
}
361

362
impl From<RasterResultDescriptor> for TypedResultDescriptor {
363
    fn from(value: RasterResultDescriptor) -> Self {
×
NEW
364
        Self::Raster(TypedRasterResultDescriptor {
×
NEW
365
            r#type: Default::default(),
×
NEW
366
            result_descriptor: value,
×
NEW
367
        })
×
UNCOV
368
    }
×
369
}
370

371
impl From<geoengine_operators::engine::VectorResultDescriptor> for TypedResultDescriptor {
372
    fn from(value: geoengine_operators::engine::VectorResultDescriptor) -> Self {
×
NEW
373
        Self::Vector(TypedVectorResultDescriptor {
×
NEW
374
            r#type: Default::default(),
×
NEW
375
            result_descriptor: value.into(),
×
NEW
376
        })
×
UNCOV
377
    }
×
378
}
379

380
impl From<VectorResultDescriptor> for TypedResultDescriptor {
381
    fn from(value: VectorResultDescriptor) -> Self {
×
NEW
382
        Self::Vector(TypedVectorResultDescriptor {
×
NEW
383
            r#type: Default::default(),
×
NEW
384
            result_descriptor: value,
×
NEW
385
        })
×
UNCOV
386
    }
×
387
}
388

389
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, ToSchema)]
195✔
390
pub struct MockDatasetDataSourceLoadingInfo {
391
    pub points: Vec<Coordinate2D>,
392
}
393

394
impl From<geoengine_operators::mock::MockDatasetDataSourceLoadingInfo>
395
    for MockDatasetDataSourceLoadingInfo
396
{
397
    fn from(value: geoengine_operators::mock::MockDatasetDataSourceLoadingInfo) -> Self {
×
398
        Self {
×
399
            points: value.points.into_iter().map(Into::into).collect(),
×
400
        }
×
401
    }
×
402
}
403

404
impl From<MockDatasetDataSourceLoadingInfo>
405
    for geoengine_operators::mock::MockDatasetDataSourceLoadingInfo
406
{
407
    fn from(value: MockDatasetDataSourceLoadingInfo) -> Self {
×
408
        Self {
×
409
            points: value.points.into_iter().map(Into::into).collect(),
×
410
        }
×
411
    }
×
412
}
413

414
#[type_tag(value = "MockMetaData")]
90✔
415
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, ToSchema)]
175✔
416
#[serde(rename_all = "camelCase")]
417
pub struct MockMetaData {
418
    pub loading_info: MockDatasetDataSourceLoadingInfo,
419
    pub result_descriptor: VectorResultDescriptor,
420
}
421
#[type_tag(value = "OgrMetaData")]
90✔
422
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, ToSchema)]
175✔
423
#[serde(rename_all = "camelCase")]
424
pub struct OgrMetaData {
425
    pub loading_info: OgrSourceDataset,
426
    pub result_descriptor: VectorResultDescriptor,
427
}
428

429
impl
430
    From<
431
        geoengine_operators::engine::StaticMetaData<
432
            geoengine_operators::mock::MockDatasetDataSourceLoadingInfo,
433
            geoengine_operators::engine::VectorResultDescriptor,
434
            geoengine_datatypes::primitives::QueryRectangle<
435
                geoengine_datatypes::primitives::BoundingBox2D,
436
                ColumnSelection,
437
            >,
438
        >,
439
    > for MockMetaData
440
{
441
    fn from(
×
442
        value: geoengine_operators::engine::StaticMetaData<
×
443
            geoengine_operators::mock::MockDatasetDataSourceLoadingInfo,
×
444
            geoengine_operators::engine::VectorResultDescriptor,
×
445
            geoengine_datatypes::primitives::QueryRectangle<
×
446
                geoengine_datatypes::primitives::BoundingBox2D,
×
447
                ColumnSelection,
×
448
            >,
×
449
        >,
×
450
    ) -> Self {
×
451
        Self {
×
NEW
452
            r#type: Default::default(),
×
453
            loading_info: value.loading_info.into(),
×
454
            result_descriptor: value.result_descriptor.into(),
×
455
        }
×
456
    }
×
457
}
458

459
impl
460
    From<
461
        geoengine_operators::engine::StaticMetaData<
462
            geoengine_operators::source::OgrSourceDataset,
463
            geoengine_operators::engine::VectorResultDescriptor,
464
            geoengine_datatypes::primitives::QueryRectangle<
465
                geoengine_datatypes::primitives::BoundingBox2D,
466
                ColumnSelection,
467
            >,
468
        >,
469
    > for OgrMetaData
470
{
UNCOV
471
    fn from(
×
UNCOV
472
        value: geoengine_operators::engine::StaticMetaData<
×
UNCOV
473
            geoengine_operators::source::OgrSourceDataset,
×
UNCOV
474
            geoengine_operators::engine::VectorResultDescriptor,
×
UNCOV
475
            geoengine_datatypes::primitives::QueryRectangle<
×
UNCOV
476
                geoengine_datatypes::primitives::BoundingBox2D,
×
UNCOV
477
                ColumnSelection,
×
UNCOV
478
            >,
×
UNCOV
479
        >,
×
UNCOV
480
    ) -> Self {
×
UNCOV
481
        Self {
×
NEW
482
            r#type: Default::default(),
×
UNCOV
483
            loading_info: value.loading_info.into(),
×
UNCOV
484
            result_descriptor: value.result_descriptor.into(),
×
UNCOV
485
        }
×
UNCOV
486
    }
×
487
}
488

489
impl From<MockMetaData>
490
    for geoengine_operators::engine::StaticMetaData<
491
        geoengine_operators::mock::MockDatasetDataSourceLoadingInfo,
492
        geoengine_operators::engine::VectorResultDescriptor,
493
        geoengine_datatypes::primitives::QueryRectangle<
494
            geoengine_datatypes::primitives::BoundingBox2D,
495
            ColumnSelection,
496
        >,
497
    >
498
{
499
    fn from(value: MockMetaData) -> Self {
×
500
        Self {
×
501
            loading_info: value.loading_info.into(),
×
502
            result_descriptor: value.result_descriptor.into(),
×
503
            phantom: Default::default(),
×
504
        }
×
505
    }
×
506
}
507

508
impl From<OgrMetaData>
509
    for geoengine_operators::engine::StaticMetaData<
510
        geoengine_operators::source::OgrSourceDataset,
511
        geoengine_operators::engine::VectorResultDescriptor,
512
        geoengine_datatypes::primitives::QueryRectangle<
513
            geoengine_datatypes::primitives::BoundingBox2D,
514
            ColumnSelection,
515
        >,
516
    >
517
{
518
    fn from(value: OgrMetaData) -> Self {
3✔
519
        Self {
3✔
520
            loading_info: value.loading_info.into(),
3✔
521
            result_descriptor: value.result_descriptor.into(),
3✔
522
            phantom: Default::default(),
3✔
523
        }
3✔
524
    }
3✔
525
}
526

527
#[type_tag(value = "GdalStatic")]
90✔
528
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, ToSchema)]
175✔
529
#[serde(rename_all = "camelCase")]
530
pub struct GdalMetaDataStatic {
531
    pub time: Option<TimeInterval>,
532
    pub params: GdalDatasetParameters,
533
    pub result_descriptor: RasterResultDescriptor,
534
    #[serde(default)]
535
    pub cache_ttl: CacheTtlSeconds,
536
}
537

538
impl From<geoengine_operators::source::GdalMetaDataStatic> for GdalMetaDataStatic {
539
    fn from(value: geoengine_operators::source::GdalMetaDataStatic) -> Self {
×
540
        Self {
×
NEW
541
            r#type: Default::default(),
×
542
            time: value.time.map(Into::into),
×
543
            params: value.params.into(),
×
544
            result_descriptor: value.result_descriptor.into(),
×
545
            cache_ttl: value.cache_ttl.into(),
×
546
        }
×
547
    }
×
548
}
549

550
impl From<GdalMetaDataStatic> for geoengine_operators::source::GdalMetaDataStatic {
551
    fn from(value: GdalMetaDataStatic) -> Self {
×
552
        Self {
×
553
            time: value.time.map(Into::into),
×
554
            params: value.params.into(),
×
555
            result_descriptor: value.result_descriptor.into(),
×
556
            cache_ttl: value.cache_ttl.into(),
×
557
        }
×
558
    }
×
559
}
560

561
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, ToSchema)]
145✔
562
#[serde(rename_all = "camelCase")]
563
pub struct OgrSourceDataset {
564
    #[schema(value_type = String)]
565
    pub file_name: PathBuf,
566
    pub layer_name: String,
567
    pub data_type: Option<VectorDataType>,
568
    #[serde(default)]
569
    pub time: OgrSourceDatasetTimeType,
570
    pub default_geometry: Option<TypedGeometry>,
571
    pub columns: Option<OgrSourceColumnSpec>,
572
    #[serde(default)]
573
    pub force_ogr_time_filter: bool,
574
    #[serde(default)]
575
    pub force_ogr_spatial_filter: bool,
576
    pub on_error: OgrSourceErrorSpec,
577
    pub sql_query: Option<String>,
578
    pub attribute_query: Option<String>,
579
    #[serde(default)]
580
    pub cache_ttl: CacheTtlSeconds,
581
}
582

583
impl From<geoengine_operators::source::OgrSourceDataset> for OgrSourceDataset {
584
    fn from(value: geoengine_operators::source::OgrSourceDataset) -> Self {
4✔
585
        Self {
4✔
586
            file_name: value.file_name,
4✔
587
            layer_name: value.layer_name,
4✔
588
            data_type: value.data_type.map(Into::into),
4✔
589
            time: value.time.into(),
4✔
590
            default_geometry: value.default_geometry.map(Into::into),
4✔
591
            columns: value.columns.map(Into::into),
4✔
592
            force_ogr_time_filter: value.force_ogr_time_filter,
4✔
593
            force_ogr_spatial_filter: value.force_ogr_spatial_filter,
4✔
594
            on_error: value.on_error.into(),
4✔
595
            sql_query: value.sql_query,
4✔
596
            attribute_query: value.attribute_query,
4✔
597
            cache_ttl: value.cache_ttl.into(),
4✔
598
        }
4✔
599
    }
4✔
600
}
601

602
impl From<OgrSourceDataset> for geoengine_operators::source::OgrSourceDataset {
603
    fn from(value: OgrSourceDataset) -> Self {
3✔
604
        Self {
3✔
605
            file_name: value.file_name,
3✔
606
            layer_name: value.layer_name,
3✔
607
            data_type: value.data_type.map(Into::into),
3✔
608
            time: value.time.into(),
3✔
609
            default_geometry: value.default_geometry.map(Into::into),
3✔
610
            columns: value.columns.map(Into::into),
3✔
611
            force_ogr_time_filter: value.force_ogr_time_filter,
3✔
612
            force_ogr_spatial_filter: value.force_ogr_spatial_filter,
3✔
613
            on_error: value.on_error.into(),
3✔
614
            sql_query: value.sql_query,
3✔
615
            attribute_query: value.attribute_query,
3✔
616
            cache_ttl: value.cache_ttl.into(),
3✔
617
        }
3✔
618
    }
3✔
619
}
620

621
#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, ToSchema)]
895✔
622
#[serde(rename_all = "camelCase", untagged)]
623
#[schema(discriminator = "format")]
624
pub enum OgrSourceTimeFormat {
625
    Custom(OgrSourceTimeFormatCustom),
626
    UnixTimeStamp(OgrSourceTimeFormatUnixTimeStamp),
627
    Auto(OgrSourceTimeFormatAuto),
628
}
629

630
#[type_tag(tag = "format", value = "custom")]
450✔
631
#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, ToSchema)]
900✔
632
#[serde(rename_all = "camelCase")]
633
pub struct OgrSourceTimeFormatCustom {
634
    custom_format: DateTimeParseFormat,
635
}
636

637
#[type_tag(tag = "format", value = "unixTimeStamp")]
450✔
638
#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, ToSchema)]
900✔
639
#[serde(rename_all = "camelCase")]
640
pub struct OgrSourceTimeFormatUnixTimeStamp {
641
    timestamp_type: UnixTimeStampType,
642
    #[serde(skip)]
643
    #[serde(default = "DateTimeParseFormat::unix")]
644
    fmt: DateTimeParseFormat,
645
}
646

647
#[type_tag(tag = "format", value = "auto")]
450✔
648
#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, ToSchema, Default)]
900✔
649
#[serde(rename_all = "camelCase")]
650
pub struct OgrSourceTimeFormatAuto {}
651

652
impl From<geoengine_operators::source::OgrSourceTimeFormat> for OgrSourceTimeFormat {
653
    fn from(value: geoengine_operators::source::OgrSourceTimeFormat) -> Self {
×
654
        match value {
×
655
            geoengine_operators::source::OgrSourceTimeFormat::Custom { custom_format } => {
×
NEW
656
                Self::Custom(OgrSourceTimeFormatCustom {
×
NEW
657
                    format: Default::default(),
×
658
                    custom_format: custom_format.into(),
×
NEW
659
                })
×
660
            }
661
            geoengine_operators::source::OgrSourceTimeFormat::UnixTimeStamp {
662
                timestamp_type,
×
663
                fmt,
×
NEW
664
            } => Self::UnixTimeStamp(OgrSourceTimeFormatUnixTimeStamp {
×
NEW
665
                format: Default::default(),
×
666
                timestamp_type: timestamp_type.into(),
×
667
                fmt: fmt.into(),
×
NEW
668
            }),
×
669
            geoengine_operators::source::OgrSourceTimeFormat::Auto => {
NEW
670
                Self::Auto(Default::default())
×
671
            }
672
        }
673
    }
×
674
}
675

676
impl From<OgrSourceTimeFormat> for geoengine_operators::source::OgrSourceTimeFormat {
677
    fn from(value: OgrSourceTimeFormat) -> Self {
×
678
        match value {
×
NEW
679
            OgrSourceTimeFormat::Custom(OgrSourceTimeFormatCustom { custom_format, .. }) => {
×
NEW
680
                Self::Custom {
×
NEW
681
                    custom_format: custom_format.into(),
×
NEW
682
                }
×
683
            }
684
            OgrSourceTimeFormat::UnixTimeStamp(OgrSourceTimeFormatUnixTimeStamp {
685
                timestamp_type,
×
686
                fmt,
×
NEW
687
                ..
×
NEW
688
            }) => Self::UnixTimeStamp {
×
689
                timestamp_type: timestamp_type.into(),
×
690
                fmt: fmt.into(),
×
691
            },
×
NEW
692
            OgrSourceTimeFormat::Auto(_) => Self::Auto,
×
693
        }
694
    }
×
695
}
696

697
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
915✔
698
#[serde(rename_all = "camelCase")]
699
pub enum UnixTimeStampType {
700
    EpochSeconds,
701
    EpochMilliseconds,
702
}
703

704
impl From<geoengine_operators::source::UnixTimeStampType> for UnixTimeStampType {
705
    fn from(value: geoengine_operators::source::UnixTimeStampType) -> Self {
×
706
        match value {
×
707
            geoengine_operators::source::UnixTimeStampType::EpochSeconds => Self::EpochSeconds,
×
708
            geoengine_operators::source::UnixTimeStampType::EpochMilliseconds => {
709
                Self::EpochMilliseconds
×
710
            }
711
        }
712
    }
×
713
}
714

715
impl From<UnixTimeStampType> for geoengine_operators::source::UnixTimeStampType {
716
    fn from(value: UnixTimeStampType) -> Self {
×
717
        match value {
×
718
            UnixTimeStampType::EpochSeconds => Self::EpochSeconds,
×
719
            UnixTimeStampType::EpochMilliseconds => Self::EpochMilliseconds,
×
720
        }
721
    }
×
722
}
723

724
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
215✔
725
#[serde(rename_all = "lowercase")]
726
pub enum OgrSourceErrorSpec {
727
    Ignore,
728
    Abort,
729
}
730

731
impl From<geoengine_operators::source::OgrSourceErrorSpec> for OgrSourceErrorSpec {
732
    fn from(value: geoengine_operators::source::OgrSourceErrorSpec) -> Self {
4✔
733
        match value {
4✔
734
            geoengine_operators::source::OgrSourceErrorSpec::Ignore => Self::Ignore,
4✔
735
            geoengine_operators::source::OgrSourceErrorSpec::Abort => Self::Abort,
×
736
        }
737
    }
4✔
738
}
739

740
impl From<OgrSourceErrorSpec> for geoengine_operators::source::OgrSourceErrorSpec {
741
    fn from(value: OgrSourceErrorSpec) -> Self {
3✔
742
        match value {
3✔
743
            OgrSourceErrorSpec::Ignore => Self::Ignore,
3✔
744
            OgrSourceErrorSpec::Abort => Self::Abort,
×
745
        }
746
    }
3✔
747
}
748

749
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, ToSchema)]
215✔
750
#[serde(rename_all = "camelCase", untagged)]
751
#[schema(discriminator = "type")]
752
pub enum OgrSourceDatasetTimeType {
753
    None(OgrSourceDatasetTimeTypeNone),
754
    Start(OgrSourceDatasetTimeTypeStart),
755
    StartEnd(OgrSourceDatasetTimeTypeStartEnd),
756
    StartDuration(OgrSourceDatasetTimeTypeStartDuration),
757
}
758

759
#[type_tag(value = "none")]
110✔
760
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, ToSchema)]
220✔
761
#[serde(rename_all = "camelCase")]
762
pub struct OgrSourceDatasetTimeTypeNone {}
763

764
#[type_tag(value = "start")]
110✔
765
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, ToSchema)]
220✔
766
#[serde(rename_all = "camelCase")]
767
pub struct OgrSourceDatasetTimeTypeStart {
768
    pub start_field: String,
769
    pub start_format: OgrSourceTimeFormat,
770
    pub duration: OgrSourceDurationSpec,
771
}
772

773
#[type_tag(value = "start+end")]
110✔
774
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, ToSchema)]
220✔
775
#[serde(rename_all = "camelCase")]
776
pub struct OgrSourceDatasetTimeTypeStartEnd {
777
    pub start_field: String,
778
    pub start_format: OgrSourceTimeFormat,
779
    pub end_field: String,
780
    pub end_format: OgrSourceTimeFormat,
781
}
782

783
#[type_tag(value = "start+duration")]
110✔
784
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, ToSchema)]
220✔
785
#[serde(rename_all = "camelCase")]
786
pub struct OgrSourceDatasetTimeTypeStartDuration {
787
    pub start_field: String,
788
    pub start_format: OgrSourceTimeFormat,
789
    pub duration_field: String,
790
}
791

792
impl From<geoengine_operators::source::OgrSourceDatasetTimeType> for OgrSourceDatasetTimeType {
793
    fn from(value: geoengine_operators::source::OgrSourceDatasetTimeType) -> Self {
4✔
794
        match value {
4✔
795
            geoengine_operators::source::OgrSourceDatasetTimeType::None => {
796
                Self::None(OgrSourceDatasetTimeTypeNone {
4✔
797
                    r#type: Default::default(),
4✔
798
                })
4✔
799
            }
800
            geoengine_operators::source::OgrSourceDatasetTimeType::Start {
801
                start_field,
×
802
                start_format,
×
803
                duration,
×
NEW
804
            } => Self::Start(OgrSourceDatasetTimeTypeStart {
×
NEW
805
                r#type: Default::default(),
×
806
                start_field,
×
807
                start_format: start_format.into(),
×
808
                duration: duration.into(),
×
NEW
809
            }),
×
810
            geoengine_operators::source::OgrSourceDatasetTimeType::StartEnd {
811
                start_field,
×
812
                start_format,
×
813
                end_field,
×
814
                end_format,
×
NEW
815
            } => Self::StartEnd(OgrSourceDatasetTimeTypeStartEnd {
×
NEW
816
                r#type: Default::default(),
×
817
                start_field,
×
818
                start_format: start_format.into(),
×
819
                end_field,
×
820
                end_format: end_format.into(),
×
NEW
821
            }),
×
822
            geoengine_operators::source::OgrSourceDatasetTimeType::StartDuration {
823
                start_field,
×
824
                start_format,
×
825
                duration_field,
×
NEW
826
            } => Self::StartDuration(OgrSourceDatasetTimeTypeStartDuration {
×
NEW
827
                r#type: Default::default(),
×
828
                start_field,
×
829
                start_format: start_format.into(),
×
830
                duration_field,
×
NEW
831
            }),
×
832
        }
833
    }
4✔
834
}
835

836
impl From<OgrSourceDatasetTimeType> for geoengine_operators::source::OgrSourceDatasetTimeType {
837
    fn from(value: OgrSourceDatasetTimeType) -> Self {
3✔
838
        match value {
3✔
839
            OgrSourceDatasetTimeType::None(..) => Self::None,
3✔
840
            OgrSourceDatasetTimeType::Start(OgrSourceDatasetTimeTypeStart {
841
                start_field,
×
842
                start_format,
×
843
                duration,
×
NEW
844
                ..
×
NEW
845
            }) => Self::Start {
×
846
                start_field,
×
847
                start_format: start_format.into(),
×
848
                duration: duration.into(),
×
849
            },
×
850
            OgrSourceDatasetTimeType::StartEnd(OgrSourceDatasetTimeTypeStartEnd {
851
                start_field,
×
852
                start_format,
×
853
                end_field,
×
854
                end_format,
×
NEW
855
                ..
×
NEW
856
            }) => Self::StartEnd {
×
857
                start_field,
×
858
                start_format: start_format.into(),
×
859
                end_field,
×
860
                end_format: end_format.into(),
×
861
            },
×
862
            OgrSourceDatasetTimeType::StartDuration(OgrSourceDatasetTimeTypeStartDuration {
863
                start_field,
×
864
                start_format,
×
865
                duration_field,
×
NEW
866
                ..
×
NEW
867
            }) => Self::StartDuration {
×
868
                start_field,
×
869
                start_format: start_format.into(),
×
870
                duration_field,
×
871
            },
×
872
        }
873
    }
3✔
874
}
875

876
/// If no time is specified, expect to parse none
877
impl Default for OgrSourceDatasetTimeType {
878
    fn default() -> Self {
×
NEW
879
        Self::None(OgrSourceDatasetTimeTypeNone {
×
NEW
880
            r#type: Default::default(),
×
NEW
881
        })
×
UNCOV
882
    }
×
883
}
884

885
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
235✔
886
#[serde(rename_all = "camelCase", untagged)]
887
#[schema(discriminator = "type")]
888
pub enum OgrSourceDurationSpec {
889
    Infinite(OgrSourceDurationSpecInfinite),
890
    Zero(OgrSourceDurationSpecZero),
891
    Value(OgrSourceDurationSpecValue),
892
}
893

894
#[type_tag(value = "infinite")]
120✔
895
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema, Default)]
240✔
896
pub struct OgrSourceDurationSpecInfinite {}
897

898
#[type_tag(value = "zero")]
120✔
899
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema, Default)]
240✔
900
pub struct OgrSourceDurationSpecZero {}
901

902
#[type_tag(value = "value")]
120✔
903
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
240✔
904
pub struct OgrSourceDurationSpecValue {
905
    #[serde(flatten)]
906
    pub time_step: TimeStep,
907
}
908

909
impl From<geoengine_operators::source::OgrSourceDurationSpec> for OgrSourceDurationSpec {
910
    fn from(value: geoengine_operators::source::OgrSourceDurationSpec) -> Self {
×
911
        match value {
×
912
            geoengine_operators::source::OgrSourceDurationSpec::Infinite => {
NEW
913
                Self::Infinite(Default::default())
×
914
            }
915
            geoengine_operators::source::OgrSourceDurationSpec::Zero => {
NEW
916
                Self::Zero(Default::default())
×
917
            }
NEW
918
            geoengine_operators::source::OgrSourceDurationSpec::Value(v) => {
×
NEW
919
                Self::Value(OgrSourceDurationSpecValue {
×
NEW
920
                    r#type: Default::default(),
×
NEW
921
                    time_step: v.into(),
×
NEW
922
                })
×
923
            }
924
        }
925
    }
×
926
}
927

928
impl From<OgrSourceDurationSpec> for geoengine_operators::source::OgrSourceDurationSpec {
929
    fn from(value: OgrSourceDurationSpec) -> Self {
×
930
        match value {
×
NEW
931
            OgrSourceDurationSpec::Infinite(..) => Self::Infinite,
×
NEW
932
            OgrSourceDurationSpec::Zero(..) => Self::Zero,
×
NEW
933
            OgrSourceDurationSpec::Value(v) => {
×
NEW
934
                Self::Value(geoengine_datatypes::primitives::TimeStep {
×
NEW
935
                    ..v.time_step.into()
×
NEW
936
                })
×
937
            }
938
        }
939
    }
×
940
}
941

942
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
215✔
943
pub enum TypedGeometry {
944
    Data(NoGeometry),
945
    MultiPoint(MultiPoint),
946
    MultiLineString(MultiLineString),
947
    MultiPolygon(MultiPolygon),
948
}
949

950
impl From<geoengine_datatypes::primitives::TypedGeometry> for TypedGeometry {
951
    fn from(value: geoengine_datatypes::primitives::TypedGeometry) -> Self {
×
952
        match value {
×
953
            geoengine_datatypes::primitives::TypedGeometry::Data(x) => Self::Data(x.into()),
×
954
            geoengine_datatypes::primitives::TypedGeometry::MultiPoint(x) => {
×
955
                Self::MultiPoint(x.into())
×
956
            }
957
            geoengine_datatypes::primitives::TypedGeometry::MultiLineString(x) => {
×
958
                Self::MultiLineString(x.into())
×
959
            }
960
            geoengine_datatypes::primitives::TypedGeometry::MultiPolygon(x) => {
×
961
                Self::MultiPolygon(x.into())
×
962
            }
963
        }
964
    }
×
965
}
966

967
impl From<TypedGeometry> for geoengine_datatypes::primitives::TypedGeometry {
968
    fn from(value: TypedGeometry) -> Self {
×
969
        match value {
×
970
            TypedGeometry::Data(x) => Self::Data(x.into()),
×
971
            TypedGeometry::MultiPoint(x) => Self::MultiPoint(x.into()),
×
972
            TypedGeometry::MultiLineString(x) => Self::MultiLineString(x.into()),
×
973
            TypedGeometry::MultiPolygon(x) => Self::MultiPolygon(x.into()),
×
974
        }
975
    }
×
976
}
977

978
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
160✔
979
#[serde(rename_all = "camelCase")]
980
pub struct OgrSourceColumnSpec {
981
    pub format_specifics: Option<FormatSpecifics>,
982
    pub x: String,
983
    pub y: Option<String>,
984
    #[serde(default)]
985
    pub int: Vec<String>,
986
    #[serde(default)]
987
    pub float: Vec<String>,
988
    #[serde(default)]
989
    pub text: Vec<String>,
990
    #[serde(default)]
991
    pub bool: Vec<String>,
992
    #[serde(default)]
993
    pub datetime: Vec<String>,
994
    pub rename: Option<HashMap<String, String>>,
995
}
996

997
impl From<geoengine_operators::source::OgrSourceColumnSpec> for OgrSourceColumnSpec {
998
    fn from(value: geoengine_operators::source::OgrSourceColumnSpec) -> Self {
1✔
999
        Self {
1✔
1000
            format_specifics: value.format_specifics.map(Into::into),
1✔
1001
            x: value.x,
1✔
1002
            y: value.y,
1✔
1003
            int: value.int,
1✔
1004
            float: value.float,
1✔
1005
            text: value.text,
1✔
1006
            bool: value.bool,
1✔
1007
            datetime: value.datetime,
1✔
1008
            rename: value.rename,
1✔
1009
        }
1✔
1010
    }
1✔
1011
}
1012

1013
impl From<OgrSourceColumnSpec> for geoengine_operators::source::OgrSourceColumnSpec {
1014
    fn from(value: OgrSourceColumnSpec) -> Self {
2✔
1015
        Self {
2✔
1016
            format_specifics: value.format_specifics.map(Into::into),
2✔
1017
            x: value.x,
2✔
1018
            y: value.y,
2✔
1019
            int: value.int,
2✔
1020
            float: value.float,
2✔
1021
            text: value.text,
2✔
1022
            bool: value.bool,
2✔
1023
            datetime: value.datetime,
2✔
1024
            rename: value.rename,
2✔
1025
        }
2✔
1026
    }
2✔
1027
}
1028

1029
#[type_tag(value = "GdalMetaDataRegular")]
90✔
1030
#[derive(Serialize, Deserialize, Debug, Clone, ToSchema, PartialEq)]
175✔
1031
#[serde(rename_all = "camelCase")]
1032
pub struct GdalMetaDataRegular {
1033
    pub result_descriptor: RasterResultDescriptor,
1034
    pub params: GdalDatasetParameters,
1035
    pub time_placeholders: HashMap<String, GdalSourceTimePlaceholder>,
1036
    pub data_time: TimeInterval,
1037
    pub step: TimeStep,
1038
    #[serde(default)]
1039
    pub cache_ttl: CacheTtlSeconds,
1040
}
1041

1042
impl From<geoengine_operators::source::GdalMetaDataRegular> for GdalMetaDataRegular {
1043
    fn from(value: geoengine_operators::source::GdalMetaDataRegular) -> Self {
4✔
1044
        Self {
4✔
1045
            r#type: Default::default(),
4✔
1046
            result_descriptor: value.result_descriptor.into(),
4✔
1047
            params: value.params.into(),
4✔
1048
            time_placeholders: value
4✔
1049
                .time_placeholders
4✔
1050
                .into_iter()
4✔
1051
                .map(|(k, v)| (k, v.into()))
4✔
1052
                .collect(),
4✔
1053
            data_time: value.data_time.into(),
4✔
1054
            step: value.step.into(),
4✔
1055
            cache_ttl: value.cache_ttl.into(),
4✔
1056
        }
4✔
1057
    }
4✔
1058
}
1059

1060
impl From<GdalMetaDataRegular> for geoengine_operators::source::GdalMetaDataRegular {
1061
    fn from(value: GdalMetaDataRegular) -> Self {
4✔
1062
        Self {
4✔
1063
            result_descriptor: value.result_descriptor.into(),
4✔
1064
            params: value.params.into(),
4✔
1065
            time_placeholders: value
4✔
1066
                .time_placeholders
4✔
1067
                .into_iter()
4✔
1068
                .map(|(k, v)| (k, v.into()))
4✔
1069
                .collect(),
4✔
1070
            data_time: value.data_time.into(),
4✔
1071
            step: value.step.into(),
4✔
1072
            cache_ttl: value.cache_ttl.into(),
4✔
1073
        }
4✔
1074
    }
4✔
1075
}
1076

1077
/// Parameters for loading data using Gdal
1078
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, ToSchema)]
565✔
1079
#[serde(rename_all = "camelCase")]
1080
pub struct GdalDatasetParameters {
1081
    #[schema(value_type = String)]
1082
    pub file_path: PathBuf,
1083
    pub rasterband_channel: usize,
1084
    pub geo_transform: GdalDatasetGeoTransform, // TODO: discuss if we need this at all
1085
    pub width: usize,
1086
    pub height: usize,
1087
    pub file_not_found_handling: FileNotFoundHandling,
1088
    #[serde(with = "float_option_with_nan")]
1089
    #[serde(default)]
1090
    pub no_data_value: Option<f64>,
1091
    pub properties_mapping: Option<Vec<GdalMetadataMapping>>,
1092
    // Dataset open option as strings, e.g. `vec!["UserPwd=geoengine:pwd".to_owned(), "HttpAuth=BASIC".to_owned()]`
1093
    pub gdal_open_options: Option<Vec<String>>,
1094
    // Configs as key, value pairs that will be set as thread local config options, e.g.
1095
    // `vec!["AWS_REGION".to_owned(), "eu-central-1".to_owned()]` and unset afterwards
1096
    // TODO: validate the config options: only allow specific keys and specific values
1097
    pub gdal_config_options: Option<Vec<GdalConfigOption>>,
1098
    #[serde(default)]
1099
    pub allow_alphaband_as_mask: bool,
1100
}
1101

1102
impl From<geoengine_operators::source::GdalDatasetParameters> for GdalDatasetParameters {
1103
    fn from(value: geoengine_operators::source::GdalDatasetParameters) -> Self {
4✔
1104
        Self {
4✔
1105
            file_path: value.file_path,
4✔
1106
            rasterband_channel: value.rasterband_channel,
4✔
1107
            geo_transform: value.geo_transform.into(),
4✔
1108
            width: value.width,
4✔
1109
            height: value.height,
4✔
1110
            file_not_found_handling: value.file_not_found_handling.into(),
4✔
1111
            no_data_value: value.no_data_value,
4✔
1112
            properties_mapping: value
4✔
1113
                .properties_mapping
4✔
1114
                .map(|x| x.into_iter().map(Into::into).collect()),
4✔
1115
            gdal_open_options: value.gdal_open_options,
4✔
1116
            gdal_config_options: value
4✔
1117
                .gdal_config_options
4✔
1118
                .map(|x| x.into_iter().map(Into::into).collect()),
4✔
1119
            allow_alphaband_as_mask: value.allow_alphaband_as_mask,
4✔
1120
        }
4✔
1121
    }
4✔
1122
}
1123

1124
impl From<GdalDatasetParameters> for geoengine_operators::source::GdalDatasetParameters {
1125
    fn from(value: GdalDatasetParameters) -> Self {
4✔
1126
        Self {
4✔
1127
            file_path: value.file_path,
4✔
1128
            rasterband_channel: value.rasterband_channel,
4✔
1129
            geo_transform: value.geo_transform.into(),
4✔
1130
            width: value.width,
4✔
1131
            height: value.height,
4✔
1132
            file_not_found_handling: value.file_not_found_handling.into(),
4✔
1133
            no_data_value: value.no_data_value,
4✔
1134
            properties_mapping: value
4✔
1135
                .properties_mapping
4✔
1136
                .map(|x| x.into_iter().map(Into::into).collect()),
4✔
1137
            gdal_open_options: value.gdal_open_options,
4✔
1138
            gdal_config_options: value
4✔
1139
                .gdal_config_options
4✔
1140
                .map(|x| x.into_iter().map(Into::into).collect()),
4✔
1141
            allow_alphaband_as_mask: value.allow_alphaband_as_mask,
4✔
1142
            retry: None,
4✔
1143
        }
4✔
1144
    }
4✔
1145
}
1146

1147
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, ToSchema)]
195✔
1148
pub struct GdalSourceTimePlaceholder {
1149
    pub format: DateTimeParseFormat,
1150
    pub reference: TimeReference,
1151
}
1152

1153
impl From<geoengine_operators::source::GdalSourceTimePlaceholder> for GdalSourceTimePlaceholder {
1154
    fn from(value: geoengine_operators::source::GdalSourceTimePlaceholder) -> Self {
4✔
1155
        Self {
4✔
1156
            format: value.format.into(),
4✔
1157
            reference: value.reference.into(),
4✔
1158
        }
4✔
1159
    }
4✔
1160
}
1161

1162
impl From<GdalSourceTimePlaceholder> for geoengine_operators::source::GdalSourceTimePlaceholder {
1163
    fn from(value: GdalSourceTimePlaceholder) -> Self {
4✔
1164
        Self {
4✔
1165
            format: value.format.into(),
4✔
1166
            reference: value.reference.into(),
4✔
1167
        }
4✔
1168
    }
4✔
1169
}
1170

1171
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, ToSchema)]
775✔
1172
#[serde(rename_all = "camelCase")]
1173
pub struct GdalDatasetGeoTransform {
1174
    pub origin_coordinate: Coordinate2D,
1175
    pub x_pixel_size: f64,
1176
    pub y_pixel_size: f64,
1177
}
1178

1179
impl From<geoengine_operators::source::GdalDatasetGeoTransform> for GdalDatasetGeoTransform {
1180
    fn from(value: geoengine_operators::source::GdalDatasetGeoTransform) -> Self {
4✔
1181
        Self {
4✔
1182
            origin_coordinate: value.origin_coordinate.into(),
4✔
1183
            x_pixel_size: value.x_pixel_size,
4✔
1184
            y_pixel_size: value.y_pixel_size,
4✔
1185
        }
4✔
1186
    }
4✔
1187
}
1188

1189
impl From<GdalDatasetGeoTransform> for geoengine_operators::source::GdalDatasetGeoTransform {
1190
    fn from(value: GdalDatasetGeoTransform) -> Self {
4✔
1191
        Self {
4✔
1192
            origin_coordinate: value.origin_coordinate.into(),
4✔
1193
            x_pixel_size: value.x_pixel_size,
4✔
1194
            y_pixel_size: value.y_pixel_size,
4✔
1195
        }
4✔
1196
    }
4✔
1197
}
1198

1199
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, ToSchema)]
775✔
1200
pub enum FileNotFoundHandling {
1201
    NoData, // output tiles filled with nodata
1202
    Error,  // return error tile
1203
}
1204

1205
impl From<geoengine_operators::source::FileNotFoundHandling> for FileNotFoundHandling {
1206
    fn from(value: geoengine_operators::source::FileNotFoundHandling) -> Self {
4✔
1207
        match value {
4✔
1208
            geoengine_operators::source::FileNotFoundHandling::NoData => Self::NoData,
4✔
1209
            geoengine_operators::source::FileNotFoundHandling::Error => Self::Error,
×
1210
        }
1211
    }
4✔
1212
}
1213

1214
impl From<FileNotFoundHandling> for geoengine_operators::source::FileNotFoundHandling {
1215
    fn from(value: FileNotFoundHandling) -> Self {
4✔
1216
        match value {
4✔
1217
            FileNotFoundHandling::NoData => Self::NoData,
4✔
1218
            FileNotFoundHandling::Error => Self::Error,
×
1219
        }
1220
    }
4✔
1221
}
1222

1223
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ToSchema)]
775✔
1224
pub struct GdalMetadataMapping {
1225
    pub source_key: RasterPropertiesKey,
1226
    pub target_key: RasterPropertiesKey,
1227
    pub target_type: RasterPropertiesEntryType,
1228
}
1229

1230
impl From<geoengine_operators::source::GdalMetadataMapping> for GdalMetadataMapping {
1231
    fn from(value: geoengine_operators::source::GdalMetadataMapping) -> Self {
×
1232
        Self {
×
1233
            source_key: value.source_key.into(),
×
1234
            target_key: value.target_key.into(),
×
1235
            target_type: value.target_type.into(),
×
1236
        }
×
1237
    }
×
1238
}
1239

1240
impl From<GdalMetadataMapping> for geoengine_operators::source::GdalMetadataMapping {
1241
    fn from(value: GdalMetadataMapping) -> Self {
×
1242
        Self {
×
1243
            source_key: value.source_key.into(),
×
1244
            target_key: value.target_key.into(),
×
1245
            target_type: value.target_type.into(),
×
1246
        }
×
1247
    }
×
1248
}
1249

1250
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, ToSchema)]
215✔
1251
#[serde(rename_all = "camelCase")]
1252
pub enum TimeReference {
1253
    Start,
1254
    End,
1255
}
1256

1257
impl From<geoengine_operators::source::TimeReference> for TimeReference {
1258
    fn from(value: geoengine_operators::source::TimeReference) -> Self {
4✔
1259
        match value {
4✔
1260
            geoengine_operators::source::TimeReference::Start => Self::Start,
4✔
1261
            geoengine_operators::source::TimeReference::End => Self::End,
×
1262
        }
1263
    }
4✔
1264
}
1265

1266
impl From<TimeReference> for geoengine_operators::source::TimeReference {
1267
    fn from(value: TimeReference) -> Self {
4✔
1268
        match value {
4✔
1269
            TimeReference::Start => Self::Start,
4✔
1270
            TimeReference::End => Self::End,
×
1271
        }
1272
    }
4✔
1273
}
1274

1275
/// Meta data for 4D `NetCDF` CF datasets
1276
#[type_tag(value = "GdalMetaDataNetCdfCf")]
90✔
1277
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, ToSchema)]
175✔
1278
#[serde(rename_all = "camelCase")]
1279
pub struct GdalMetadataNetCdfCf {
1280
    pub result_descriptor: RasterResultDescriptor,
1281
    pub params: GdalDatasetParameters,
1282
    pub start: TimeInstance,
1283
    /// We use the end to specify the last, non-inclusive valid time point.
1284
    /// Queries behind this point return no data.
1285
    /// TODO: Alternatively, we could think about using the number of possible time steps in the future.
1286
    pub end: TimeInstance,
1287
    pub step: TimeStep,
1288
    /// A band offset specifies the first band index to use for the first point in time.
1289
    /// All other time steps are added to this offset.
1290
    pub band_offset: usize,
1291
    #[serde(default)]
1292
    pub cache_ttl: CacheTtlSeconds,
1293
}
1294

1295
impl From<geoengine_operators::source::GdalMetadataNetCdfCf> for GdalMetadataNetCdfCf {
1296
    fn from(value: geoengine_operators::source::GdalMetadataNetCdfCf) -> Self {
×
1297
        Self {
×
NEW
1298
            r#type: Default::default(),
×
1299
            result_descriptor: value.result_descriptor.into(),
×
1300
            params: value.params.into(),
×
1301
            start: value.start.into(),
×
1302
            end: value.end.into(),
×
1303
            step: value.step.into(),
×
1304
            band_offset: value.band_offset,
×
1305
            cache_ttl: value.cache_ttl.into(),
×
1306
        }
×
1307
    }
×
1308
}
1309

1310
impl From<GdalMetadataNetCdfCf> for geoengine_operators::source::GdalMetadataNetCdfCf {
1311
    fn from(value: GdalMetadataNetCdfCf) -> Self {
×
1312
        Self {
×
1313
            result_descriptor: value.result_descriptor.into(),
×
1314
            params: value.params.into(),
×
1315
            start: value.start.into(),
×
1316
            end: value.end.into(),
×
1317
            step: value.step.into(),
×
1318
            band_offset: value.band_offset,
×
1319
            cache_ttl: value.cache_ttl.into(),
×
1320
        }
×
1321
    }
×
1322
}
1323

1324
#[type_tag(value = "GdalMetaDataList")]
90✔
1325
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, ToSchema)]
175✔
1326
#[serde(rename_all = "camelCase")]
1327
pub struct GdalMetaDataList {
1328
    pub result_descriptor: RasterResultDescriptor,
1329
    pub params: Vec<GdalLoadingInfoTemporalSlice>,
1330
}
1331

1332
impl From<geoengine_operators::source::GdalMetaDataList> for GdalMetaDataList {
1333
    fn from(value: geoengine_operators::source::GdalMetaDataList) -> Self {
×
1334
        Self {
×
NEW
1335
            r#type: Default::default(),
×
1336
            result_descriptor: value.result_descriptor.into(),
×
1337
            params: value.params.into_iter().map(Into::into).collect(),
×
1338
        }
×
1339
    }
×
1340
}
1341

1342
impl From<GdalMetaDataList> for geoengine_operators::source::GdalMetaDataList {
1343
    fn from(value: GdalMetaDataList) -> Self {
×
1344
        Self {
×
1345
            result_descriptor: value.result_descriptor.into(),
×
1346
            params: value.params.into_iter().map(Into::into).collect(),
×
1347
        }
×
1348
    }
×
1349
}
1350

1351
/// one temporal slice of the dataset that requires reading from exactly one Gdal dataset
1352
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, ToSchema)]
195✔
1353
#[serde(rename_all = "camelCase")]
1354
pub struct GdalLoadingInfoTemporalSlice {
1355
    pub time: TimeInterval,
1356
    pub params: Option<GdalDatasetParameters>,
1357
    #[serde(default)]
1358
    pub cache_ttl: CacheTtlSeconds,
1359
}
1360

1361
impl From<geoengine_operators::source::GdalLoadingInfoTemporalSlice>
1362
    for GdalLoadingInfoTemporalSlice
1363
{
1364
    fn from(value: geoengine_operators::source::GdalLoadingInfoTemporalSlice) -> Self {
×
1365
        Self {
×
1366
            time: value.time.into(),
×
1367
            params: value.params.map(Into::into),
×
1368
            cache_ttl: value.cache_ttl.into(),
×
1369
        }
×
1370
    }
×
1371
}
1372

1373
impl From<GdalLoadingInfoTemporalSlice>
1374
    for geoengine_operators::source::GdalLoadingInfoTemporalSlice
1375
{
1376
    fn from(value: GdalLoadingInfoTemporalSlice) -> Self {
×
1377
        Self {
×
1378
            time: value.time.into(),
×
1379
            params: value.params.map(Into::into),
×
1380
            cache_ttl: value.cache_ttl.into(),
×
1381
        }
×
1382
    }
×
1383
}
1384

1385
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
255✔
1386
#[serde(rename_all = "lowercase")]
1387
pub enum CsvHeader {
1388
    Yes,
1389
    No,
1390
    Auto,
1391
}
1392

1393
impl From<geoengine_operators::source::CsvHeader> for CsvHeader {
1394
    fn from(value: geoengine_operators::source::CsvHeader) -> Self {
×
1395
        match value {
×
1396
            geoengine_operators::source::CsvHeader::Yes => Self::Yes,
×
1397
            geoengine_operators::source::CsvHeader::No => Self::No,
×
1398
            geoengine_operators::source::CsvHeader::Auto => Self::Auto,
×
1399
        }
1400
    }
×
1401
}
1402

1403
impl From<CsvHeader> for geoengine_operators::source::CsvHeader {
1404
    fn from(value: CsvHeader) -> Self {
×
1405
        match value {
×
1406
            CsvHeader::Yes => Self::Yes,
×
1407
            CsvHeader::No => Self::No,
×
1408
            CsvHeader::Auto => Self::Auto,
×
1409
        }
1410
    }
×
1411
}
1412

1413
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
235✔
1414
#[serde(rename_all = "camelCase")]
1415
pub enum FormatSpecifics {
1416
    Csv { header: CsvHeader },
1417
}
1418

1419
impl From<geoengine_operators::source::FormatSpecifics> for FormatSpecifics {
1420
    fn from(value: geoengine_operators::source::FormatSpecifics) -> Self {
×
1421
        match value {
×
1422
            geoengine_operators::source::FormatSpecifics::Csv { header } => Self::Csv {
×
1423
                header: header.into(),
×
1424
            },
×
1425
        }
×
1426
    }
×
1427
}
1428

1429
impl From<FormatSpecifics> for geoengine_operators::source::FormatSpecifics {
1430
    fn from(value: FormatSpecifics) -> Self {
×
1431
        match value {
×
1432
            FormatSpecifics::Csv { header } => Self::Csv {
×
1433
                header: header.into(),
×
1434
            },
×
1435
        }
×
1436
    }
×
1437
}
1438

1439
#[cfg(test)]
1440
mod tests {
1441
    use serde_json::json;
1442

1443
    use super::*;
1444

1445
    #[test]
1446
    fn it_checks_duplicates_while_deserializing_band_descriptors() {
1✔
1447
        assert_eq!(
1✔
1448
            serde_json::from_value::<RasterBandDescriptors>(json!([{
1✔
1449
                "name": "foo",
1✔
1450
                "measurement": {
1✔
1451
                    "type": "unitless"
1✔
1452
                }
1✔
1453
            },{
1✔
1454
                "name": "bar",
1✔
1455
                "measurement": {
1✔
1456
                    "type": "unitless"
1✔
1457
                }
1✔
1458
            }]))
1✔
1459
            .unwrap(),
1✔
1460
            RasterBandDescriptors::new(vec![
1✔
1461
                RasterBandDescriptor {
1✔
1462
                    name: "foo".into(),
1✔
1463
                    measurement: Measurement::Unitless(Default::default()),
1✔
1464
                },
1✔
1465
                RasterBandDescriptor {
1✔
1466
                    name: "bar".into(),
1✔
1467
                    measurement: Measurement::Unitless(Default::default()),
1✔
1468
                },
1✔
1469
            ])
1✔
1470
            .unwrap()
1✔
1471
        );
1✔
1472

1473
        assert!(
1✔
1474
            serde_json::from_value::<RasterBandDescriptors>(json!([{
1✔
1475
                "name": "foo",
1✔
1476
                "measurement": {
1✔
1477
                    "type": "unitless"
1✔
1478
                }
1✔
1479
            },{
1✔
1480
                "name": "foo",
1✔
1481
                "measurement": {
1✔
1482
                    "type": "unitless"
1✔
1483
                }
1✔
1484
            }]))
1✔
1485
            .is_err()
1✔
1486
        );
1✔
1487
    }
1✔
1488
}
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