bbox_feature_server/
endpoints.rs1use crate::filter_params::FilterParams;
2use crate::inventory::Inventory;
3use crate::service::FeatureService;
4use actix_web::{web, Error, HttpRequest, HttpResponse};
5use bbox_core::api::OgcApiInventory;
6use bbox_core::endpoints::absurl;
7use bbox_core::ogcapi::{ApiLink, CoreCollections};
8use bbox_core::service::ServiceEndpoints;
9use bbox_core::templates::{create_env_embedded, html_accepted, render_endpoint};
10use minijinja::{context, Environment};
11use once_cell::sync::Lazy;
12use std::collections::HashMap;
13
14async fn collections(
16 _ogcapi: web::Data<OgcApiInventory>,
17 inventory: web::Data<Inventory>,
18 req: HttpRequest,
19) -> Result<HttpResponse, Error> {
20 let collections = CoreCollections {
21 links: vec![ApiLink {
22 href: absurl(&req, "/collections.json"),
23 rel: Some("self".to_string()),
24 type_: Some("application/json".to_string()),
25 title: Some("this document".to_string()),
26 hreflang: None,
27 length: None,
28 }],
29 collections: inventory.collections(), };
32 if html_accepted(&req).await {
33 render_endpoint(
34 &TEMPLATES,
35 "collections.html",
36 context!(cur_menu=>"Collections", collections => &collections),
37 )
38 .await
39 } else {
40 Ok(HttpResponse::Ok().json(collections))
41 }
42}
43
44async fn collection(
46 inventory: web::Data<Inventory>,
47 req: HttpRequest,
48 collection_id: web::Path<String>,
49) -> Result<HttpResponse, Error> {
50 if let Some(collection) = inventory.core_collection(&collection_id) {
51 if html_accepted(&req).await {
52 render_endpoint(
53 &TEMPLATES,
54 "collection.html",
55 context!(cur_menu=>"Collections", collection => &collection),
56 )
57 .await
58 } else {
59 Ok(HttpResponse::Ok().json(collection))
60 }
61 } else {
62 Ok(HttpResponse::NotFound().finish())
63 }
64}
65
66async fn queryables(
68 inventory: web::Data<Inventory>,
69 req: HttpRequest,
70 collection_id: web::Path<String>,
71) -> Result<HttpResponse, Error> {
72 if let Some(queryables) = inventory.collection_queryables(&collection_id).await {
73 if html_accepted(&req).await {
74 render_endpoint(
75 &TEMPLATES,
76 "queryables.html",
77 context!(cur_menu=>"Collections", queryables => &queryables),
78 )
79 .await
80 } else {
81 Ok(HttpResponse::Ok()
82 .content_type("application/geo+json")
83 .json(queryables))
84 }
85 } else {
86 Ok(HttpResponse::NotFound().finish())
87 }
88}
89
90async fn features(
92 inventory: web::Data<Inventory>,
93 req: HttpRequest,
94 collection_id: web::Path<String>,
95) -> Result<HttpResponse, Error> {
96 if let Some(collection) = inventory.core_collection(&collection_id) {
97 let mut filters: HashMap<String, String> =
98 match serde_urlencoded::from_str::<Vec<(String, String)>>(req.query_string()) {
99 Ok(f) => f
100 .iter()
101 .map(|k| (k.0.to_lowercase(), k.1.to_owned()))
102 .collect(),
103 Err(_e) => return Ok(HttpResponse::BadRequest().finish()),
104 };
105
106 let bbox = filters.remove("bbox");
107 let datetime = filters.remove("datetime");
108
109 let offset = if let Some(offset_str) = filters.get("offset") {
110 match offset_str.parse::<u32>() {
111 Ok(o) => {
112 filters.remove("offset");
113 Some(o)
114 }
115 Err(_e) => return Ok(HttpResponse::BadRequest().finish()),
116 }
117 } else {
118 None
119 };
120 let limit = if let Some(limit_str) = filters.get("limit") {
121 match limit_str.parse::<u32>() {
122 Ok(o) => {
123 filters.remove("limit");
124 Some(o)
125 }
126 Err(_e) => return Ok(HttpResponse::BadRequest().finish()),
127 }
128 } else {
129 None
130 };
131
132 let fp = FilterParams {
133 offset,
134 limit,
135 bbox,
136 datetime,
137 filters,
138 };
139
140 if let Some(features) = inventory.collection_items(&collection_id, &fp).await {
141 if html_accepted(&req).await {
142 render_endpoint(
143 &TEMPLATES,
144 "features.html",
145 context!(cur_menu=>"Collections", collection => &collection, features => &features),
146 ).await
147 } else {
148 Ok(HttpResponse::Ok()
149 .content_type("application/geo+json")
150 .json(features))
151 }
152 } else {
153 Ok(HttpResponse::NotFound().finish())
154 }
155 } else {
156 Ok(HttpResponse::NotFound().finish())
157 }
158}
159
160async fn feature(
162 inventory: web::Data<Inventory>,
163 req: HttpRequest,
164 path: web::Path<(String, String)>,
165) -> Result<HttpResponse, Error> {
166 let (collection_id, feature_id) = path.into_inner();
167 if let Some(collection) = inventory.core_collection(&collection_id) {
168 if let Some(feature) = inventory.collection_item(&collection_id, &feature_id).await {
169 if html_accepted(&req).await {
170 render_endpoint(
171 &TEMPLATES,
172 "feature.html",
173 context!(cur_menu=>"Collections", collection => &collection, feature => &feature),
174 ).await
175 } else {
176 Ok(HttpResponse::Ok()
177 .content_type("application/geo+json")
178 .json(feature))
179 }
180 } else {
181 Ok(HttpResponse::NotFound().finish())
182 }
183 } else {
184 Ok(HttpResponse::NotFound().finish())
185 }
186}
187
188#[cfg(feature = "html")]
189#[derive(rust_embed::RustEmbed)]
190#[folder = "templates/"]
191struct Templates;
192
193#[cfg(not(feature = "html"))]
194type Templates = bbox_core::templates::NoTemplates;
195
196static TEMPLATES: Lazy<Environment<'static>> = Lazy::new(create_env_embedded::<Templates>);
197
198impl ServiceEndpoints for FeatureService {
199 fn register_endpoints(&self, cfg: &mut web::ServiceConfig) {
200 cfg.app_data(web::Data::new(self.inventory.clone()))
201 .service(web::resource("/collections").route(web::get().to(collections)))
202 .service(web::resource("/collections.json").route(web::get().to(collections)))
203 .service(
204 web::resource("/collections/{collectionId}.json").route(web::get().to(collection)),
205 )
206 .service(web::resource("/collections/{collectionId}").route(web::get().to(collection)))
207 .service(
208 web::resource("/collections/{collectionId}/queryables.json")
209 .route(web::get().to(queryables)),
210 )
211 .service(
212 web::resource("/collections/{collectionId}/queryables")
213 .route(web::get().to(queryables)),
214 )
215 .service(
216 web::resource("/collections/{collectionId}/items").route(web::get().to(features)),
217 )
218 .service(
219 web::resource("/collections/{collectionId}/items.json")
220 .route(web::get().to(features)),
221 )
222 .service(
223 web::resource("/collections/{collectionId}/items/{featureId}.json")
224 .route(web::get().to(feature)),
225 )
226 .service(
227 web::resource("/collections/{collectionId}/items/{featureId}")
228 .route(web::get().to(feature)),
229 );
230 }
231}