oxigdal_services/ogc_features/
types.rs1use serde::{Deserialize, Serialize};
4
5use super::error::FeaturesError;
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
9pub struct Link {
10 pub href: String,
12
13 pub rel: String,
15
16 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
18 pub type_: Option<String>,
19
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub title: Option<String>,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub hreflang: Option<String>,
27}
28
29impl Link {
30 pub fn new(href: impl Into<String>, rel: impl Into<String>) -> Self {
32 Self {
33 href: href.into(),
34 rel: rel.into(),
35 type_: None,
36 title: None,
37 hreflang: None,
38 }
39 }
40
41 pub fn with_type(mut self, t: impl Into<String>) -> Self {
43 self.type_ = Some(t.into());
44 self
45 }
46
47 pub fn with_title(mut self, title: impl Into<String>) -> Self {
49 self.title = Some(title.into());
50 self
51 }
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct LandingPage {
57 pub title: String,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub description: Option<String>,
63
64 pub links: Vec<Link>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70#[serde(rename_all = "camelCase")]
71pub struct ConformanceClasses {
72 pub conforms_to: Vec<String>,
74}
75
76impl ConformanceClasses {
77 pub fn ogc_features_core() -> Self {
79 Self {
80 conforms_to: vec![
81 "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core".to_string(),
82 "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30".to_string(),
83 "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/html".to_string(),
84 "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson".to_string(),
85 ],
86 }
87 }
88
89 pub fn with_crs() -> Self {
91 let mut base = Self::ogc_features_core();
92 base.conforms_to
93 .push("http://www.opengis.net/spec/ogcapi-features-2/1.0/conf/crs".to_string());
94 base
95 }
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct SpatialExtent {
101 pub bbox: Vec<[f64; 4]>,
103
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub crs: Option<String>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct TemporalExtent {
112 pub interval: Vec<[Option<String>; 2]>,
114
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub trs: Option<String>,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct Extent {
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub spatial: Option<SpatialExtent>,
126
127 #[serde(skip_serializing_if = "Option::is_none")]
129 pub temporal: Option<TemporalExtent>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134#[serde(rename_all = "camelCase")]
135pub struct Collection {
136 pub id: String,
138
139 #[serde(skip_serializing_if = "Option::is_none")]
141 pub title: Option<String>,
142
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub description: Option<String>,
146
147 pub links: Vec<Link>,
149
150 #[serde(skip_serializing_if = "Option::is_none")]
152 pub extent: Option<Extent>,
153
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub item_type: Option<String>,
157
158 #[serde(default)]
160 pub crs: Vec<String>,
161
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub storage_crs: Option<String>,
165}
166
167impl Collection {
168 pub fn new(id: impl Into<String>) -> Self {
170 Self {
171 id: id.into(),
172 title: None,
173 description: None,
174 links: vec![],
175 extent: None,
176 item_type: Some("feature".to_string()),
177 crs: vec![],
178 storage_crs: None,
179 }
180 }
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct Collections {
186 pub links: Vec<Link>,
188
189 pub collections: Vec<Collection>,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
195#[serde(untagged)]
196pub enum FeatureId {
197 String(String),
199 Integer(i64),
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct Feature {
206 #[serde(rename = "type")]
208 pub type_: String,
209
210 #[serde(skip_serializing_if = "Option::is_none")]
212 pub id: Option<FeatureId>,
213
214 pub geometry: Option<serde_json::Value>,
216
217 pub properties: Option<serde_json::Value>,
219
220 #[serde(skip_serializing_if = "Option::is_none")]
222 pub links: Option<Vec<Link>>,
223}
224
225impl Feature {
226 pub fn new() -> Self {
228 Self {
229 type_: "Feature".to_string(),
230 id: None,
231 geometry: None,
232 properties: None,
233 links: None,
234 }
235 }
236}
237
238impl Default for Feature {
239 fn default() -> Self {
240 Self::new()
241 }
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize)]
246#[serde(rename_all = "camelCase")]
247pub struct FeatureCollection {
248 #[serde(rename = "type")]
250 pub type_: String,
251
252 pub features: Vec<Feature>,
254
255 #[serde(skip_serializing_if = "Option::is_none")]
257 pub links: Option<Vec<Link>>,
258
259 #[serde(skip_serializing_if = "Option::is_none")]
261 pub time_stamp: Option<String>,
262
263 #[serde(skip_serializing_if = "Option::is_none")]
265 pub number_matched: Option<u64>,
266
267 #[serde(skip_serializing_if = "Option::is_none")]
269 pub number_returned: Option<u64>,
270}
271
272impl FeatureCollection {
273 pub fn new() -> Self {
275 Self {
276 type_: "FeatureCollection".to_string(),
277 features: vec![],
278 links: None,
279 time_stamp: None,
280 number_matched: None,
281 number_returned: None,
282 }
283 }
284}
285
286impl Default for FeatureCollection {
287 fn default() -> Self {
288 Self::new()
289 }
290}
291
292#[derive(Debug, Clone, PartialEq)]
294pub enum DateTimeFilter {
295 Instant(String),
297 Interval(Option<String>, Option<String>),
299}
300
301impl DateTimeFilter {
302 pub fn parse(s: &str) -> Result<Self, FeaturesError> {
310 if s.is_empty() {
311 return Err(FeaturesError::InvalidDatetime(
312 "datetime value is empty".to_string(),
313 ));
314 }
315
316 if let Some(slash_pos) = s.find('/') {
317 let start_str = &s[..slash_pos];
318 let end_str = &s[slash_pos + 1..];
319
320 let start = if start_str == ".." || start_str.is_empty() {
321 None
322 } else {
323 Some(start_str.to_string())
324 };
325 let end = if end_str == ".." || end_str.is_empty() {
326 None
327 } else {
328 Some(end_str.to_string())
329 };
330
331 Ok(DateTimeFilter::Interval(start, end))
332 } else {
333 Ok(DateTimeFilter::Instant(s.to_string()))
334 }
335 }
336}