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

geo-engine / geoengine / 13809415963

12 Mar 2025 10:42AM UTC coverage: 90.026% (-0.05%) from 90.076%
13809415963

Pull #1013

github

web-flow
Merge b51e2554c into c96026921
Pull Request #1013: Update-utoipa

787 of 935 new or added lines in 41 files covered. (84.17%)

28 existing lines in 10 files now uncovered.

125995 of 139954 relevant lines covered (90.03%)

57510.86 hits per line

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

52.05
/services/src/api/model/datatypes.rs
1
use crate::error::{self, Error, Result};
2
use crate::identifier;
3
use geoengine_datatypes::operations::image::RgbParams;
4
use geoengine_datatypes::primitives::{
5
    AxisAlignedRectangle, MultiLineStringAccess, MultiPointAccess, MultiPolygonAccess,
6
};
7
use geoengine_macros::type_tag;
8
use ordered_float::NotNan;
9
use postgres_types::{FromSql, ToSql};
10
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
11
use snafu::ResultExt;
12
use std::{
13
    collections::{BTreeMap, HashMap},
14
    fmt::{Debug, Formatter},
15
    str::FromStr,
16
};
17
use utoipa::{PartialSchema, ToSchema};
18

19
identifier!(DataProviderId);
20

21
impl From<DataProviderId> for geoengine_datatypes::dataset::DataProviderId {
22
    fn from(value: DataProviderId) -> Self {
8✔
23
        Self(value.0)
8✔
24
    }
8✔
25
}
26

27
// Identifier for datasets managed by Geo Engine
28
identifier!(DatasetId);
29

30
impl From<DatasetId> for geoengine_datatypes::dataset::DatasetId {
31
    fn from(value: DatasetId) -> Self {
2✔
32
        Self(value.0)
2✔
33
    }
2✔
34
}
35

36
#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize, Serialize, ToSchema)]
60✔
37
#[serde(rename_all = "camelCase", untagged)]
38
#[schema(discriminator = "type")]
39
/// The identifier for loadable data. It is used in the source operators to get the loading info (aka parametrization)
40
/// for accessing the data. Internal data is loaded from datasets, external from `DataProvider`s.
41
pub enum DataId {
42
    Internal(InternalDataId),
43
    External(ExternalDataId),
44
}
45

46
#[type_tag(tag = "internal")]
32✔
47
#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize, Serialize, ToSchema)]
64✔
48
#[serde(rename_all = "camelCase")]
49
pub struct InternalDataId {
50
    pub dataset_id: DatasetId,
51
}
52

53
impl DataId {
54
    pub fn internal(&self) -> Option<DatasetId> {
×
NEW
55
        if let Self::Internal(internal) = self {
×
NEW
56
            return Some(internal.dataset_id);
×
57
        }
×
58
        None
×
59
    }
×
60

61
    pub fn external(&self) -> Option<ExternalDataId> {
×
62
        if let Self::External(id) = self {
×
63
            return Some(id.clone());
×
64
        }
×
65
        None
×
66
    }
×
67
}
68

69
impl From<DatasetId> for DataId {
70
    fn from(value: DatasetId) -> Self {
×
NEW
71
        Self::Internal(InternalDataId {
×
NEW
72
            r#type: Default::default(),
×
NEW
73
            dataset_id: value,
×
NEW
74
        })
×
UNCOV
75
    }
×
76
}
77

78
impl From<ExternalDataId> for DataId {
79
    fn from(value: ExternalDataId) -> Self {
×
80
        Self::External(value)
×
81
    }
×
82
}
83

84
impl From<ExternalDataId> for geoengine_datatypes::dataset::DataId {
85
    fn from(value: ExternalDataId) -> Self {
×
86
        Self::External(value.into())
×
87
    }
×
88
}
89

90
impl From<DatasetId> for geoengine_datatypes::dataset::DataId {
91
    fn from(value: DatasetId) -> Self {
×
92
        Self::Internal {
×
93
            dataset_id: value.into(),
×
94
        }
×
95
    }
×
96
}
97

98
impl From<ExternalDataId> for geoengine_datatypes::dataset::ExternalDataId {
99
    fn from(value: ExternalDataId) -> Self {
×
100
        Self {
×
101
            provider_id: value.provider_id.into(),
×
102
            layer_id: value.layer_id.into(),
×
103
        }
×
104
    }
×
105
}
106

107
impl From<geoengine_datatypes::dataset::DataId> for DataId {
108
    fn from(id: geoengine_datatypes::dataset::DataId) -> Self {
4✔
109
        match id {
4✔
110
            geoengine_datatypes::dataset::DataId::Internal { dataset_id } => {
4✔
111
                Self::Internal(InternalDataId {
4✔
112
                    r#type: Default::default(),
4✔
113
                    dataset_id: dataset_id.into(),
4✔
114
                })
4✔
115
            }
116
            geoengine_datatypes::dataset::DataId::External(external_id) => {
×
117
                Self::External(external_id.into())
×
118
            }
119
        }
120
    }
4✔
121
}
122

123
impl From<DataId> for geoengine_datatypes::dataset::DataId {
124
    fn from(id: DataId) -> Self {
×
125
        match id {
×
NEW
126
            DataId::Internal(internal) => Self::Internal {
×
NEW
127
                dataset_id: internal.dataset_id.into(),
×
128
            },
×
129
            DataId::External(external_id) => Self::External(external_id.into()),
×
130
        }
131
    }
×
132
}
133

134
impl From<&DataId> for geoengine_datatypes::dataset::DataId {
135
    fn from(id: &DataId) -> Self {
×
136
        match id {
×
NEW
137
            DataId::Internal(internal) => Self::Internal {
×
NEW
138
                dataset_id: internal.dataset_id.into(),
×
139
            },
×
140
            DataId::External(external_id) => Self::External(external_id.into()),
×
141
        }
142
    }
×
143
}
144

145
impl From<geoengine_datatypes::dataset::DatasetId> for DatasetId {
146
    fn from(id: geoengine_datatypes::dataset::DatasetId) -> Self {
4✔
147
        Self(id.0)
4✔
148
    }
4✔
149
}
150

151
impl From<&DatasetId> for geoengine_datatypes::dataset::DatasetId {
UNCOV
152
    fn from(value: &DatasetId) -> Self {
×
UNCOV
153
        Self(value.0)
×
UNCOV
154
    }
×
155
}
156

157
impl From<&ExternalDataId> for geoengine_datatypes::dataset::ExternalDataId {
158
    fn from(value: &ExternalDataId) -> Self {
×
159
        Self {
×
160
            provider_id: value.provider_id.into(),
×
161
            layer_id: value.layer_id.clone().into(),
×
162
        }
×
163
    }
×
164
}
165

166
/// The user-facing identifier for loadable data.
167
/// It can be resolved into a [`DataId`].
168
#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize, Serialize)]
169
// TODO: Have separate type once `geoengine_datatypes::dataset::NamedData` is not part of the API anymore.
170
#[serde(
171
    from = "geoengine_datatypes::dataset::NamedData",
172
    into = "geoengine_datatypes::dataset::NamedData"
173
)]
174
pub struct NamedData {
175
    pub namespace: Option<String>,
176
    pub provider: Option<String>,
177
    pub name: String,
178
}
179

180
impl From<geoengine_datatypes::dataset::NamedData> for NamedData {
181
    fn from(
1✔
182
        geoengine_datatypes::dataset::NamedData {
1✔
183
            namespace,
1✔
184
            provider,
1✔
185
            name,
1✔
186
        }: geoengine_datatypes::dataset::NamedData,
1✔
187
    ) -> Self {
1✔
188
        Self {
1✔
189
            namespace,
1✔
190
            provider,
1✔
191
            name,
1✔
192
        }
1✔
193
    }
1✔
194
}
195

196
impl From<&geoengine_datatypes::dataset::NamedData> for NamedData {
197
    fn from(named_data: &geoengine_datatypes::dataset::NamedData) -> Self {
×
198
        Self::from(named_data.clone())
×
199
    }
×
200
}
201

202
impl From<NamedData> for geoengine_datatypes::dataset::NamedData {
203
    fn from(
1✔
204
        NamedData {
1✔
205
            namespace,
1✔
206
            provider,
1✔
207
            name,
1✔
208
        }: NamedData,
1✔
209
    ) -> Self {
1✔
210
        Self {
1✔
211
            namespace,
1✔
212
            provider,
1✔
213
            name,
1✔
214
        }
1✔
215
    }
1✔
216
}
217

218
impl From<&NamedData> for geoengine_datatypes::dataset::NamedData {
219
    fn from(named_data: &NamedData) -> Self {
×
220
        Self::from(named_data.clone())
×
221
    }
×
222
}
223

224
impl PartialSchema for NamedData {
225
    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::Schema> {
4✔
226
        use utoipa::openapi::schema::{ObjectBuilder, SchemaType, Type};
227
        ObjectBuilder::new()
4✔
228
            .schema_type(SchemaType::Type(Type::String))
4✔
229
            .into()
4✔
230
    }
4✔
231
}
232

233
impl ToSchema for NamedData {}
234

235
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash, ToSchema, ToSql, FromSql)]
356✔
236
pub struct LayerId(pub String); // TODO: differentiate between internal layer ids (UUID) and external layer ids (String)
237

238
impl From<LayerId> for geoengine_datatypes::dataset::LayerId {
239
    fn from(value: LayerId) -> Self {
15✔
240
        Self(value.0)
15✔
241
    }
15✔
242
}
243

244
impl std::fmt::Display for LayerId {
245
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
×
246
        write!(f, "{}", self.0)
×
247
    }
×
248
}
249

250
#[type_tag(tag = "external")]
40✔
251
#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize, Serialize, ToSchema)]
76✔
252
#[serde(rename_all = "camelCase")]
253
pub struct ExternalDataId {
254
    pub provider_id: DataProviderId,
255
    pub layer_id: LayerId,
256
}
257

258
impl From<geoengine_datatypes::dataset::ExternalDataId> for ExternalDataId {
259
    fn from(id: geoengine_datatypes::dataset::ExternalDataId) -> Self {
×
260
        Self {
×
NEW
261
            r#type: Default::default(),
×
262
            provider_id: id.provider_id.into(),
×
263
            layer_id: id.layer_id.into(),
×
264
        }
×
265
    }
×
266
}
267

268
impl From<geoengine_datatypes::dataset::DataProviderId> for DataProviderId {
269
    fn from(id: geoengine_datatypes::dataset::DataProviderId) -> Self {
×
270
        Self(id.0)
×
271
    }
×
272
}
273

274
impl From<geoengine_datatypes::dataset::LayerId> for LayerId {
275
    fn from(id: geoengine_datatypes::dataset::LayerId) -> Self {
1✔
276
        Self(id.0)
1✔
277
    }
1✔
278
}
279

280
/// A spatial reference authority that is part of a spatial reference definition
281
#[derive(
282
    Debug,
283
    Copy,
284
    Clone,
285
    Eq,
286
    PartialEq,
287
    Ord,
288
    PartialOrd,
289
    Serialize,
290
    Deserialize,
291
    ToSchema,
12✔
292
    FromSql,
×
293
    ToSql,
294
)]
295
#[serde(rename_all = "SCREAMING-KEBAB-CASE")]
296
pub enum SpatialReferenceAuthority {
297
    Epsg,
298
    SrOrg,
299
    Iau2000,
300
    Esri,
301
}
302

303
impl From<geoengine_datatypes::spatial_reference::SpatialReferenceAuthority>
304
    for SpatialReferenceAuthority
305
{
306
    fn from(value: geoengine_datatypes::spatial_reference::SpatialReferenceAuthority) -> Self {
50✔
307
        match value {
50✔
308
            geoengine_datatypes::spatial_reference::SpatialReferenceAuthority::Epsg => Self::Epsg,
49✔
309
            geoengine_datatypes::spatial_reference::SpatialReferenceAuthority::SrOrg => Self::SrOrg,
1✔
310
            geoengine_datatypes::spatial_reference::SpatialReferenceAuthority::Iau2000 => {
311
                Self::Iau2000
×
312
            }
313
            geoengine_datatypes::spatial_reference::SpatialReferenceAuthority::Esri => Self::Esri,
×
314
        }
315
    }
50✔
316
}
317

318
impl From<SpatialReferenceAuthority>
319
    for geoengine_datatypes::spatial_reference::SpatialReferenceAuthority
320
{
321
    fn from(value: SpatialReferenceAuthority) -> Self {
10✔
322
        match value {
10✔
323
            SpatialReferenceAuthority::Epsg => Self::Epsg,
10✔
324
            SpatialReferenceAuthority::SrOrg => Self::SrOrg,
×
325
            SpatialReferenceAuthority::Iau2000 => Self::Iau2000,
×
326
            SpatialReferenceAuthority::Esri => Self::Esri,
×
327
        }
328
    }
10✔
329
}
330

331
impl FromStr for SpatialReferenceAuthority {
332
    type Err = error::Error;
333

334
    fn from_str(s: &str) -> Result<Self, Self::Err> {
32✔
335
        Ok(match s {
32✔
336
            "EPSG" => SpatialReferenceAuthority::Epsg,
32✔
337
            "SR-ORG" => SpatialReferenceAuthority::SrOrg,
×
338
            "IAU2000" => SpatialReferenceAuthority::Iau2000,
×
339
            "ESRI" => SpatialReferenceAuthority::Esri,
×
340
            _ => {
341
                return Err(error::Error::InvalidSpatialReferenceString {
×
342
                    spatial_reference_string: s.into(),
×
343
                })
×
344
            }
345
        })
346
    }
32✔
347
}
348

349
impl std::fmt::Display for SpatialReferenceAuthority {
350
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46✔
351
        write!(
46✔
352
            f,
46✔
353
            "{}",
46✔
354
            match self {
46✔
355
                SpatialReferenceAuthority::Epsg => "EPSG",
46✔
356
                SpatialReferenceAuthority::SrOrg => "SR-ORG",
×
357
                SpatialReferenceAuthority::Iau2000 => "IAU2000",
×
358
                SpatialReferenceAuthority::Esri => "ESRI",
×
359
            }
360
        )
361
    }
46✔
362
}
363

364
/// A spatial reference consists of an authority and a code
365
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, FromSql, ToSql)]
×
366
pub struct SpatialReference {
367
    authority: SpatialReferenceAuthority,
368
    code: u32,
369
}
370

371
impl SpatialReference {
372
    pub fn proj_string(self) -> Result<String> {
19✔
373
        match self.authority {
×
374
            SpatialReferenceAuthority::Epsg | SpatialReferenceAuthority::Iau2000 => {
375
                Ok(format!("{}:{}", self.authority, self.code))
19✔
376
            }
377
            // poor-mans integration of Meteosat Second Generation
378
            SpatialReferenceAuthority::SrOrg if self.code == 81 => Ok("+proj=geos +lon_0=0 +h=35785831 +x_0=0 +y_0=0 +ellps=WGS84 +units=m +no_defs +type=crs".to_owned()),
×
379
            SpatialReferenceAuthority::SrOrg | SpatialReferenceAuthority::Esri => {
380
                Err(error::Error::ProjStringUnresolvable { spatial_ref: self })
×
381
                //TODO: we might need to look them up somehow! Best solution would be a registry where we can store user definexd srs strings.
382
            }
383
        }
384
    }
19✔
385

386
    /// Return the srs-string "authority:code"
387
    #[allow(clippy::trivially_copy_pass_by_ref)]
388
    pub fn srs_string(&self) -> String {
19✔
389
        format!("{}:{}", self.authority, self.code)
19✔
390
    }
19✔
391
}
392

393
impl From<geoengine_datatypes::spatial_reference::SpatialReference> for SpatialReference {
394
    fn from(value: geoengine_datatypes::spatial_reference::SpatialReference) -> Self {
50✔
395
        Self {
50✔
396
            authority: (*value.authority()).into(),
50✔
397
            code: value.code(),
50✔
398
        }
50✔
399
    }
50✔
400
}
401

402
impl From<SpatialReference> for geoengine_datatypes::spatial_reference::SpatialReference {
403
    fn from(value: SpatialReference) -> Self {
10✔
404
        geoengine_datatypes::spatial_reference::SpatialReference::new(
10✔
405
            value.authority.into(),
10✔
406
            value.code,
10✔
407
        )
10✔
408
    }
10✔
409
}
410

411
impl SpatialReference {
412
    pub fn new(authority: SpatialReferenceAuthority, code: u32) -> Self {
37✔
413
        Self { authority, code }
37✔
414
    }
37✔
415

416
    pub fn authority(&self) -> &SpatialReferenceAuthority {
1✔
417
        &self.authority
1✔
418
    }
1✔
419

420
    pub fn code(self) -> u32 {
1✔
421
        self.code
1✔
422
    }
1✔
423
}
424

425
impl FromStr for SpatialReference {
426
    type Err = error::Error;
427

428
    fn from_str(s: &str) -> Result<Self, Self::Err> {
32✔
429
        let mut split = s.split(':');
32✔
430

32✔
431
        match (split.next(), split.next(), split.next()) {
32✔
432
            (Some(authority), Some(code), None) => Ok(Self::new(
32✔
433
                authority.parse()?,
32✔
434
                code.parse::<u32>().context(error::ParseU32)?,
32✔
435
            )),
436
            _ => Err(error::Error::InvalidSpatialReferenceString {
×
437
                spatial_reference_string: s.into(),
×
438
            }),
×
439
        }
440
    }
32✔
441
}
442

443
impl std::fmt::Display for SpatialReference {
444
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7✔
445
        write!(f, "{}:{}", self.authority, self.code)
7✔
446
    }
7✔
447
}
448

449
impl Serialize for SpatialReference {
450
    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
1✔
451
    where
1✔
452
        S: Serializer,
1✔
453
    {
1✔
454
        serializer.serialize_str(&self.to_string())
1✔
455
    }
1✔
456
}
457

458
/// Helper struct for deserializing a `SpatialReferencce`
459
struct SpatialReferenceDeserializeVisitor;
460

461
impl Visitor<'_> for SpatialReferenceDeserializeVisitor {
462
    type Value = SpatialReference;
463

464
    fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
×
465
        formatter.write_str("a spatial reference in the form authority:code")
×
466
    }
×
467

468
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
18✔
469
    where
18✔
470
        E: serde::de::Error,
18✔
471
    {
18✔
472
        v.parse().map_err(serde::de::Error::custom)
18✔
473
    }
18✔
474
}
475

476
impl<'de> Deserialize<'de> for SpatialReference {
477
    fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
18✔
478
    where
18✔
479
        D: Deserializer<'de>,
18✔
480
    {
18✔
481
        deserializer.deserialize_str(SpatialReferenceDeserializeVisitor)
18✔
482
    }
18✔
483
}
484

485
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
486
pub enum SpatialReferenceOption {
487
    SpatialReference(SpatialReference),
488
    Unreferenced,
489
}
490

491
impl From<geoengine_datatypes::spatial_reference::SpatialReferenceOption>
492
    for SpatialReferenceOption
493
{
494
    fn from(value: geoengine_datatypes::spatial_reference::SpatialReferenceOption) -> Self {
22✔
495
        match value {
22✔
496
            geoengine_datatypes::spatial_reference::SpatialReferenceOption::SpatialReference(s) => {
19✔
497
                Self::SpatialReference(s.into())
19✔
498
            }
499
            geoengine_datatypes::spatial_reference::SpatialReferenceOption::Unreferenced => {
500
                Self::Unreferenced
3✔
501
            }
502
        }
503
    }
22✔
504
}
505

506
impl From<SpatialReferenceOption>
507
    for geoengine_datatypes::spatial_reference::SpatialReferenceOption
508
{
509
    fn from(value: SpatialReferenceOption) -> Self {
7✔
510
        match value {
7✔
511
            SpatialReferenceOption::SpatialReference(sr) => Self::SpatialReference(sr.into()),
6✔
512
            SpatialReferenceOption::Unreferenced => Self::Unreferenced,
1✔
513
        }
514
    }
7✔
515
}
516

517
impl From<SpatialReference> for SpatialReferenceOption {
518
    fn from(spatial_reference: SpatialReference) -> Self {
6✔
519
        Self::SpatialReference(spatial_reference)
6✔
520
    }
6✔
521
}
522

523
impl std::fmt::Display for SpatialReferenceOption {
524
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8✔
525
        match self {
8✔
526
            SpatialReferenceOption::SpatialReference(p) => write!(f, "{p}"),
6✔
527
            SpatialReferenceOption::Unreferenced => Ok(()),
2✔
528
        }
529
    }
8✔
530
}
531

532
impl Serialize for SpatialReferenceOption {
533
    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
8✔
534
    where
8✔
535
        S: Serializer,
8✔
536
    {
8✔
537
        serializer.serialize_str(&self.to_string())
8✔
538
    }
8✔
539
}
540

541
/// Helper struct for deserializing a `SpatialReferenceOption`
542
struct SpatialReferenceOptionDeserializeVisitor;
543

544
impl Visitor<'_> for SpatialReferenceOptionDeserializeVisitor {
545
    type Value = SpatialReferenceOption;
546

547
    fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
×
548
        formatter.write_str("a spatial reference in the form authority:code")
×
549
    }
×
550

551
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
7✔
552
    where
7✔
553
        E: serde::de::Error,
7✔
554
    {
7✔
555
        if v.is_empty() {
7✔
556
            return Ok(SpatialReferenceOption::Unreferenced);
1✔
557
        }
6✔
558

559
        let spatial_reference: SpatialReference = v.parse().map_err(serde::de::Error::custom)?;
6✔
560

561
        Ok(spatial_reference.into())
6✔
562
    }
7✔
563
}
564

565
impl<'de> Deserialize<'de> for SpatialReferenceOption {
566
    fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
7✔
567
    where
7✔
568
        D: Deserializer<'de>,
7✔
569
    {
7✔
570
        deserializer.deserialize_str(SpatialReferenceOptionDeserializeVisitor)
7✔
571
    }
7✔
572
}
573

574
impl From<Option<SpatialReference>> for SpatialReferenceOption {
575
    fn from(option: Option<SpatialReference>) -> Self {
×
576
        match option {
×
577
            Some(p) => SpatialReferenceOption::SpatialReference(p),
×
578
            None => SpatialReferenceOption::Unreferenced,
×
579
        }
580
    }
×
581
}
582

583
impl From<SpatialReferenceOption> for Option<SpatialReference> {
584
    fn from(option: SpatialReferenceOption) -> Self {
14✔
585
        match option {
14✔
586
            SpatialReferenceOption::SpatialReference(p) => Some(p),
14✔
587
            SpatialReferenceOption::Unreferenced => None,
×
588
        }
589
    }
14✔
590
}
591

592
impl ToSql for SpatialReferenceOption {
593
    fn to_sql(
×
594
        &self,
×
595
        ty: &postgres_types::Type,
×
596
        out: &mut bytes::BytesMut,
×
597
    ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>>
×
598
    where
×
599
        Self: Sized,
×
600
    {
×
601
        match self {
×
602
            SpatialReferenceOption::SpatialReference(sref) => sref.to_sql(ty, out),
×
603
            SpatialReferenceOption::Unreferenced => Ok(postgres_types::IsNull::Yes),
×
604
        }
605
    }
×
606

607
    fn accepts(ty: &postgres_types::Type) -> bool
×
608
    where
×
609
        Self: Sized,
×
610
    {
×
611
        <SpatialReference as ToSql>::accepts(ty)
×
612
    }
×
613

614
    fn to_sql_checked(
×
615
        &self,
×
616
        ty: &postgres_types::Type,
×
617
        out: &mut bytes::BytesMut,
×
618
    ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
×
619
        match self {
×
620
            SpatialReferenceOption::SpatialReference(sref) => sref.to_sql_checked(ty, out),
×
621
            SpatialReferenceOption::Unreferenced => Ok(postgres_types::IsNull::Yes),
×
622
        }
623
    }
×
624
}
625

626
impl<'a> FromSql<'a> for SpatialReferenceOption {
627
    fn from_sql(
×
628
        ty: &postgres_types::Type,
×
629
        raw: &'a [u8],
×
630
    ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
×
631
        Ok(SpatialReferenceOption::SpatialReference(
×
632
            SpatialReference::from_sql(ty, raw)?,
×
633
        ))
634
    }
×
635

636
    fn from_sql_null(
×
637
        _: &postgres_types::Type,
×
638
    ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
×
639
        Ok(SpatialReferenceOption::Unreferenced)
×
640
    }
×
641

642
    fn accepts(ty: &postgres_types::Type) -> bool {
×
643
        <SpatialReference as FromSql>::accepts(ty)
×
644
    }
×
645
}
646

647
/// An enum that contains all possible vector data types
648
#[derive(
649
    Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize, Copy, Clone, ToSchema,
1,004✔
650
)]
651
pub enum VectorDataType {
652
    Data,
653
    MultiPoint,
654
    MultiLineString,
655
    MultiPolygon,
656
}
657

658
impl From<geoengine_datatypes::collections::VectorDataType> for VectorDataType {
659
    fn from(value: geoengine_datatypes::collections::VectorDataType) -> Self {
5✔
660
        match value {
5✔
661
            geoengine_datatypes::collections::VectorDataType::Data => Self::Data,
3✔
662
            geoengine_datatypes::collections::VectorDataType::MultiPoint => Self::MultiPoint,
2✔
663
            geoengine_datatypes::collections::VectorDataType::MultiLineString => {
664
                Self::MultiLineString
×
665
            }
666
            geoengine_datatypes::collections::VectorDataType::MultiPolygon => Self::MultiPolygon,
×
667
        }
668
    }
5✔
669
}
670

671
impl From<VectorDataType> for geoengine_datatypes::collections::VectorDataType {
672
    fn from(value: VectorDataType) -> Self {
5✔
673
        match value {
5✔
674
            VectorDataType::Data => Self::Data,
1✔
675
            VectorDataType::MultiPoint => Self::MultiPoint,
4✔
676
            VectorDataType::MultiLineString => Self::MultiLineString,
×
677
            VectorDataType::MultiPolygon => Self::MultiPolygon,
×
678
        }
679
    }
5✔
680
}
681

682
#[derive(
683
    Clone,
684
    Copy,
685
    Debug,
686
    Deserialize,
687
    PartialEq,
688
    PartialOrd,
689
    Serialize,
690
    Default,
691
    ToSchema,
6,020✔
692
    ToSql,
×
693
    FromSql,
×
694
)]
695
pub struct Coordinate2D {
696
    pub x: f64,
697
    pub y: f64,
698
}
699

700
impl From<geoengine_datatypes::primitives::Coordinate2D> for Coordinate2D {
701
    fn from(coordinate: geoengine_datatypes::primitives::Coordinate2D) -> Self {
72✔
702
        Self {
72✔
703
            x: coordinate.x,
72✔
704
            y: coordinate.y,
72✔
705
        }
72✔
706
    }
72✔
707
}
708

709
impl From<Coordinate2D> for geoengine_datatypes::primitives::Coordinate2D {
710
    fn from(coordinate: Coordinate2D) -> Self {
26✔
711
        Self {
26✔
712
            x: coordinate.x,
26✔
713
            y: coordinate.y,
26✔
714
        }
26✔
715
    }
26✔
716
}
717

718
#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Debug, ToSchema, ToSql, FromSql)]
1,284✔
719
#[serde(rename_all = "camelCase")]
720
/// A bounding box that includes all border points.
721
/// Note: may degenerate to a point!
722
pub struct BoundingBox2D {
723
    pub lower_left_coordinate: Coordinate2D,
724
    pub upper_right_coordinate: Coordinate2D,
725
}
726

727
impl From<geoengine_datatypes::primitives::BoundingBox2D> for BoundingBox2D {
728
    fn from(bbox: geoengine_datatypes::primitives::BoundingBox2D) -> Self {
28✔
729
        Self {
28✔
730
            lower_left_coordinate:
28✔
731
                geoengine_datatypes::primitives::AxisAlignedRectangle::lower_left(&bbox).into(),
28✔
732
            upper_right_coordinate:
28✔
733
                geoengine_datatypes::primitives::AxisAlignedRectangle::upper_right(&bbox).into(),
28✔
734
        }
28✔
735
    }
28✔
736
}
737

738
impl From<BoundingBox2D> for geoengine_datatypes::primitives::BoundingBox2D {
739
    fn from(bbox: BoundingBox2D) -> Self {
1✔
740
        Self::new_unchecked(
1✔
741
            bbox.lower_left_coordinate.into(),
1✔
742
            bbox.upper_right_coordinate.into(),
1✔
743
        )
1✔
744
    }
1✔
745
}
746

747
/// An object that composes the date and a timestamp with time zone.
748
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
749
pub struct DateTimeString {
750
    datetime: chrono::DateTime<chrono::Utc>,
751
}
752

753
// TODO: use derive ToSchema when OpenAPI derive does not break
754
impl PartialSchema for DateTimeString {
755
    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
4✔
756
        use utoipa::openapi::schema::{ObjectBuilder, SchemaType, Type};
757
        ObjectBuilder::new()
4✔
758
            .schema_type(SchemaType::Type(Type::String))
4✔
759
            .into()
4✔
760
    }
4✔
761
}
762

763
impl ToSchema for DateTimeString {
764
    // fn name() -> Cow<'static, str> {
765
    //     "DateTime2".into()
766
    // }
767
}
768

769
impl FromStr for DateTimeString {
770
    type Err = geoengine_datatypes::primitives::DateTimeError;
771

772
    fn from_str(input: &str) -> Result<Self, Self::Err> {
×
773
        let date_time = chrono::DateTime::<chrono::FixedOffset>::from_str(input).map_err(|e| {
×
774
            Self::Err::DateParse {
×
775
                source: Box::new(e),
×
776
            }
×
777
        })?;
×
778

779
        Ok(date_time.into())
×
780
    }
×
781
}
782

783
impl From<chrono::DateTime<chrono::FixedOffset>> for DateTimeString {
784
    fn from(datetime: chrono::DateTime<chrono::FixedOffset>) -> Self {
×
785
        Self {
×
786
            datetime: datetime.into(),
×
787
        }
×
788
    }
×
789
}
790

791
impl From<geoengine_datatypes::primitives::DateTime> for DateTimeString {
792
    fn from(datetime: geoengine_datatypes::primitives::DateTime) -> Self {
×
793
        Self {
×
794
            datetime: datetime.into(),
×
795
        }
×
796
    }
×
797
}
798

799
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize, ToSchema)]
716✔
800
#[serde(rename_all = "camelCase")]
801
pub enum FeatureDataType {
802
    Category,
803
    Int,
804
    Float,
805
    Text,
806
    Bool,
807
    DateTime,
808
}
809

810
impl From<geoengine_datatypes::primitives::FeatureDataType> for FeatureDataType {
811
    fn from(value: geoengine_datatypes::primitives::FeatureDataType) -> Self {
2✔
812
        match value {
2✔
813
            geoengine_datatypes::primitives::FeatureDataType::Category => Self::Category,
×
814
            geoengine_datatypes::primitives::FeatureDataType::Int => Self::Int,
1✔
815
            geoengine_datatypes::primitives::FeatureDataType::Float => Self::Float,
×
816
            geoengine_datatypes::primitives::FeatureDataType::Text => Self::Text,
1✔
817
            geoengine_datatypes::primitives::FeatureDataType::Bool => Self::Bool,
×
818
            geoengine_datatypes::primitives::FeatureDataType::DateTime => Self::DateTime,
×
819
        }
820
    }
2✔
821
}
822

823
impl From<FeatureDataType> for geoengine_datatypes::primitives::FeatureDataType {
824
    fn from(value: FeatureDataType) -> Self {
10✔
825
        match value {
10✔
826
            FeatureDataType::Category => Self::Category,
×
827
            FeatureDataType::Int => Self::Int,
2✔
828
            FeatureDataType::Float => Self::Float,
2✔
829
            FeatureDataType::Text => Self::Text,
6✔
830
            FeatureDataType::Bool => Self::Bool,
×
831
            FeatureDataType::DateTime => Self::DateTime,
×
832
        }
833
    }
10✔
834
}
835

836
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
1,436✔
837
#[serde(rename_all = "camelCase", untagged)]
838
#[schema(discriminator = "type")]
839
pub enum Measurement {
840
    Unitless(UnitlessMeasurement),
841
    Continuous(ContinuousMeasurement),
842
    Classification(ClassificationMeasurement),
843
}
844

845
impl From<geoengine_datatypes::primitives::Measurement> for Measurement {
846
    fn from(value: geoengine_datatypes::primitives::Measurement) -> Self {
6✔
847
        match value {
6✔
848
            geoengine_datatypes::primitives::Measurement::Unitless => {
849
                Self::Unitless(UnitlessMeasurement {
2✔
850
                    r#type: Default::default(),
2✔
851
                })
2✔
852
            }
853
            geoengine_datatypes::primitives::Measurement::Continuous(cm) => {
4✔
854
                Self::Continuous(cm.into())
4✔
855
            }
856
            geoengine_datatypes::primitives::Measurement::Classification(cm) => {
×
857
                Self::Classification(cm.into())
×
858
            }
859
        }
860
    }
6✔
861
}
862

863
impl From<Measurement> for geoengine_datatypes::primitives::Measurement {
864
    fn from(value: Measurement) -> Self {
14✔
865
        match value {
14✔
866
            Measurement::Unitless { .. } => Self::Unitless,
10✔
867
            Measurement::Continuous(cm) => Self::Continuous(cm.into()),
4✔
868
            Measurement::Classification(cm) => Self::Classification(cm.into()),
×
869
        }
870
    }
14✔
871
}
872

873
#[type_tag(tag = "unitless")]
720✔
874
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema, Default)]
1,440✔
875
pub struct UnitlessMeasurement {}
876

877
#[type_tag(tag = "continuous")]
728✔
878
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
1,088✔
879
pub struct ContinuousMeasurement {
880
    pub measurement: String,
881
    pub unit: Option<String>,
882
}
883

884
impl From<geoengine_datatypes::primitives::ContinuousMeasurement> for ContinuousMeasurement {
885
    fn from(value: geoengine_datatypes::primitives::ContinuousMeasurement) -> Self {
4✔
886
        Self {
4✔
887
            r#type: Default::default(),
4✔
888
            measurement: value.measurement,
4✔
889
            unit: value.unit,
4✔
890
        }
4✔
891
    }
4✔
892
}
893

894
impl From<ContinuousMeasurement> for geoengine_datatypes::primitives::ContinuousMeasurement {
895
    fn from(value: ContinuousMeasurement) -> Self {
4✔
896
        Self {
4✔
897
            measurement: value.measurement,
4✔
898
            unit: value.unit,
4✔
899
        }
4✔
900
    }
4✔
901
}
902

903
#[type_tag(tag = "classification")]
728✔
904
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
1,452✔
905
#[serde(
906
    try_from = "SerializableClassificationMeasurement",
907
    into = "SerializableClassificationMeasurement"
908
)]
909
pub struct ClassificationMeasurement {
910
    pub measurement: String,
911
    pub classes: HashMap<u8, String>,
912
}
913

914
impl From<geoengine_datatypes::primitives::ClassificationMeasurement>
915
    for ClassificationMeasurement
916
{
917
    fn from(value: geoengine_datatypes::primitives::ClassificationMeasurement) -> Self {
×
918
        Self {
×
NEW
919
            r#type: Default::default(),
×
920
            measurement: value.measurement,
×
921
            classes: value.classes,
×
922
        }
×
923
    }
×
924
}
925

926
impl From<ClassificationMeasurement>
927
    for geoengine_datatypes::primitives::ClassificationMeasurement
928
{
929
    fn from(value: ClassificationMeasurement) -> Self {
×
930
        Self {
×
931
            measurement: value.measurement,
×
932
            classes: value.classes,
×
933
        }
×
934
    }
×
935
}
936

937
/// A type that is solely for serde's serializability.
938
/// You cannot serialize floats as JSON map keys.
939
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
940
pub struct SerializableClassificationMeasurement {
941
    pub measurement: String,
942
    // use a BTreeMap to preserve the order of the keys
943
    pub classes: BTreeMap<String, String>,
944
}
945

946
impl From<ClassificationMeasurement> for SerializableClassificationMeasurement {
947
    fn from(measurement: ClassificationMeasurement) -> Self {
×
948
        let mut classes = BTreeMap::new();
×
949
        for (k, v) in measurement.classes {
×
950
            classes.insert(k.to_string(), v);
×
951
        }
×
952
        Self {
×
953
            measurement: measurement.measurement,
×
954
            classes,
×
955
        }
×
956
    }
×
957
}
958

959
impl TryFrom<SerializableClassificationMeasurement> for ClassificationMeasurement {
960
    type Error = <u8 as FromStr>::Err;
961

962
    fn try_from(measurement: SerializableClassificationMeasurement) -> Result<Self, Self::Error> {
×
963
        let mut classes = HashMap::with_capacity(measurement.classes.len());
×
964
        for (k, v) in measurement.classes {
×
965
            classes.insert(k.parse::<u8>()?, v);
×
966
        }
967
        Ok(Self {
×
NEW
968
            r#type: Default::default(),
×
969
            measurement: measurement.measurement,
×
970
            classes,
×
971
        })
×
972
    }
×
973
}
974

975
/// A partition of space that include the upper left but excludes the lower right coordinate
976
#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Debug, ToSchema, FromSql, ToSql)]
728✔
977
#[serde(rename_all = "camelCase")]
978
pub struct SpatialPartition2D {
979
    pub upper_left_coordinate: Coordinate2D,
980
    pub lower_right_coordinate: Coordinate2D,
981
}
982

983
impl From<geoengine_datatypes::primitives::SpatialPartition2D> for SpatialPartition2D {
984
    fn from(value: geoengine_datatypes::primitives::SpatialPartition2D) -> Self {
6✔
985
        Self {
6✔
986
            upper_left_coordinate: value.upper_left().into(),
6✔
987
            lower_right_coordinate: value.lower_right().into(),
6✔
988
        }
6✔
989
    }
6✔
990
}
991

992
impl From<SpatialPartition2D> for geoengine_datatypes::primitives::SpatialPartition2D {
993
    fn from(value: SpatialPartition2D) -> Self {
10✔
994
        Self::new_unchecked(
10✔
995
            value.upper_left_coordinate.into(),
10✔
996
            value.lower_right_coordinate.into(),
10✔
997
        )
10✔
998
    }
10✔
999
}
1000

1001
/// A spatio-temporal rectangle with a specified resolution
1002
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
192✔
1003
#[serde(rename_all = "camelCase")]
1004
// #[aliases( // TODO: find solution?
1005
//     VectorQueryRectangle = QueryRectangle<BoundingBox2D>,
1006
//     RasterQueryRectangle = QueryRectangle<SpatialPartition2D>,
1007
//     PlotQueryRectangle = QueryRectangle<BoundingBox2D>)
1008
// ]
1009
pub struct QueryRectangle<SpatialBounds> {
1010
    pub spatial_bounds: SpatialBounds,
1011
    pub time_interval: TimeInterval,
1012
    pub spatial_resolution: SpatialResolution,
1013
}
1014

1015
pub type RasterQueryRectangle = QueryRectangle<SpatialPartition2D>;
1016
pub type VectorQueryRectangle = QueryRectangle<BoundingBox2D>;
1017
pub type PlotQueryRectangle = QueryRectangle<BoundingBox2D>;
1018

1019
impl
1020
    From<
1021
        geoengine_datatypes::primitives::QueryRectangle<
1022
            geoengine_datatypes::primitives::SpatialPartition2D,
1023
            geoengine_datatypes::primitives::BandSelection,
1024
        >,
1025
    > for RasterQueryRectangle
1026
{
1027
    fn from(
2✔
1028
        value: geoengine_datatypes::primitives::QueryRectangle<
2✔
1029
            geoengine_datatypes::primitives::SpatialPartition2D,
2✔
1030
            geoengine_datatypes::primitives::BandSelection,
2✔
1031
        >,
2✔
1032
    ) -> Self {
2✔
1033
        Self {
2✔
1034
            spatial_bounds: value.spatial_bounds.into(),
2✔
1035
            time_interval: value.time_interval.into(),
2✔
1036
            spatial_resolution: value.spatial_resolution.into(),
2✔
1037
        }
2✔
1038
    }
2✔
1039
}
1040

1041
impl From<QueryRectangle<SpatialPartition2D>>
1042
    for geoengine_datatypes::primitives::RasterQueryRectangle
1043
{
1044
    fn from(value: QueryRectangle<SpatialPartition2D>) -> Self {
3✔
1045
        Self {
3✔
1046
            spatial_bounds: value.spatial_bounds.into(),
3✔
1047
            time_interval: value.time_interval.into(),
3✔
1048
            spatial_resolution: value.spatial_resolution.into(),
3✔
1049
            attributes: geoengine_datatypes::primitives::BandSelection::first(), // TODO: adjust once API supports attribute selection
3✔
1050
        }
3✔
1051
    }
3✔
1052
}
1053

1054
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, ToSchema)]
12✔
1055
pub struct BandSelection(pub Vec<usize>);
1056

1057
impl From<geoengine_datatypes::primitives::BandSelection> for BandSelection {
1058
    fn from(value: geoengine_datatypes::primitives::BandSelection) -> Self {
×
1059
        Self(value.as_vec().into_iter().map(|b| b as usize).collect())
×
1060
    }
×
1061
}
1062

1063
impl TryFrom<BandSelection> for geoengine_datatypes::primitives::BandSelection {
1064
    type Error = Error;
1065

1066
    fn try_from(value: BandSelection) -> Result<Self> {
6✔
1067
        geoengine_datatypes::primitives::BandSelection::new(
6✔
1068
            value.0.into_iter().map(|b| b as u32).collect(),
8✔
1069
        )
6✔
1070
        .map_err(Into::into)
6✔
1071
    }
6✔
1072
}
1073

1074
/// The spatial resolution in SRS units
1075
#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Serialize, ToSchema)]
1,144✔
1076
pub struct SpatialResolution {
1077
    pub x: f64,
1078
    pub y: f64,
1079
}
1080

1081
impl From<geoengine_datatypes::primitives::SpatialResolution> for SpatialResolution {
1082
    fn from(value: geoengine_datatypes::primitives::SpatialResolution) -> Self {
6✔
1083
        Self {
6✔
1084
            x: value.x,
6✔
1085
            y: value.y,
6✔
1086
        }
6✔
1087
    }
6✔
1088
}
1089

1090
impl From<SpatialResolution> for geoengine_datatypes::primitives::SpatialResolution {
1091
    fn from(value: SpatialResolution) -> Self {
10✔
1092
        Self {
10✔
1093
            x: value.x,
10✔
1094
            y: value.y,
10✔
1095
        }
10✔
1096
    }
10✔
1097
}
1098

1099
#[derive(
1100
    Clone, Copy, Serialize, PartialEq, Eq, PartialOrd, Ord, Debug, ToSchema, FromSql, ToSql,
5,356✔
1101
)]
1102
#[repr(C)]
1103
#[postgres(transparent)]
1104
pub struct TimeInstance(i64);
1105

1106
impl FromStr for TimeInstance {
1107
    type Err = geoengine_datatypes::primitives::DateTimeError;
1108

1109
    fn from_str(s: &str) -> Result<Self, Self::Err> {
×
NEW
1110
        let date_time = DateTimeString::from_str(s)?;
×
1111
        Ok(date_time.into())
×
1112
    }
×
1113
}
1114

1115
impl From<geoengine_datatypes::primitives::TimeInstance> for TimeInstance {
1116
    fn from(value: geoengine_datatypes::primitives::TimeInstance) -> Self {
146✔
1117
        Self(value.inner())
146✔
1118
    }
146✔
1119
}
1120

1121
impl From<TimeInstance> for geoengine_datatypes::primitives::TimeInstance {
1122
    fn from(value: TimeInstance) -> Self {
82✔
1123
        geoengine_datatypes::primitives::TimeInstance::from_millis_unchecked(value.inner())
82✔
1124
    }
82✔
1125
}
1126

1127
impl From<DateTimeString> for TimeInstance {
NEW
1128
    fn from(datetime: DateTimeString) -> Self {
×
1129
        Self::from(&datetime)
×
1130
    }
×
1131
}
1132

1133
impl From<&DateTimeString> for TimeInstance {
NEW
1134
    fn from(datetime: &DateTimeString) -> Self {
×
1135
        geoengine_datatypes::primitives::TimeInstance::from_millis_unchecked(
×
1136
            datetime.datetime.timestamp_millis(),
×
1137
        )
×
1138
        .into()
×
1139
    }
×
1140
}
1141

1142
impl TimeInstance {
1143
    pub const fn inner(self) -> i64 {
82✔
1144
        self.0
82✔
1145
    }
82✔
1146
}
1147

1148
impl<'de> Deserialize<'de> for TimeInstance {
1149
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
18✔
1150
    where
18✔
1151
        D: serde::Deserializer<'de>,
18✔
1152
    {
18✔
1153
        struct IsoStringOrUnixTimestamp;
1154

1155
        impl serde::de::Visitor<'_> for IsoStringOrUnixTimestamp {
1156
            type Value = TimeInstance;
1157

1158
            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
×
1159
                formatter.write_str("RFC 3339 timestamp string or Unix timestamp integer")
×
1160
            }
×
1161

1162
            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
×
1163
            where
×
1164
                E: serde::de::Error,
×
1165
            {
×
1166
                TimeInstance::from_str(value).map_err(E::custom)
×
1167
            }
×
1168

1169
            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
18✔
1170
            where
18✔
1171
                E: serde::de::Error,
18✔
1172
            {
18✔
1173
                geoengine_datatypes::primitives::TimeInstance::from_millis(v)
18✔
1174
                    .map(Into::into)
18✔
1175
                    .map_err(E::custom)
18✔
1176
            }
18✔
1177

1178
            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
18✔
1179
            where
18✔
1180
                E: serde::de::Error,
18✔
1181
            {
18✔
1182
                Self::visit_i64(self, v as i64)
18✔
1183
            }
18✔
1184
        }
1185

1186
        deserializer.deserialize_any(IsoStringOrUnixTimestamp)
18✔
1187
    }
18✔
1188
}
1189

1190
/// A time granularity.
1191
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema, FromSql, ToSql)]
764✔
1192
#[serde(rename_all = "camelCase")]
1193
pub enum TimeGranularity {
1194
    Millis,
1195
    Seconds,
1196
    Minutes,
1197
    Hours,
1198
    Days,
1199
    Months,
1200
    Years,
1201
}
1202

1203
impl From<geoengine_datatypes::primitives::TimeGranularity> for TimeGranularity {
1204
    fn from(value: geoengine_datatypes::primitives::TimeGranularity) -> Self {
4✔
1205
        match value {
4✔
1206
            geoengine_datatypes::primitives::TimeGranularity::Millis => Self::Millis,
×
1207
            geoengine_datatypes::primitives::TimeGranularity::Seconds => Self::Seconds,
×
1208
            geoengine_datatypes::primitives::TimeGranularity::Minutes => Self::Minutes,
×
1209
            geoengine_datatypes::primitives::TimeGranularity::Hours => Self::Hours,
×
1210
            geoengine_datatypes::primitives::TimeGranularity::Days => Self::Days,
×
1211
            geoengine_datatypes::primitives::TimeGranularity::Months => Self::Months,
4✔
1212
            geoengine_datatypes::primitives::TimeGranularity::Years => Self::Years,
×
1213
        }
1214
    }
4✔
1215
}
1216

1217
impl From<TimeGranularity> for geoengine_datatypes::primitives::TimeGranularity {
1218
    fn from(value: TimeGranularity) -> Self {
4✔
1219
        match value {
4✔
1220
            TimeGranularity::Millis => Self::Millis,
×
1221
            TimeGranularity::Seconds => Self::Seconds,
×
1222
            TimeGranularity::Minutes => Self::Minutes,
×
1223
            TimeGranularity::Hours => Self::Hours,
×
1224
            TimeGranularity::Days => Self::Days,
×
1225
            TimeGranularity::Months => Self::Months,
4✔
1226
            TimeGranularity::Years => Self::Years,
×
1227
        }
1228
    }
4✔
1229
}
1230

1231
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema, FromSql, ToSql)]
748✔
1232
pub struct TimeStep {
1233
    pub granularity: TimeGranularity,
1234
    pub step: u32, // TODO: ensure on deserialization it is > 0
1235
}
1236

1237
impl From<geoengine_datatypes::primitives::TimeStep> for TimeStep {
1238
    fn from(value: geoengine_datatypes::primitives::TimeStep) -> Self {
4✔
1239
        Self {
4✔
1240
            granularity: value.granularity.into(),
4✔
1241
            step: value.step,
4✔
1242
        }
4✔
1243
    }
4✔
1244
}
1245

1246
impl From<TimeStep> for geoengine_datatypes::primitives::TimeStep {
1247
    fn from(value: TimeStep) -> Self {
4✔
1248
        Self {
4✔
1249
            granularity: value.granularity.into(),
4✔
1250
            step: value.step,
4✔
1251
        }
4✔
1252
    }
4✔
1253
}
1254

1255
/// Stores time intervals in ms in close-open semantic [start, end)
1256
#[derive(Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ToSql, FromSql, ToSchema)]
2,596✔
1257
pub struct TimeInterval {
1258
    start: TimeInstance,
1259
    end: TimeInstance,
1260
}
1261

1262
impl From<TimeInterval> for geoengine_datatypes::primitives::TimeInterval {
1263
    fn from(value: TimeInterval) -> Self {
41✔
1264
        geoengine_datatypes::primitives::TimeInterval::new_unchecked::<
41✔
1265
            geoengine_datatypes::primitives::TimeInstance,
41✔
1266
            geoengine_datatypes::primitives::TimeInstance,
41✔
1267
        >(value.start.into(), value.end.into())
41✔
1268
    }
41✔
1269
}
1270

1271
impl From<geoengine_datatypes::primitives::TimeInterval> for TimeInterval {
1272
    fn from(value: geoengine_datatypes::primitives::TimeInterval) -> Self {
64✔
1273
        Self {
64✔
1274
            start: value.start().into(),
64✔
1275
            end: value.end().into(),
64✔
1276
        }
64✔
1277
    }
64✔
1278
}
1279

1280
impl core::fmt::Debug for TimeInterval {
1281
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
×
1282
        write!(
×
1283
            f,
×
1284
            "TimeInterval [{}, {})",
×
1285
            self.start.inner(),
×
1286
            &self.end.inner()
×
1287
        )
×
1288
    }
×
1289
}
1290

1291
#[derive(
1292
    Debug,
1293
    Ord,
1294
    PartialOrd,
1295
    Eq,
1296
    PartialEq,
1297
    Hash,
1298
    Deserialize,
1299
    Serialize,
1300
    Copy,
1301
    Clone,
1302
    ToSchema,
860✔
1303
    FromSql,
×
1304
    ToSql,
1305
)]
1306
pub enum RasterDataType {
1307
    U8,
1308
    U16,
1309
    U32,
1310
    U64,
1311
    I8,
1312
    I16,
1313
    I32,
1314
    I64,
1315
    F32,
1316
    F64,
1317
}
1318

1319
impl From<geoengine_datatypes::raster::RasterDataType> for RasterDataType {
1320
    fn from(value: geoengine_datatypes::raster::RasterDataType) -> Self {
4✔
1321
        match value {
4✔
1322
            geoengine_datatypes::raster::RasterDataType::U8 => Self::U8,
4✔
1323
            geoengine_datatypes::raster::RasterDataType::U16 => Self::U16,
×
1324
            geoengine_datatypes::raster::RasterDataType::U32 => Self::U32,
×
1325
            geoengine_datatypes::raster::RasterDataType::U64 => Self::U64,
×
1326
            geoengine_datatypes::raster::RasterDataType::I8 => Self::I8,
×
1327
            geoengine_datatypes::raster::RasterDataType::I16 => Self::I16,
×
1328
            geoengine_datatypes::raster::RasterDataType::I32 => Self::I32,
×
1329
            geoengine_datatypes::raster::RasterDataType::I64 => Self::I64,
×
1330
            geoengine_datatypes::raster::RasterDataType::F32 => Self::F32,
×
1331
            geoengine_datatypes::raster::RasterDataType::F64 => Self::F64,
×
1332
        }
1333
    }
4✔
1334
}
1335

1336
impl From<RasterDataType> for geoengine_datatypes::raster::RasterDataType {
1337
    fn from(value: RasterDataType) -> Self {
4✔
1338
        match value {
4✔
1339
            RasterDataType::U8 => Self::U8,
4✔
1340
            RasterDataType::U16 => Self::U16,
×
1341
            RasterDataType::U32 => Self::U32,
×
1342
            RasterDataType::U64 => Self::U64,
×
1343
            RasterDataType::I8 => Self::I8,
×
1344
            RasterDataType::I16 => Self::I16,
×
1345
            RasterDataType::I32 => Self::I32,
×
1346
            RasterDataType::I64 => Self::I64,
×
1347
            RasterDataType::F32 => Self::F32,
×
1348
            RasterDataType::F64 => Self::F64,
×
1349
        }
1350
    }
4✔
1351
}
1352

1353
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
×
1354
#[serde(rename_all = "UPPERCASE")]
1355
pub enum ResamplingMethod {
1356
    Nearest,
1357
    Average,
1358
    Bilinear,
1359
    Cubic,
1360
    CubicSpline,
1361
    Lanczos,
1362
}
1363

1364
impl std::fmt::Display for ResamplingMethod {
1365
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
1366
        match self {
×
1367
            ResamplingMethod::Nearest => write!(f, "NEAREST"),
×
1368
            ResamplingMethod::Average => write!(f, "AVERAGE"),
×
1369
            ResamplingMethod::Bilinear => write!(f, "BILINEAR"),
×
1370
            ResamplingMethod::Cubic => write!(f, "CUBIC"),
×
1371
            ResamplingMethod::CubicSpline => write!(f, "CUBICSPLINE"),
×
1372
            ResamplingMethod::Lanczos => write!(f, "LANCZOS"),
×
1373
        }
1374
    }
×
1375
}
1376

1377
impl From<ResamplingMethod> for geoengine_datatypes::util::gdal::ResamplingMethod {
1378
    fn from(value: ResamplingMethod) -> Self {
×
1379
        match value {
×
1380
            ResamplingMethod::Nearest => Self::Nearest,
×
1381
            ResamplingMethod::Average => Self::Average,
×
1382
            ResamplingMethod::Bilinear => Self::Bilinear,
×
1383
            ResamplingMethod::Cubic => Self::Cubic,
×
1384
            ResamplingMethod::CubicSpline => Self::CubicSpline,
×
1385
            ResamplingMethod::Lanczos => Self::Lanczos,
×
1386
        }
1387
    }
×
1388
}
1389

1390
/// `RgbaColor` defines a 32 bit RGB color with alpha value
1391
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
1392
pub struct RgbaColor(pub [u8; 4]);
1393

1394
impl PartialSchema for RgbaColor {
1395
    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::Schema> {
10,384✔
1396
        use utoipa::openapi::schema::{ArrayBuilder, ObjectBuilder, SchemaType, Type};
1397
        ArrayBuilder::new()
10,384✔
1398
            .items(ObjectBuilder::new().schema_type(SchemaType::Type(Type::Integer)))
10,384✔
1399
            .min_items(Some(4))
10,384✔
1400
            .max_items(Some(4))
10,384✔
1401
            .into()
10,384✔
1402
    }
10,384✔
1403
}
1404

1405
// manual implementation utoipa generates an integer field
1406
impl ToSchema for RgbaColor {}
1407

1408
impl From<geoengine_datatypes::operations::image::RgbaColor> for RgbaColor {
1409
    fn from(color: geoengine_datatypes::operations::image::RgbaColor) -> Self {
27✔
1410
        Self(color.into_inner())
27✔
1411
    }
27✔
1412
}
1413

1414
impl From<RgbaColor> for geoengine_datatypes::operations::image::RgbaColor {
1415
    fn from(color: RgbaColor) -> Self {
22✔
1416
        Self::new(color.0[0], color.0[1], color.0[2], color.0[3])
22✔
1417
    }
22✔
1418
}
1419

1420
/// A container type for breakpoints that specify a value to color mapping
1421
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
1422
pub struct Breakpoint {
1423
    pub value: NotNanF64,
1424
    pub color: RgbaColor,
1425
}
1426

1427
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
1428
pub struct NotNanF64(NotNan<f64>);
1429

1430
impl From<NotNan<f64>> for NotNanF64 {
1431
    fn from(value: NotNan<f64>) -> Self {
10✔
1432
        Self(value)
10✔
1433
    }
10✔
1434
}
1435

1436
impl From<NotNanF64> for NotNan<f64> {
1437
    fn from(value: NotNanF64) -> Self {
8✔
1438
        value.0
8✔
1439
    }
8✔
1440
}
1441

1442
impl ToSql for NotNanF64 {
1443
    fn to_sql(
×
1444
        &self,
×
1445
        ty: &postgres_types::Type,
×
1446
        w: &mut bytes::BytesMut,
×
1447
    ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
×
1448
        <f64 as ToSql>::to_sql(&self.0.into_inner(), ty, w)
×
1449
    }
×
1450

1451
    fn accepts(ty: &postgres_types::Type) -> bool {
×
1452
        <f64 as ToSql>::accepts(ty)
×
1453
    }
×
1454

1455
    postgres_types::to_sql_checked!();
1456
}
1457

1458
impl<'a> FromSql<'a> for NotNanF64 {
1459
    fn from_sql(
×
1460
        ty: &postgres_types::Type,
×
1461
        raw: &'a [u8],
×
1462
    ) -> Result<NotNanF64, Box<dyn std::error::Error + Sync + Send>> {
×
1463
        let value = <f64 as FromSql>::from_sql(ty, raw)?;
×
1464

1465
        Ok(NotNanF64(value.try_into()?))
×
1466
    }
×
1467

1468
    fn accepts(ty: &postgres_types::Type) -> bool {
×
1469
        <f64 as FromSql>::accepts(ty)
×
1470
    }
×
1471
}
1472

1473
impl PartialSchema for Breakpoint {
1474
    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::Schema> {
2,084✔
1475
        use utoipa::openapi::schema::{Object, ObjectBuilder, Ref, SchemaType, Type};
1476
        ObjectBuilder::new()
2,084✔
1477
            .property("value", Object::with_type(SchemaType::Type(Type::Number)))
2,084✔
1478
            .property("color", Ref::from_schema_name("RgbaColor"))
2,084✔
1479
            .required("value")
2,084✔
1480
            .required("color")
2,084✔
1481
            .into()
2,084✔
1482
    }
2,084✔
1483
}
1484

1485
// manual implementation because of NotNan
1486
impl ToSchema for Breakpoint {}
1487

1488
impl From<geoengine_datatypes::operations::image::Breakpoint> for Breakpoint {
1489
    fn from(breakpoint: geoengine_datatypes::operations::image::Breakpoint) -> Self {
10✔
1490
        Self {
10✔
1491
            value: breakpoint.value.into(),
10✔
1492
            color: breakpoint.color.into(),
10✔
1493
        }
10✔
1494
    }
10✔
1495
}
1496

1497
impl From<Breakpoint> for geoengine_datatypes::operations::image::Breakpoint {
1498
    fn from(breakpoint: Breakpoint) -> Self {
8✔
1499
        Self {
8✔
1500
            value: breakpoint.value.into(),
8✔
1501
            color: breakpoint.color.into(),
8✔
1502
        }
8✔
1503
    }
8✔
1504
}
1505

1506
#[type_tag(tag = "linearGradient")]
2,080✔
1507
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, ToSchema)]
4,156✔
1508
#[serde(rename_all = "camelCase")]
1509
pub struct LinearGradient {
1510
    pub breakpoints: Vec<Breakpoint>,
1511
    pub no_data_color: RgbaColor,
1512
    pub over_color: RgbaColor,
1513
    pub under_color: RgbaColor,
1514
}
1515

1516
#[type_tag(tag = "logarithmicGradient")]
2,080✔
1517
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, ToSchema)]
4,156✔
1518
#[serde(rename_all = "camelCase")]
1519
pub struct LogarithmicGradient {
1520
    pub breakpoints: Vec<Breakpoint>,
1521
    pub no_data_color: RgbaColor,
1522
    pub over_color: RgbaColor,
1523
    pub under_color: RgbaColor,
1524
}
1525

1526
#[type_tag(tag = "palette")]
2,072✔
1527
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, ToSchema)]
4,144✔
1528
#[serde(rename_all = "camelCase")]
1529
pub struct PaletteColorizer {
1530
    pub colors: Palette,
1531
    pub no_data_color: RgbaColor,
1532
    pub default_color: RgbaColor,
1533
}
1534

1535
/// A colorizer specifies a mapping between raster values and an output image
1536
/// There are different variants that perform different kinds of mapping.
1537
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, ToSchema)]
4,140✔
1538
#[serde(rename_all = "camelCase", untagged)]
1539
#[schema(discriminator = "type")]
1540
pub enum Colorizer {
1541
    LinearGradient(LinearGradient),
1542
    LogarithmicGradient(LogarithmicGradient),
1543
    Palette(PaletteColorizer),
1544
}
1545

1546
impl From<geoengine_datatypes::operations::image::Colorizer> for Colorizer {
1547
    fn from(v: geoengine_datatypes::operations::image::Colorizer) -> Self {
5✔
1548
        match v {
5✔
1549
            geoengine_datatypes::operations::image::Colorizer::LinearGradient {
1550
                breakpoints,
5✔
1551
                no_data_color,
5✔
1552
                over_color,
5✔
1553
                under_color,
5✔
1554
            } => Self::LinearGradient(LinearGradient {
5✔
1555
                r#type: Default::default(),
5✔
1556
                breakpoints: breakpoints
5✔
1557
                    .into_iter()
5✔
1558
                    .map(Into::into)
5✔
1559
                    .collect::<Vec<Breakpoint>>(),
5✔
1560
                no_data_color: no_data_color.into(),
5✔
1561
                over_color: over_color.into(),
5✔
1562
                under_color: under_color.into(),
5✔
1563
            }),
5✔
1564
            geoengine_datatypes::operations::image::Colorizer::LogarithmicGradient {
1565
                breakpoints,
×
1566
                no_data_color,
×
1567
                over_color,
×
1568
                under_color,
×
1569
            } => Self::LogarithmicGradient(LogarithmicGradient {
×
NEW
1570
                r#type: Default::default(),
×
1571
                breakpoints: breakpoints
×
1572
                    .into_iter()
×
1573
                    .map(Into::into)
×
1574
                    .collect::<Vec<Breakpoint>>(),
×
1575
                no_data_color: no_data_color.into(),
×
1576
                over_color: over_color.into(),
×
1577
                under_color: under_color.into(),
×
1578
            }),
×
1579
            geoengine_datatypes::operations::image::Colorizer::Palette {
1580
                colors,
×
1581
                no_data_color,
×
1582
                default_color,
×
NEW
1583
            } => Self::Palette(PaletteColorizer {
×
NEW
1584
                r#type: Default::default(),
×
1585
                colors: colors.into(),
×
1586
                no_data_color: no_data_color.into(),
×
1587
                default_color: default_color.into(),
×
NEW
1588
            }),
×
1589
        }
1590
    }
5✔
1591
}
1592

1593
impl From<Colorizer> for geoengine_datatypes::operations::image::Colorizer {
1594
    fn from(v: Colorizer) -> Self {
4✔
1595
        match v {
4✔
1596
            Colorizer::LinearGradient(linear_gradient) => Self::LinearGradient {
4✔
1597
                breakpoints: linear_gradient
4✔
1598
                    .breakpoints
4✔
1599
                    .into_iter()
4✔
1600
                    .map(Into::into)
4✔
1601
                    .collect::<Vec<geoengine_datatypes::operations::image::Breakpoint>>(),
4✔
1602
                no_data_color: linear_gradient.no_data_color.into(),
4✔
1603
                over_color: linear_gradient.over_color.into(),
4✔
1604
                under_color: linear_gradient.under_color.into(),
4✔
1605
            },
4✔
1606
            Colorizer::LogarithmicGradient(logarithmic_gradient) => Self::LogarithmicGradient {
×
1607
                breakpoints: logarithmic_gradient
×
1608
                    .breakpoints
×
1609
                    .into_iter()
×
1610
                    .map(Into::into)
×
1611
                    .collect::<Vec<geoengine_datatypes::operations::image::Breakpoint>>(),
×
1612
                no_data_color: logarithmic_gradient.no_data_color.into(),
×
1613
                over_color: logarithmic_gradient.over_color.into(),
×
1614
                under_color: logarithmic_gradient.under_color.into(),
×
1615
            },
×
1616

1617
            Colorizer::Palette(PaletteColorizer {
1618
                colors,
×
1619
                no_data_color,
×
1620
                default_color,
×
NEW
1621
                ..
×
NEW
1622
            }) => Self::Palette {
×
1623
                colors: colors.into(),
×
1624
                no_data_color: no_data_color.into(),
×
1625
                default_color: default_color.into(),
×
1626
            },
×
1627
        }
1628
    }
4✔
1629
}
1630

1631
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, ToSchema)]
348✔
1632
#[serde(rename_all = "camelCase", untagged)]
1633
#[schema(discriminator = "type")]
1634
pub enum RasterColorizer {
1635
    SingleBand(SingleBandRasterColorizer),
1636
    MultiBand(MultiBandRasterColorizer),
1637
}
1638

1639
#[type_tag(tag = "singleBand")]
176✔
1640
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, ToSchema)]
352✔
1641
#[serde(rename_all = "camelCase")]
1642
pub struct SingleBandRasterColorizer {
1643
    pub band: u32,
1644
    pub band_colorizer: Colorizer,
1645
}
1646

1647
#[type_tag(tag = "multiBand")]
176✔
1648
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, ToSchema)]
352✔
1649
#[serde(rename_all = "camelCase")]
1650
pub struct MultiBandRasterColorizer {
1651
    /// The band index of the red channel.
1652
    pub red_band: u32,
1653
    /// The minimum value for the red channel.
1654
    pub red_min: f64,
1655
    /// The maximum value for the red channel.
1656
    pub red_max: f64,
1657
    /// A scaling factor for the red channel between 0 and 1.
1658
    #[serde(default = "num_traits::One::one")]
1659
    pub red_scale: f64,
1660

1661
    /// The band index of the green channel.
1662
    pub green_band: u32,
1663
    /// The minimum value for the red channel.
1664
    pub green_min: f64,
1665
    /// The maximum value for the red channel.
1666
    pub green_max: f64,
1667
    /// A scaling factor for the green channel between 0 and 1.
1668
    #[serde(default = "num_traits::One::one")]
1669
    pub green_scale: f64,
1670

1671
    /// The band index of the blue channel.
1672
    pub blue_band: u32,
1673
    /// The minimum value for the red channel.
1674
    pub blue_min: f64,
1675
    /// The maximum value for the red channel.
1676
    pub blue_max: f64,
1677
    /// A scaling factor for the blue channel between 0 and 1.
1678
    #[serde(default = "num_traits::One::one")]
1679
    pub blue_scale: f64,
1680

1681
    /// The color to use for no data values.
1682
    /// If not specified, the no data values will be transparent.
1683
    #[serde(default = "rgba_transparent")]
1684
    pub no_data_color: RgbaColor,
1685
}
1686

1687
fn rgba_transparent() -> RgbaColor {
×
1688
    RgbaColor([0, 0, 0, 0])
×
1689
}
×
1690

1691
impl Eq for RasterColorizer {}
1692

1693
impl RasterColorizer {
1694
    pub fn band_selection(&self) -> BandSelection {
6✔
1695
        match self {
6✔
1696
            RasterColorizer::SingleBand(SingleBandRasterColorizer { band, .. }) => {
4✔
1697
                BandSelection(vec![*band as usize])
4✔
1698
            }
1699
            RasterColorizer::MultiBand(MultiBandRasterColorizer {
1700
                red_band,
2✔
1701
                green_band,
2✔
1702
                blue_band,
2✔
1703
                ..
2✔
1704
            }) => {
2✔
1705
                let mut bands = Vec::with_capacity(3);
2✔
1706
                for band in [
6✔
1707
                    *red_band as usize,
2✔
1708
                    *green_band as usize,
2✔
1709
                    *blue_band as usize,
2✔
1710
                ] {
1711
                    if !bands.contains(&band) {
6✔
1712
                        bands.push(band);
4✔
1713
                    }
4✔
1714
                }
1715
                bands.sort_unstable(); // bands will be returned in ascending order anyway
2✔
1716
                BandSelection(bands)
2✔
1717
            }
1718
        }
1719
    }
6✔
1720
}
1721

1722
impl From<geoengine_datatypes::operations::image::RasterColorizer> for RasterColorizer {
1723
    fn from(v: geoengine_datatypes::operations::image::RasterColorizer) -> Self {
×
1724
        match v {
×
1725
            geoengine_datatypes::operations::image::RasterColorizer::SingleBand {
1726
                band,
×
1727
                band_colorizer: colorizer,
×
NEW
1728
            } => Self::SingleBand(SingleBandRasterColorizer {
×
NEW
1729
                r#type: Default::default(),
×
1730
                band,
×
1731
                band_colorizer: colorizer.into(),
×
NEW
1732
            }),
×
1733
            geoengine_datatypes::operations::image::RasterColorizer::MultiBand {
1734
                red_band,
×
1735
                green_band,
×
1736
                blue_band,
×
1737
                rgb_params,
×
NEW
1738
            } => Self::MultiBand(MultiBandRasterColorizer {
×
NEW
1739
                r#type: Default::default(),
×
1740
                red_band,
×
1741
                green_band,
×
1742
                blue_band,
×
1743
                red_min: rgb_params.red_min,
×
1744
                red_max: rgb_params.red_max,
×
1745
                red_scale: rgb_params.red_scale,
×
1746
                green_min: rgb_params.green_min,
×
1747
                green_max: rgb_params.green_max,
×
1748
                green_scale: rgb_params.green_scale,
×
1749
                blue_min: rgb_params.blue_min,
×
1750
                blue_max: rgb_params.blue_max,
×
1751
                blue_scale: rgb_params.blue_scale,
×
1752
                no_data_color: rgb_params.no_data_color.into(),
×
NEW
1753
            }),
×
1754
        }
1755
    }
×
1756
}
1757

1758
impl From<RasterColorizer> for geoengine_datatypes::operations::image::RasterColorizer {
1759
    fn from(v: RasterColorizer) -> Self {
6✔
1760
        match v {
6✔
1761
            RasterColorizer::SingleBand(SingleBandRasterColorizer {
1762
                band,
4✔
1763
                band_colorizer: colorizer,
4✔
1764
                ..
4✔
1765
            }) => Self::SingleBand {
4✔
1766
                band,
4✔
1767
                band_colorizer: colorizer.into(),
4✔
1768
            },
4✔
1769
            RasterColorizer::MultiBand(MultiBandRasterColorizer {
1770
                red_band,
2✔
1771
                red_min,
2✔
1772
                red_max,
2✔
1773
                red_scale,
2✔
1774
                green_band,
2✔
1775
                green_min,
2✔
1776
                green_max,
2✔
1777
                green_scale,
2✔
1778
                blue_band,
2✔
1779
                blue_min,
2✔
1780
                blue_max,
2✔
1781
                blue_scale,
2✔
1782
                no_data_color,
2✔
1783
                ..
2✔
1784
            }) => Self::MultiBand {
2✔
1785
                red_band,
2✔
1786
                green_band,
2✔
1787
                blue_band,
2✔
1788
                rgb_params: RgbParams {
2✔
1789
                    red_min,
2✔
1790
                    red_max,
2✔
1791
                    red_scale,
2✔
1792
                    green_min,
2✔
1793
                    green_max,
2✔
1794
                    green_scale,
2✔
1795
                    blue_min,
2✔
1796
                    blue_max,
2✔
1797
                    blue_scale,
2✔
1798
                    no_data_color: no_data_color.into(),
2✔
1799
                },
2✔
1800
            },
2✔
1801
        }
1802
    }
6✔
1803
}
1804

1805
/// A map from value to color
1806
///
1807
/// It is assumed that is has at least one and at most 256 entries.
1808
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
4,156✔
1809
#[serde(try_from = "SerializablePalette", into = "SerializablePalette")]
1810
#[schema(value_type = HashMap<f64, RgbaColor>)]
1811
pub struct Palette(pub HashMap<NotNan<f64>, RgbaColor>);
1812

1813
impl From<geoengine_datatypes::operations::image::Palette> for Palette {
1814
    fn from(palette: geoengine_datatypes::operations::image::Palette) -> Self {
×
1815
        Self(
×
1816
            palette
×
1817
                .into_inner()
×
1818
                .into_iter()
×
1819
                .map(|(value, color)| (value, color.into()))
×
1820
                .collect(),
×
1821
        )
×
1822
    }
×
1823
}
1824

1825
impl From<Palette> for geoengine_datatypes::operations::image::Palette {
1826
    fn from(palette: Palette) -> Self {
×
1827
        Self::new(
×
1828
            palette
×
1829
                .0
×
1830
                .into_iter()
×
1831
                .map(|(value, color)| (value, color.into()))
×
1832
                .collect(),
×
1833
        )
×
1834
    }
×
1835
}
1836

1837
/// A type that is solely for serde's serializability.
1838
/// You cannot serialize floats as JSON map keys.
1839
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1840
pub struct SerializablePalette(HashMap<String, RgbaColor>);
1841

1842
impl From<Palette> for SerializablePalette {
1843
    fn from(palette: Palette) -> Self {
×
1844
        Self(
×
1845
            palette
×
1846
                .0
×
1847
                .into_iter()
×
1848
                .map(|(k, v)| (k.to_string(), v))
×
1849
                .collect(),
×
1850
        )
×
1851
    }
×
1852
}
1853

1854
impl TryFrom<SerializablePalette> for Palette {
1855
    type Error = <NotNan<f64> as FromStr>::Err;
1856

1857
    fn try_from(palette: SerializablePalette) -> Result<Self, Self::Error> {
×
1858
        let mut inner = HashMap::<NotNan<f64>, RgbaColor>::with_capacity(palette.0.len());
×
1859
        for (k, v) in palette.0 {
×
1860
            inner.insert(k.parse()?, v);
×
1861
        }
1862
        Ok(Self(inner))
×
1863
    }
×
1864
}
1865

1866
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Hash, Eq, PartialOrd, Ord, ToSchema)]
944✔
1867
pub struct RasterPropertiesKey {
1868
    pub domain: Option<String>,
1869
    pub key: String,
1870
}
1871

1872
impl From<geoengine_datatypes::raster::RasterPropertiesKey> for RasterPropertiesKey {
1873
    fn from(value: geoengine_datatypes::raster::RasterPropertiesKey) -> Self {
×
1874
        Self {
×
1875
            domain: value.domain,
×
1876
            key: value.key,
×
1877
        }
×
1878
    }
×
1879
}
1880

1881
impl From<RasterPropertiesKey> for geoengine_datatypes::raster::RasterPropertiesKey {
1882
    fn from(value: RasterPropertiesKey) -> Self {
×
1883
        Self {
×
1884
            domain: value.domain,
×
1885
            key: value.key,
×
1886
        }
×
1887
    }
×
1888
}
1889

1890
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ToSchema)]
636✔
1891
pub enum RasterPropertiesEntryType {
1892
    Number,
1893
    String,
1894
}
1895

1896
impl From<geoengine_datatypes::raster::RasterPropertiesEntryType> for RasterPropertiesEntryType {
1897
    fn from(value: geoengine_datatypes::raster::RasterPropertiesEntryType) -> Self {
×
1898
        match value {
×
1899
            geoengine_datatypes::raster::RasterPropertiesEntryType::Number => Self::Number,
×
1900
            geoengine_datatypes::raster::RasterPropertiesEntryType::String => Self::String,
×
1901
        }
1902
    }
×
1903
}
1904

1905
impl From<RasterPropertiesEntryType> for geoengine_datatypes::raster::RasterPropertiesEntryType {
1906
    fn from(value: RasterPropertiesEntryType) -> Self {
×
1907
        match value {
×
1908
            RasterPropertiesEntryType::Number => Self::Number,
×
1909
            RasterPropertiesEntryType::String => Self::String,
×
1910
        }
1911
    }
×
1912
}
1913

1914
#[derive(PartialEq, Eq, Clone, Debug)]
1915
pub struct DateTimeParseFormat {
1916
    fmt: String,
1917
    has_tz: bool,
1918
    has_time: bool,
1919
}
1920

1921
impl PartialSchema for DateTimeParseFormat {
1922
    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::Schema> {
368✔
1923
        use utoipa::openapi::schema::{ObjectBuilder, SchemaType, Type};
1924
        ObjectBuilder::new()
368✔
1925
            .schema_type(SchemaType::Type(Type::String))
368✔
1926
            .into()
368✔
1927
    }
368✔
1928
}
1929

1930
impl ToSchema for DateTimeParseFormat {}
1931

1932
impl<'de> Deserialize<'de> for DateTimeParseFormat {
1933
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
4✔
1934
    where
4✔
1935
        D: serde::Deserializer<'de>,
4✔
1936
    {
4✔
1937
        let s = String::deserialize(deserializer)?;
4✔
1938
        Ok(geoengine_datatypes::primitives::DateTimeParseFormat::custom(s).into())
4✔
1939
    }
4✔
1940
}
1941

1942
impl Serialize for DateTimeParseFormat {
1943
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
5✔
1944
    where
5✔
1945
        S: serde::Serializer,
5✔
1946
    {
5✔
1947
        serializer.serialize_str(&self.fmt)
5✔
1948
    }
5✔
1949
}
1950

1951
impl From<geoengine_datatypes::primitives::DateTimeParseFormat> for DateTimeParseFormat {
1952
    fn from(value: geoengine_datatypes::primitives::DateTimeParseFormat) -> Self {
8✔
1953
        Self {
8✔
1954
            fmt: value.parse_format().to_string(),
8✔
1955
            has_tz: value.has_tz(),
8✔
1956
            has_time: value.has_time(),
8✔
1957
        }
8✔
1958
    }
8✔
1959
}
1960

1961
impl From<DateTimeParseFormat> for geoengine_datatypes::primitives::DateTimeParseFormat {
1962
    fn from(value: DateTimeParseFormat) -> Self {
4✔
1963
        Self::custom(value.fmt)
4✔
1964
    }
4✔
1965
}
1966

1967
impl DateTimeParseFormat {
1968
    // this is used as default value
1969
    pub fn unix() -> Self {
×
1970
        geoengine_datatypes::primitives::DateTimeParseFormat::unix().into()
×
1971
    }
×
1972
}
1973

1974
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
332✔
1975
pub struct NoGeometry;
1976

1977
impl From<geoengine_datatypes::primitives::NoGeometry> for NoGeometry {
1978
    fn from(_: geoengine_datatypes::primitives::NoGeometry) -> Self {
×
1979
        Self {}
×
1980
    }
×
1981
}
1982

1983
impl From<NoGeometry> for geoengine_datatypes::primitives::NoGeometry {
1984
    fn from(_: NoGeometry) -> Self {
×
1985
        Self {}
×
1986
    }
×
1987
}
1988

1989
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
332✔
1990
pub struct MultiPoint {
1991
    pub coordinates: Vec<Coordinate2D>,
1992
}
1993

1994
impl From<geoengine_datatypes::primitives::MultiPoint> for MultiPoint {
1995
    fn from(value: geoengine_datatypes::primitives::MultiPoint) -> Self {
×
1996
        Self {
×
1997
            coordinates: value.points().iter().map(|x| (*x).into()).collect(),
×
1998
        }
×
1999
    }
×
2000
}
2001

2002
impl From<MultiPoint> for geoengine_datatypes::primitives::MultiPoint {
2003
    fn from(value: MultiPoint) -> Self {
×
2004
        Self::new(value.coordinates.into_iter().map(Into::into).collect())
×
2005
            .expect("it should always be able to convert it")
×
2006
    }
×
2007
}
2008

2009
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
332✔
2010
pub struct MultiLineString {
2011
    pub coordinates: Vec<Vec<Coordinate2D>>,
2012
}
2013

2014
impl From<geoengine_datatypes::primitives::MultiLineString> for MultiLineString {
2015
    fn from(value: geoengine_datatypes::primitives::MultiLineString) -> Self {
×
2016
        Self {
×
2017
            coordinates: value
×
2018
                .lines()
×
2019
                .iter()
×
2020
                .map(|x| x.iter().map(|x| (*x).into()).collect())
×
2021
                .collect(),
×
2022
        }
×
2023
    }
×
2024
}
2025

2026
impl From<MultiLineString> for geoengine_datatypes::primitives::MultiLineString {
2027
    fn from(value: MultiLineString) -> Self {
×
2028
        Self::new(
×
2029
            value
×
2030
                .coordinates
×
2031
                .into_iter()
×
2032
                .map(|x| x.into_iter().map(Into::into).collect())
×
2033
                .collect(),
×
2034
        )
×
2035
        .expect("it should always be able to convert it")
×
2036
    }
×
2037
}
2038

2039
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
332✔
2040
pub struct MultiPolygon {
2041
    pub polygons: Vec<Vec<Vec<Coordinate2D>>>,
2042
}
2043

2044
impl From<geoengine_datatypes::primitives::MultiPolygon> for MultiPolygon {
2045
    fn from(value: geoengine_datatypes::primitives::MultiPolygon) -> Self {
×
2046
        Self {
×
2047
            polygons: value
×
2048
                .polygons()
×
2049
                .iter()
×
2050
                .map(|x| {
×
2051
                    x.iter()
×
2052
                        .map(|y| y.iter().map(|y| (*y).into()).collect())
×
2053
                        .collect()
×
2054
                })
×
2055
                .collect(),
×
2056
        }
×
2057
    }
×
2058
}
2059

2060
impl From<MultiPolygon> for geoengine_datatypes::primitives::MultiPolygon {
2061
    fn from(value: MultiPolygon) -> Self {
×
2062
        Self::new(
×
2063
            value
×
2064
                .polygons
×
2065
                .iter()
×
2066
                .map(|x| {
×
2067
                    x.iter()
×
2068
                        .map(|y| y.iter().map(|y| (*y).into()).collect())
×
2069
                        .collect()
×
2070
                })
×
2071
                .collect(),
×
2072
        )
×
2073
        .expect("it should always be able to convert it")
×
2074
    }
×
2075
}
2076

2077
#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, Clone)]
2078
pub struct StringPair((String, String));
2079

2080
pub type GdalConfigOption = StringPair;
2081
pub type AxisLabels = StringPair;
2082

2083
impl PartialSchema for StringPair {
2084
    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::Schema> {
172✔
2085
        use utoipa::openapi::schema::{ArrayBuilder, Object, SchemaType, Type};
2086
        ArrayBuilder::new()
172✔
2087
            .items(Object::with_type(SchemaType::Type(Type::String)))
172✔
2088
            .min_items(Some(2))
172✔
2089
            .max_items(Some(2))
172✔
2090
            .into()
172✔
2091
    }
172✔
2092
}
2093

2094
impl ToSchema for StringPair {
2095
    // fn aliases() -> Vec<(&'a str, utoipa::openapi::Schema)> { // TODO: how to do this?
2096
    //     let utoipa::openapi::RefOr::T(unpacked_schema) = Self::schema().1 else {
2097
    //         unreachable!()
2098
    //     };
2099
    //     vec![
2100
    //         ("GdalConfigOption", unpacked_schema.clone()),
2101
    //         ("AxisLabels", unpacked_schema),
2102
    //     ]
2103
    // }
2104
}
2105

2106
impl From<(String, String)> for StringPair {
2107
    fn from(value: (String, String)) -> Self {
32✔
2108
        Self(value)
32✔
2109
    }
32✔
2110
}
2111

2112
impl From<StringPair> for (String, String) {
2113
    fn from(value: StringPair) -> Self {
×
2114
        value.0
×
2115
    }
×
2116
}
2117

2118
impl From<StringPair> for geoengine_datatypes::util::StringPair {
2119
    fn from(value: StringPair) -> Self {
2✔
2120
        Self::new(value.0 .0, value.0 .1)
2✔
2121
    }
2✔
2122
}
2123

2124
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize, ToSchema)]
44✔
2125
pub enum PlotOutputFormat {
2126
    JsonPlain,
2127
    JsonVega,
2128
    ImagePng,
2129
}
2130

2131
#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize, PartialOrd, Deserialize, ToSchema)]
908✔
2132
pub struct CacheTtlSeconds(u32);
2133

2134
const MAX_CACHE_TTL_SECONDS: u32 = 31_536_000; // 1 year
2135

2136
impl CacheTtlSeconds {
2137
    pub fn new(seconds: u32) -> Self {
×
2138
        Self(seconds.min(MAX_CACHE_TTL_SECONDS))
×
2139
    }
×
2140

2141
    pub fn max() -> Self {
×
2142
        Self(MAX_CACHE_TTL_SECONDS)
×
2143
    }
×
2144

2145
    pub fn seconds(self) -> u32 {
×
2146
        self.0
×
2147
    }
×
2148
}
2149

2150
impl From<geoengine_datatypes::primitives::CacheTtlSeconds> for CacheTtlSeconds {
2151
    fn from(value: geoengine_datatypes::primitives::CacheTtlSeconds) -> Self {
8✔
2152
        Self(value.seconds())
8✔
2153
    }
8✔
2154
}
2155

2156
impl From<CacheTtlSeconds> for geoengine_datatypes::primitives::CacheTtlSeconds {
2157
    fn from(value: CacheTtlSeconds) -> Self {
7✔
2158
        Self::new(value.0)
7✔
2159
    }
7✔
2160
}
2161

2162
impl ToSql for CacheTtlSeconds {
2163
    fn to_sql(
×
2164
        &self,
×
2165
        ty: &postgres_types::Type,
×
2166
        w: &mut bytes::BytesMut,
×
2167
    ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
×
2168
        <i32 as ToSql>::to_sql(&(self.0 as i32), ty, w)
×
2169
    }
×
2170

2171
    fn accepts(ty: &postgres_types::Type) -> bool {
×
2172
        <i32 as ToSql>::accepts(ty)
×
2173
    }
×
2174

2175
    postgres_types::to_sql_checked!();
2176
}
2177

2178
impl<'a> FromSql<'a> for CacheTtlSeconds {
2179
    fn from_sql(
×
2180
        ty: &postgres_types::Type,
×
2181
        raw: &'a [u8],
×
2182
    ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
×
2183
        Ok(Self(<i32 as FromSql>::from_sql(ty, raw)? as u32))
×
2184
    }
×
2185

2186
    fn accepts(ty: &postgres_types::Type) -> bool {
×
2187
        <i32 as FromSql>::accepts(ty)
×
2188
    }
×
2189
}
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