1use crate::error::{ServiceError, ServiceResult};
7use crate::wcs::WcsState;
8use axum::{
9 http::header,
10 response::{IntoResponse, Response},
11};
12use bytes::Bytes;
13use serde::Deserialize;
14
15#[derive(Debug, Deserialize)]
17#[serde(rename_all = "UPPERCASE")]
18pub struct DescribeCoverageParams {
19 #[serde(rename = "COVERAGEID")]
21 pub coverage_id: String,
22}
23
24#[derive(Debug, Deserialize)]
26#[serde(rename_all = "UPPERCASE")]
27pub struct GetCoverageParams {
28 #[serde(rename = "COVERAGEID")]
30 pub coverage_id: String,
31 pub format: String,
33 pub subset: Option<String>,
35 pub scale_factor: Option<f64>,
37 pub scale_axes: Option<String>,
39 pub scale_size: Option<String>,
41 pub range_subset: Option<String>,
43}
44
45pub async fn handle_describe_coverage(
47 state: &WcsState,
48 _version: &str,
49 params: &serde_json::Value,
50) -> Result<Response, ServiceError> {
51 let params: DescribeCoverageParams = serde_json::from_value(params.clone())
52 .map_err(|e| ServiceError::InvalidParameter("Parameters".to_string(), e.to_string()))?;
53
54 let coverage_ids: Vec<&str> = params.coverage_id.split(',').map(|s| s.trim()).collect();
55
56 for coverage_id in &coverage_ids {
58 if state.get_coverage(coverage_id).is_none() {
59 return Err(ServiceError::NotFound(format!(
60 "Coverage not found: {}",
61 coverage_id
62 )));
63 }
64 }
65
66 generate_coverage_descriptions(state, &coverage_ids)
67}
68
69pub async fn handle_get_coverage(
71 state: &WcsState,
72 _version: &str,
73 params: &serde_json::Value,
74) -> Result<Response, ServiceError> {
75 let params: GetCoverageParams = serde_json::from_value(params.clone())
76 .map_err(|e| ServiceError::InvalidParameter("Parameters".to_string(), e.to_string()))?;
77
78 let coverage = state
79 .get_coverage(¶ms.coverage_id)
80 .ok_or_else(|| ServiceError::NotFound(format!("Coverage: {}", params.coverage_id)))?;
81
82 let subset = parse_subset(¶ms.subset)?;
84
85 let data = retrieve_coverage_data(&coverage, &subset, ¶ms).await?;
87
88 encode_coverage(data, ¶ms.format, &coverage)
90}
91
92fn generate_coverage_descriptions(
94 state: &WcsState,
95 coverage_ids: &[&str],
96) -> Result<Response, ServiceError> {
97 use quick_xml::{
98 Writer,
99 events::{BytesDecl, BytesEnd, BytesStart, Event},
100 };
101 use std::io::Cursor;
102
103 let mut writer = Writer::new(Cursor::new(Vec::new()));
104
105 writer
106 .write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
107 .map_err(|e| ServiceError::Xml(e.to_string()))?;
108
109 let mut root = BytesStart::new("wcs:CoverageDescriptions");
110 root.push_attribute(("xmlns:wcs", "http://www.opengis.net/wcs/2.0"));
111 root.push_attribute(("xmlns:gml", "http://www.opengis.net/gml/3.2"));
112 root.push_attribute(("xmlns:gmlcov", "http://www.opengis.net/gmlcov/1.0"));
113 root.push_attribute(("xmlns:swe", "http://www.opengis.net/swe/2.0"));
114
115 writer
116 .write_event(Event::Start(root))
117 .map_err(|e| ServiceError::Xml(e.to_string()))?;
118
119 for coverage_id in coverage_ids {
120 let coverage = state
121 .get_coverage(coverage_id)
122 .ok_or_else(|| ServiceError::NotFound(format!("Coverage: {}", coverage_id)))?;
123
124 write_coverage_description(&mut writer, &coverage)?;
125 }
126
127 writer
128 .write_event(Event::End(BytesEnd::new("wcs:CoverageDescriptions")))
129 .map_err(|e| ServiceError::Xml(e.to_string()))?;
130
131 let xml = String::from_utf8(writer.into_inner().into_inner())
132 .map_err(|e| ServiceError::Xml(e.to_string()))?;
133
134 Ok(([(header::CONTENT_TYPE, "application/xml")], xml).into_response())
135}
136
137fn write_coverage_description(
139 writer: &mut quick_xml::Writer<std::io::Cursor<Vec<u8>>>,
140 coverage: &crate::wcs::CoverageInfo,
141) -> ServiceResult<()> {
142 use quick_xml::events::{BytesEnd, BytesStart, Event};
143
144 writer
145 .write_event(Event::Start(BytesStart::new("wcs:CoverageDescription")))
146 .map_err(|e| ServiceError::Xml(e.to_string()))?;
147
148 write_text_element(writer, "wcs:CoverageId", &coverage.coverage_id)?;
150
151 let mut bbox = BytesStart::new("ows:BoundingBox");
153 bbox.push_attribute(("crs", coverage.native_crs.as_str()));
154 bbox.push_attribute(("dimensions", "2"));
155 writer
156 .write_event(Event::Start(bbox))
157 .map_err(|e| ServiceError::Xml(e.to_string()))?;
158
159 write_text_element(
160 writer,
161 "ows:LowerCorner",
162 &format!("{} {}", coverage.bbox.0, coverage.bbox.1),
163 )?;
164 write_text_element(
165 writer,
166 "ows:UpperCorner",
167 &format!("{} {}", coverage.bbox.2, coverage.bbox.3),
168 )?;
169
170 writer
171 .write_event(Event::End(BytesEnd::new("ows:BoundingBox")))
172 .map_err(|e| ServiceError::Xml(e.to_string()))?;
173
174 write_grid_description(writer, coverage)?;
176
177 write_range_type(writer, coverage)?;
179
180 writer
181 .write_event(Event::End(BytesEnd::new("wcs:CoverageDescription")))
182 .map_err(|e| ServiceError::Xml(e.to_string()))?;
183
184 Ok(())
185}
186
187fn write_grid_description(
189 writer: &mut quick_xml::Writer<std::io::Cursor<Vec<u8>>>,
190 coverage: &crate::wcs::CoverageInfo,
191) -> ServiceResult<()> {
192 use quick_xml::events::{BytesEnd, BytesStart, Event};
193
194 writer
195 .write_event(Event::Start(BytesStart::new("gml:domainSet")))
196 .map_err(|e| ServiceError::Xml(e.to_string()))?;
197
198 writer
199 .write_event(Event::Start(BytesStart::new("gml:RectifiedGrid")))
200 .map_err(|e| ServiceError::Xml(e.to_string()))?;
201
202 write_text_element(
204 writer,
205 "gml:limits",
206 &format!("0 0 {} {}", coverage.grid_size.0, coverage.grid_size.1),
207 )?;
208
209 write_text_element(writer, "gml:axisLabels", "i j")?;
211
212 write_text_element(
214 writer,
215 "gml:origin",
216 &format!("{} {}", coverage.grid_origin.0, coverage.grid_origin.1),
217 )?;
218
219 write_text_element(
221 writer,
222 "gml:offsetVector",
223 &format!("{} 0", coverage.grid_resolution.0),
224 )?;
225 write_text_element(
226 writer,
227 "gml:offsetVector",
228 &format!("0 {}", coverage.grid_resolution.1),
229 )?;
230
231 writer
232 .write_event(Event::End(BytesEnd::new("gml:RectifiedGrid")))
233 .map_err(|e| ServiceError::Xml(e.to_string()))?;
234
235 writer
236 .write_event(Event::End(BytesEnd::new("gml:domainSet")))
237 .map_err(|e| ServiceError::Xml(e.to_string()))?;
238
239 Ok(())
240}
241
242fn write_range_type(
244 writer: &mut quick_xml::Writer<std::io::Cursor<Vec<u8>>>,
245 coverage: &crate::wcs::CoverageInfo,
246) -> ServiceResult<()> {
247 use quick_xml::events::{BytesEnd, BytesStart, Event};
248
249 writer
250 .write_event(Event::Start(BytesStart::new("gmlcov:rangeType")))
251 .map_err(|e| ServiceError::Xml(e.to_string()))?;
252
253 writer
254 .write_event(Event::Start(BytesStart::new("swe:DataRecord")))
255 .map_err(|e| ServiceError::Xml(e.to_string()))?;
256
257 for band_name in coverage.band_names.iter() {
258 writer
259 .write_event(Event::Start(BytesStart::new("swe:field")))
260 .map_err(|e| ServiceError::Xml(e.to_string()))?;
261
262 write_text_element(writer, "swe:Quantity", band_name)?;
263
264 writer
265 .write_event(Event::End(BytesEnd::new("swe:field")))
266 .map_err(|e| ServiceError::Xml(e.to_string()))?;
267 }
268
269 writer
270 .write_event(Event::End(BytesEnd::new("swe:DataRecord")))
271 .map_err(|e| ServiceError::Xml(e.to_string()))?;
272
273 writer
274 .write_event(Event::End(BytesEnd::new("gmlcov:rangeType")))
275 .map_err(|e| ServiceError::Xml(e.to_string()))?;
276
277 Ok(())
278}
279
280#[derive(Debug)]
282#[allow(dead_code)]
283struct Subset {
284 x_range: Option<(f64, f64)>,
286 y_range: Option<(f64, f64)>,
288 time_range: Option<(String, String)>,
290}
291
292fn parse_subset(subset_str: &Option<String>) -> ServiceResult<Subset> {
294 let subset = Subset {
295 x_range: None,
296 y_range: None,
297 time_range: None,
298 };
299
300 if let Some(_s) = subset_str {
301 }
305
306 Ok(subset)
307}
308
309#[allow(dead_code)]
311struct CoverageData {
312 data: Vec<u8>,
314 width: usize,
316 height: usize,
318 bands: usize,
320}
321
322async fn retrieve_coverage_data(
324 coverage: &crate::wcs::CoverageInfo,
325 _subset: &Subset,
326 _params: &GetCoverageParams,
327) -> ServiceResult<CoverageData> {
328 use crate::wcs::CoverageSource;
329
330 match &coverage.source {
331 CoverageSource::File(_path) => {
332 Ok(CoverageData {
335 data: vec![0u8; coverage.grid_size.0 * coverage.grid_size.1 * coverage.band_count],
336 width: coverage.grid_size.0,
337 height: coverage.grid_size.1,
338 bands: coverage.band_count,
339 })
340 }
341 CoverageSource::Url(_url) => Err(ServiceError::Coverage(
342 "URL-based coverages not yet implemented".to_string(),
343 )),
344 CoverageSource::Memory => Err(ServiceError::Coverage(
345 "In-memory coverages not yet implemented".to_string(),
346 )),
347 }
348}
349
350fn encode_coverage(
352 data: CoverageData,
353 format: &str,
354 coverage: &crate::wcs::CoverageInfo,
355) -> Result<Response, ServiceError> {
356 match format {
357 "image/tiff" | "image/geotiff" => encode_as_geotiff(data, coverage),
358 "image/png" => encode_as_png(data, coverage),
359 "image/jpeg" => encode_as_jpeg(data, coverage),
360 _ => Err(ServiceError::UnsupportedFormat(format.to_string())),
361 }
362}
363
364fn encode_as_geotiff(
366 data: CoverageData,
367 coverage: &crate::wcs::CoverageInfo,
368) -> Result<Response, ServiceError> {
369 let bytes = Bytes::from(data.data);
372
373 Ok((
374 [
375 (header::CONTENT_TYPE, "image/tiff"),
376 (
377 header::CONTENT_DISPOSITION,
378 &format!("attachment; filename=\"{}.tif\"", coverage.coverage_id),
379 ),
380 ],
381 bytes,
382 )
383 .into_response())
384}
385
386fn encode_as_png(
388 data: CoverageData,
389 coverage: &crate::wcs::CoverageInfo,
390) -> Result<Response, ServiceError> {
391 if data.bands < 3 {
393 return Err(ServiceError::Coverage(
394 "PNG requires at least 3 bands".to_string(),
395 ));
396 }
397
398 let bytes = Bytes::from(data.data);
399
400 Ok((
401 [
402 (header::CONTENT_TYPE, "image/png"),
403 (
404 header::CONTENT_DISPOSITION,
405 &format!("attachment; filename=\"{}.png\"", coverage.coverage_id),
406 ),
407 ],
408 bytes,
409 )
410 .into_response())
411}
412
413fn encode_as_jpeg(
415 data: CoverageData,
416 coverage: &crate::wcs::CoverageInfo,
417) -> Result<Response, ServiceError> {
418 let bytes = Bytes::from(data.data);
419
420 Ok((
421 [
422 (header::CONTENT_TYPE, "image/jpeg"),
423 (
424 header::CONTENT_DISPOSITION,
425 &format!("attachment; filename=\"{}.jpg\"", coverage.coverage_id),
426 ),
427 ],
428 bytes,
429 )
430 .into_response())
431}
432
433fn write_text_element(
435 writer: &mut quick_xml::Writer<std::io::Cursor<Vec<u8>>>,
436 tag: &str,
437 text: &str,
438) -> ServiceResult<()> {
439 use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
440
441 writer
442 .write_event(Event::Start(BytesStart::new(tag)))
443 .map_err(|e| ServiceError::Xml(e.to_string()))?;
444
445 writer
446 .write_event(Event::Text(BytesText::new(text)))
447 .map_err(|e| ServiceError::Xml(e.to_string()))?;
448
449 writer
450 .write_event(Event::End(BytesEnd::new(tag)))
451 .map_err(|e| ServiceError::Xml(e.to_string()))?;
452
453 Ok(())
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459 use crate::wcs::{CoverageInfo, CoverageSource, ServiceInfo, WcsState};
460
461 #[tokio::test]
462 async fn test_describe_coverage() -> Result<(), Box<dyn std::error::Error>> {
463 let info = ServiceInfo {
464 title: "Test WCS".to_string(),
465 abstract_text: None,
466 provider: "COOLJAPAN OU".to_string(),
467 service_url: "http://localhost/wcs".to_string(),
468 versions: vec!["2.0.1".to_string()],
469 };
470
471 let state = WcsState::new(info);
472
473 let coverage = CoverageInfo {
474 coverage_id: "test".to_string(),
475 title: "Test Coverage".to_string(),
476 abstract_text: None,
477 native_crs: "EPSG:4326".to_string(),
478 bbox: (-180.0, -90.0, 180.0, 90.0),
479 grid_size: (1024, 512),
480 grid_origin: (-180.0, 90.0),
481 grid_resolution: (0.35, -0.35),
482 band_count: 1,
483 band_names: vec!["Band1".to_string()],
484 data_type: "Byte".to_string(),
485 source: CoverageSource::Memory,
486 formats: vec!["image/tiff".to_string()],
487 };
488
489 state.add_coverage(coverage)?;
490
491 let params = serde_json::json!({
492 "COVERAGEID": "test"
493 });
494
495 let response = handle_describe_coverage(&state, "2.0.1", ¶ms).await?;
496
497 let (parts, _) = response.into_parts();
498 assert_eq!(
499 parts
500 .headers
501 .get(header::CONTENT_TYPE)
502 .and_then(|h| h.to_str().ok()),
503 Some("application/xml")
504 );
505 Ok(())
506 }
507}