Skip to main content

oxigdal_services/wcs/
coverage.rs

1//! WCS coverage description and retrieval
2//!
3//! Implements DescribeCoverage and GetCoverage operations for
4//! raster data access and format conversion.
5
6use 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/// DescribeCoverage parameters
16#[derive(Debug, Deserialize)]
17#[serde(rename_all = "UPPERCASE")]
18pub struct DescribeCoverageParams {
19    /// Coverage IDs (comma-separated)
20    #[serde(rename = "COVERAGEID")]
21    pub coverage_id: String,
22}
23
24/// GetCoverage parameters
25#[derive(Debug, Deserialize)]
26#[serde(rename_all = "UPPERCASE")]
27pub struct GetCoverageParams {
28    /// Coverage ID
29    #[serde(rename = "COVERAGEID")]
30    pub coverage_id: String,
31    /// Output format
32    pub format: String,
33    /// Subset (trim/slice operations)
34    pub subset: Option<String>,
35    /// Scaling factor
36    pub scale_factor: Option<f64>,
37    /// Scale axes
38    pub scale_axes: Option<String>,
39    /// Scale size
40    pub scale_size: Option<String>,
41    /// Range subset (band selection)
42    pub range_subset: Option<String>,
43}
44
45/// Handle DescribeCoverage request
46pub 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    // Validate all coverage IDs
57    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
69/// Handle GetCoverage request
70pub 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(&params.coverage_id)
80        .ok_or_else(|| ServiceError::NotFound(format!("Coverage: {}", params.coverage_id)))?;
81
82    // Parse subset parameters
83    let subset = parse_subset(&params.subset)?;
84
85    // Get coverage data
86    let data = retrieve_coverage_data(&coverage, &subset, &params).await?;
87
88    // Encode in requested format
89    encode_coverage(data, &params.format, &coverage)
90}
91
92/// Generate coverage descriptions XML
93fn 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
137/// Write single coverage description
138fn 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    // CoverageId
149    write_text_element(writer, "wcs:CoverageId", &coverage.coverage_id)?;
150
151    // BoundingBox
152    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    // Grid envelope and resolution
175    write_grid_description(writer, coverage)?;
176
177    // Range type
178    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
187/// Write grid description
188fn 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    // Grid limits
203    write_text_element(
204        writer,
205        "gml:limits",
206        &format!("0 0 {} {}", coverage.grid_size.0, coverage.grid_size.1),
207    )?;
208
209    // Axis labels
210    write_text_element(writer, "gml:axisLabels", "i j")?;
211
212    // Origin
213    write_text_element(
214        writer,
215        "gml:origin",
216        &format!("{} {}", coverage.grid_origin.0, coverage.grid_origin.1),
217    )?;
218
219    // Offset vectors
220    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
242/// Write range type description
243fn 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/// Subset specification
281#[derive(Debug)]
282#[allow(dead_code)]
283struct Subset {
284    /// X range (min, max)
285    x_range: Option<(f64, f64)>,
286    /// Y range (min, max)
287    y_range: Option<(f64, f64)>,
288    /// Time range
289    time_range: Option<(String, String)>,
290}
291
292/// Parse subset parameter
293fn 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        // Parse subset expressions like "x(min,max)" or "Lat(40,50)"
302        // Simple implementation - full WCS would support more complex subsetting
303        // For now, return empty subset
304    }
305
306    Ok(subset)
307}
308
309/// Coverage data
310#[allow(dead_code)]
311struct CoverageData {
312    /// Raster data
313    data: Vec<u8>,
314    /// Width
315    width: usize,
316    /// Height
317    height: usize,
318    /// Band count
319    bands: usize,
320}
321
322/// Retrieve coverage data
323async 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            // Load from file using OxiGDAL
333            // For now, return placeholder data
334            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
350/// Encode coverage in requested format
351fn 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
364/// Encode as GeoTIFF
365fn encode_as_geotiff(
366    data: CoverageData,
367    coverage: &crate::wcs::CoverageInfo,
368) -> Result<Response, ServiceError> {
369    // Use OxiGDAL GeoTIFF writer
370    // For now, return placeholder
371    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
386/// Encode as PNG
387fn encode_as_png(
388    data: CoverageData,
389    coverage: &crate::wcs::CoverageInfo,
390) -> Result<Response, ServiceError> {
391    // For PNG, we need RGB data
392    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
413/// Encode as JPEG
414fn 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
433/// Helper to write simple text element
434fn 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", &params).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}