pub mod capabilities;
pub mod coverage;
use crate::error::{ServiceError, ServiceResult};
use axum::{
extract::{Query, State},
response::Response,
};
use serde::Deserialize;
use std::sync::Arc;
#[derive(Clone)]
pub struct WcsState {
pub service_info: Arc<ServiceInfo>,
pub coverages: Arc<dashmap::DashMap<String, CoverageInfo>>,
}
#[derive(Debug, Clone)]
pub struct ServiceInfo {
pub title: String,
pub abstract_text: Option<String>,
pub provider: String,
pub service_url: String,
pub versions: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct CoverageInfo {
pub coverage_id: String,
pub title: String,
pub abstract_text: Option<String>,
pub native_crs: String,
pub bbox: (f64, f64, f64, f64),
pub grid_size: (usize, usize),
pub grid_origin: (f64, f64),
pub grid_resolution: (f64, f64),
pub band_count: usize,
pub band_names: Vec<String>,
pub data_type: String,
pub source: CoverageSource,
pub formats: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum CoverageSource {
File(std::path::PathBuf),
Url(String),
Memory,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub struct WcsRequest {
pub service: Option<String>,
pub version: Option<String>,
pub request: String,
#[serde(flatten)]
pub params: serde_json::Value,
}
impl WcsState {
pub fn new(service_info: ServiceInfo) -> Self {
Self {
service_info: Arc::new(service_info),
coverages: Arc::new(dashmap::DashMap::new()),
}
}
pub fn add_coverage(&self, info: CoverageInfo) -> ServiceResult<()> {
self.coverages.insert(info.coverage_id.clone(), info);
Ok(())
}
pub fn get_coverage(&self, coverage_id: &str) -> Option<CoverageInfo> {
self.coverages
.get(coverage_id)
.map(|entry| entry.value().clone())
}
}
pub async fn handle_wcs_request(
State(state): State<WcsState>,
Query(params): Query<WcsRequest>,
) -> Result<Response, ServiceError> {
if let Some(ref service) = params.service {
if service.to_uppercase() != "WCS" {
return Err(ServiceError::InvalidParameter(
"SERVICE".to_string(),
format!("Expected 'WCS', got '{}'", service),
));
}
}
match params.request.to_uppercase().as_str() {
"GETCAPABILITIES" => {
let version = params.version.as_deref().unwrap_or("2.0.1");
capabilities::handle_get_capabilities(&state, version).await
}
"DESCRIBECOVERAGE" => {
let version = params.version.as_deref().unwrap_or("2.0.1");
coverage::handle_describe_coverage(&state, version, ¶ms.params).await
}
"GETCOVERAGE" => {
let version = params.version.as_deref().unwrap_or("2.0.1");
coverage::handle_get_coverage(&state, version, ¶ms.params).await
}
_ => Err(ServiceError::UnsupportedOperation(params.request.clone())),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wcs_state_creation() {
let info = ServiceInfo {
title: "Test WCS".to_string(),
abstract_text: Some("Test service".to_string()),
provider: "COOLJAPAN OU".to_string(),
service_url: "http://localhost/wcs".to_string(),
versions: vec!["2.0.1".to_string()],
};
let state = WcsState::new(info);
assert_eq!(state.service_info.title, "Test WCS");
}
#[test]
fn test_add_coverage() {
let info = ServiceInfo {
title: "Test WCS".to_string(),
abstract_text: None,
provider: "COOLJAPAN OU".to_string(),
service_url: "http://localhost/wcs".to_string(),
versions: vec!["2.0.1".to_string()],
};
let state = WcsState::new(info);
let coverage = CoverageInfo {
coverage_id: "test_coverage".to_string(),
title: "Test Coverage".to_string(),
abstract_text: None,
native_crs: "EPSG:4326".to_string(),
bbox: (-180.0, -90.0, 180.0, 90.0),
grid_size: (1024, 512),
grid_origin: (-180.0, 90.0),
grid_resolution: (0.35, -0.35),
band_count: 3,
band_names: vec!["Red".to_string(), "Green".to_string(), "Blue".to_string()],
data_type: "Byte".to_string(),
source: CoverageSource::Memory,
formats: vec!["image/tiff".to_string(), "image/png".to_string()],
};
assert!(
state.add_coverage(coverage).is_ok(),
"Failed to add coverage"
);
let retrieved = state.get_coverage("test_coverage");
assert!(retrieved.is_some());
assert_eq!(
retrieved.as_ref().map(|c| &c.coverage_id),
Some(&"test_coverage".to_string())
);
}
}