oxigdal_services/ogc_features/
server.rs1use chrono::Utc;
4
5use super::error::FeaturesError;
6use super::query::QueryParams;
7use super::types::{
8 Collection, Collections, ConformanceClasses, Feature, FeatureCollection, LandingPage, Link,
9};
10
11pub const MAX_LIMIT: u32 = 10_000;
13
14pub struct FeaturesServer {
16 pub title: String,
18 pub description: String,
20 pub base_url: String,
22 pub collections: Vec<Collection>,
24}
25
26impl FeaturesServer {
27 pub fn new(title: impl Into<String>, base_url: impl Into<String>) -> Self {
29 Self {
30 title: title.into(),
31 description: String::new(),
32 base_url: base_url.into(),
33 collections: vec![],
34 }
35 }
36
37 pub fn add_collection(&mut self, collection: Collection) {
39 self.collections.push(collection);
40 }
41
42 pub fn landing_page(&self) -> LandingPage {
44 let base = &self.base_url;
45 LandingPage {
46 title: self.title.clone(),
47 description: if self.description.is_empty() {
48 None
49 } else {
50 Some(self.description.clone())
51 },
52 links: vec![
53 Link::new(base.clone(), "self")
54 .with_type("application/json")
55 .with_title("This document"),
56 Link::new(format!("{base}/api"), "service-desc")
57 .with_type("application/vnd.oai.openapi+json;version=3.0")
58 .with_title("The API definition"),
59 Link::new(format!("{base}/conformance"), "conformance")
60 .with_type("application/json")
61 .with_title("Conformance classes"),
62 Link::new(format!("{base}/collections"), "data")
63 .with_type("application/json")
64 .with_title("Access the data"),
65 ],
66 }
67 }
68
69 pub fn conformance(&self) -> ConformanceClasses {
71 ConformanceClasses::with_crs()
72 }
73
74 pub fn list_collections(&self) -> Collections {
76 let base = &self.base_url;
77 Collections {
78 links: vec![
79 Link::new(format!("{base}/collections"), "self")
80 .with_type("application/json")
81 .with_title("Collections"),
82 ],
83 collections: self.collections.clone(),
84 }
85 }
86
87 pub fn get_collection(&self, id: &str) -> Option<&Collection> {
89 self.collections.iter().find(|c| c.id == id)
90 }
91
92 pub fn build_items_response(
106 &self,
107 collection_id: &str,
108 features: Vec<Feature>,
109 params: &QueryParams,
110 total_matched: Option<u64>,
111 ) -> Result<FeatureCollection, FeaturesError> {
112 if self.get_collection(collection_id).is_none() {
114 return Err(FeaturesError::CollectionNotFound(collection_id.to_string()));
115 }
116
117 let limit = params.effective_limit();
118 if limit > MAX_LIMIT {
119 return Err(FeaturesError::LimitExceeded {
120 requested: limit,
121 max: MAX_LIMIT,
122 });
123 }
124
125 let offset = params.effective_offset() as usize;
126 let limit_usize = limit as usize;
127 let total = features.len();
128
129 let page: Vec<Feature> = features
131 .into_iter()
132 .skip(offset)
133 .take(limit_usize)
134 .collect();
135
136 let number_returned = page.len() as u64;
137 let number_matched = total_matched.unwrap_or(total as u64);
138
139 let base = &self.base_url;
141 let items_base = format!("{base}/collections/{collection_id}/items");
142 let mut links: Vec<Link> = vec![
143 Link::new(
144 format!("{items_base}?limit={limit}&offset={offset}"),
145 "self",
146 )
147 .with_type("application/geo+json")
148 .with_title("This page"),
149 ];
150
151 let next_offset = offset + limit_usize;
153 if next_offset < total {
154 links.push(
155 Link::new(
156 format!("{items_base}?limit={limit}&offset={next_offset}"),
157 "next",
158 )
159 .with_type("application/geo+json")
160 .with_title("Next page"),
161 );
162 }
163
164 if offset > 0 {
166 let prev_offset = offset.saturating_sub(limit_usize);
167 links.push(
168 Link::new(
169 format!("{items_base}?limit={limit}&offset={prev_offset}"),
170 "prev",
171 )
172 .with_type("application/geo+json")
173 .with_title("Previous page"),
174 );
175 }
176
177 Ok(FeatureCollection {
178 type_: "FeatureCollection".to_string(),
179 features: page,
180 links: Some(links),
181 time_stamp: Some(Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string()),
182 number_matched: Some(number_matched),
183 number_returned: Some(number_returned),
184 })
185 }
186}