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

geo-engine / geoengine / 15000477198

13 May 2025 03:21PM UTC coverage: 89.911%. First build
15000477198

Pull #1055

github

web-flow
Merge afed450ba into 4d3e935a3
Pull Request #1055: Fix classification measurement serialization

60 of 69 new or added lines in 1 file covered. (86.96%)

126631 of 140841 relevant lines covered (89.91%)

57149.63 hits per line

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

55.2
/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::Error as SerdeError;
11
use serde::ser::SerializeMap;
12
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor};
13
use snafu::ResultExt;
14
use std::{
15
    collections::{BTreeMap, HashMap},
16
    fmt::{Debug, Formatter},
17
    str::FromStr,
18
};
19
use utoipa::{PartialSchema, ToSchema};
20

21
identifier!(DataProviderId);
22

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

29
// Identifier for datasets managed by Geo Engine
30
identifier!(DatasetId);
31

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

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

48
#[type_tag(value = "internal")]
40✔
49
#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize, Serialize, ToSchema)]
80✔
50
#[serde(rename_all = "camelCase")]
51
pub struct InternalDataId {
52
    pub dataset_id: DatasetId,
53
}
54

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

235
impl ToSchema for NamedData {}
236

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

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

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

252
#[type_tag(value = "external")]
50✔
253
#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize, Serialize, ToSchema)]
95✔
254
#[serde(rename_all = "camelCase")]
255
pub struct ExternalDataId {
256
    pub provider_id: DataProviderId,
257
    pub layer_id: LayerId,
258
}
259

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

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

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

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

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

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

333
impl FromStr for SpatialReferenceAuthority {
334
    type Err = error::Error;
335

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

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

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

373
impl SpatialReference {
374
    pub fn proj_string(self) -> Result<String> {
19✔
375
        match self.authority {
×
376
            SpatialReferenceAuthority::Epsg | SpatialReferenceAuthority::Iau2000 => {
377
                Ok(format!("{}:{}", self.authority, self.code))
19✔
378
            }
379
            // poor-mans integration of Meteosat Second Generation
380
            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()),
×
381
            SpatialReferenceAuthority::SrOrg | SpatialReferenceAuthority::Esri => {
382
                Err(error::Error::ProjStringUnresolvable { spatial_ref: self })
×
383
                //TODO: we might need to look them up somehow! Best solution would be a registry where we can store user definexd srs strings.
384
            }
385
        }
386
    }
19✔
387

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

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

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

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

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

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

427
impl FromStr for SpatialReference {
428
    type Err = error::Error;
429

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

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

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

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

460
/// Helper struct for deserializing a `SpatialReferencce`
461
struct SpatialReferenceDeserializeVisitor;
462

463
impl Visitor<'_> for SpatialReferenceDeserializeVisitor {
464
    type Value = SpatialReference;
465

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

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

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

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

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

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

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

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

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

543
/// Helper struct for deserializing a `SpatialReferenceOption`
544
struct SpatialReferenceOptionDeserializeVisitor;
545

546
impl Visitor<'_> for SpatialReferenceOptionDeserializeVisitor {
547
    type Value = SpatialReferenceOption;
548

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

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

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

563
        Ok(spatial_reference.into())
6✔
564
    }
7✔
565
}
566

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

765
impl ToSchema for DateTimeString {}
766

767
impl FromStr for DateTimeString {
768
    type Err = geoengine_datatypes::primitives::DateTimeError;
769

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

777
        Ok(date_time.into())
×
778
    }
×
779
}
780

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

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

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

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

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

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

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

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

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

875
#[type_tag(value = "continuous")]
730✔
876
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
1,090✔
877
pub struct ContinuousMeasurement {
878
    pub measurement: String,
879
    pub unit: Option<String>,
880
}
881

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

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

901
#[type_tag(value = "classification")]
730✔
902
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
1,455✔
903
pub struct ClassificationMeasurement {
904
    pub measurement: String,
905
    // use a BTreeMap to preserve the order of the keys
906
    #[serde(serialize_with = "serialize_classes")]
907
    #[serde(deserialize_with = "deserialize_classes")]
908
    pub classes: BTreeMap<u8, String>,
909
}
910

911
fn serialize_classes<S>(classes: &BTreeMap<u8, String>, serializer: S) -> Result<S::Ok, S::Error>
1✔
912
where
1✔
913
    S: Serializer,
1✔
914
{
1✔
915
    let mut map = serializer.serialize_map(Some(classes.len()))?;
1✔
916
    for (k, v) in classes {
3✔
917
        map.serialize_entry(&k.to_string(), v)?;
2✔
918
    }
919
    map.end()
1✔
920
}
1✔
921

922
fn deserialize_classes<'de, D>(deserializer: D) -> Result<BTreeMap<u8, String>, D::Error>
2✔
923
where
2✔
924
    D: Deserializer<'de>,
2✔
925
{
2✔
926
    let map = BTreeMap::<String, String>::deserialize(deserializer)?;
2✔
927
    let mut classes = BTreeMap::new();
2✔
928
    for (k, v) in map {
4✔
929
        classes.insert(
3✔
930
            k.parse::<u8>()
3✔
931
                .map_err(|e| D::Error::custom(format!("Failed to parse key as u8: {e}")))?,
3✔
932
            v,
2✔
933
        );
934
    }
935
    Ok(classes)
1✔
936
}
2✔
937

938
impl From<geoengine_datatypes::primitives::ClassificationMeasurement>
939
    for ClassificationMeasurement
940
{
NEW
941
    fn from(value: geoengine_datatypes::primitives::ClassificationMeasurement) -> Self {
×
942
        let mut classes = BTreeMap::new();
×
NEW
943
        for (k, v) in value.classes {
×
NEW
944
            classes.insert(k, v);
×
945
        }
×
946

947
        Self {
×
NEW
948
            r#type: Default::default(),
×
NEW
949
            measurement: value.measurement,
×
950
            classes,
×
951
        }
×
952
    }
×
953
}
954

955
impl From<ClassificationMeasurement>
956
    for geoengine_datatypes::primitives::ClassificationMeasurement
957
{
NEW
958
    fn from(measurement: ClassificationMeasurement) -> Self {
×
959
        let mut classes = HashMap::with_capacity(measurement.classes.len());
×
960
        for (k, v) in measurement.classes {
×
NEW
961
            classes.insert(k, v);
×
962
        }
×
963

NEW
964
        Self {
×
965
            measurement: measurement.measurement,
×
966
            classes,
×
NEW
967
        }
×
968
    }
×
969
}
970

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

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

988
impl From<SpatialPartition2D> for geoengine_datatypes::primitives::SpatialPartition2D {
989
    fn from(value: SpatialPartition2D) -> Self {
10✔
990
        Self::new_unchecked(
10✔
991
            value.upper_left_coordinate.into(),
10✔
992
            value.lower_right_coordinate.into(),
10✔
993
        )
10✔
994
    }
10✔
995
}
996

997
/// A spatio-temporal rectangle with a specified resolution
998
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
55✔
999
#[serde(rename_all = "camelCase")]
1000
pub struct RasterQueryRectangle {
1001
    pub spatial_bounds: SpatialPartition2D,
1002
    pub time_interval: TimeInterval,
1003
    pub spatial_resolution: SpatialResolution,
1004
}
1005

1006
/// A spatio-temporal rectangle with a specified resolution
1007
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
15✔
1008
#[serde(rename_all = "camelCase")]
1009
pub struct VectorQueryRectangle {
1010
    pub spatial_bounds: BoundingBox2D,
1011
    pub time_interval: TimeInterval,
1012
    pub spatial_resolution: SpatialResolution,
1013
}
1014
/// A spatio-temporal rectangle with a specified resolution
1015
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
15✔
1016
#[serde(rename_all = "camelCase")]
1017
pub struct PlotQueryRectangle {
1018
    pub spatial_bounds: BoundingBox2D,
1019
    pub time_interval: TimeInterval,
1020
    pub spatial_resolution: SpatialResolution,
1021
}
1022

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

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

1056
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, ToSchema)]
15✔
1057
pub struct BandSelection(pub Vec<usize>);
1058

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

1065
impl TryFrom<BandSelection> for geoengine_datatypes::primitives::BandSelection {
1066
    type Error = Error;
1067

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

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

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

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

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

1108
impl FromStr for TimeInstance {
1109
    type Err = geoengine_datatypes::primitives::DateTimeError;
1110

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

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

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

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

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

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

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

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

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

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

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

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

1188
        deserializer.deserialize_any(IsoStringOrUnixTimestamp)
18✔
1189
    }
18✔
1190
}
1191

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1407
// manual implementation utoipa generates an integer field
1408
impl ToSchema for RgbaColor {}
1409

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

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

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

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

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

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

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

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

1457
    postgres_types::to_sql_checked!();
1458
}
1459

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

1467
        Ok(NotNanF64(value.try_into()?))
×
1468
    }
×
1469

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

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

1487
// manual implementation because of NotNan
1488
impl ToSchema for Breakpoint {}
1489

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

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

1508
#[type_tag(value = "linearGradient")]
2,600✔
1509
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, ToSchema)]
5,195✔
1510
#[serde(rename_all = "camelCase")]
1511
pub struct LinearGradient {
1512
    pub breakpoints: Vec<Breakpoint>,
1513
    pub no_data_color: RgbaColor,
1514
    pub over_color: RgbaColor,
1515
    pub under_color: RgbaColor,
1516
}
1517

1518
#[type_tag(value = "logarithmicGradient")]
2,600✔
1519
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, ToSchema)]
5,195✔
1520
#[serde(rename_all = "camelCase")]
1521
pub struct LogarithmicGradient {
1522
    pub breakpoints: Vec<Breakpoint>,
1523
    pub no_data_color: RgbaColor,
1524
    pub over_color: RgbaColor,
1525
    pub under_color: RgbaColor,
1526
}
1527

1528
#[type_tag(value = "palette")]
2,590✔
1529
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, ToSchema)]
5,180✔
1530
#[serde(rename_all = "camelCase")]
1531
pub struct PaletteColorizer {
1532
    pub colors: Palette,
1533
    pub no_data_color: RgbaColor,
1534
    pub default_color: RgbaColor,
1535
}
1536

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

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

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

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

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

1641
#[type_tag(value = "singleBand")]
220✔
1642
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, ToSchema)]
440✔
1643
#[serde(rename_all = "camelCase")]
1644
pub struct SingleBandRasterColorizer {
1645
    pub band: u32,
1646
    pub band_colorizer: Colorizer,
1647
}
1648

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

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

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

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

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

1693
impl Eq for RasterColorizer {}
1694

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1932
impl ToSchema for DateTimeParseFormat {}
1933

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

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

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

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

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

1976
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
235✔
1977
pub struct NoGeometry;
1978

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

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

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

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

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

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

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

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

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

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

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

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

2082
pub type GdalConfigOption = StringPair;
2083
pub type AxisLabels = StringPair;
2084

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

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

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

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

2120
impl From<StringPair> for geoengine_datatypes::util::StringPair {
2121
    fn from(value: StringPair) -> Self {
2✔
2122
        Self::new(value.0.0, value.0.1)
2✔
2123
    }
2✔
2124
}
2125

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

2133
#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize, PartialOrd, Deserialize, ToSchema)]
955✔
2134
pub struct CacheTtlSeconds(u32);
2135

2136
const MAX_CACHE_TTL_SECONDS: u32 = 31_536_000; // 1 year
2137

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

2143
    pub fn max() -> Self {
×
2144
        Self(MAX_CACHE_TTL_SECONDS)
×
2145
    }
×
2146

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

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

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

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

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

2177
    postgres_types::to_sql_checked!();
2178
}
2179

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

2188
    fn accepts(ty: &postgres_types::Type) -> bool {
×
2189
        <i32 as FromSql>::accepts(ty)
×
2190
    }
×
2191
}
2192

2193
#[cfg(test)]
2194
mod tests {
2195
    use crate::api::model::datatypes::ClassificationMeasurement;
2196
    use crate::error::Error;
2197
    use std::collections::BTreeMap;
2198

2199
    #[test]
2200
    fn it_serializes_classification_measurement() -> Result<(), Error> {
1✔
2201
        let measurement = ClassificationMeasurement {
1✔
2202
            r#type: Default::default(),
1✔
2203
            measurement: "Test".to_string(),
1✔
2204
            classes: BTreeMap::<u8, String>::from([
1✔
2205
                (0, "Class 0".to_string()),
1✔
2206
                (1, "Class 1".to_string()),
1✔
2207
            ]),
1✔
2208
        };
1✔
2209

2210
        let serialized = serde_json::to_string(&measurement)?;
1✔
2211

2212
        assert_eq!(
1✔
2213
            serialized,
1✔
2214
            r#"{"type":"classification","measurement":"Test","classes":{"0":"Class 0","1":"Class 1"}}"#
1✔
2215
        );
1✔
2216
        Ok(())
1✔
2217
    }
1✔
2218

2219
    #[test]
2220
    fn it_deserializes_classification_measurement() -> Result<(), Error> {
1✔
2221
        let measurement = ClassificationMeasurement {
1✔
2222
            r#type: Default::default(),
1✔
2223
            measurement: "Test".to_string(),
1✔
2224
            classes: BTreeMap::<u8, String>::from([
1✔
2225
                (0, "Class 0".to_string()),
1✔
2226
                (1, "Class 1".to_string()),
1✔
2227
            ]),
1✔
2228
        };
1✔
2229

1✔
2230
        let serialized = r#"{"type":"classification","measurement":"Test","classes":{"0":"Class 0","1":"Class 1"}}"#;
1✔
2231
        let deserialized: ClassificationMeasurement = serde_json::from_str(serialized)?;
1✔
2232

2233
        assert_eq!(measurement, deserialized);
1✔
2234
        Ok(())
1✔
2235
    }
1✔
2236

2237
    #[test]
2238
    fn it_throws_error_on_deserializing_non_integer_classification_measurement_class_value() {
1✔
2239
        let serialized =
1✔
2240
            r#"{"type":"classification","measurement":"Test","classes":{"Zero":"Class 0"}}"#;
1✔
2241
        let deserialized = serde_json::from_str::<ClassificationMeasurement>(serialized);
1✔
2242

1✔
2243
        assert!(deserialized.is_err());
1✔
2244
        assert_eq!(
1✔
2245
            deserialized.unwrap_err().to_string(),
1✔
2246
            "Failed to parse key as u8: invalid digit found in string at line 1 column 75"
1✔
2247
        );
1✔
2248
    }
1✔
2249
}
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