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

geo-engine / geoengine / 23058781799

13 Mar 2026 03:48PM UTC coverage: 88.252% (+0.1%) from 88.133%
23058781799

push

github

web-flow
feat: add histogram and statistics plot operators to openapi.json (#1130)

* feat: add histogram and statistics plot operators to openapi.json

* feat: implement api_operator macro for generating API specifications

* fix: add SingleRasterOrVector operator and source schemas to OpenAPI

* refactor: clean up unused schemas and improve justfile commands

* fix: expose RasterDatasetFromWorkflowResult for testing

* refactor: update Workflow enum and improve ToSchema implementation

* docs: enhance documentation for api_operator macro and string_token macro

399 of 436 new or added lines in 9 files covered. (91.51%)

13 existing lines in 2 files now uncovered.

113256 of 128333 relevant lines covered (88.25%)

504640.83 hits per line

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

57.1
/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_datatypes::raster::GridBounds;
8
use geoengine_macros::type_tag;
9
use ordered_float::NotNan;
10
use postgres_types::{FromSql, ToSql};
11
use serde::de::Error as SerdeError;
12
use serde::ser::SerializeMap;
13
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor};
14
use snafu::ResultExt;
15
use std::borrow::Cow;
16
use std::{
17
    collections::{BTreeMap, HashMap},
18
    fmt::{Debug, Formatter},
19
    str::FromStr,
20
};
21
use utoipa::{PartialSchema, ToSchema, openapi};
22

23
identifier!(DataProviderId);
24

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

31
// Identifier for datasets managed by Geo Engine
32
identifier!(DatasetId);
33

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

237
impl ToSchema for NamedData {}
238

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

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

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

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

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

272
impl From<geoengine_datatypes::dataset::DataProviderId> for DataProviderId {
273
    fn from(id: geoengine_datatypes::dataset::DataProviderId) -> Self {
11✔
274
        Self(id.0)
11✔
275
    }
11✔
276
}
277

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

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

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

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

335
impl FromStr for SpatialReferenceAuthority {
336
    type Err = error::Error;
337

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

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

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

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

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

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

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

415
impl SpatialReference {
416
    pub fn new(authority: SpatialReferenceAuthority, code: u32) -> Self {
63✔
417
        Self { authority, code }
63✔
418
    }
63✔
419

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

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

429
impl FromStr for SpatialReference {
430
    type Err = error::Error;
431

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

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

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

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

462
/// Helper struct for deserializing a `SpatialReferencce`
463
struct SpatialReferenceDeserializeVisitor;
464

465
impl Visitor<'_> for SpatialReferenceDeserializeVisitor {
466
    type Value = SpatialReference;
467

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

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

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

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

495
impl From<geoengine_datatypes::spatial_reference::SpatialReferenceOption>
496
    for SpatialReferenceOption
497
{
498
    fn from(value: geoengine_datatypes::spatial_reference::SpatialReferenceOption) -> Self {
30✔
499
        match value {
30✔
500
            geoengine_datatypes::spatial_reference::SpatialReferenceOption::SpatialReference(s) => {
18✔
501
                Self::SpatialReference(s.into())
18✔
502
            }
503
            geoengine_datatypes::spatial_reference::SpatialReferenceOption::Unreferenced => {
504
                Self::Unreferenced
12✔
505
            }
506
        }
507
    }
30✔
508
}
509

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

521
impl From<SpatialReference> for SpatialReferenceOption {
522
    fn from(spatial_reference: SpatialReference) -> Self {
30✔
523
        Self::SpatialReference(spatial_reference)
30✔
524
    }
30✔
525
}
526

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

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

545
/// Helper struct for deserializing a `SpatialReferenceOption`
546
struct SpatialReferenceOptionDeserializeVisitor;
547

548
impl Visitor<'_> for SpatialReferenceOptionDeserializeVisitor {
549
    type Value = SpatialReferenceOption;
550

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

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

563
        let spatial_reference: SpatialReference = v.parse().map_err(serde::de::Error::custom)?;
30✔
564

565
        Ok(spatial_reference.into())
30✔
566
    }
31✔
567
}
568

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

751
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
752
#[serde(rename_all = "camelCase")]
753
pub struct SpatialGridDefinition {
754
    pub geo_transform: GeoTransform,
755
    pub grid_bounds: GridBoundingBox2D,
756
}
757

758
impl From<geoengine_datatypes::raster::SpatialGridDefinition> for SpatialGridDefinition {
759
    fn from(value: geoengine_datatypes::raster::SpatialGridDefinition) -> Self {
14✔
760
        Self {
14✔
761
            geo_transform: value.geo_transform().into(),
14✔
762
            grid_bounds: value.grid_bounds().into(),
14✔
763
        }
14✔
764
    }
14✔
765
}
766

767
impl From<SpatialGridDefinition> for geoengine_datatypes::raster::SpatialGridDefinition {
768
    fn from(value: SpatialGridDefinition) -> Self {
20✔
769
        geoengine_datatypes::raster::SpatialGridDefinition::new(
20✔
770
            value.geo_transform.into(),
20✔
771
            value.grid_bounds.into(),
20✔
772
        )
773
    }
20✔
774
}
775

776
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ToSchema)]
777
#[serde(rename_all = "camelCase")]
778
pub struct GeoTransform {
779
    pub origin_coordinate: Coordinate2D,
780
    pub x_pixel_size: f64,
781
    pub y_pixel_size: f64,
782
}
783

784
impl From<geoengine_datatypes::raster::GeoTransform> for GeoTransform {
785
    fn from(value: geoengine_datatypes::raster::GeoTransform) -> Self {
14✔
786
        GeoTransform {
14✔
787
            origin_coordinate: value.origin_coordinate().into(),
14✔
788
            x_pixel_size: value.x_pixel_size(),
14✔
789
            y_pixel_size: value.y_pixel_size(),
14✔
790
        }
14✔
791
    }
14✔
792
}
793

794
impl From<GeoTransform> for geoengine_datatypes::raster::GeoTransform {
795
    fn from(value: GeoTransform) -> Self {
20✔
796
        geoengine_datatypes::raster::GeoTransform::new(
20✔
797
            value.origin_coordinate.into(),
20✔
798
            value.x_pixel_size,
20✔
799
            value.y_pixel_size,
20✔
800
        )
801
    }
20✔
802
}
803

804
impl From<geoengine_operators::source::GdalDatasetGeoTransform> for GeoTransform {
805
    fn from(value: geoengine_operators::source::GdalDatasetGeoTransform) -> Self {
52✔
806
        Self {
52✔
807
            origin_coordinate: value.origin_coordinate.into(),
52✔
808
            x_pixel_size: value.x_pixel_size,
52✔
809
            y_pixel_size: value.y_pixel_size,
52✔
810
        }
52✔
811
    }
52✔
812
}
813

814
impl From<GeoTransform> for geoengine_operators::source::GdalDatasetGeoTransform {
815
    fn from(value: GeoTransform) -> Self {
803✔
816
        Self {
803✔
817
            origin_coordinate: value.origin_coordinate.into(),
803✔
818
            x_pixel_size: value.x_pixel_size,
803✔
819
            y_pixel_size: value.y_pixel_size,
803✔
820
        }
803✔
821
    }
803✔
822
}
823

824
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
825
#[serde(rename_all = "camelCase")]
826
pub struct GridIdx2D {
827
    pub y_idx: isize,
828
    pub x_idx: isize,
829
}
830

831
impl From<geoengine_datatypes::raster::GridIdx2D> for GridIdx2D {
832
    fn from(value: geoengine_datatypes::raster::GridIdx2D) -> Self {
28✔
833
        Self {
28✔
834
            y_idx: value.y(),
28✔
835
            x_idx: value.x(),
28✔
836
        }
28✔
837
    }
28✔
838
}
839

840
impl From<GridIdx2D> for geoengine_datatypes::raster::GridIdx2D {
841
    fn from(value: GridIdx2D) -> Self {
×
842
        geoengine_datatypes::raster::GridIdx::new_y_x(value.y_idx, value.x_idx)
×
843
    }
×
844
}
845

846
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
847
#[serde(rename_all = "camelCase")]
848
pub struct GridBoundingBox2D {
849
    pub top_left_idx: GridIdx2D,
850
    pub bottom_right_idx: GridIdx2D,
851
}
852

853
impl From<geoengine_datatypes::raster::GridBoundingBox2D> for GridBoundingBox2D {
854
    fn from(value: geoengine_datatypes::raster::GridBoundingBox2D) -> Self {
14✔
855
        Self {
14✔
856
            top_left_idx: value.min_index().into(),
14✔
857
            bottom_right_idx: value.max_index().into(),
14✔
858
        }
14✔
859
    }
14✔
860
}
861

862
impl From<GridBoundingBox2D> for geoengine_datatypes::raster::GridBoundingBox2D {
863
    fn from(value: GridBoundingBox2D) -> Self {
20✔
864
        geoengine_datatypes::raster::GridBoundingBox2D::new_min_max(
20✔
865
            value.top_left_idx.y_idx,
20✔
866
            value.bottom_right_idx.y_idx,
20✔
867
            value.top_left_idx.x_idx,
20✔
868
            value.bottom_right_idx.x_idx,
20✔
869
        )
870
        .expect("Bounds were correct before") // TODO: maybe try from?
20✔
871
    }
20✔
872
}
873

874
/// An object that composes the date and a timestamp with time zone.
875
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
876
pub struct DateTimeString {
877
    datetime: chrono::DateTime<chrono::Utc>,
878
}
879

880
// TODO: use derive ToSchema when OpenAPI derive does not break
881
impl PartialSchema for DateTimeString {
UNCOV
882
    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
×
883
        use utoipa::openapi::schema::{ObjectBuilder, SchemaType, Type};
UNCOV
884
        ObjectBuilder::new()
×
UNCOV
885
            .schema_type(SchemaType::Type(Type::String))
×
UNCOV
886
            .into()
×
UNCOV
887
    }
×
888
}
889

890
impl ToSchema for DateTimeString {}
891

892
impl FromStr for DateTimeString {
893
    type Err = geoengine_datatypes::primitives::DateTimeError;
894

895
    fn from_str(input: &str) -> Result<Self, Self::Err> {
82✔
896
        let date_time = chrono::DateTime::<chrono::FixedOffset>::from_str(input).map_err(|e| {
82✔
897
            Self::Err::DateParse {
×
898
                source: Box::new(e),
×
899
            }
×
900
        })?;
×
901

902
        Ok(date_time.into())
82✔
903
    }
82✔
904
}
905

906
impl From<chrono::DateTime<chrono::FixedOffset>> for DateTimeString {
907
    fn from(datetime: chrono::DateTime<chrono::FixedOffset>) -> Self {
82✔
908
        Self {
82✔
909
            datetime: datetime.into(),
82✔
910
        }
82✔
911
    }
82✔
912
}
913

914
impl From<geoengine_datatypes::primitives::DateTime> for DateTimeString {
915
    fn from(datetime: geoengine_datatypes::primitives::DateTime) -> Self {
×
916
        Self {
×
917
            datetime: datetime.into(),
×
918
        }
×
919
    }
×
920
}
921

922
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize, ToSchema)]
923
#[serde(rename_all = "camelCase")]
924
pub enum FeatureDataType {
925
    Category,
926
    Int,
927
    Float,
928
    Text,
929
    Bool,
930
    DateTime,
931
}
932

933
impl From<geoengine_datatypes::primitives::FeatureDataType> for FeatureDataType {
934
    fn from(value: geoengine_datatypes::primitives::FeatureDataType) -> Self {
6✔
935
        match value {
6✔
936
            geoengine_datatypes::primitives::FeatureDataType::Category => Self::Category,
×
937
            geoengine_datatypes::primitives::FeatureDataType::Int => Self::Int,
2✔
938
            geoengine_datatypes::primitives::FeatureDataType::Float => Self::Float,
3✔
939
            geoengine_datatypes::primitives::FeatureDataType::Text => Self::Text,
1✔
940
            geoengine_datatypes::primitives::FeatureDataType::Bool => Self::Bool,
×
941
            geoengine_datatypes::primitives::FeatureDataType::DateTime => Self::DateTime,
×
942
        }
943
    }
6✔
944
}
945

946
impl From<FeatureDataType> for geoengine_datatypes::primitives::FeatureDataType {
947
    fn from(value: FeatureDataType) -> Self {
20✔
948
        match value {
20✔
949
            FeatureDataType::Category => Self::Category,
×
950
            FeatureDataType::Int => Self::Int,
4✔
951
            FeatureDataType::Float => Self::Float,
4✔
952
            FeatureDataType::Text => Self::Text,
12✔
953
            FeatureDataType::Bool => Self::Bool,
×
954
            FeatureDataType::DateTime => Self::DateTime,
×
955
        }
956
    }
20✔
957
}
958

959
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
960
#[serde(rename_all = "camelCase", untagged)]
961
#[schema(discriminator = "type")]
962
pub enum Measurement {
963
    Unitless(UnitlessMeasurement),
964
    Continuous(ContinuousMeasurement),
965
    Classification(ClassificationMeasurement),
966
}
967

968
impl From<geoengine_datatypes::primitives::Measurement> for Measurement {
969
    fn from(value: geoengine_datatypes::primitives::Measurement) -> Self {
21✔
970
        match value {
21✔
971
            geoengine_datatypes::primitives::Measurement::Unitless => {
972
                Self::Unitless(UnitlessMeasurement {
9✔
973
                    r#type: Default::default(),
9✔
974
                })
9✔
975
            }
976
            geoengine_datatypes::primitives::Measurement::Continuous(cm) => {
12✔
977
                Self::Continuous(cm.into())
12✔
978
            }
979
            geoengine_datatypes::primitives::Measurement::Classification(cm) => {
×
980
                Self::Classification(cm.into())
×
981
            }
982
        }
983
    }
21✔
984
}
985

986
impl From<Measurement> for geoengine_datatypes::primitives::Measurement {
987
    fn from(value: Measurement) -> Self {
47✔
988
        match value {
47✔
989
            Measurement::Unitless { .. } => Self::Unitless,
39✔
990
            Measurement::Continuous(cm) => Self::Continuous(cm.into()),
8✔
991
            Measurement::Classification(cm) => Self::Classification(cm.into()),
×
992
        }
993
    }
47✔
994
}
995

996
#[type_tag(value = "unitless")]
997
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema, Default)]
998
pub struct UnitlessMeasurement {}
999

1000
#[type_tag(value = "continuous")]
1001
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
1002
pub struct ContinuousMeasurement {
1003
    pub measurement: String,
1004
    pub unit: Option<String>,
1005
}
1006

1007
impl From<geoengine_datatypes::primitives::ContinuousMeasurement> for ContinuousMeasurement {
1008
    fn from(value: geoengine_datatypes::primitives::ContinuousMeasurement) -> Self {
12✔
1009
        Self {
12✔
1010
            r#type: Default::default(),
12✔
1011
            measurement: value.measurement,
12✔
1012
            unit: value.unit,
12✔
1013
        }
12✔
1014
    }
12✔
1015
}
1016

1017
impl From<ContinuousMeasurement> for geoengine_datatypes::primitives::ContinuousMeasurement {
1018
    fn from(value: ContinuousMeasurement) -> Self {
8✔
1019
        Self {
8✔
1020
            measurement: value.measurement,
8✔
1021
            unit: value.unit,
8✔
1022
        }
8✔
1023
    }
8✔
1024
}
1025

1026
#[derive(Clone, Debug, PartialEq, Eq)]
1027
pub struct SerializableClasses(BTreeMap<u8, String>);
1028

1029
impl PartialSchema for SerializableClasses {
1030
    fn schema() -> openapi::RefOr<openapi::schema::Schema> {
×
1031
        BTreeMap::<String, String>::schema()
×
1032
    }
×
1033
}
1034

1035
impl ToSchema for SerializableClasses {
1036
    fn name() -> Cow<'static, str> {
×
1037
        <BTreeMap<String, String> as ToSchema>::name() // TODO: is this needed?
×
1038
    }
×
1039
}
1040

1041
impl Serialize for SerializableClasses {
1042
    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
×
1043
    where
×
1044
        S: Serializer,
×
1045
    {
1046
        let classes: BTreeMap<String, &String> =
×
1047
            self.0.iter().map(|(k, v)| (k.to_string(), v)).collect();
×
1048
        classes.serialize(serializer)
×
1049
    }
×
1050
}
1051

1052
impl<'de> Deserialize<'de> for SerializableClasses {
1053
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
×
1054
    where
×
1055
        D: Deserializer<'de>,
×
1056
    {
1057
        let tree: BTreeMap<String, String> = Deserialize::deserialize(deserializer)?;
×
1058
        let classes: Result<BTreeMap<u8, String>, _> = tree
×
1059
            .into_iter()
×
1060
            .map(|(k, v)| k.parse::<u8>().map(|x| (x, v)))
×
1061
            .collect();
×
1062
        Ok(SerializableClasses(
1063
            classes.map_err(serde::de::Error::custom)?,
×
1064
        ))
1065
    }
×
1066
}
1067

1068
#[type_tag(value = "classification")]
1069
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
1070
pub struct ClassificationMeasurement {
1071
    pub measurement: String,
1072
    // use a BTreeMap to preserve the order of the keys
1073
    #[serde(serialize_with = "serialize_classes")]
1074
    #[serde(deserialize_with = "deserialize_classes")]
1075
    pub classes: BTreeMap<u8, String>,
1076
}
1077

1078
fn serialize_classes<S>(classes: &BTreeMap<u8, String>, serializer: S) -> Result<S::Ok, S::Error>
1✔
1079
where
1✔
1080
    S: Serializer,
1✔
1081
{
1082
    let mut map = serializer.serialize_map(Some(classes.len()))?;
1✔
1083
    for (k, v) in classes {
2✔
1084
        map.serialize_entry(&k.to_string(), v)?;
2✔
1085
    }
1086
    map.end()
1✔
1087
}
1✔
1088

1089
fn deserialize_classes<'de, D>(deserializer: D) -> Result<BTreeMap<u8, String>, D::Error>
2✔
1090
where
2✔
1091
    D: Deserializer<'de>,
2✔
1092
{
1093
    let map = BTreeMap::<String, String>::deserialize(deserializer)?;
2✔
1094
    let mut classes = BTreeMap::new();
2✔
1095
    for (k, v) in map {
3✔
1096
        classes.insert(
3✔
1097
            k.parse::<u8>()
3✔
1098
                .map_err(|e| D::Error::custom(format!("Failed to parse key as u8: {e}")))?,
3✔
1099
            v,
2✔
1100
        );
1101
    }
1102
    Ok(classes)
1✔
1103
}
2✔
1104

1105
impl From<geoengine_datatypes::primitives::ClassificationMeasurement>
1106
    for ClassificationMeasurement
1107
{
1108
    fn from(value: geoengine_datatypes::primitives::ClassificationMeasurement) -> Self {
×
1109
        Self {
×
1110
            r#type: Default::default(),
×
1111
            measurement: value.measurement,
×
1112
            classes: value.classes,
×
1113
        }
×
1114
    }
×
1115
}
1116

1117
impl From<ClassificationMeasurement>
1118
    for geoengine_datatypes::primitives::ClassificationMeasurement
1119
{
1120
    fn from(measurement: ClassificationMeasurement) -> Self {
×
1121
        Self {
×
1122
            measurement: measurement.measurement,
×
1123
            classes: measurement.classes,
×
1124
        }
×
1125
    }
×
1126
}
1127

1128
/// A partition of space that include the upper left but excludes the lower right coordinate
1129
#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Debug, ToSchema, FromSql, ToSql)]
×
1130
#[serde(rename_all = "camelCase")]
1131
pub struct SpatialPartition2D {
1132
    pub upper_left_coordinate: Coordinate2D,
1133
    pub lower_right_coordinate: Coordinate2D,
1134
}
1135

1136
impl From<geoengine_datatypes::primitives::SpatialPartition2D> for SpatialPartition2D {
1137
    fn from(value: geoengine_datatypes::primitives::SpatialPartition2D) -> Self {
50✔
1138
        Self {
50✔
1139
            upper_left_coordinate: value.upper_left().into(),
50✔
1140
            lower_right_coordinate: value.lower_right().into(),
50✔
1141
        }
50✔
1142
    }
50✔
1143
}
1144

1145
impl From<SpatialPartition2D> for geoengine_datatypes::primitives::SpatialPartition2D {
1146
    fn from(value: SpatialPartition2D) -> Self {
1✔
1147
        Self::new_unchecked(
1✔
1148
            value.upper_left_coordinate.into(),
1✔
1149
            value.lower_right_coordinate.into(),
1✔
1150
        )
1151
    }
1✔
1152
}
1153

1154
/// A spatio-temporal rectangle with a specified resolution
1155
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
1156
#[serde(rename_all = "camelCase")]
1157
pub struct RasterToDatasetQueryRectangle {
1158
    pub spatial_bounds: SpatialPartition2D,
1159
    pub time_interval: TimeInterval,
1160
}
1161

1162
/// A spatio-temporal rectangle with a specified resolution
1163
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
1164
#[serde(rename_all = "camelCase")]
1165
pub struct VectorQueryRectangle {
1166
    pub spatial_bounds: BoundingBox2D,
1167
    pub time_interval: TimeInterval,
1168
    pub spatial_resolution: SpatialResolution,
1169
}
1170

1171
/// A spatio-temporal rectangle with a specified resolution
1172
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
1173
#[serde(rename_all = "camelCase")]
1174
pub struct PlotQueryRectangle {
1175
    pub spatial_bounds: BoundingBox2D,
1176
    pub time_interval: TimeInterval,
1177
    pub spatial_resolution: SpatialResolution,
1178
}
1179

1180
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, ToSchema)]
1181
pub struct BandSelection(pub Vec<usize>);
1182

1183
impl From<geoengine_datatypes::primitives::BandSelection> for BandSelection {
1184
    fn from(value: geoengine_datatypes::primitives::BandSelection) -> Self {
1✔
1185
        Self(value.as_vec().into_iter().map(|b| b as usize).collect())
1✔
1186
    }
1✔
1187
}
1188

1189
impl TryFrom<BandSelection> for geoengine_datatypes::primitives::BandSelection {
1190
    type Error = Error;
1191

1192
    fn try_from(value: BandSelection) -> Result<Self> {
8✔
1193
        geoengine_datatypes::primitives::BandSelection::new(
8✔
1194
            value.0.into_iter().map(|b| b as u32).collect(),
10✔
1195
        )
1196
        .map_err(Into::into)
8✔
1197
    }
8✔
1198
}
1199

1200
/// The spatial resolution in SRS units
1201
#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Serialize, ToSchema)]
1202
pub struct SpatialResolution {
1203
    pub x: f64,
1204
    pub y: f64,
1205
}
1206

1207
impl From<geoengine_datatypes::primitives::SpatialResolution> for SpatialResolution {
1208
    fn from(value: geoengine_datatypes::primitives::SpatialResolution) -> Self {
×
1209
        Self {
×
1210
            x: value.x,
×
1211
            y: value.y,
×
1212
        }
×
1213
    }
×
1214
}
1215

1216
impl From<SpatialResolution> for geoengine_datatypes::primitives::SpatialResolution {
1217
    fn from(value: SpatialResolution) -> Self {
×
1218
        Self {
×
1219
            x: value.x,
×
1220
            y: value.y,
×
1221
        }
×
1222
    }
×
1223
}
1224

1225
#[derive(
1226
    Clone, Copy, Serialize, PartialEq, Eq, PartialOrd, Ord, Debug, ToSchema, FromSql, ToSql,
1227
)]
1228
#[repr(C)]
1229
#[postgres(transparent)]
1230
pub struct TimeInstance(i64);
1231

1232
impl FromStr for TimeInstance {
1233
    type Err = geoengine_datatypes::primitives::DateTimeError;
1234

1235
    fn from_str(s: &str) -> Result<Self, Self::Err> {
82✔
1236
        let date_time = DateTimeString::from_str(s)?;
82✔
1237
        Ok(date_time.into())
82✔
1238
    }
82✔
1239
}
1240

1241
impl From<geoengine_datatypes::primitives::TimeInstance> for TimeInstance {
1242
    fn from(value: geoengine_datatypes::primitives::TimeInstance) -> Self {
1,361✔
1243
        Self(value.inner())
1,361✔
1244
    }
1,361✔
1245
}
1246

1247
impl From<TimeInstance> for geoengine_datatypes::primitives::TimeInstance {
1248
    fn from(value: TimeInstance) -> Self {
961✔
1249
        geoengine_datatypes::primitives::TimeInstance::from_millis_unchecked(value.inner())
961✔
1250
    }
961✔
1251
}
1252

1253
impl From<DateTimeString> for TimeInstance {
1254
    fn from(datetime: DateTimeString) -> Self {
82✔
1255
        Self::from(&datetime)
82✔
1256
    }
82✔
1257
}
1258

1259
impl From<&DateTimeString> for TimeInstance {
1260
    fn from(datetime: &DateTimeString) -> Self {
82✔
1261
        geoengine_datatypes::primitives::TimeInstance::from_millis_unchecked(
82✔
1262
            datetime.datetime.timestamp_millis(),
82✔
1263
        )
1264
        .into()
82✔
1265
    }
82✔
1266
}
1267

1268
impl TimeInstance {
1269
    pub const fn inner(self) -> i64 {
985✔
1270
        self.0
985✔
1271
    }
985✔
1272
}
1273

1274
impl<'de> Deserialize<'de> for TimeInstance {
1275
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1,039✔
1276
    where
1,039✔
1277
        D: serde::Deserializer<'de>,
1,039✔
1278
    {
1279
        struct IsoStringOrUnixTimestamp;
1280

1281
        impl serde::de::Visitor<'_> for IsoStringOrUnixTimestamp {
1282
            type Value = TimeInstance;
1283

1284
            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
×
1285
                formatter.write_str("RFC 3339 timestamp string or Unix timestamp integer")
×
1286
            }
×
1287

1288
            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
20✔
1289
            where
20✔
1290
                E: serde::de::Error,
20✔
1291
            {
1292
                TimeInstance::from_str(value).map_err(E::custom)
20✔
1293
            }
20✔
1294

1295
            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
1,019✔
1296
            where
1,019✔
1297
                E: serde::de::Error,
1,019✔
1298
            {
1299
                geoengine_datatypes::primitives::TimeInstance::from_millis(v)
1,019✔
1300
                    .map(Into::into)
1,019✔
1301
                    .map_err(E::custom)
1,019✔
1302
            }
1,019✔
1303

1304
            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
1,019✔
1305
            where
1,019✔
1306
                E: serde::de::Error,
1,019✔
1307
            {
1308
                Self::visit_i64(self, v as i64)
1,019✔
1309
            }
1,019✔
1310
        }
1311

1312
        deserializer.deserialize_any(IsoStringOrUnixTimestamp)
1,039✔
1313
    }
1,039✔
1314
}
1315

1316
/// A time granularity.
1317
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema, FromSql, ToSql)]
×
1318
#[serde(rename_all = "camelCase")]
1319
pub enum TimeGranularity {
1320
    Millis,
1321
    Seconds,
1322
    Minutes,
1323
    Hours,
1324
    Days,
1325
    Months,
1326
    Years,
1327
}
1328

1329
impl From<geoengine_datatypes::primitives::TimeGranularity> for TimeGranularity {
1330
    fn from(value: geoengine_datatypes::primitives::TimeGranularity) -> Self {
14✔
1331
        match value {
14✔
1332
            geoengine_datatypes::primitives::TimeGranularity::Millis => Self::Millis,
×
1333
            geoengine_datatypes::primitives::TimeGranularity::Seconds => Self::Seconds,
×
1334
            geoengine_datatypes::primitives::TimeGranularity::Minutes => Self::Minutes,
×
1335
            geoengine_datatypes::primitives::TimeGranularity::Hours => Self::Hours,
×
1336
            geoengine_datatypes::primitives::TimeGranularity::Days => Self::Days,
×
1337
            geoengine_datatypes::primitives::TimeGranularity::Months => Self::Months,
14✔
1338
            geoengine_datatypes::primitives::TimeGranularity::Years => Self::Years,
×
1339
        }
1340
    }
14✔
1341
}
1342

1343
impl From<TimeGranularity> for geoengine_datatypes::primitives::TimeGranularity {
1344
    fn from(value: TimeGranularity) -> Self {
13✔
1345
        match value {
13✔
1346
            TimeGranularity::Millis => Self::Millis,
×
1347
            TimeGranularity::Seconds => Self::Seconds,
×
1348
            TimeGranularity::Minutes => Self::Minutes,
×
1349
            TimeGranularity::Hours => Self::Hours,
×
1350
            TimeGranularity::Days => Self::Days,
×
1351
            TimeGranularity::Months => Self::Months,
13✔
1352
            TimeGranularity::Years => Self::Years,
×
1353
        }
1354
    }
13✔
1355
}
1356

1357
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema, FromSql, ToSql)]
×
1358
pub struct TimeStep {
1359
    pub granularity: TimeGranularity,
1360
    pub step: u32, // TODO: ensure on deserialization it is > 0
1361
}
1362

1363
impl From<geoengine_datatypes::primitives::TimeStep> for TimeStep {
1364
    fn from(value: geoengine_datatypes::primitives::TimeStep) -> Self {
14✔
1365
        Self {
14✔
1366
            granularity: value.granularity.into(),
14✔
1367
            step: value.step,
14✔
1368
        }
14✔
1369
    }
14✔
1370
}
1371

1372
impl From<TimeStep> for geoengine_datatypes::primitives::TimeStep {
1373
    fn from(value: TimeStep) -> Self {
13✔
1374
        Self {
13✔
1375
            granularity: value.granularity.into(),
13✔
1376
            step: value.step,
13✔
1377
        }
13✔
1378
    }
13✔
1379
}
1380

1381
/// Stores time intervals in ms in close-open semantic [start, end)
1382
#[derive(Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ToSql, FromSql, ToSchema)]
×
1383
pub struct TimeInterval {
1384
    pub start: TimeInstance,
1385
    pub end: TimeInstance,
1386
}
1387

1388
impl From<TimeInterval> for geoengine_datatypes::primitives::TimeInterval {
1389
    fn from(value: TimeInterval) -> Self {
409✔
1390
        geoengine_datatypes::primitives::TimeInterval::new_unchecked::<
409✔
1391
            geoengine_datatypes::primitives::TimeInstance,
409✔
1392
            geoengine_datatypes::primitives::TimeInstance,
409✔
1393
        >(value.start.into(), value.end.into())
409✔
1394
    }
409✔
1395
}
1396

1397
impl From<geoengine_datatypes::primitives::TimeInterval> for TimeInterval {
1398
    fn from(value: geoengine_datatypes::primitives::TimeInterval) -> Self {
125✔
1399
        Self {
125✔
1400
            start: value.start().into(),
125✔
1401
            end: value.end().into(),
125✔
1402
        }
125✔
1403
    }
125✔
1404
}
1405

1406
impl core::fmt::Debug for TimeInterval {
1407
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
12✔
1408
        write!(
12✔
1409
            f,
12✔
1410
            "TimeInterval [{}, {})",
1411
            self.start.inner(),
12✔
1412
            &self.end.inner()
12✔
1413
        )
1414
    }
12✔
1415
}
1416

1417
#[derive(
1418
    Debug,
1419
    Ord,
1420
    PartialOrd,
1421
    Eq,
1422
    PartialEq,
1423
    Hash,
1424
    Deserialize,
1425
    Serialize,
1426
    Copy,
1427
    Clone,
1428
    ToSchema,
1429
    FromSql,
×
1430
    ToSql,
1431
)]
1432
pub enum RasterDataType {
1433
    U8,
1434
    U16,
1435
    U32,
1436
    U64,
1437
    I8,
1438
    I16,
1439
    I32,
1440
    I64,
1441
    F32,
1442
    F64,
1443
}
1444

1445
impl From<geoengine_datatypes::raster::RasterDataType> for RasterDataType {
1446
    fn from(value: geoengine_datatypes::raster::RasterDataType) -> Self {
20✔
1447
        match value {
20✔
1448
            geoengine_datatypes::raster::RasterDataType::U8 => Self::U8,
14✔
1449
            geoengine_datatypes::raster::RasterDataType::U16 => Self::U16,
×
1450
            geoengine_datatypes::raster::RasterDataType::U32 => Self::U32,
×
1451
            geoengine_datatypes::raster::RasterDataType::U64 => Self::U64,
×
1452
            geoengine_datatypes::raster::RasterDataType::I8 => Self::I8,
×
1453
            geoengine_datatypes::raster::RasterDataType::I16 => Self::I16,
×
1454
            geoengine_datatypes::raster::RasterDataType::I32 => Self::I32,
×
1455
            geoengine_datatypes::raster::RasterDataType::I64 => Self::I64,
3✔
1456
            geoengine_datatypes::raster::RasterDataType::F32 => Self::F32,
3✔
1457
            geoengine_datatypes::raster::RasterDataType::F64 => Self::F64,
×
1458
        }
1459
    }
20✔
1460
}
1461

1462
impl From<RasterDataType> for geoengine_datatypes::raster::RasterDataType {
1463
    fn from(value: RasterDataType) -> Self {
24✔
1464
        match value {
24✔
1465
            RasterDataType::U8 => Self::U8,
9✔
1466
            RasterDataType::U16 => Self::U16,
9✔
1467
            RasterDataType::U32 => Self::U32,
×
1468
            RasterDataType::U64 => Self::U64,
×
1469
            RasterDataType::I8 => Self::I8,
×
1470
            RasterDataType::I16 => Self::I16,
×
1471
            RasterDataType::I32 => Self::I32,
×
1472
            RasterDataType::I64 => Self::I64,
3✔
1473
            RasterDataType::F32 => Self::F32,
3✔
1474
            RasterDataType::F64 => Self::F64,
×
1475
        }
1476
    }
24✔
1477
}
1478

1479
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
1480
#[serde(rename_all = "UPPERCASE")]
1481
pub enum ResamplingMethod {
1482
    Nearest,
1483
    Average,
1484
    Bilinear,
1485
    Cubic,
1486
    CubicSpline,
1487
    Lanczos,
1488
}
1489

1490
impl std::fmt::Display for ResamplingMethod {
1491
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
1492
        match self {
×
1493
            ResamplingMethod::Nearest => write!(f, "NEAREST"),
×
1494
            ResamplingMethod::Average => write!(f, "AVERAGE"),
×
1495
            ResamplingMethod::Bilinear => write!(f, "BILINEAR"),
×
1496
            ResamplingMethod::Cubic => write!(f, "CUBIC"),
×
1497
            ResamplingMethod::CubicSpline => write!(f, "CUBICSPLINE"),
×
1498
            ResamplingMethod::Lanczos => write!(f, "LANCZOS"),
×
1499
        }
1500
    }
×
1501
}
1502

1503
impl From<ResamplingMethod> for geoengine_datatypes::util::gdal::ResamplingMethod {
1504
    fn from(value: ResamplingMethod) -> Self {
×
1505
        match value {
×
1506
            ResamplingMethod::Nearest => Self::Nearest,
×
1507
            ResamplingMethod::Average => Self::Average,
×
1508
            ResamplingMethod::Bilinear => Self::Bilinear,
×
1509
            ResamplingMethod::Cubic => Self::Cubic,
×
1510
            ResamplingMethod::CubicSpline => Self::CubicSpline,
×
1511
            ResamplingMethod::Lanczos => Self::Lanczos,
×
1512
        }
1513
    }
×
1514
}
1515

1516
/// `RgbaColor` defines a 32 bit RGB color with alpha value
1517
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
1518
pub struct RgbaColor(pub [u8; 4]);
1519

1520
impl PartialSchema for RgbaColor {
1521
    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::Schema> {
12,980✔
1522
        use utoipa::openapi::schema::{ArrayBuilder, ObjectBuilder, SchemaType, Type};
1523
        ArrayBuilder::new()
12,980✔
1524
            .items(ObjectBuilder::new().schema_type(SchemaType::Type(Type::Integer)))
12,980✔
1525
            .min_items(Some(4))
12,980✔
1526
            .max_items(Some(4))
12,980✔
1527
            .into()
12,980✔
1528
    }
12,980✔
1529
}
1530

1531
// manual implementation utoipa generates an integer field
1532
impl ToSchema for RgbaColor {}
1533

1534
impl From<geoengine_datatypes::operations::image::RgbaColor> for RgbaColor {
1535
    fn from(color: geoengine_datatypes::operations::image::RgbaColor) -> Self {
32✔
1536
        Self(color.into_inner())
32✔
1537
    }
32✔
1538
}
1539

1540
impl From<RgbaColor> for geoengine_datatypes::operations::image::RgbaColor {
1541
    fn from(color: RgbaColor) -> Self {
27✔
1542
        Self::new(color.0[0], color.0[1], color.0[2], color.0[3])
27✔
1543
    }
27✔
1544
}
1545

1546
/// A container type for breakpoints that specify a value to color mapping
1547
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
1548
pub struct Breakpoint {
1549
    pub value: NotNanF64,
1550
    pub color: RgbaColor,
1551
}
1552

1553
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
1554
pub struct NotNanF64(NotNan<f64>);
1555

1556
impl From<NotNan<f64>> for NotNanF64 {
1557
    fn from(value: NotNan<f64>) -> Self {
12✔
1558
        Self(value)
12✔
1559
    }
12✔
1560
}
1561

1562
impl From<NotNanF64> for NotNan<f64> {
1563
    fn from(value: NotNanF64) -> Self {
10✔
1564
        value.0
10✔
1565
    }
10✔
1566
}
1567

1568
impl ToSql for NotNanF64 {
1569
    fn to_sql(
×
1570
        &self,
×
1571
        ty: &postgres_types::Type,
×
1572
        w: &mut bytes::BytesMut,
×
1573
    ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
×
1574
        <f64 as ToSql>::to_sql(&self.0.into_inner(), ty, w)
×
1575
    }
×
1576

1577
    fn accepts(ty: &postgres_types::Type) -> bool {
×
1578
        <f64 as ToSql>::accepts(ty)
×
1579
    }
×
1580

1581
    postgres_types::to_sql_checked!();
1582
}
1583

1584
impl<'a> FromSql<'a> for NotNanF64 {
1585
    fn from_sql(
×
1586
        ty: &postgres_types::Type,
×
1587
        raw: &'a [u8],
×
1588
    ) -> Result<NotNanF64, Box<dyn std::error::Error + Sync + Send>> {
×
1589
        let value = <f64 as FromSql>::from_sql(ty, raw)?;
×
1590

1591
        Ok(NotNanF64(value.try_into()?))
×
1592
    }
×
1593

1594
    fn accepts(ty: &postgres_types::Type) -> bool {
×
1595
        <f64 as FromSql>::accepts(ty)
×
1596
    }
×
1597
}
1598

1599
impl PartialSchema for Breakpoint {
1600
    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::Schema> {
2,605✔
1601
        use utoipa::openapi::schema::{Object, ObjectBuilder, Ref, SchemaType, Type};
1602
        ObjectBuilder::new()
2,605✔
1603
            .property("value", Object::with_type(SchemaType::Type(Type::Number)))
2,605✔
1604
            .property("color", Ref::from_schema_name("RgbaColor"))
2,605✔
1605
            .required("value")
2,605✔
1606
            .required("color")
2,605✔
1607
            .into()
2,605✔
1608
    }
2,605✔
1609
}
1610

1611
// manual implementation because of NotNan
1612
impl ToSchema for Breakpoint {}
1613

1614
impl From<geoengine_datatypes::operations::image::Breakpoint> for Breakpoint {
1615
    fn from(breakpoint: geoengine_datatypes::operations::image::Breakpoint) -> Self {
12✔
1616
        Self {
12✔
1617
            value: breakpoint.value.into(),
12✔
1618
            color: breakpoint.color.into(),
12✔
1619
        }
12✔
1620
    }
12✔
1621
}
1622

1623
impl From<Breakpoint> for geoengine_datatypes::operations::image::Breakpoint {
1624
    fn from(breakpoint: Breakpoint) -> Self {
10✔
1625
        Self {
10✔
1626
            value: breakpoint.value.into(),
10✔
1627
            color: breakpoint.color.into(),
10✔
1628
        }
10✔
1629
    }
10✔
1630
}
1631

1632
#[type_tag(value = "linearGradient")]
1633
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, ToSchema)]
1634
#[serde(rename_all = "camelCase")]
1635
pub struct LinearGradient {
1636
    pub breakpoints: Vec<Breakpoint>,
1637
    pub no_data_color: RgbaColor,
1638
    pub over_color: RgbaColor,
1639
    pub under_color: RgbaColor,
1640
}
1641

1642
#[type_tag(value = "logarithmicGradient")]
1643
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, ToSchema)]
1644
#[serde(rename_all = "camelCase")]
1645
pub struct LogarithmicGradient {
1646
    pub breakpoints: Vec<Breakpoint>,
1647
    pub no_data_color: RgbaColor,
1648
    pub over_color: RgbaColor,
1649
    pub under_color: RgbaColor,
1650
}
1651

1652
#[type_tag(value = "palette")]
1653
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, ToSchema)]
1654
#[serde(rename_all = "camelCase")]
1655
pub struct PaletteColorizer {
1656
    pub colors: Palette,
1657
    pub no_data_color: RgbaColor,
1658
    pub default_color: RgbaColor,
1659
}
1660

1661
/// A colorizer specifies a mapping between raster values and an output image
1662
/// There are different variants that perform different kinds of mapping.
1663
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, ToSchema)]
1664
#[serde(rename_all = "camelCase", untagged)]
1665
#[schema(discriminator = "type")]
1666
pub enum Colorizer {
1667
    LinearGradient(LinearGradient),
1668
    LogarithmicGradient(LogarithmicGradient),
1669
    Palette(PaletteColorizer),
1670
}
1671

1672
impl From<geoengine_datatypes::operations::image::Colorizer> for Colorizer {
1673
    fn from(v: geoengine_datatypes::operations::image::Colorizer) -> Self {
6✔
1674
        match v {
6✔
1675
            geoengine_datatypes::operations::image::Colorizer::LinearGradient {
1676
                breakpoints,
6✔
1677
                no_data_color,
6✔
1678
                over_color,
6✔
1679
                under_color,
6✔
1680
            } => Self::LinearGradient(LinearGradient {
6✔
1681
                r#type: Default::default(),
6✔
1682
                breakpoints: breakpoints
6✔
1683
                    .into_iter()
6✔
1684
                    .map(Into::into)
6✔
1685
                    .collect::<Vec<Breakpoint>>(),
6✔
1686
                no_data_color: no_data_color.into(),
6✔
1687
                over_color: over_color.into(),
6✔
1688
                under_color: under_color.into(),
6✔
1689
            }),
6✔
1690
            geoengine_datatypes::operations::image::Colorizer::LogarithmicGradient {
1691
                breakpoints,
×
1692
                no_data_color,
×
1693
                over_color,
×
1694
                under_color,
×
1695
            } => Self::LogarithmicGradient(LogarithmicGradient {
×
1696
                r#type: Default::default(),
×
1697
                breakpoints: breakpoints
×
1698
                    .into_iter()
×
1699
                    .map(Into::into)
×
1700
                    .collect::<Vec<Breakpoint>>(),
×
1701
                no_data_color: no_data_color.into(),
×
1702
                over_color: over_color.into(),
×
1703
                under_color: under_color.into(),
×
1704
            }),
×
1705
            geoengine_datatypes::operations::image::Colorizer::Palette {
1706
                colors,
×
1707
                no_data_color,
×
1708
                default_color,
×
1709
            } => Self::Palette(PaletteColorizer {
×
1710
                r#type: Default::default(),
×
1711
                colors: colors.into(),
×
1712
                no_data_color: no_data_color.into(),
×
1713
                default_color: default_color.into(),
×
1714
            }),
×
1715
        }
1716
    }
6✔
1717
}
1718

1719
impl From<Colorizer> for geoengine_datatypes::operations::image::Colorizer {
1720
    fn from(v: Colorizer) -> Self {
5✔
1721
        match v {
5✔
1722
            Colorizer::LinearGradient(linear_gradient) => Self::LinearGradient {
5✔
1723
                breakpoints: linear_gradient
5✔
1724
                    .breakpoints
5✔
1725
                    .into_iter()
5✔
1726
                    .map(Into::into)
5✔
1727
                    .collect::<Vec<geoengine_datatypes::operations::image::Breakpoint>>(),
5✔
1728
                no_data_color: linear_gradient.no_data_color.into(),
5✔
1729
                over_color: linear_gradient.over_color.into(),
5✔
1730
                under_color: linear_gradient.under_color.into(),
5✔
1731
            },
5✔
1732
            Colorizer::LogarithmicGradient(logarithmic_gradient) => Self::LogarithmicGradient {
×
1733
                breakpoints: logarithmic_gradient
×
1734
                    .breakpoints
×
1735
                    .into_iter()
×
1736
                    .map(Into::into)
×
1737
                    .collect::<Vec<geoengine_datatypes::operations::image::Breakpoint>>(),
×
1738
                no_data_color: logarithmic_gradient.no_data_color.into(),
×
1739
                over_color: logarithmic_gradient.over_color.into(),
×
1740
                under_color: logarithmic_gradient.under_color.into(),
×
1741
            },
×
1742

1743
            Colorizer::Palette(PaletteColorizer {
1744
                colors,
×
1745
                no_data_color,
×
1746
                default_color,
×
1747
                ..
1748
            }) => Self::Palette {
×
1749
                colors: colors.into(),
×
1750
                no_data_color: no_data_color.into(),
×
1751
                default_color: default_color.into(),
×
1752
            },
×
1753
        }
1754
    }
5✔
1755
}
1756

1757
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, ToSchema)]
1758
#[serde(rename_all = "camelCase", untagged)]
1759
#[schema(discriminator = "type")]
1760
pub enum RasterColorizer {
1761
    SingleBand(SingleBandRasterColorizer),
1762
    MultiBand(MultiBandRasterColorizer),
1763
}
1764

1765
#[type_tag(value = "singleBand")]
1766
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, ToSchema)]
1767
#[serde(rename_all = "camelCase")]
1768
pub struct SingleBandRasterColorizer {
1769
    pub band: u32,
1770
    pub band_colorizer: Colorizer,
1771
}
1772

1773
#[type_tag(value = "multiBand")]
1774
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, ToSchema)]
1775
#[serde(rename_all = "camelCase")]
1776
pub struct MultiBandRasterColorizer {
1777
    /// The band index of the red channel.
1778
    pub red_band: u32,
1779
    /// The minimum value for the red channel.
1780
    pub red_min: f64,
1781
    /// The maximum value for the red channel.
1782
    pub red_max: f64,
1783
    /// A scaling factor for the red channel between 0 and 1.
1784
    #[serde(default = "num_traits::One::one")]
1785
    pub red_scale: f64,
1786

1787
    /// The band index of the green channel.
1788
    pub green_band: u32,
1789
    /// The minimum value for the red channel.
1790
    pub green_min: f64,
1791
    /// The maximum value for the red channel.
1792
    pub green_max: f64,
1793
    /// A scaling factor for the green channel between 0 and 1.
1794
    #[serde(default = "num_traits::One::one")]
1795
    pub green_scale: f64,
1796

1797
    /// The band index of the blue channel.
1798
    pub blue_band: u32,
1799
    /// The minimum value for the red channel.
1800
    pub blue_min: f64,
1801
    /// The maximum value for the red channel.
1802
    pub blue_max: f64,
1803
    /// A scaling factor for the blue channel between 0 and 1.
1804
    #[serde(default = "num_traits::One::one")]
1805
    pub blue_scale: f64,
1806

1807
    /// The color to use for no data values.
1808
    /// If not specified, the no data values will be transparent.
1809
    #[serde(default = "rgba_transparent")]
1810
    pub no_data_color: RgbaColor,
1811
}
1812

1813
fn rgba_transparent() -> RgbaColor {
×
1814
    RgbaColor([0, 0, 0, 0])
×
1815
}
×
1816

1817
impl Eq for RasterColorizer {}
1818

1819
impl RasterColorizer {
1820
    pub fn band_selection(&self) -> BandSelection {
7✔
1821
        match self {
7✔
1822
            RasterColorizer::SingleBand(SingleBandRasterColorizer { band, .. }) => {
5✔
1823
                BandSelection(vec![*band as usize])
5✔
1824
            }
1825
            RasterColorizer::MultiBand(MultiBandRasterColorizer {
1826
                red_band,
2✔
1827
                green_band,
2✔
1828
                blue_band,
2✔
1829
                ..
1830
            }) => {
1831
                let mut bands = Vec::with_capacity(3);
2✔
1832
                for band in [
6✔
1833
                    *red_band as usize,
2✔
1834
                    *green_band as usize,
2✔
1835
                    *blue_band as usize,
2✔
1836
                ] {
2✔
1837
                    if !bands.contains(&band) {
6✔
1838
                        bands.push(band);
4✔
1839
                    }
4✔
1840
                }
1841
                bands.sort_unstable(); // bands will be returned in ascending order anyway
2✔
1842
                BandSelection(bands)
2✔
1843
            }
1844
        }
1845
    }
7✔
1846
}
1847

1848
impl From<geoengine_datatypes::operations::image::RasterColorizer> for RasterColorizer {
1849
    fn from(v: geoengine_datatypes::operations::image::RasterColorizer) -> Self {
×
1850
        match v {
×
1851
            geoengine_datatypes::operations::image::RasterColorizer::SingleBand {
1852
                band,
×
1853
                band_colorizer: colorizer,
×
1854
            } => Self::SingleBand(SingleBandRasterColorizer {
×
1855
                r#type: Default::default(),
×
1856
                band,
×
1857
                band_colorizer: colorizer.into(),
×
1858
            }),
×
1859
            geoengine_datatypes::operations::image::RasterColorizer::MultiBand {
1860
                red_band,
×
1861
                green_band,
×
1862
                blue_band,
×
1863
                rgb_params,
×
1864
            } => Self::MultiBand(MultiBandRasterColorizer {
×
1865
                r#type: Default::default(),
×
1866
                red_band,
×
1867
                green_band,
×
1868
                blue_band,
×
1869
                red_min: rgb_params.red_min,
×
1870
                red_max: rgb_params.red_max,
×
1871
                red_scale: rgb_params.red_scale,
×
1872
                green_min: rgb_params.green_min,
×
1873
                green_max: rgb_params.green_max,
×
1874
                green_scale: rgb_params.green_scale,
×
1875
                blue_min: rgb_params.blue_min,
×
1876
                blue_max: rgb_params.blue_max,
×
1877
                blue_scale: rgb_params.blue_scale,
×
1878
                no_data_color: rgb_params.no_data_color.into(),
×
1879
            }),
×
1880
        }
1881
    }
×
1882
}
1883

1884
impl From<RasterColorizer> for geoengine_datatypes::operations::image::RasterColorizer {
1885
    fn from(v: RasterColorizer) -> Self {
7✔
1886
        match v {
7✔
1887
            RasterColorizer::SingleBand(SingleBandRasterColorizer {
1888
                band,
5✔
1889
                band_colorizer: colorizer,
5✔
1890
                ..
1891
            }) => Self::SingleBand {
5✔
1892
                band,
5✔
1893
                band_colorizer: colorizer.into(),
5✔
1894
            },
5✔
1895
            RasterColorizer::MultiBand(MultiBandRasterColorizer {
1896
                red_band,
2✔
1897
                red_min,
2✔
1898
                red_max,
2✔
1899
                red_scale,
2✔
1900
                green_band,
2✔
1901
                green_min,
2✔
1902
                green_max,
2✔
1903
                green_scale,
2✔
1904
                blue_band,
2✔
1905
                blue_min,
2✔
1906
                blue_max,
2✔
1907
                blue_scale,
2✔
1908
                no_data_color,
2✔
1909
                ..
1910
            }) => Self::MultiBand {
2✔
1911
                red_band,
2✔
1912
                green_band,
2✔
1913
                blue_band,
2✔
1914
                rgb_params: RgbParams {
2✔
1915
                    red_min,
2✔
1916
                    red_max,
2✔
1917
                    red_scale,
2✔
1918
                    green_min,
2✔
1919
                    green_max,
2✔
1920
                    green_scale,
2✔
1921
                    blue_min,
2✔
1922
                    blue_max,
2✔
1923
                    blue_scale,
2✔
1924
                    no_data_color: no_data_color.into(),
2✔
1925
                },
2✔
1926
            },
2✔
1927
        }
1928
    }
7✔
1929
}
1930

1931
/// A map from value to color
1932
///
1933
/// It is assumed that is has at least one and at most 256 entries.
1934
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
1935
#[serde(try_from = "SerializablePalette", into = "SerializablePalette")]
1936
#[schema(value_type = HashMap<f64, RgbaColor>)]
1937
pub struct Palette(pub HashMap<NotNan<f64>, RgbaColor>);
1938

1939
impl From<geoengine_datatypes::operations::image::Palette> for Palette {
1940
    fn from(palette: geoengine_datatypes::operations::image::Palette) -> Self {
×
1941
        Self(
1942
            palette
×
1943
                .into_inner()
×
1944
                .into_iter()
×
1945
                .map(|(value, color)| (value, color.into()))
×
1946
                .collect(),
×
1947
        )
1948
    }
×
1949
}
1950

1951
impl From<Palette> for geoengine_datatypes::operations::image::Palette {
1952
    fn from(palette: Palette) -> Self {
×
1953
        Self::new(
×
1954
            palette
×
1955
                .0
×
1956
                .into_iter()
×
1957
                .map(|(value, color)| (value, color.into()))
×
1958
                .collect(),
×
1959
        )
1960
    }
×
1961
}
1962

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

1968
impl From<Palette> for SerializablePalette {
1969
    fn from(palette: Palette) -> Self {
×
1970
        Self(
1971
            palette
×
1972
                .0
×
1973
                .into_iter()
×
1974
                .map(|(k, v)| (k.to_string(), v))
×
1975
                .collect(),
×
1976
        )
1977
    }
×
1978
}
1979

1980
impl TryFrom<SerializablePalette> for Palette {
1981
    type Error = <NotNan<f64> as FromStr>::Err;
1982

1983
    fn try_from(palette: SerializablePalette) -> Result<Self, Self::Error> {
×
1984
        let mut inner = HashMap::<NotNan<f64>, RgbaColor>::with_capacity(palette.0.len());
×
1985
        for (k, v) in palette.0 {
×
1986
            inner.insert(k.parse()?, v);
×
1987
        }
1988
        Ok(Self(inner))
×
1989
    }
×
1990
}
1991

1992
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Hash, Eq, PartialOrd, Ord, ToSchema)]
1993
pub struct RasterPropertiesKey {
1994
    pub domain: Option<String>,
1995
    pub key: String,
1996
}
1997

1998
impl From<geoengine_datatypes::raster::RasterPropertiesKey> for RasterPropertiesKey {
1999
    fn from(value: geoengine_datatypes::raster::RasterPropertiesKey) -> Self {
×
2000
        Self {
×
2001
            domain: value.domain,
×
2002
            key: value.key,
×
2003
        }
×
2004
    }
×
2005
}
2006

2007
impl From<RasterPropertiesKey> for geoengine_datatypes::raster::RasterPropertiesKey {
2008
    fn from(value: RasterPropertiesKey) -> Self {
×
2009
        Self {
×
2010
            domain: value.domain,
×
2011
            key: value.key,
×
2012
        }
×
2013
    }
×
2014
}
2015

2016
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ToSchema)]
2017
pub enum RasterPropertiesEntryType {
2018
    Number,
2019
    String,
2020
}
2021

2022
impl From<geoengine_datatypes::raster::RasterPropertiesEntryType> for RasterPropertiesEntryType {
2023
    fn from(value: geoengine_datatypes::raster::RasterPropertiesEntryType) -> Self {
×
2024
        match value {
×
2025
            geoengine_datatypes::raster::RasterPropertiesEntryType::Number => Self::Number,
×
2026
            geoengine_datatypes::raster::RasterPropertiesEntryType::String => Self::String,
×
2027
        }
2028
    }
×
2029
}
2030

2031
impl From<RasterPropertiesEntryType> for geoengine_datatypes::raster::RasterPropertiesEntryType {
2032
    fn from(value: RasterPropertiesEntryType) -> Self {
×
2033
        match value {
×
2034
            RasterPropertiesEntryType::Number => Self::Number,
×
2035
            RasterPropertiesEntryType::String => Self::String,
×
2036
        }
2037
    }
×
2038
}
2039

2040
#[derive(PartialEq, Eq, Clone, Debug)]
2041
pub struct DateTimeParseFormat {
2042
    fmt: String,
2043
    has_tz: bool,
2044
    has_time: bool,
2045
}
2046

2047
impl PartialSchema for DateTimeParseFormat {
2048
    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::Schema> {
280✔
2049
        use utoipa::openapi::schema::{ObjectBuilder, SchemaType, Type};
2050
        ObjectBuilder::new()
280✔
2051
            .schema_type(SchemaType::Type(Type::String))
280✔
2052
            .into()
280✔
2053
    }
280✔
2054
}
2055

2056
impl ToSchema for DateTimeParseFormat {}
2057

2058
impl<'de> Deserialize<'de> for DateTimeParseFormat {
2059
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
4✔
2060
    where
4✔
2061
        D: serde::Deserializer<'de>,
4✔
2062
    {
2063
        let s = String::deserialize(deserializer)?;
4✔
2064
        Ok(geoengine_datatypes::primitives::DateTimeParseFormat::custom(s).into())
4✔
2065
    }
4✔
2066
}
2067

2068
impl Serialize for DateTimeParseFormat {
2069
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
5✔
2070
    where
5✔
2071
        S: serde::Serializer,
5✔
2072
    {
2073
        serializer.serialize_str(&self.fmt)
5✔
2074
    }
5✔
2075
}
2076

2077
impl From<geoengine_datatypes::primitives::DateTimeParseFormat> for DateTimeParseFormat {
2078
    fn from(value: geoengine_datatypes::primitives::DateTimeParseFormat) -> Self {
8✔
2079
        Self {
8✔
2080
            fmt: value.parse_format().to_string(),
8✔
2081
            has_tz: value.has_tz(),
8✔
2082
            has_time: value.has_time(),
8✔
2083
        }
8✔
2084
    }
8✔
2085
}
2086

2087
impl From<DateTimeParseFormat> for geoengine_datatypes::primitives::DateTimeParseFormat {
2088
    fn from(value: DateTimeParseFormat) -> Self {
4✔
2089
        Self::custom(value.fmt)
4✔
2090
    }
4✔
2091
}
2092

2093
impl DateTimeParseFormat {
2094
    // this is used as default value
2095
    pub fn unix() -> Self {
×
2096
        geoengine_datatypes::primitives::DateTimeParseFormat::unix().into()
×
2097
    }
×
2098
}
2099

2100
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
2101
pub struct NoGeometry;
2102

2103
impl From<geoengine_datatypes::primitives::NoGeometry> for NoGeometry {
2104
    fn from(_: geoengine_datatypes::primitives::NoGeometry) -> Self {
×
2105
        Self {}
×
2106
    }
×
2107
}
2108

2109
impl From<NoGeometry> for geoengine_datatypes::primitives::NoGeometry {
2110
    fn from(_: NoGeometry) -> Self {
×
2111
        Self {}
×
2112
    }
×
2113
}
2114

2115
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
2116
pub struct MultiPoint {
2117
    pub coordinates: Vec<Coordinate2D>,
2118
}
2119

2120
impl From<geoengine_datatypes::primitives::MultiPoint> for MultiPoint {
2121
    fn from(value: geoengine_datatypes::primitives::MultiPoint) -> Self {
×
2122
        Self {
2123
            coordinates: value.points().iter().map(|x| (*x).into()).collect(),
×
2124
        }
2125
    }
×
2126
}
2127

2128
impl From<MultiPoint> for geoengine_datatypes::primitives::MultiPoint {
2129
    fn from(value: MultiPoint) -> Self {
×
2130
        Self::new(value.coordinates.into_iter().map(Into::into).collect())
×
2131
            .expect("it should always be able to convert it")
×
2132
    }
×
2133
}
2134

2135
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
2136
pub struct MultiLineString {
2137
    pub coordinates: Vec<Vec<Coordinate2D>>,
2138
}
2139

2140
impl From<geoengine_datatypes::primitives::MultiLineString> for MultiLineString {
2141
    fn from(value: geoengine_datatypes::primitives::MultiLineString) -> Self {
×
2142
        Self {
2143
            coordinates: value
×
2144
                .lines()
×
2145
                .iter()
×
2146
                .map(|x| x.iter().map(|x| (*x).into()).collect())
×
2147
                .collect(),
×
2148
        }
2149
    }
×
2150
}
2151

2152
impl From<MultiLineString> for geoengine_datatypes::primitives::MultiLineString {
2153
    fn from(value: MultiLineString) -> Self {
×
2154
        Self::new(
×
2155
            value
×
2156
                .coordinates
×
2157
                .into_iter()
×
2158
                .map(|x| x.into_iter().map(Into::into).collect())
×
2159
                .collect(),
×
2160
        )
2161
        .expect("it should always be able to convert it")
×
2162
    }
×
2163
}
2164

2165
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
2166
pub struct MultiPolygon {
2167
    pub polygons: Vec<Vec<Vec<Coordinate2D>>>,
2168
}
2169

2170
impl From<geoengine_datatypes::primitives::MultiPolygon> for MultiPolygon {
2171
    fn from(value: geoengine_datatypes::primitives::MultiPolygon) -> Self {
×
2172
        Self {
2173
            polygons: value
×
2174
                .polygons()
×
2175
                .iter()
×
2176
                .map(|x| {
×
2177
                    x.iter()
×
2178
                        .map(|y| y.iter().map(|y| (*y).into()).collect())
×
2179
                        .collect()
×
2180
                })
×
2181
                .collect(),
×
2182
        }
2183
    }
×
2184
}
2185

2186
impl From<MultiPolygon> for geoengine_datatypes::primitives::MultiPolygon {
2187
    fn from(value: MultiPolygon) -> Self {
×
2188
        Self::new(
×
2189
            value
×
2190
                .polygons
×
2191
                .iter()
×
2192
                .map(|x| {
×
2193
                    x.iter()
×
2194
                        .map(|y| y.iter().map(|y| (*y).into()).collect())
×
2195
                        .collect()
×
2196
                })
×
2197
                .collect(),
×
2198
        )
2199
        .expect("it should always be able to convert it")
×
2200
    }
×
2201
}
2202

2203
#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, Clone)]
2204
pub struct StringPair((String, String));
2205

2206
pub type GdalConfigOption = StringPair;
2207
pub type AxisLabels = StringPair;
2208

2209
impl PartialSchema for StringPair {
2210
    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::Schema> {
240✔
2211
        use utoipa::openapi::schema::{ArrayBuilder, Object, SchemaType, Type};
2212
        ArrayBuilder::new()
240✔
2213
            .items(Object::with_type(SchemaType::Type(Type::String)))
240✔
2214
            .min_items(Some(2))
240✔
2215
            .max_items(Some(2))
240✔
2216
            .into()
240✔
2217
    }
240✔
2218
}
2219

2220
impl ToSchema for StringPair {
2221
    // fn aliases() -> Vec<(&'a str, utoipa::openapi::Schema)> { // TODO: how to do this?
2222
    //     let utoipa::openapi::RefOr::T(unpacked_schema) = Self::schema().1 else {
2223
    //         unreachable!()
2224
    //     };
2225
    //     vec![
2226
    //         ("GdalConfigOption", unpacked_schema.clone()),
2227
    //         ("AxisLabels", unpacked_schema),
2228
    //     ]
2229
    // }
2230
}
2231

2232
impl From<(String, String)> for StringPair {
2233
    fn from(value: (String, String)) -> Self {
31✔
2234
        Self(value)
31✔
2235
    }
31✔
2236
}
2237

2238
impl From<StringPair> for (String, String) {
2239
    fn from(value: StringPair) -> Self {
×
2240
        value.0
×
2241
    }
×
2242
}
2243

2244
impl From<StringPair> for geoengine_datatypes::util::StringPair {
2245
    fn from(value: StringPair) -> Self {
2✔
2246
        Self::new(value.0.0, value.0.1)
2✔
2247
    }
2✔
2248
}
2249

2250
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize, ToSchema)]
2251
pub enum PlotOutputFormat {
2252
    JsonPlain,
2253
    JsonVega,
2254
    ImagePng,
2255
}
2256

2257
#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize, PartialOrd, Deserialize, ToSchema)]
2258
pub struct CacheTtlSeconds(u32);
2259

2260
const MAX_CACHE_TTL_SECONDS: u32 = 31_536_000; // 1 year
2261

2262
impl CacheTtlSeconds {
2263
    pub fn new(seconds: u32) -> Self {
×
2264
        Self(seconds.min(MAX_CACHE_TTL_SECONDS))
×
2265
    }
×
2266

2267
    pub fn max() -> Self {
×
2268
        Self(MAX_CACHE_TTL_SECONDS)
×
2269
    }
×
2270

2271
    pub fn seconds(self) -> u32 {
×
2272
        self.0
×
2273
    }
×
2274
}
2275

2276
impl From<geoengine_datatypes::primitives::CacheTtlSeconds> for CacheTtlSeconds {
2277
    fn from(value: geoengine_datatypes::primitives::CacheTtlSeconds) -> Self {
9✔
2278
        Self(value.seconds())
9✔
2279
    }
9✔
2280
}
2281

2282
impl From<CacheTtlSeconds> for geoengine_datatypes::primitives::CacheTtlSeconds {
2283
    fn from(value: CacheTtlSeconds) -> Self {
10✔
2284
        Self::new(value.0)
10✔
2285
    }
10✔
2286
}
2287

2288
impl ToSql for CacheTtlSeconds {
2289
    fn to_sql(
×
2290
        &self,
×
2291
        ty: &postgres_types::Type,
×
2292
        w: &mut bytes::BytesMut,
×
2293
    ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
×
2294
        <i32 as ToSql>::to_sql(&(self.0 as i32), ty, w)
×
2295
    }
×
2296

2297
    fn accepts(ty: &postgres_types::Type) -> bool {
×
2298
        <i32 as ToSql>::accepts(ty)
×
2299
    }
×
2300

2301
    postgres_types::to_sql_checked!();
2302
}
2303

2304
impl<'a> FromSql<'a> for CacheTtlSeconds {
2305
    fn from_sql(
×
2306
        ty: &postgres_types::Type,
×
2307
        raw: &'a [u8],
×
2308
    ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
×
2309
        Ok(Self(<i32 as FromSql>::from_sql(ty, raw)? as u32))
×
2310
    }
×
2311

2312
    fn accepts(ty: &postgres_types::Type) -> bool {
×
2313
        <i32 as FromSql>::accepts(ty)
×
2314
    }
×
2315
}
2316

2317
/// A struct describing tensor shape for `MlModelMetadata`
2318
#[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize, Serialize, ToSchema, FromSql, ToSql)]
×
2319
pub struct MlTensorShape3D {
2320
    pub y: u32,
2321
    pub x: u32,
2322
    pub bands: u32, // TODO: named attributes?
2323
}
2324

2325
impl MlTensorShape3D {
2326
    pub fn new_y_x_attr(y: u32, x: u32, bands: u32) -> Self {
×
2327
        Self { y, x, bands }
×
2328
    }
×
2329

2330
    pub fn new_single_pixel_bands(bands: u32) -> Self {
×
2331
        Self { y: 1, x: 1, bands }
×
2332
    }
×
2333

2334
    pub fn new_single_pixel_single_band() -> Self {
×
2335
        Self::new_single_pixel_bands(1)
×
2336
    }
×
2337
}
2338

2339
impl From<geoengine_datatypes::machine_learning::MlTensorShape3D> for MlTensorShape3D {
2340
    fn from(value: geoengine_datatypes::machine_learning::MlTensorShape3D) -> Self {
6✔
2341
        Self {
6✔
2342
            y: value.y,
6✔
2343
            x: value.x,
6✔
2344
            bands: value.bands,
6✔
2345
        }
6✔
2346
    }
6✔
2347
}
2348

2349
impl From<MlTensorShape3D> for geoengine_datatypes::machine_learning::MlTensorShape3D {
2350
    fn from(value: MlTensorShape3D) -> Self {
6✔
2351
        Self {
6✔
2352
            y: value.y,
6✔
2353
            x: value.x,
6✔
2354
            bands: value.bands,
6✔
2355
        }
6✔
2356
    }
6✔
2357
}
2358

2359
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
2360
pub struct MlModelName {
2361
    pub namespace: Option<String>,
2362
    pub name: String,
2363
}
2364

2365
impl From<MlModelName> for geoengine_datatypes::machine_learning::MlModelName {
2366
    fn from(name: MlModelName) -> Self {
9✔
2367
        Self {
9✔
2368
            namespace: name.namespace,
9✔
2369
            name: name.name,
9✔
2370
        }
9✔
2371
    }
9✔
2372
}
2373

2374
impl From<geoengine_datatypes::machine_learning::MlModelName> for MlModelName {
2375
    fn from(name: geoengine_datatypes::machine_learning::MlModelName) -> Self {
8✔
2376
        Self {
8✔
2377
            namespace: name.namespace,
8✔
2378
            name: name.name,
8✔
2379
        }
8✔
2380
    }
8✔
2381
}
2382

2383
impl std::fmt::Display for MlModelName {
2384
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1✔
2385
        let dt_mmn: geoengine_datatypes::machine_learning::MlModelName = self.clone().into();
1✔
2386
        std::fmt::Display::fmt(&dt_mmn, f)
1✔
2387
    }
1✔
2388
}
2389

2390
impl Serialize for MlModelName {
2391
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
4✔
2392
    where
4✔
2393
        S: serde::Serializer,
4✔
2394
    {
2395
        geoengine_datatypes::machine_learning::MlModelName::serialize(
4✔
2396
            &self.clone().into(),
4✔
2397
            serializer,
4✔
2398
        )
2399
    }
4✔
2400
}
2401

2402
impl<'de> Deserialize<'de> for MlModelName {
2403
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
4✔
2404
    where
4✔
2405
        D: serde::Deserializer<'de>,
4✔
2406
    {
2407
        geoengine_datatypes::machine_learning::MlModelName::deserialize(deserializer)
4✔
2408
            .map(Into::into)
4✔
2409
    }
4✔
2410
}
2411

2412
impl ToSchema for MlModelName {} // TODO: why is this needed?
2413

2414
impl PartialSchema for MlModelName {
2415
    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::Schema> {
35✔
2416
        use utoipa::openapi::schema::{ObjectBuilder, SchemaType, Type};
2417
        ObjectBuilder::new()
35✔
2418
            .schema_type(SchemaType::Type(Type::String))
35✔
2419
            .into()
35✔
2420
    }
35✔
2421
}
2422

2423
#[cfg(test)]
2424
mod tests {
2425
    use crate::api::model::datatypes::ClassificationMeasurement;
2426
    use crate::error::Error;
2427
    use std::collections::BTreeMap;
2428

2429
    #[test]
2430
    fn it_serializes_classification_measurement() -> Result<(), Error> {
1✔
2431
        let measurement = ClassificationMeasurement {
1✔
2432
            r#type: Default::default(),
1✔
2433
            measurement: "Test".to_string(),
1✔
2434
            classes: BTreeMap::<u8, String>::from([
1✔
2435
                (0, "Class 0".to_string()),
1✔
2436
                (1, "Class 1".to_string()),
1✔
2437
            ]),
1✔
2438
        };
1✔
2439

2440
        let serialized = serde_json::to_string(&measurement)?;
1✔
2441

2442
        assert_eq!(
1✔
2443
            serialized,
2444
            r#"{"type":"classification","measurement":"Test","classes":{"0":"Class 0","1":"Class 1"}}"#
2445
        );
2446
        Ok(())
1✔
2447
    }
1✔
2448

2449
    #[test]
2450
    fn it_deserializes_classification_measurement() -> Result<(), Error> {
1✔
2451
        let measurement = ClassificationMeasurement {
1✔
2452
            r#type: Default::default(),
1✔
2453
            measurement: "Test".to_string(),
1✔
2454
            classes: BTreeMap::<u8, String>::from([
1✔
2455
                (0, "Class 0".to_string()),
1✔
2456
                (1, "Class 1".to_string()),
1✔
2457
            ]),
1✔
2458
        };
1✔
2459

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

2463
        assert_eq!(measurement, deserialized);
1✔
2464
        Ok(())
1✔
2465
    }
1✔
2466

2467
    #[test]
2468
    fn it_throws_error_on_deserializing_non_integer_classification_measurement_class_value() {
1✔
2469
        let serialized =
1✔
2470
            r#"{"type":"classification","measurement":"Test","classes":{"Zero":"Class 0"}}"#;
1✔
2471
        let deserialized = serde_json::from_str::<ClassificationMeasurement>(serialized);
1✔
2472

2473
        assert!(deserialized.is_err());
1✔
2474
        assert_eq!(
1✔
2475
            deserialized.unwrap_err().to_string(),
1✔
2476
            "Failed to parse key as u8: invalid digit found in string at line 1 column 75"
2477
        );
2478
    }
1✔
2479
}
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