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

geo-engine / geoengine / 12767614094

14 Jan 2025 12:26PM UTC coverage: 90.64% (+0.06%) from 90.576%
12767614094

push

github

web-flow
Merge pull request #1006 from geo-engine/migrate-pro-api

Migrate-pro-api

1106 of 1152 new or added lines in 24 files covered. (96.01%)

248 existing lines in 13 files now uncovered.

133501 of 147287 relevant lines covered (90.64%)

54652.85 hits per line

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

51.86
/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 ordered_float::NotNan;
8
use postgres_types::{FromSql, ToSql};
9
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
10
use snafu::ResultExt;
11
use std::{
12
    collections::{BTreeMap, HashMap},
13
    fmt::{Debug, Formatter},
14
    str::FromStr,
15
};
16
use utoipa::ToSchema;
17

18
identifier!(DataProviderId);
19

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

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

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

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

48
impl DataId {
49
    pub fn internal(&self) -> Option<DatasetId> {
×
50
        if let Self::Internal {
51
            dataset_id: dataset,
×
52
        } = self
×
53
        {
54
            return Some(*dataset);
×
55
        }
×
56
        None
×
57
    }
×
58

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

67
impl From<DatasetId> for DataId {
UNCOV
68
    fn from(value: DatasetId) -> Self {
×
UNCOV
69
        Self::Internal { dataset_id: value }
×
UNCOV
70
    }
×
71
}
72

73
impl From<ExternalDataId> for DataId {
74
    fn from(value: ExternalDataId) -> Self {
×
75
        Self::External(value)
×
76
    }
×
77
}
78

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

85
impl From<DatasetId> for geoengine_datatypes::dataset::DataId {
86
    fn from(value: DatasetId) -> Self {
×
87
        Self::Internal {
×
88
            dataset_id: value.into(),
×
89
        }
×
90
    }
×
91
}
92

93
impl From<ExternalDataId> for geoengine_datatypes::dataset::ExternalDataId {
94
    fn from(value: ExternalDataId) -> Self {
×
95
        Self {
×
96
            provider_id: value.provider_id.into(),
×
97
            layer_id: value.layer_id.into(),
×
98
        }
×
99
    }
×
100
}
101

102
impl From<geoengine_datatypes::dataset::DataId> for DataId {
103
    fn from(id: geoengine_datatypes::dataset::DataId) -> Self {
4✔
104
        match id {
4✔
105
            geoengine_datatypes::dataset::DataId::Internal { dataset_id } => Self::Internal {
4✔
106
                dataset_id: dataset_id.into(),
4✔
107
            },
4✔
108
            geoengine_datatypes::dataset::DataId::External(external_id) => {
×
109
                Self::External(external_id.into())
×
110
            }
111
        }
112
    }
4✔
113
}
114

115
impl From<DataId> for geoengine_datatypes::dataset::DataId {
116
    fn from(id: DataId) -> Self {
×
117
        match id {
×
118
            DataId::Internal { dataset_id } => Self::Internal {
×
119
                dataset_id: dataset_id.into(),
×
120
            },
×
121
            DataId::External(external_id) => Self::External(external_id.into()),
×
122
        }
123
    }
×
124
}
125

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

137
impl From<geoengine_datatypes::dataset::DatasetId> for DatasetId {
138
    fn from(id: geoengine_datatypes::dataset::DatasetId) -> Self {
4✔
139
        Self(id.0)
4✔
140
    }
4✔
141
}
142

143
impl From<&DatasetId> for geoengine_datatypes::dataset::DatasetId {
144
    fn from(value: &DatasetId) -> Self {
2✔
145
        Self(value.0)
2✔
146
    }
2✔
147
}
148

149
impl From<&ExternalDataId> for geoengine_datatypes::dataset::ExternalDataId {
150
    fn from(value: &ExternalDataId) -> Self {
×
151
        Self {
×
152
            provider_id: value.provider_id.into(),
×
153
            layer_id: value.layer_id.clone().into(),
×
154
        }
×
155
    }
×
156
}
157

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

172
impl From<geoengine_datatypes::dataset::NamedData> for NamedData {
173
    fn from(
1✔
174
        geoengine_datatypes::dataset::NamedData {
1✔
175
            namespace,
1✔
176
            provider,
1✔
177
            name,
1✔
178
        }: geoengine_datatypes::dataset::NamedData,
1✔
179
    ) -> Self {
1✔
180
        Self {
1✔
181
            namespace,
1✔
182
            provider,
1✔
183
            name,
1✔
184
        }
1✔
185
    }
1✔
186
}
187

188
impl From<&geoengine_datatypes::dataset::NamedData> for NamedData {
189
    fn from(named_data: &geoengine_datatypes::dataset::NamedData) -> Self {
×
190
        Self::from(named_data.clone())
×
191
    }
×
192
}
193

194
impl From<NamedData> for geoengine_datatypes::dataset::NamedData {
195
    fn from(
1✔
196
        NamedData {
1✔
197
            namespace,
1✔
198
            provider,
1✔
199
            name,
1✔
200
        }: NamedData,
1✔
201
    ) -> Self {
1✔
202
        Self {
1✔
203
            namespace,
1✔
204
            provider,
1✔
205
            name,
1✔
206
        }
1✔
207
    }
1✔
208
}
209

210
impl From<&NamedData> for geoengine_datatypes::dataset::NamedData {
211
    fn from(named_data: &NamedData) -> Self {
×
212
        Self::from(named_data.clone())
×
213
    }
×
214
}
215

216
impl<'a> ToSchema<'a> for NamedData {
217
    fn schema() -> (&'a str, utoipa::openapi::RefOr<utoipa::openapi::Schema>) {
2✔
218
        use utoipa::openapi::*;
219
        (
2✔
220
            "NamedData",
2✔
221
            ObjectBuilder::new().schema_type(SchemaType::String).into(),
2✔
222
        )
2✔
223
    }
2✔
224
}
225

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

229
impl From<LayerId> for geoengine_datatypes::dataset::LayerId {
230
    fn from(value: LayerId) -> Self {
14✔
231
        Self(value.0)
14✔
232
    }
14✔
233
}
234

235
impl std::fmt::Display for LayerId {
236
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
×
237
        write!(f, "{}", self.0)
×
238
    }
×
239
}
240

241
#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize, Serialize, ToSchema)]
2✔
242
#[serde(rename_all = "camelCase")]
243
pub struct ExternalDataId {
244
    pub provider_id: DataProviderId,
245
    pub layer_id: LayerId,
246
}
247

248
impl From<geoengine_datatypes::dataset::ExternalDataId> for ExternalDataId {
249
    fn from(id: geoengine_datatypes::dataset::ExternalDataId) -> Self {
×
250
        Self {
×
251
            provider_id: id.provider_id.into(),
×
252
            layer_id: id.layer_id.into(),
×
253
        }
×
254
    }
×
255
}
256

257
impl From<geoengine_datatypes::dataset::DataProviderId> for DataProviderId {
258
    fn from(id: geoengine_datatypes::dataset::DataProviderId) -> Self {
×
259
        Self(id.0)
×
260
    }
×
261
}
262

263
impl From<geoengine_datatypes::dataset::LayerId> for LayerId {
264
    fn from(id: geoengine_datatypes::dataset::LayerId) -> Self {
1✔
265
        Self(id.0)
1✔
266
    }
1✔
267
}
268

269
/// A spatial reference authority that is part of a spatial reference definition
270
#[derive(
271
    Debug,
272
    Copy,
273
    Clone,
274
    Eq,
275
    PartialEq,
276
    Ord,
277
    PartialOrd,
278
    Serialize,
279
    Deserialize,
280
    ToSchema,
2✔
281
    FromSql,
×
282
    ToSql,
×
283
)]
284
#[serde(rename_all = "SCREAMING-KEBAB-CASE")]
285
pub enum SpatialReferenceAuthority {
286
    Epsg,
287
    SrOrg,
288
    Iau2000,
289
    Esri,
290
}
291

292
impl From<geoengine_datatypes::spatial_reference::SpatialReferenceAuthority>
293
    for SpatialReferenceAuthority
294
{
295
    fn from(value: geoengine_datatypes::spatial_reference::SpatialReferenceAuthority) -> Self {
50✔
296
        match value {
50✔
297
            geoengine_datatypes::spatial_reference::SpatialReferenceAuthority::Epsg => Self::Epsg,
49✔
298
            geoengine_datatypes::spatial_reference::SpatialReferenceAuthority::SrOrg => Self::SrOrg,
1✔
299
            geoengine_datatypes::spatial_reference::SpatialReferenceAuthority::Iau2000 => {
300
                Self::Iau2000
×
301
            }
302
            geoengine_datatypes::spatial_reference::SpatialReferenceAuthority::Esri => Self::Esri,
×
303
        }
304
    }
50✔
305
}
306

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

320
impl FromStr for SpatialReferenceAuthority {
321
    type Err = error::Error;
322

323
    fn from_str(s: &str) -> Result<Self, Self::Err> {
32✔
324
        Ok(match s {
32✔
325
            "EPSG" => SpatialReferenceAuthority::Epsg,
32✔
326
            "SR-ORG" => SpatialReferenceAuthority::SrOrg,
×
327
            "IAU2000" => SpatialReferenceAuthority::Iau2000,
×
328
            "ESRI" => SpatialReferenceAuthority::Esri,
×
329
            _ => {
330
                return Err(error::Error::InvalidSpatialReferenceString {
×
331
                    spatial_reference_string: s.into(),
×
332
                })
×
333
            }
334
        })
335
    }
32✔
336
}
337

338
impl std::fmt::Display for SpatialReferenceAuthority {
339
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46✔
340
        write!(
46✔
341
            f,
46✔
342
            "{}",
46✔
343
            match self {
46✔
344
                SpatialReferenceAuthority::Epsg => "EPSG",
46✔
345
                SpatialReferenceAuthority::SrOrg => "SR-ORG",
×
346
                SpatialReferenceAuthority::Iau2000 => "IAU2000",
×
347
                SpatialReferenceAuthority::Esri => "ESRI",
×
348
            }
349
        )
350
    }
46✔
351
}
352

353
/// A spatial reference consists of an authority and a code
354
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, FromSql, ToSql)]
×
355
pub struct SpatialReference {
356
    authority: SpatialReferenceAuthority,
357
    code: u32,
358
}
359

360
impl SpatialReference {
361
    pub fn proj_string(self) -> Result<String> {
19✔
362
        match self.authority {
×
363
            SpatialReferenceAuthority::Epsg | SpatialReferenceAuthority::Iau2000 => {
364
                Ok(format!("{}:{}", self.authority, self.code))
19✔
365
            }
366
            // poor-mans integration of Meteosat Second Generation
367
            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()),
×
368
            SpatialReferenceAuthority::SrOrg | SpatialReferenceAuthority::Esri => {
369
                Err(error::Error::ProjStringUnresolvable { spatial_ref: self })
×
370
                //TODO: we might need to look them up somehow! Best solution would be a registry where we can store user definexd srs strings.
371
            }
372
        }
373
    }
19✔
374

375
    /// Return the srs-string "authority:code"
376
    #[allow(clippy::trivially_copy_pass_by_ref)]
377
    pub fn srs_string(&self) -> String {
19✔
378
        format!("{}:{}", self.authority, self.code)
19✔
379
    }
19✔
380
}
381

382
impl From<geoengine_datatypes::spatial_reference::SpatialReference> for SpatialReference {
383
    fn from(value: geoengine_datatypes::spatial_reference::SpatialReference) -> Self {
50✔
384
        Self {
50✔
385
            authority: (*value.authority()).into(),
50✔
386
            code: value.code(),
50✔
387
        }
50✔
388
    }
50✔
389
}
390

391
impl From<SpatialReference> for geoengine_datatypes::spatial_reference::SpatialReference {
392
    fn from(value: SpatialReference) -> Self {
10✔
393
        geoengine_datatypes::spatial_reference::SpatialReference::new(
10✔
394
            value.authority.into(),
10✔
395
            value.code,
10✔
396
        )
10✔
397
    }
10✔
398
}
399

400
impl SpatialReference {
401
    pub fn new(authority: SpatialReferenceAuthority, code: u32) -> Self {
37✔
402
        Self { authority, code }
37✔
403
    }
37✔
404

405
    pub fn authority(&self) -> &SpatialReferenceAuthority {
1✔
406
        &self.authority
1✔
407
    }
1✔
408

409
    pub fn code(self) -> u32 {
1✔
410
        self.code
1✔
411
    }
1✔
412
}
413

414
impl FromStr for SpatialReference {
415
    type Err = error::Error;
416

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

32✔
420
        match (split.next(), split.next(), split.next()) {
32✔
421
            (Some(authority), Some(code), None) => Ok(Self::new(
32✔
422
                authority.parse()?,
32✔
423
                code.parse::<u32>().context(error::ParseU32)?,
32✔
424
            )),
425
            _ => Err(error::Error::InvalidSpatialReferenceString {
×
426
                spatial_reference_string: s.into(),
×
427
            }),
×
428
        }
429
    }
32✔
430
}
431

432
impl std::fmt::Display for SpatialReference {
433
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7✔
434
        write!(f, "{}:{}", self.authority, self.code)
7✔
435
    }
7✔
436
}
437

438
impl Serialize for SpatialReference {
439
    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
1✔
440
    where
1✔
441
        S: Serializer,
1✔
442
    {
1✔
443
        serializer.serialize_str(&self.to_string())
1✔
444
    }
1✔
445
}
446

447
/// Helper struct for deserializing a `SpatialReferencce`
448
struct SpatialReferenceDeserializeVisitor;
449

450
impl Visitor<'_> for SpatialReferenceDeserializeVisitor {
451
    type Value = SpatialReference;
452

453
    fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
×
454
        formatter.write_str("a spatial reference in the form authority:code")
×
455
    }
×
456

457
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
18✔
458
    where
18✔
459
        E: serde::de::Error,
18✔
460
    {
18✔
461
        v.parse().map_err(serde::de::Error::custom)
18✔
462
    }
18✔
463
}
464

465
impl<'de> Deserialize<'de> for SpatialReference {
466
    fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
18✔
467
    where
18✔
468
        D: Deserializer<'de>,
18✔
469
    {
18✔
470
        deserializer.deserialize_str(SpatialReferenceDeserializeVisitor)
18✔
471
    }
18✔
472
}
473

474
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
475
pub enum SpatialReferenceOption {
476
    SpatialReference(SpatialReference),
477
    Unreferenced,
478
}
479

480
impl From<geoengine_datatypes::spatial_reference::SpatialReferenceOption>
481
    for SpatialReferenceOption
482
{
483
    fn from(value: geoengine_datatypes::spatial_reference::SpatialReferenceOption) -> Self {
19✔
484
        match value {
19✔
485
            geoengine_datatypes::spatial_reference::SpatialReferenceOption::SpatialReference(s) => {
19✔
486
                Self::SpatialReference(s.into())
19✔
487
            }
488
            geoengine_datatypes::spatial_reference::SpatialReferenceOption::Unreferenced => {
489
                Self::Unreferenced
×
490
            }
491
        }
492
    }
19✔
493
}
494

495
impl From<SpatialReferenceOption>
496
    for geoengine_datatypes::spatial_reference::SpatialReferenceOption
497
{
498
    fn from(value: SpatialReferenceOption) -> Self {
7✔
499
        match value {
7✔
500
            SpatialReferenceOption::SpatialReference(sr) => Self::SpatialReference(sr.into()),
6✔
501
            SpatialReferenceOption::Unreferenced => Self::Unreferenced,
1✔
502
        }
503
    }
7✔
504
}
505

506
impl From<SpatialReference> for SpatialReferenceOption {
507
    fn from(spatial_reference: SpatialReference) -> Self {
6✔
508
        Self::SpatialReference(spatial_reference)
6✔
509
    }
6✔
510
}
511

512
impl std::fmt::Display for SpatialReferenceOption {
513
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6✔
514
        match self {
6✔
515
            SpatialReferenceOption::SpatialReference(p) => write!(f, "{p}"),
6✔
516
            SpatialReferenceOption::Unreferenced => Ok(()),
×
517
        }
518
    }
6✔
519
}
520

521
impl Serialize for SpatialReferenceOption {
522
    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
6✔
523
    where
6✔
524
        S: Serializer,
6✔
525
    {
6✔
526
        serializer.serialize_str(&self.to_string())
6✔
527
    }
6✔
528
}
529

530
/// Helper struct for deserializing a `SpatialReferenceOption`
531
struct SpatialReferenceOptionDeserializeVisitor;
532

533
impl Visitor<'_> for SpatialReferenceOptionDeserializeVisitor {
534
    type Value = SpatialReferenceOption;
535

536
    fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
×
537
        formatter.write_str("a spatial reference in the form authority:code")
×
538
    }
×
539

540
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
7✔
541
    where
7✔
542
        E: serde::de::Error,
7✔
543
    {
7✔
544
        if v.is_empty() {
7✔
545
            return Ok(SpatialReferenceOption::Unreferenced);
1✔
546
        }
6✔
547

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

550
        Ok(spatial_reference.into())
6✔
551
    }
7✔
552
}
553

554
impl<'de> Deserialize<'de> for SpatialReferenceOption {
555
    fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
7✔
556
    where
7✔
557
        D: Deserializer<'de>,
7✔
558
    {
7✔
559
        deserializer.deserialize_str(SpatialReferenceOptionDeserializeVisitor)
7✔
560
    }
7✔
561
}
562

563
impl From<Option<SpatialReference>> for SpatialReferenceOption {
564
    fn from(option: Option<SpatialReference>) -> Self {
×
565
        match option {
×
566
            Some(p) => SpatialReferenceOption::SpatialReference(p),
×
567
            None => SpatialReferenceOption::Unreferenced,
×
568
        }
569
    }
×
570
}
571

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

581
impl ToSql for SpatialReferenceOption {
582
    fn to_sql(
×
583
        &self,
×
584
        ty: &postgres_types::Type,
×
585
        out: &mut bytes::BytesMut,
×
586
    ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>>
×
587
    where
×
588
        Self: Sized,
×
589
    {
×
590
        match self {
×
591
            SpatialReferenceOption::SpatialReference(sref) => sref.to_sql(ty, out),
×
592
            SpatialReferenceOption::Unreferenced => Ok(postgres_types::IsNull::Yes),
×
593
        }
594
    }
×
595

596
    fn accepts(ty: &postgres_types::Type) -> bool
×
597
    where
×
598
        Self: Sized,
×
599
    {
×
600
        <SpatialReference as ToSql>::accepts(ty)
×
601
    }
×
602

603
    fn to_sql_checked(
×
604
        &self,
×
605
        ty: &postgres_types::Type,
×
606
        out: &mut bytes::BytesMut,
×
607
    ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
×
608
        match self {
×
609
            SpatialReferenceOption::SpatialReference(sref) => sref.to_sql_checked(ty, out),
×
610
            SpatialReferenceOption::Unreferenced => Ok(postgres_types::IsNull::Yes),
×
611
        }
612
    }
×
613
}
614

615
impl<'a> FromSql<'a> for SpatialReferenceOption {
616
    fn from_sql(
×
617
        ty: &postgres_types::Type,
×
618
        raw: &'a [u8],
×
619
    ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
×
620
        Ok(SpatialReferenceOption::SpatialReference(
×
621
            SpatialReference::from_sql(ty, raw)?,
×
622
        ))
623
    }
×
624

625
    fn from_sql_null(
×
626
        _: &postgres_types::Type,
×
627
    ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
×
628
        Ok(SpatialReferenceOption::Unreferenced)
×
629
    }
×
630

631
    fn accepts(ty: &postgres_types::Type) -> bool {
×
632
        <SpatialReference as FromSql>::accepts(ty)
×
633
    }
×
634
}
635

636
/// An enum that contains all possible vector data types
637
#[derive(
638
    Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize, Copy, Clone, ToSchema,
2✔
639
)]
640
pub enum VectorDataType {
641
    Data,
642
    MultiPoint,
643
    MultiLineString,
644
    MultiPolygon,
645
}
646

647
impl From<geoengine_datatypes::collections::VectorDataType> for VectorDataType {
648
    fn from(value: geoengine_datatypes::collections::VectorDataType) -> Self {
2✔
649
        match value {
2✔
650
            geoengine_datatypes::collections::VectorDataType::Data => Self::Data,
×
651
            geoengine_datatypes::collections::VectorDataType::MultiPoint => Self::MultiPoint,
2✔
652
            geoengine_datatypes::collections::VectorDataType::MultiLineString => {
653
                Self::MultiLineString
×
654
            }
655
            geoengine_datatypes::collections::VectorDataType::MultiPolygon => Self::MultiPolygon,
×
656
        }
657
    }
2✔
658
}
659

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

671
#[derive(
672
    Clone,
673
    Copy,
674
    Debug,
675
    Deserialize,
676
    PartialEq,
677
    PartialOrd,
678
    Serialize,
679
    Default,
680
    ToSchema,
2✔
681
    ToSql,
×
682
    FromSql,
×
683
)]
684
pub struct Coordinate2D {
685
    pub x: f64,
686
    pub y: f64,
687
}
688

689
impl From<geoengine_datatypes::primitives::Coordinate2D> for Coordinate2D {
690
    fn from(coordinate: geoengine_datatypes::primitives::Coordinate2D) -> Self {
72✔
691
        Self {
72✔
692
            x: coordinate.x,
72✔
693
            y: coordinate.y,
72✔
694
        }
72✔
695
    }
72✔
696
}
697

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

707
#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Debug, ToSchema, ToSql, FromSql)]
2✔
708
#[serde(rename_all = "camelCase")]
709
/// A bounding box that includes all border points.
710
/// Note: may degenerate to a point!
711
pub struct BoundingBox2D {
712
    pub lower_left_coordinate: Coordinate2D,
713
    pub upper_right_coordinate: Coordinate2D,
714
}
715

716
impl From<geoengine_datatypes::primitives::BoundingBox2D> for BoundingBox2D {
717
    fn from(bbox: geoengine_datatypes::primitives::BoundingBox2D) -> Self {
28✔
718
        Self {
28✔
719
            lower_left_coordinate:
28✔
720
                geoengine_datatypes::primitives::AxisAlignedRectangle::lower_left(&bbox).into(),
28✔
721
            upper_right_coordinate:
28✔
722
                geoengine_datatypes::primitives::AxisAlignedRectangle::upper_right(&bbox).into(),
28✔
723
        }
28✔
724
    }
28✔
725
}
726

727
impl From<BoundingBox2D> for geoengine_datatypes::primitives::BoundingBox2D {
728
    fn from(bbox: BoundingBox2D) -> Self {
1✔
729
        Self::new_unchecked(
1✔
730
            bbox.lower_left_coordinate.into(),
1✔
731
            bbox.upper_right_coordinate.into(),
1✔
732
        )
1✔
733
    }
1✔
734
}
735

736
/// An object that composes the date and a timestamp with time zone.
737
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ToSchema)]
2✔
738
pub struct DateTime {
739
    datetime: chrono::DateTime<chrono::Utc>,
740
}
741

742
impl FromStr for DateTime {
743
    type Err = geoengine_datatypes::primitives::DateTimeError;
744

745
    fn from_str(input: &str) -> Result<Self, Self::Err> {
×
746
        let date_time = chrono::DateTime::<chrono::FixedOffset>::from_str(input).map_err(|e| {
×
747
            Self::Err::DateParse {
×
748
                source: Box::new(e),
×
749
            }
×
750
        })?;
×
751

752
        Ok(date_time.into())
×
753
    }
×
754
}
755

756
impl From<chrono::DateTime<chrono::FixedOffset>> for DateTime {
757
    fn from(datetime: chrono::DateTime<chrono::FixedOffset>) -> Self {
×
758
        Self {
×
759
            datetime: datetime.into(),
×
760
        }
×
761
    }
×
762
}
763

764
impl From<geoengine_datatypes::primitives::DateTime> for DateTime {
765
    fn from(datetime: geoengine_datatypes::primitives::DateTime) -> Self {
×
766
        Self {
×
767
            datetime: datetime.into(),
×
768
        }
×
769
    }
×
770
}
771

772
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize, ToSchema)]
2✔
773
#[serde(rename_all = "camelCase")]
774
pub enum FeatureDataType {
775
    Category,
776
    Int,
777
    Float,
778
    Text,
779
    Bool,
780
    DateTime,
781
}
782

783
impl From<geoengine_datatypes::primitives::FeatureDataType> for FeatureDataType {
784
    fn from(value: geoengine_datatypes::primitives::FeatureDataType) -> Self {
2✔
785
        match value {
2✔
786
            geoengine_datatypes::primitives::FeatureDataType::Category => Self::Category,
×
787
            geoengine_datatypes::primitives::FeatureDataType::Int => Self::Int,
1✔
788
            geoengine_datatypes::primitives::FeatureDataType::Float => Self::Float,
×
789
            geoengine_datatypes::primitives::FeatureDataType::Text => Self::Text,
1✔
790
            geoengine_datatypes::primitives::FeatureDataType::Bool => Self::Bool,
×
791
            geoengine_datatypes::primitives::FeatureDataType::DateTime => Self::DateTime,
×
792
        }
793
    }
2✔
794
}
795

796
impl From<FeatureDataType> for geoengine_datatypes::primitives::FeatureDataType {
797
    fn from(value: FeatureDataType) -> Self {
10✔
798
        match value {
10✔
799
            FeatureDataType::Category => Self::Category,
×
800
            FeatureDataType::Int => Self::Int,
2✔
801
            FeatureDataType::Float => Self::Float,
2✔
802
            FeatureDataType::Text => Self::Text,
6✔
803
            FeatureDataType::Bool => Self::Bool,
×
804
            FeatureDataType::DateTime => Self::DateTime,
×
805
        }
806
    }
10✔
807
}
808

809
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
2✔
810
#[serde(rename_all = "camelCase", tag = "type")]
811
pub enum Measurement {
812
    #[schema(title = "UnitlessMeasurement")]
813
    Unitless,
814
    Continuous(ContinuousMeasurement),
815
    Classification(ClassificationMeasurement),
816
}
817

818
impl From<geoengine_datatypes::primitives::Measurement> for Measurement {
819
    fn from(value: geoengine_datatypes::primitives::Measurement) -> Self {
6✔
820
        match value {
6✔
821
            geoengine_datatypes::primitives::Measurement::Unitless => Self::Unitless,
2✔
822
            geoengine_datatypes::primitives::Measurement::Continuous(cm) => {
4✔
823
                Self::Continuous(cm.into())
4✔
824
            }
825
            geoengine_datatypes::primitives::Measurement::Classification(cm) => {
×
826
                Self::Classification(cm.into())
×
827
            }
828
        }
829
    }
6✔
830
}
831

832
impl From<Measurement> for geoengine_datatypes::primitives::Measurement {
833
    fn from(value: Measurement) -> Self {
14✔
834
        match value {
14✔
835
            Measurement::Unitless => Self::Unitless,
10✔
836
            Measurement::Continuous(cm) => Self::Continuous(cm.into()),
4✔
837
            Measurement::Classification(cm) => Self::Classification(cm.into()),
×
838
        }
839
    }
14✔
840
}
841

842
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
2✔
843
pub struct ContinuousMeasurement {
844
    pub measurement: String,
845
    pub unit: Option<String>,
846
}
847

848
impl From<geoengine_datatypes::primitives::ContinuousMeasurement> for ContinuousMeasurement {
849
    fn from(value: geoengine_datatypes::primitives::ContinuousMeasurement) -> Self {
4✔
850
        Self {
4✔
851
            measurement: value.measurement,
4✔
852
            unit: value.unit,
4✔
853
        }
4✔
854
    }
4✔
855
}
856

857
impl From<ContinuousMeasurement> for geoengine_datatypes::primitives::ContinuousMeasurement {
858
    fn from(value: ContinuousMeasurement) -> Self {
4✔
859
        Self {
4✔
860
            measurement: value.measurement,
4✔
861
            unit: value.unit,
4✔
862
        }
4✔
863
    }
4✔
864
}
865

866
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
2✔
867
#[serde(
868
    try_from = "SerializableClassificationMeasurement",
869
    into = "SerializableClassificationMeasurement"
870
)]
871
pub struct ClassificationMeasurement {
872
    pub measurement: String,
873
    pub classes: HashMap<u8, String>,
874
}
875

876
impl From<geoengine_datatypes::primitives::ClassificationMeasurement>
877
    for ClassificationMeasurement
878
{
879
    fn from(value: geoengine_datatypes::primitives::ClassificationMeasurement) -> Self {
×
880
        Self {
×
881
            measurement: value.measurement,
×
882
            classes: value.classes,
×
883
        }
×
884
    }
×
885
}
886

887
impl From<ClassificationMeasurement>
888
    for geoengine_datatypes::primitives::ClassificationMeasurement
889
{
890
    fn from(value: ClassificationMeasurement) -> Self {
×
891
        Self {
×
892
            measurement: value.measurement,
×
893
            classes: value.classes,
×
894
        }
×
895
    }
×
896
}
897

898
/// A type that is solely for serde's serializability.
899
/// You cannot serialize floats as JSON map keys.
900
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
901
pub struct SerializableClassificationMeasurement {
902
    pub measurement: String,
903
    // use a BTreeMap to preserve the order of the keys
904
    pub classes: BTreeMap<String, String>,
905
}
906

907
impl From<ClassificationMeasurement> for SerializableClassificationMeasurement {
908
    fn from(measurement: ClassificationMeasurement) -> Self {
×
909
        let mut classes = BTreeMap::new();
×
910
        for (k, v) in measurement.classes {
×
911
            classes.insert(k.to_string(), v);
×
912
        }
×
913
        Self {
×
914
            measurement: measurement.measurement,
×
915
            classes,
×
916
        }
×
917
    }
×
918
}
919

920
impl TryFrom<SerializableClassificationMeasurement> for ClassificationMeasurement {
921
    type Error = <u8 as FromStr>::Err;
922

923
    fn try_from(measurement: SerializableClassificationMeasurement) -> Result<Self, Self::Error> {
×
924
        let mut classes = HashMap::with_capacity(measurement.classes.len());
×
925
        for (k, v) in measurement.classes {
×
926
            classes.insert(k.parse::<u8>()?, v);
×
927
        }
928
        Ok(Self {
×
929
            measurement: measurement.measurement,
×
930
            classes,
×
931
        })
×
932
    }
×
933
}
934

935
/// A partition of space that include the upper left but excludes the lower right coordinate
936
#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Debug, ToSchema, FromSql, ToSql)]
2✔
937
#[serde(rename_all = "camelCase")]
938
pub struct SpatialPartition2D {
939
    pub upper_left_coordinate: Coordinate2D,
940
    pub lower_right_coordinate: Coordinate2D,
941
}
942

943
impl From<geoengine_datatypes::primitives::SpatialPartition2D> for SpatialPartition2D {
944
    fn from(value: geoengine_datatypes::primitives::SpatialPartition2D) -> Self {
6✔
945
        Self {
6✔
946
            upper_left_coordinate: value.upper_left().into(),
6✔
947
            lower_right_coordinate: value.lower_right().into(),
6✔
948
        }
6✔
949
    }
6✔
950
}
951

952
impl From<SpatialPartition2D> for geoengine_datatypes::primitives::SpatialPartition2D {
953
    fn from(value: SpatialPartition2D) -> Self {
10✔
954
        Self::new_unchecked(
10✔
955
            value.upper_left_coordinate.into(),
10✔
956
            value.lower_right_coordinate.into(),
10✔
957
        )
10✔
958
    }
10✔
959
}
960

961
/// A spatio-temporal rectangle with a specified resolution
962
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
6✔
963
#[serde(rename_all = "camelCase")]
964
#[aliases(
965
    VectorQueryRectangle = QueryRectangle<BoundingBox2D>,
966
    RasterQueryRectangle = QueryRectangle<SpatialPartition2D>,
967
    PlotQueryRectangle = QueryRectangle<BoundingBox2D>)
968
]
969
pub struct QueryRectangle<SpatialBounds> {
970
    pub spatial_bounds: SpatialBounds,
971
    pub time_interval: TimeInterval,
972
    pub spatial_resolution: SpatialResolution,
973
}
974

975
impl
976
    From<
977
        geoengine_datatypes::primitives::QueryRectangle<
978
            geoengine_datatypes::primitives::SpatialPartition2D,
979
            geoengine_datatypes::primitives::BandSelection,
980
        >,
981
    > for RasterQueryRectangle
982
{
983
    fn from(
2✔
984
        value: geoengine_datatypes::primitives::QueryRectangle<
2✔
985
            geoengine_datatypes::primitives::SpatialPartition2D,
2✔
986
            geoengine_datatypes::primitives::BandSelection,
2✔
987
        >,
2✔
988
    ) -> Self {
2✔
989
        Self {
2✔
990
            spatial_bounds: value.spatial_bounds.into(),
2✔
991
            time_interval: value.time_interval.into(),
2✔
992
            spatial_resolution: value.spatial_resolution.into(),
2✔
993
        }
2✔
994
    }
2✔
995
}
996

997
impl From<QueryRectangle<SpatialPartition2D>>
998
    for geoengine_datatypes::primitives::RasterQueryRectangle
999
{
1000
    fn from(value: QueryRectangle<SpatialPartition2D>) -> Self {
3✔
1001
        Self {
3✔
1002
            spatial_bounds: value.spatial_bounds.into(),
3✔
1003
            time_interval: value.time_interval.into(),
3✔
1004
            spatial_resolution: value.spatial_resolution.into(),
3✔
1005
            attributes: geoengine_datatypes::primitives::BandSelection::first(), // TODO: adjust once API supports attribute selection
3✔
1006
        }
3✔
1007
    }
3✔
1008
}
1009

1010
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, ToSchema)]
2✔
1011
pub struct BandSelection(pub Vec<usize>);
1012

1013
impl From<geoengine_datatypes::primitives::BandSelection> for BandSelection {
1014
    fn from(value: geoengine_datatypes::primitives::BandSelection) -> Self {
×
1015
        Self(value.as_vec().into_iter().map(|b| b as usize).collect())
×
1016
    }
×
1017
}
1018

1019
impl TryFrom<BandSelection> for geoengine_datatypes::primitives::BandSelection {
1020
    type Error = Error;
1021

1022
    fn try_from(value: BandSelection) -> Result<Self> {
6✔
1023
        geoengine_datatypes::primitives::BandSelection::new(
6✔
1024
            value.0.into_iter().map(|b| b as u32).collect(),
8✔
1025
        )
6✔
1026
        .map_err(Into::into)
6✔
1027
    }
6✔
1028
}
1029

1030
/// The spatial resolution in SRS units
1031
#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Serialize, ToSchema)]
2✔
1032
pub struct SpatialResolution {
1033
    pub x: f64,
1034
    pub y: f64,
1035
}
1036

1037
impl From<geoengine_datatypes::primitives::SpatialResolution> for SpatialResolution {
1038
    fn from(value: geoengine_datatypes::primitives::SpatialResolution) -> Self {
6✔
1039
        Self {
6✔
1040
            x: value.x,
6✔
1041
            y: value.y,
6✔
1042
        }
6✔
1043
    }
6✔
1044
}
1045

1046
impl From<SpatialResolution> for geoengine_datatypes::primitives::SpatialResolution {
1047
    fn from(value: SpatialResolution) -> Self {
10✔
1048
        Self {
10✔
1049
            x: value.x,
10✔
1050
            y: value.y,
10✔
1051
        }
10✔
1052
    }
10✔
1053
}
1054

1055
#[derive(
1056
    Clone, Copy, Serialize, PartialEq, Eq, PartialOrd, Ord, Debug, ToSchema, FromSql, ToSql,
2✔
1057
)]
1058
#[repr(C)]
1059
#[postgres(transparent)]
1060
pub struct TimeInstance(i64);
1061

1062
impl FromStr for TimeInstance {
1063
    type Err = geoengine_datatypes::primitives::DateTimeError;
1064

1065
    fn from_str(s: &str) -> Result<Self, Self::Err> {
×
1066
        let date_time = DateTime::from_str(s)?;
×
1067
        Ok(date_time.into())
×
1068
    }
×
1069
}
1070

1071
impl From<geoengine_datatypes::primitives::TimeInstance> for TimeInstance {
1072
    fn from(value: geoengine_datatypes::primitives::TimeInstance) -> Self {
146✔
1073
        Self(value.inner())
146✔
1074
    }
146✔
1075
}
1076

1077
impl From<TimeInstance> for geoengine_datatypes::primitives::TimeInstance {
1078
    fn from(value: TimeInstance) -> Self {
82✔
1079
        geoengine_datatypes::primitives::TimeInstance::from_millis_unchecked(value.inner())
82✔
1080
    }
82✔
1081
}
1082

1083
impl From<DateTime> for TimeInstance {
1084
    fn from(datetime: DateTime) -> Self {
×
1085
        Self::from(&datetime)
×
1086
    }
×
1087
}
1088

1089
impl From<&DateTime> for TimeInstance {
1090
    fn from(datetime: &DateTime) -> Self {
×
1091
        geoengine_datatypes::primitives::TimeInstance::from_millis_unchecked(
×
1092
            datetime.datetime.timestamp_millis(),
×
1093
        )
×
1094
        .into()
×
1095
    }
×
1096
}
1097

1098
impl TimeInstance {
1099
    pub const fn inner(self) -> i64 {
82✔
1100
        self.0
82✔
1101
    }
82✔
1102
}
1103

1104
impl<'de> Deserialize<'de> for TimeInstance {
1105
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
18✔
1106
    where
18✔
1107
        D: serde::Deserializer<'de>,
18✔
1108
    {
18✔
1109
        struct IsoStringOrUnixTimestamp;
1110

1111
        impl serde::de::Visitor<'_> for IsoStringOrUnixTimestamp {
1112
            type Value = TimeInstance;
1113

1114
            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
×
1115
                formatter.write_str("RFC 3339 timestamp string or Unix timestamp integer")
×
1116
            }
×
1117

1118
            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
×
1119
            where
×
1120
                E: serde::de::Error,
×
1121
            {
×
1122
                TimeInstance::from_str(value).map_err(E::custom)
×
1123
            }
×
1124

1125
            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
18✔
1126
            where
18✔
1127
                E: serde::de::Error,
18✔
1128
            {
18✔
1129
                geoengine_datatypes::primitives::TimeInstance::from_millis(v)
18✔
1130
                    .map(Into::into)
18✔
1131
                    .map_err(E::custom)
18✔
1132
            }
18✔
1133

1134
            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
18✔
1135
            where
18✔
1136
                E: serde::de::Error,
18✔
1137
            {
18✔
1138
                Self::visit_i64(self, v as i64)
18✔
1139
            }
18✔
1140
        }
1141

1142
        deserializer.deserialize_any(IsoStringOrUnixTimestamp)
18✔
1143
    }
18✔
1144
}
1145

1146
/// A time granularity.
1147
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema, FromSql, ToSql)]
2✔
1148
#[serde(rename_all = "camelCase")]
1149
pub enum TimeGranularity {
1150
    Millis,
1151
    Seconds,
1152
    Minutes,
1153
    Hours,
1154
    Days,
1155
    Months,
1156
    Years,
1157
}
1158

1159
impl From<geoengine_datatypes::primitives::TimeGranularity> for TimeGranularity {
1160
    fn from(value: geoengine_datatypes::primitives::TimeGranularity) -> Self {
4✔
1161
        match value {
4✔
1162
            geoengine_datatypes::primitives::TimeGranularity::Millis => Self::Millis,
×
1163
            geoengine_datatypes::primitives::TimeGranularity::Seconds => Self::Seconds,
×
1164
            geoengine_datatypes::primitives::TimeGranularity::Minutes => Self::Minutes,
×
1165
            geoengine_datatypes::primitives::TimeGranularity::Hours => Self::Hours,
×
1166
            geoengine_datatypes::primitives::TimeGranularity::Days => Self::Days,
×
1167
            geoengine_datatypes::primitives::TimeGranularity::Months => Self::Months,
4✔
1168
            geoengine_datatypes::primitives::TimeGranularity::Years => Self::Years,
×
1169
        }
1170
    }
4✔
1171
}
1172

1173
impl From<TimeGranularity> for geoengine_datatypes::primitives::TimeGranularity {
1174
    fn from(value: TimeGranularity) -> Self {
4✔
1175
        match value {
4✔
1176
            TimeGranularity::Millis => Self::Millis,
×
1177
            TimeGranularity::Seconds => Self::Seconds,
×
1178
            TimeGranularity::Minutes => Self::Minutes,
×
1179
            TimeGranularity::Hours => Self::Hours,
×
1180
            TimeGranularity::Days => Self::Days,
×
1181
            TimeGranularity::Months => Self::Months,
4✔
1182
            TimeGranularity::Years => Self::Years,
×
1183
        }
1184
    }
4✔
1185
}
1186

1187
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema, FromSql, ToSql)]
2✔
1188
pub struct TimeStep {
1189
    pub granularity: TimeGranularity,
1190
    pub step: u32, // TODO: ensure on deserialization it is > 0
1191
}
1192

1193
impl From<geoengine_datatypes::primitives::TimeStep> for TimeStep {
1194
    fn from(value: geoengine_datatypes::primitives::TimeStep) -> Self {
4✔
1195
        Self {
4✔
1196
            granularity: value.granularity.into(),
4✔
1197
            step: value.step,
4✔
1198
        }
4✔
1199
    }
4✔
1200
}
1201

1202
impl From<TimeStep> for geoengine_datatypes::primitives::TimeStep {
1203
    fn from(value: TimeStep) -> Self {
4✔
1204
        Self {
4✔
1205
            granularity: value.granularity.into(),
4✔
1206
            step: value.step,
4✔
1207
        }
4✔
1208
    }
4✔
1209
}
1210

1211
/// Stores time intervals in ms in close-open semantic [start, end)
1212
#[derive(Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ToSql, FromSql, ToSchema)]
2✔
1213
pub struct TimeInterval {
1214
    start: TimeInstance,
1215
    end: TimeInstance,
1216
}
1217

1218
impl From<TimeInterval> for geoengine_datatypes::primitives::TimeInterval {
1219
    fn from(value: TimeInterval) -> Self {
41✔
1220
        geoengine_datatypes::primitives::TimeInterval::new_unchecked::<
41✔
1221
            geoengine_datatypes::primitives::TimeInstance,
41✔
1222
            geoengine_datatypes::primitives::TimeInstance,
41✔
1223
        >(value.start.into(), value.end.into())
41✔
1224
    }
41✔
1225
}
1226

1227
impl From<geoengine_datatypes::primitives::TimeInterval> for TimeInterval {
1228
    fn from(value: geoengine_datatypes::primitives::TimeInterval) -> Self {
64✔
1229
        Self {
64✔
1230
            start: value.start().into(),
64✔
1231
            end: value.end().into(),
64✔
1232
        }
64✔
1233
    }
64✔
1234
}
1235

1236
impl core::fmt::Debug for TimeInterval {
1237
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
×
1238
        write!(
×
1239
            f,
×
1240
            "TimeInterval [{}, {})",
×
1241
            self.start.inner(),
×
1242
            &self.end.inner()
×
1243
        )
×
1244
    }
×
1245
}
1246

1247
#[derive(
1248
    Debug,
1249
    Ord,
1250
    PartialOrd,
1251
    Eq,
1252
    PartialEq,
1253
    Hash,
1254
    Deserialize,
1255
    Serialize,
1256
    Copy,
1257
    Clone,
1258
    ToSchema,
2✔
1259
    FromSql,
48✔
1260
    ToSql,
24✔
1261
)]
1262
pub enum RasterDataType {
1263
    U8,
1264
    U16,
1265
    U32,
1266
    U64,
1267
    I8,
1268
    I16,
1269
    I32,
1270
    I64,
1271
    F32,
1272
    F64,
1273
}
1274

1275
impl From<geoengine_datatypes::raster::RasterDataType> for RasterDataType {
1276
    fn from(value: geoengine_datatypes::raster::RasterDataType) -> Self {
4✔
1277
        match value {
4✔
1278
            geoengine_datatypes::raster::RasterDataType::U8 => Self::U8,
4✔
1279
            geoengine_datatypes::raster::RasterDataType::U16 => Self::U16,
×
1280
            geoengine_datatypes::raster::RasterDataType::U32 => Self::U32,
×
1281
            geoengine_datatypes::raster::RasterDataType::U64 => Self::U64,
×
1282
            geoengine_datatypes::raster::RasterDataType::I8 => Self::I8,
×
1283
            geoengine_datatypes::raster::RasterDataType::I16 => Self::I16,
×
1284
            geoengine_datatypes::raster::RasterDataType::I32 => Self::I32,
×
1285
            geoengine_datatypes::raster::RasterDataType::I64 => Self::I64,
×
1286
            geoengine_datatypes::raster::RasterDataType::F32 => Self::F32,
×
1287
            geoengine_datatypes::raster::RasterDataType::F64 => Self::F64,
×
1288
        }
1289
    }
4✔
1290
}
1291

1292
impl From<RasterDataType> for geoengine_datatypes::raster::RasterDataType {
1293
    fn from(value: RasterDataType) -> Self {
4✔
1294
        match value {
4✔
1295
            RasterDataType::U8 => Self::U8,
4✔
1296
            RasterDataType::U16 => Self::U16,
×
1297
            RasterDataType::U32 => Self::U32,
×
1298
            RasterDataType::U64 => Self::U64,
×
1299
            RasterDataType::I8 => Self::I8,
×
1300
            RasterDataType::I16 => Self::I16,
×
1301
            RasterDataType::I32 => Self::I32,
×
1302
            RasterDataType::I64 => Self::I64,
×
1303
            RasterDataType::F32 => Self::F32,
×
1304
            RasterDataType::F64 => Self::F64,
×
1305
        }
1306
    }
4✔
1307
}
1308

1309
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
×
1310
#[serde(rename_all = "UPPERCASE")]
1311
pub enum ResamplingMethod {
1312
    Nearest,
1313
    Average,
1314
    Bilinear,
1315
    Cubic,
1316
    CubicSpline,
1317
    Lanczos,
1318
}
1319

1320
impl std::fmt::Display for ResamplingMethod {
1321
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
1322
        match self {
×
1323
            ResamplingMethod::Nearest => write!(f, "NEAREST"),
×
1324
            ResamplingMethod::Average => write!(f, "AVERAGE"),
×
1325
            ResamplingMethod::Bilinear => write!(f, "BILINEAR"),
×
1326
            ResamplingMethod::Cubic => write!(f, "CUBIC"),
×
1327
            ResamplingMethod::CubicSpline => write!(f, "CUBICSPLINE"),
×
1328
            ResamplingMethod::Lanczos => write!(f, "LANCZOS"),
×
1329
        }
1330
    }
×
1331
}
1332

1333
impl From<ResamplingMethod> for geoengine_datatypes::util::gdal::ResamplingMethod {
1334
    fn from(value: ResamplingMethod) -> Self {
×
1335
        match value {
×
1336
            ResamplingMethod::Nearest => Self::Nearest,
×
1337
            ResamplingMethod::Average => Self::Average,
×
1338
            ResamplingMethod::Bilinear => Self::Bilinear,
×
1339
            ResamplingMethod::Cubic => Self::Cubic,
×
1340
            ResamplingMethod::CubicSpline => Self::CubicSpline,
×
1341
            ResamplingMethod::Lanczos => Self::Lanczos,
×
1342
        }
1343
    }
×
1344
}
1345

1346
/// `RgbaColor` defines a 32 bit RGB color with alpha value
1347
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
1348
pub struct RgbaColor(pub [u8; 4]);
1349

1350
// manual implementation utoipa generates an integer field
1351
impl<'a> ToSchema<'a> for RgbaColor {
1352
    fn schema() -> (&'a str, utoipa::openapi::RefOr<utoipa::openapi::Schema>) {
2✔
1353
        use utoipa::openapi::*;
1354
        (
2✔
1355
            "RgbaColor",
2✔
1356
            ArrayBuilder::new()
2✔
1357
                .items(ObjectBuilder::new().schema_type(SchemaType::Integer))
2✔
1358
                .min_items(Some(4))
2✔
1359
                .max_items(Some(4))
2✔
1360
                .into(),
2✔
1361
        )
2✔
1362
    }
2✔
1363
}
1364

1365
impl From<geoengine_datatypes::operations::image::RgbaColor> for RgbaColor {
1366
    fn from(color: geoengine_datatypes::operations::image::RgbaColor) -> Self {
27✔
1367
        Self(color.into_inner())
27✔
1368
    }
27✔
1369
}
1370

1371
impl From<RgbaColor> for geoengine_datatypes::operations::image::RgbaColor {
1372
    fn from(color: RgbaColor) -> Self {
22✔
1373
        Self::new(color.0[0], color.0[1], color.0[2], color.0[3])
22✔
1374
    }
22✔
1375
}
1376

1377
/// A container type for breakpoints that specify a value to color mapping
1378
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
1379
pub struct Breakpoint {
1380
    pub value: NotNanF64,
1381
    pub color: RgbaColor,
1382
}
1383

1384
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
1385
pub struct NotNanF64(NotNan<f64>);
1386

1387
impl From<NotNan<f64>> for NotNanF64 {
1388
    fn from(value: NotNan<f64>) -> Self {
10✔
1389
        Self(value)
10✔
1390
    }
10✔
1391
}
1392

1393
impl From<NotNanF64> for NotNan<f64> {
1394
    fn from(value: NotNanF64) -> Self {
8✔
1395
        value.0
8✔
1396
    }
8✔
1397
}
1398

1399
impl ToSql for NotNanF64 {
1400
    fn to_sql(
×
1401
        &self,
×
1402
        ty: &postgres_types::Type,
×
1403
        w: &mut bytes::BytesMut,
×
1404
    ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
×
1405
        <f64 as ToSql>::to_sql(&self.0.into_inner(), ty, w)
×
1406
    }
×
1407

1408
    fn accepts(ty: &postgres_types::Type) -> bool {
×
1409
        <f64 as ToSql>::accepts(ty)
×
1410
    }
×
1411

1412
    postgres_types::to_sql_checked!();
1413
}
1414

1415
impl<'a> FromSql<'a> for NotNanF64 {
1416
    fn from_sql(
×
1417
        ty: &postgres_types::Type,
×
1418
        raw: &'a [u8],
×
1419
    ) -> Result<NotNanF64, Box<dyn std::error::Error + Sync + Send>> {
×
1420
        let value = <f64 as FromSql>::from_sql(ty, raw)?;
×
1421

1422
        Ok(NotNanF64(value.try_into()?))
×
1423
    }
×
1424

1425
    fn accepts(ty: &postgres_types::Type) -> bool {
×
1426
        <f64 as FromSql>::accepts(ty)
×
1427
    }
×
1428
}
1429

1430
// manual implementation because of NotNan
1431
impl<'a> ToSchema<'a> for Breakpoint {
1432
    fn schema() -> (&'a str, utoipa::openapi::RefOr<utoipa::openapi::Schema>) {
2✔
1433
        use utoipa::openapi::*;
1434
        (
2✔
1435
            "Breakpoint",
2✔
1436
            ObjectBuilder::new()
2✔
1437
                .property("value", Object::with_type(SchemaType::Number))
2✔
1438
                .property("color", Ref::from_schema_name("RgbaColor"))
2✔
1439
                .required("value")
2✔
1440
                .required("color")
2✔
1441
                .into(),
2✔
1442
        )
2✔
1443
    }
2✔
1444
}
1445

1446
impl From<geoengine_datatypes::operations::image::Breakpoint> for Breakpoint {
1447
    fn from(breakpoint: geoengine_datatypes::operations::image::Breakpoint) -> Self {
10✔
1448
        Self {
10✔
1449
            value: breakpoint.value.into(),
10✔
1450
            color: breakpoint.color.into(),
10✔
1451
        }
10✔
1452
    }
10✔
1453
}
1454

1455
impl From<Breakpoint> for geoengine_datatypes::operations::image::Breakpoint {
1456
    fn from(breakpoint: Breakpoint) -> Self {
8✔
1457
        Self {
8✔
1458
            value: breakpoint.value.into(),
8✔
1459
            color: breakpoint.color.into(),
8✔
1460
        }
8✔
1461
    }
8✔
1462
}
1463

1464
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, ToSchema)]
2✔
1465
#[serde(rename_all = "camelCase")]
1466
pub struct LinearGradient {
1467
    pub breakpoints: Vec<Breakpoint>,
1468
    pub no_data_color: RgbaColor,
1469
    pub over_color: RgbaColor,
1470
    pub under_color: RgbaColor,
1471
}
1472

1473
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, ToSchema)]
2✔
1474
#[serde(rename_all = "camelCase")]
1475
pub struct LogarithmicGradient {
1476
    pub breakpoints: Vec<Breakpoint>,
1477
    pub no_data_color: RgbaColor,
1478
    pub over_color: RgbaColor,
1479
    pub under_color: RgbaColor,
1480
}
1481

1482
/// A colorizer specifies a mapping between raster values and an output image
1483
/// There are different variants that perform different kinds of mapping.
1484
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, ToSchema)]
2✔
1485
#[serde(rename_all = "camelCase", tag = "type")]
1486
pub enum Colorizer {
1487
    #[serde(rename_all = "camelCase")]
1488
    LinearGradient(LinearGradient),
1489
    #[serde(rename_all = "camelCase")]
1490
    LogarithmicGradient(LogarithmicGradient),
1491
    #[serde(rename_all = "camelCase")]
1492
    #[schema(title = "PaletteColorizer")]
1493
    Palette {
1494
        colors: Palette,
1495
        no_data_color: RgbaColor,
1496
        default_color: RgbaColor,
1497
    },
1498
}
1499

1500
impl From<geoengine_datatypes::operations::image::Colorizer> for Colorizer {
1501
    fn from(v: geoengine_datatypes::operations::image::Colorizer) -> Self {
5✔
1502
        match v {
5✔
1503
            geoengine_datatypes::operations::image::Colorizer::LinearGradient {
1504
                breakpoints,
5✔
1505
                no_data_color,
5✔
1506
                over_color,
5✔
1507
                under_color,
5✔
1508
            } => Self::LinearGradient(LinearGradient {
5✔
1509
                breakpoints: breakpoints
5✔
1510
                    .into_iter()
5✔
1511
                    .map(Into::into)
5✔
1512
                    .collect::<Vec<Breakpoint>>(),
5✔
1513
                no_data_color: no_data_color.into(),
5✔
1514
                over_color: over_color.into(),
5✔
1515
                under_color: under_color.into(),
5✔
1516
            }),
5✔
1517
            geoengine_datatypes::operations::image::Colorizer::LogarithmicGradient {
1518
                breakpoints,
×
1519
                no_data_color,
×
1520
                over_color,
×
1521
                under_color,
×
1522
            } => Self::LogarithmicGradient(LogarithmicGradient {
×
1523
                breakpoints: breakpoints
×
1524
                    .into_iter()
×
1525
                    .map(Into::into)
×
1526
                    .collect::<Vec<Breakpoint>>(),
×
1527
                no_data_color: no_data_color.into(),
×
1528
                over_color: over_color.into(),
×
1529
                under_color: under_color.into(),
×
1530
            }),
×
1531
            geoengine_datatypes::operations::image::Colorizer::Palette {
1532
                colors,
×
1533
                no_data_color,
×
1534
                default_color,
×
1535
            } => Self::Palette {
×
1536
                colors: colors.into(),
×
1537
                no_data_color: no_data_color.into(),
×
1538
                default_color: default_color.into(),
×
1539
            },
×
1540
        }
1541
    }
5✔
1542
}
1543

1544
impl From<Colorizer> for geoengine_datatypes::operations::image::Colorizer {
1545
    fn from(v: Colorizer) -> Self {
4✔
1546
        match v {
4✔
1547
            Colorizer::LinearGradient(linear_gradient) => Self::LinearGradient {
4✔
1548
                breakpoints: linear_gradient
4✔
1549
                    .breakpoints
4✔
1550
                    .into_iter()
4✔
1551
                    .map(Into::into)
4✔
1552
                    .collect::<Vec<geoengine_datatypes::operations::image::Breakpoint>>(),
4✔
1553
                no_data_color: linear_gradient.no_data_color.into(),
4✔
1554
                over_color: linear_gradient.over_color.into(),
4✔
1555
                under_color: linear_gradient.under_color.into(),
4✔
1556
            },
4✔
1557
            Colorizer::LogarithmicGradient(logarithmic_gradient) => Self::LogarithmicGradient {
×
1558
                breakpoints: logarithmic_gradient
×
1559
                    .breakpoints
×
1560
                    .into_iter()
×
1561
                    .map(Into::into)
×
1562
                    .collect::<Vec<geoengine_datatypes::operations::image::Breakpoint>>(),
×
1563
                no_data_color: logarithmic_gradient.no_data_color.into(),
×
1564
                over_color: logarithmic_gradient.over_color.into(),
×
1565
                under_color: logarithmic_gradient.under_color.into(),
×
1566
            },
×
1567

1568
            Colorizer::Palette {
1569
                colors,
×
1570
                no_data_color,
×
1571
                default_color,
×
1572
            } => Self::Palette {
×
1573
                colors: colors.into(),
×
1574
                no_data_color: no_data_color.into(),
×
1575
                default_color: default_color.into(),
×
1576
            },
×
1577
        }
1578
    }
4✔
1579
}
1580

1581
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, ToSchema)]
2✔
1582
#[serde(rename_all = "camelCase", tag = "type")]
1583
pub enum RasterColorizer {
1584
    #[serde(rename_all = "camelCase")]
1585
    #[schema(title = "SingleBandRasterColorizer")]
1586
    SingleBand {
1587
        band: u32,
1588
        band_colorizer: Colorizer,
1589
    },
1590
    #[serde(rename_all = "camelCase")]
1591
    #[schema(title = "MultiBandRasterColorizer")]
1592
    MultiBand {
1593
        /// The band index of the red channel.
1594
        red_band: u32,
1595
        /// The minimum value for the red channel.
1596
        red_min: f64,
1597
        /// The maximum value for the red channel.
1598
        red_max: f64,
1599
        /// A scaling factor for the red channel between 0 and 1.
1600
        #[serde(default = "num_traits::One::one")]
1601
        red_scale: f64,
1602

1603
        /// The band index of the green channel.
1604
        green_band: u32,
1605
        /// The minimum value for the red channel.
1606
        green_min: f64,
1607
        /// The maximum value for the red channel.
1608
        green_max: f64,
1609
        /// A scaling factor for the green channel between 0 and 1.
1610
        #[serde(default = "num_traits::One::one")]
1611
        green_scale: f64,
1612

1613
        /// The band index of the blue channel.
1614
        blue_band: u32,
1615
        /// The minimum value for the red channel.
1616
        blue_min: f64,
1617
        /// The maximum value for the red channel.
1618
        blue_max: f64,
1619
        /// A scaling factor for the blue channel between 0 and 1.
1620
        #[serde(default = "num_traits::One::one")]
1621
        blue_scale: f64,
1622

1623
        /// The color to use for no data values.
1624
        /// If not specified, the no data values will be transparent.
1625
        #[serde(default = "rgba_transparent")]
1626
        no_data_color: RgbaColor,
1627
    },
1628
}
1629

1630
fn rgba_transparent() -> RgbaColor {
×
1631
    RgbaColor([0, 0, 0, 0])
×
1632
}
×
1633

1634
impl Eq for RasterColorizer {}
1635

1636
impl RasterColorizer {
1637
    pub fn band_selection(&self) -> BandSelection {
6✔
1638
        match self {
6✔
1639
            RasterColorizer::SingleBand { band, .. } => BandSelection(vec![*band as usize]),
4✔
1640
            RasterColorizer::MultiBand {
1641
                red_band,
2✔
1642
                green_band,
2✔
1643
                blue_band,
2✔
1644
                ..
2✔
1645
            } => {
2✔
1646
                let mut bands = Vec::with_capacity(3);
2✔
1647
                for band in [
6✔
1648
                    *red_band as usize,
2✔
1649
                    *green_band as usize,
2✔
1650
                    *blue_band as usize,
2✔
1651
                ] {
1652
                    if !bands.contains(&band) {
6✔
1653
                        bands.push(band);
4✔
1654
                    }
4✔
1655
                }
1656
                bands.sort_unstable(); // bands will be returned in ascending order anyway
2✔
1657
                BandSelection(bands)
2✔
1658
            }
1659
        }
1660
    }
6✔
1661
}
1662

1663
impl From<geoengine_datatypes::operations::image::RasterColorizer> for RasterColorizer {
1664
    fn from(v: geoengine_datatypes::operations::image::RasterColorizer) -> Self {
×
1665
        match v {
×
1666
            geoengine_datatypes::operations::image::RasterColorizer::SingleBand {
1667
                band,
×
1668
                band_colorizer: colorizer,
×
1669
            } => Self::SingleBand {
×
1670
                band,
×
1671
                band_colorizer: colorizer.into(),
×
1672
            },
×
1673
            geoengine_datatypes::operations::image::RasterColorizer::MultiBand {
1674
                red_band,
×
1675
                green_band,
×
1676
                blue_band,
×
1677
                rgb_params,
×
1678
            } => Self::MultiBand {
×
1679
                red_band,
×
1680
                green_band,
×
1681
                blue_band,
×
1682
                red_min: rgb_params.red_min,
×
1683
                red_max: rgb_params.red_max,
×
1684
                red_scale: rgb_params.red_scale,
×
1685
                green_min: rgb_params.green_min,
×
1686
                green_max: rgb_params.green_max,
×
1687
                green_scale: rgb_params.green_scale,
×
1688
                blue_min: rgb_params.blue_min,
×
1689
                blue_max: rgb_params.blue_max,
×
1690
                blue_scale: rgb_params.blue_scale,
×
1691
                no_data_color: rgb_params.no_data_color.into(),
×
1692
            },
×
1693
        }
1694
    }
×
1695
}
1696

1697
impl From<RasterColorizer> for geoengine_datatypes::operations::image::RasterColorizer {
1698
    fn from(v: RasterColorizer) -> Self {
6✔
1699
        match v {
6✔
1700
            RasterColorizer::SingleBand {
1701
                band,
4✔
1702
                band_colorizer: colorizer,
4✔
1703
            } => Self::SingleBand {
4✔
1704
                band,
4✔
1705
                band_colorizer: colorizer.into(),
4✔
1706
            },
4✔
1707
            RasterColorizer::MultiBand {
1708
                red_band,
2✔
1709
                red_min,
2✔
1710
                red_max,
2✔
1711
                red_scale,
2✔
1712
                green_band,
2✔
1713
                green_min,
2✔
1714
                green_max,
2✔
1715
                green_scale,
2✔
1716
                blue_band,
2✔
1717
                blue_min,
2✔
1718
                blue_max,
2✔
1719
                blue_scale,
2✔
1720
                no_data_color,
2✔
1721
            } => Self::MultiBand {
2✔
1722
                red_band,
2✔
1723
                green_band,
2✔
1724
                blue_band,
2✔
1725
                rgb_params: RgbParams {
2✔
1726
                    red_min,
2✔
1727
                    red_max,
2✔
1728
                    red_scale,
2✔
1729
                    green_min,
2✔
1730
                    green_max,
2✔
1731
                    green_scale,
2✔
1732
                    blue_min,
2✔
1733
                    blue_max,
2✔
1734
                    blue_scale,
2✔
1735
                    no_data_color: no_data_color.into(),
2✔
1736
                },
2✔
1737
            },
2✔
1738
        }
1739
    }
6✔
1740
}
1741

1742
/// A map from value to color
1743
///
1744
/// It is assumed that is has at least one and at most 256 entries.
1745
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
2✔
1746
#[serde(try_from = "SerializablePalette", into = "SerializablePalette")]
1747
pub struct Palette(pub HashMap<NotNan<f64>, RgbaColor>);
1748

1749
impl From<geoengine_datatypes::operations::image::Palette> for Palette {
1750
    fn from(palette: geoengine_datatypes::operations::image::Palette) -> Self {
×
1751
        Self(
×
1752
            palette
×
1753
                .into_inner()
×
1754
                .into_iter()
×
1755
                .map(|(value, color)| (value, color.into()))
×
1756
                .collect(),
×
1757
        )
×
1758
    }
×
1759
}
1760

1761
impl From<Palette> for geoengine_datatypes::operations::image::Palette {
1762
    fn from(palette: Palette) -> Self {
×
1763
        Self::new(
×
1764
            palette
×
1765
                .0
×
1766
                .into_iter()
×
1767
                .map(|(value, color)| (value, color.into()))
×
1768
                .collect(),
×
1769
        )
×
1770
    }
×
1771
}
1772

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

1778
impl From<Palette> for SerializablePalette {
1779
    fn from(palette: Palette) -> Self {
×
1780
        Self(
×
1781
            palette
×
1782
                .0
×
1783
                .into_iter()
×
1784
                .map(|(k, v)| (k.to_string(), v))
×
1785
                .collect(),
×
1786
        )
×
1787
    }
×
1788
}
1789

1790
impl TryFrom<SerializablePalette> for Palette {
1791
    type Error = <NotNan<f64> as FromStr>::Err;
1792

1793
    fn try_from(palette: SerializablePalette) -> Result<Self, Self::Error> {
×
1794
        let mut inner = HashMap::<NotNan<f64>, RgbaColor>::with_capacity(palette.0.len());
×
1795
        for (k, v) in palette.0 {
×
1796
            inner.insert(k.parse()?, v);
×
1797
        }
1798
        Ok(Self(inner))
×
1799
    }
×
1800
}
1801

1802
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Hash, Eq, PartialOrd, Ord, ToSchema)]
2✔
1803
pub struct RasterPropertiesKey {
1804
    pub domain: Option<String>,
1805
    pub key: String,
1806
}
1807

1808
impl From<geoengine_datatypes::raster::RasterPropertiesKey> for RasterPropertiesKey {
1809
    fn from(value: geoengine_datatypes::raster::RasterPropertiesKey) -> Self {
×
1810
        Self {
×
1811
            domain: value.domain,
×
1812
            key: value.key,
×
1813
        }
×
1814
    }
×
1815
}
1816

1817
impl From<RasterPropertiesKey> for geoengine_datatypes::raster::RasterPropertiesKey {
1818
    fn from(value: RasterPropertiesKey) -> Self {
×
1819
        Self {
×
1820
            domain: value.domain,
×
1821
            key: value.key,
×
1822
        }
×
1823
    }
×
1824
}
1825

1826
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ToSchema)]
2✔
1827
pub enum RasterPropertiesEntryType {
1828
    Number,
1829
    String,
1830
}
1831

1832
impl From<geoengine_datatypes::raster::RasterPropertiesEntryType> for RasterPropertiesEntryType {
1833
    fn from(value: geoengine_datatypes::raster::RasterPropertiesEntryType) -> Self {
×
1834
        match value {
×
1835
            geoengine_datatypes::raster::RasterPropertiesEntryType::Number => Self::Number,
×
1836
            geoengine_datatypes::raster::RasterPropertiesEntryType::String => Self::String,
×
1837
        }
1838
    }
×
1839
}
1840

1841
impl From<RasterPropertiesEntryType> for geoengine_datatypes::raster::RasterPropertiesEntryType {
1842
    fn from(value: RasterPropertiesEntryType) -> Self {
×
1843
        match value {
×
1844
            RasterPropertiesEntryType::Number => Self::Number,
×
1845
            RasterPropertiesEntryType::String => Self::String,
×
1846
        }
1847
    }
×
1848
}
1849

1850
#[derive(PartialEq, Eq, Clone, Debug)]
1851
pub struct DateTimeParseFormat {
1852
    fmt: String,
1853
    has_tz: bool,
1854
    has_time: bool,
1855
}
1856

1857
impl<'a> ToSchema<'a> for DateTimeParseFormat {
1858
    fn schema() -> (&'a str, utoipa::openapi::RefOr<utoipa::openapi::Schema>) {
2✔
1859
        use utoipa::openapi::*;
1860
        (
2✔
1861
            "DateTimeParseFormat",
2✔
1862
            ObjectBuilder::new().schema_type(SchemaType::String).into(),
2✔
1863
        )
2✔
1864
    }
2✔
1865
}
1866

1867
impl<'de> Deserialize<'de> for DateTimeParseFormat {
1868
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
4✔
1869
    where
4✔
1870
        D: serde::Deserializer<'de>,
4✔
1871
    {
4✔
1872
        let s = String::deserialize(deserializer)?;
4✔
1873
        Ok(geoengine_datatypes::primitives::DateTimeParseFormat::custom(s).into())
4✔
1874
    }
4✔
1875
}
1876

1877
impl Serialize for DateTimeParseFormat {
1878
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
5✔
1879
    where
5✔
1880
        S: serde::Serializer,
5✔
1881
    {
5✔
1882
        serializer.serialize_str(&self.fmt)
5✔
1883
    }
5✔
1884
}
1885

1886
impl From<geoengine_datatypes::primitives::DateTimeParseFormat> for DateTimeParseFormat {
1887
    fn from(value: geoengine_datatypes::primitives::DateTimeParseFormat) -> Self {
8✔
1888
        Self {
8✔
1889
            fmt: value.parse_format().to_string(),
8✔
1890
            has_tz: value.has_tz(),
8✔
1891
            has_time: value.has_time(),
8✔
1892
        }
8✔
1893
    }
8✔
1894
}
1895

1896
impl From<DateTimeParseFormat> for geoengine_datatypes::primitives::DateTimeParseFormat {
1897
    fn from(value: DateTimeParseFormat) -> Self {
4✔
1898
        Self::custom(value.fmt)
4✔
1899
    }
4✔
1900
}
1901

1902
impl DateTimeParseFormat {
1903
    // this is used as default value
1904
    pub fn unix() -> Self {
×
1905
        geoengine_datatypes::primitives::DateTimeParseFormat::unix().into()
×
1906
    }
×
1907
}
1908

1909
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
2✔
1910
pub struct NoGeometry;
1911

1912
impl From<geoengine_datatypes::primitives::NoGeometry> for NoGeometry {
1913
    fn from(_: geoengine_datatypes::primitives::NoGeometry) -> Self {
×
1914
        Self {}
×
1915
    }
×
1916
}
1917

1918
impl From<NoGeometry> for geoengine_datatypes::primitives::NoGeometry {
1919
    fn from(_: NoGeometry) -> Self {
×
1920
        Self {}
×
1921
    }
×
1922
}
1923

1924
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
2✔
1925
pub struct MultiPoint {
1926
    pub coordinates: Vec<Coordinate2D>,
1927
}
1928

1929
impl From<geoengine_datatypes::primitives::MultiPoint> for MultiPoint {
1930
    fn from(value: geoengine_datatypes::primitives::MultiPoint) -> Self {
×
1931
        Self {
×
1932
            coordinates: value.points().iter().map(|x| (*x).into()).collect(),
×
1933
        }
×
1934
    }
×
1935
}
1936

1937
impl From<MultiPoint> for geoengine_datatypes::primitives::MultiPoint {
1938
    fn from(value: MultiPoint) -> Self {
×
1939
        Self::new(value.coordinates.into_iter().map(Into::into).collect())
×
1940
            .expect("it should always be able to convert it")
×
1941
    }
×
1942
}
1943

1944
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
2✔
1945
pub struct MultiLineString {
1946
    pub coordinates: Vec<Vec<Coordinate2D>>,
1947
}
1948

1949
impl From<geoengine_datatypes::primitives::MultiLineString> for MultiLineString {
1950
    fn from(value: geoengine_datatypes::primitives::MultiLineString) -> Self {
×
1951
        Self {
×
1952
            coordinates: value
×
1953
                .lines()
×
1954
                .iter()
×
1955
                .map(|x| x.iter().map(|x| (*x).into()).collect())
×
1956
                .collect(),
×
1957
        }
×
1958
    }
×
1959
}
1960

1961
impl From<MultiLineString> for geoengine_datatypes::primitives::MultiLineString {
1962
    fn from(value: MultiLineString) -> Self {
×
1963
        Self::new(
×
1964
            value
×
1965
                .coordinates
×
1966
                .into_iter()
×
1967
                .map(|x| x.into_iter().map(Into::into).collect())
×
1968
                .collect(),
×
1969
        )
×
1970
        .expect("it should always be able to convert it")
×
1971
    }
×
1972
}
1973

1974
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
2✔
1975
pub struct MultiPolygon {
1976
    pub polygons: Vec<Vec<Vec<Coordinate2D>>>,
1977
}
1978

1979
impl From<geoengine_datatypes::primitives::MultiPolygon> for MultiPolygon {
1980
    fn from(value: geoengine_datatypes::primitives::MultiPolygon) -> Self {
×
1981
        Self {
×
1982
            polygons: value
×
1983
                .polygons()
×
1984
                .iter()
×
1985
                .map(|x| {
×
1986
                    x.iter()
×
1987
                        .map(|y| y.iter().map(|y| (*y).into()).collect())
×
1988
                        .collect()
×
1989
                })
×
1990
                .collect(),
×
1991
        }
×
1992
    }
×
1993
}
1994

1995
impl From<MultiPolygon> for geoengine_datatypes::primitives::MultiPolygon {
1996
    fn from(value: MultiPolygon) -> Self {
×
1997
        Self::new(
×
1998
            value
×
1999
                .polygons
×
2000
                .iter()
×
2001
                .map(|x| {
×
2002
                    x.iter()
×
2003
                        .map(|y| y.iter().map(|y| (*y).into()).collect())
×
2004
                        .collect()
×
2005
                })
×
2006
                .collect(),
×
2007
        )
×
2008
        .expect("it should always be able to convert it")
×
2009
    }
×
2010
}
2011

2012
#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, Clone)]
2013
pub struct StringPair((String, String));
2014

2015
pub type GdalConfigOption = StringPair;
2016
pub type AxisLabels = StringPair;
2017

2018
impl<'a> ToSchema<'a> for StringPair {
2019
    fn schema() -> (&'a str, utoipa::openapi::RefOr<utoipa::openapi::Schema>) {
6✔
2020
        use utoipa::openapi::*;
2021
        (
6✔
2022
            "StringPair",
6✔
2023
            ArrayBuilder::new()
6✔
2024
                .items(Object::with_type(SchemaType::String))
6✔
2025
                .min_items(Some(2))
6✔
2026
                .max_items(Some(2))
6✔
2027
                .into(),
6✔
2028
        )
6✔
2029
    }
6✔
2030

2031
    fn aliases() -> Vec<(&'a str, utoipa::openapi::Schema)> {
6✔
2032
        let utoipa::openapi::RefOr::T(unpacked_schema) = Self::schema().1 else {
6✔
2033
            unreachable!()
×
2034
        };
2035
        vec![
6✔
2036
            ("GdalConfigOption", unpacked_schema.clone()),
6✔
2037
            ("AxisLabels", unpacked_schema),
6✔
2038
        ]
6✔
2039
    }
6✔
2040
}
2041

2042
impl From<(String, String)> for StringPair {
2043
    fn from(value: (String, String)) -> Self {
32✔
2044
        Self(value)
32✔
2045
    }
32✔
2046
}
2047

2048
impl From<StringPair> for (String, String) {
2049
    fn from(value: StringPair) -> Self {
×
2050
        value.0
×
2051
    }
×
2052
}
2053

2054
impl From<StringPair> for geoengine_datatypes::util::StringPair {
2055
    fn from(value: StringPair) -> Self {
2✔
2056
        Self::new(value.0 .0, value.0 .1)
2✔
2057
    }
2✔
2058
}
2059

2060
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize, ToSchema)]
2✔
2061
pub enum PlotOutputFormat {
2062
    JsonPlain,
2063
    JsonVega,
2064
    ImagePng,
2065
}
2066

2067
#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize, PartialOrd, Deserialize, ToSchema)]
2✔
2068
pub struct CacheTtlSeconds(u32);
2069

2070
const MAX_CACHE_TTL_SECONDS: u32 = 31_536_000; // 1 year
2071

2072
impl CacheTtlSeconds {
2073
    pub fn new(seconds: u32) -> Self {
×
2074
        Self(seconds.min(MAX_CACHE_TTL_SECONDS))
×
2075
    }
×
2076

2077
    pub fn max() -> Self {
×
2078
        Self(MAX_CACHE_TTL_SECONDS)
×
2079
    }
×
2080

2081
    pub fn seconds(self) -> u32 {
×
2082
        self.0
×
2083
    }
×
2084
}
2085

2086
impl From<geoengine_datatypes::primitives::CacheTtlSeconds> for CacheTtlSeconds {
2087
    fn from(value: geoengine_datatypes::primitives::CacheTtlSeconds) -> Self {
5✔
2088
        Self(value.seconds())
5✔
2089
    }
5✔
2090
}
2091

2092
impl From<CacheTtlSeconds> for geoengine_datatypes::primitives::CacheTtlSeconds {
2093
    fn from(value: CacheTtlSeconds) -> Self {
7✔
2094
        Self::new(value.0)
7✔
2095
    }
7✔
2096
}
2097

2098
impl ToSql for CacheTtlSeconds {
2099
    fn to_sql(
×
2100
        &self,
×
2101
        ty: &postgres_types::Type,
×
2102
        w: &mut bytes::BytesMut,
×
2103
    ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
×
2104
        <i32 as ToSql>::to_sql(&(self.0 as i32), ty, w)
×
2105
    }
×
2106

2107
    fn accepts(ty: &postgres_types::Type) -> bool {
×
2108
        <i32 as ToSql>::accepts(ty)
×
2109
    }
×
2110

2111
    postgres_types::to_sql_checked!();
2112
}
2113

2114
impl<'a> FromSql<'a> for CacheTtlSeconds {
2115
    fn from_sql(
×
2116
        ty: &postgres_types::Type,
×
2117
        raw: &'a [u8],
×
2118
    ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
×
2119
        Ok(Self(<i32 as FromSql>::from_sql(ty, raw)? as u32))
×
2120
    }
×
2121

2122
    fn accepts(ty: &postgres_types::Type) -> bool {
×
2123
        <i32 as FromSql>::accepts(ty)
×
2124
    }
×
2125
}
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

© 2025 Coveralls, Inc