1use crate::cache::TileCache;
9use crate::config::ImageFormat;
10use crate::dataset_registry::Dataset;
11use crate::dataset_registry::{DatasetRegistry, LayerInfo};
12use crate::handlers::rendering::{RasterRenderer, RenderStyle, encode_image};
13use axum::{
14 extract::{Query, State},
15 http::{StatusCode, header},
16 response::{IntoResponse, Response},
17};
18use bytes::Bytes;
19use oxigdal_core::buffer::RasterBuffer;
20use quick_xml::Writer;
21use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
22use serde::Deserialize;
23use std::io::Cursor;
24use std::sync::Arc;
25use thiserror::Error;
26use tracing::debug;
27
28#[derive(Debug, Clone, Copy)]
30struct SourceRegion {
31 x: u64,
32 y: u64,
33 width: u64,
34 height: u64,
35}
36
37impl SourceRegion {
38 fn new(x: u64, y: u64, width: u64, height: u64) -> Self {
39 Self {
40 x,
41 y,
42 width,
43 height,
44 }
45 }
46}
47
48#[derive(Debug, Clone, Copy)]
50struct TargetDimensions {
51 width: u64,
52 height: u64,
53}
54
55impl TargetDimensions {
56 fn new(width: u64, height: u64) -> Self {
57 Self { width, height }
58 }
59}
60
61#[derive(Debug, Error)]
63pub enum WmsError {
64 #[error("Invalid parameter: {0}")]
66 InvalidParameter(String),
67
68 #[error("Missing required parameter: {0}")]
70 MissingParameter(String),
71
72 #[error("Layer not found: {0}")]
74 LayerNotFound(String),
75
76 #[error("Invalid CRS: {0}")]
78 InvalidCrs(String),
79
80 #[error("Invalid bounding box: {0}")]
82 InvalidBbox(String),
83
84 #[error("Rendering error: {0}")]
86 Rendering(String),
87
88 #[error("GDAL error: {0}")]
90 Gdal(#[from] oxigdal_core::OxiGdalError),
91
92 #[error("Registry error: {0}")]
94 Registry(#[from] crate::dataset_registry::RegistryError),
95
96 #[error("Unsupported format: {0}")]
98 UnsupportedFormat(String),
99}
100
101impl IntoResponse for WmsError {
102 fn into_response(self) -> Response {
103 let (status, message) = match self {
104 WmsError::InvalidParameter(_) | WmsError::MissingParameter(_) => {
105 (StatusCode::BAD_REQUEST, self.to_string())
106 }
107 WmsError::LayerNotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
108 _ => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
109 };
110
111 let xml = format!(
113 r#"<?xml version="1.0" encoding="UTF-8"?>
114<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">
115 <ServiceException>{}</ServiceException>
116</ServiceExceptionReport>"#,
117 message
118 );
119
120 (
121 status,
122 [(header::CONTENT_TYPE, "application/vnd.ogc.se_xml")],
123 xml,
124 )
125 .into_response()
126 }
127}
128
129#[derive(Clone)]
131pub struct WmsState {
132 pub registry: DatasetRegistry,
134
135 pub cache: TileCache,
137
138 pub service_url: String,
140
141 pub service_title: String,
143 pub service_abstract: String,
145}
146
147#[derive(Debug, Deserialize)]
149#[allow(dead_code)]
150pub struct GetCapabilitiesParams {
151 #[serde(rename = "SERVICE")]
152 service: Option<String>,
153
154 #[serde(rename = "REQUEST")]
155 request: Option<String>,
156
157 #[serde(rename = "VERSION")]
158 version: Option<String>,
159}
160
161#[derive(Debug, Deserialize)]
163#[allow(dead_code)]
164pub struct GetMapParams {
165 #[serde(rename = "SERVICE")]
166 service: Option<String>,
167
168 #[serde(rename = "REQUEST")]
169 request: Option<String>,
170
171 #[serde(rename = "VERSION")]
172 version: Option<String>,
173
174 #[serde(rename = "LAYERS")]
175 layers: String,
176
177 #[serde(rename = "STYLES")]
178 styles: Option<String>,
179
180 #[serde(rename = "CRS")]
181 crs: Option<String>,
182
183 #[serde(rename = "SRS")]
184 srs: Option<String>,
185
186 #[serde(rename = "BBOX")]
187 bbox: String,
188
189 #[serde(rename = "WIDTH")]
190 width: u32,
191
192 #[serde(rename = "HEIGHT")]
193 height: u32,
194
195 #[serde(rename = "FORMAT")]
196 format: String,
197
198 #[serde(rename = "TRANSPARENT")]
199 transparent: Option<bool>,
200
201 #[serde(rename = "BGCOLOR")]
202 bgcolor: Option<String>,
203}
204
205#[derive(Debug, Deserialize)]
207#[allow(dead_code)]
208pub struct GetFeatureInfoParams {
209 #[serde(rename = "SERVICE")]
210 service: Option<String>,
211
212 #[serde(rename = "REQUEST")]
213 request: Option<String>,
214
215 #[serde(rename = "VERSION")]
216 version: Option<String>,
217
218 #[serde(rename = "LAYERS")]
219 layers: String,
220
221 #[serde(rename = "QUERY_LAYERS")]
222 query_layers: String,
223
224 #[serde(rename = "CRS")]
225 crs: Option<String>,
226
227 #[serde(rename = "SRS")]
228 srs: Option<String>,
229
230 #[serde(rename = "BBOX")]
231 bbox: String,
232
233 #[serde(rename = "WIDTH")]
234 width: u32,
235
236 #[serde(rename = "HEIGHT")]
237 height: u32,
238
239 #[serde(rename = "I")]
240 i: Option<u32>,
241
242 #[serde(rename = "X")]
243 x: Option<u32>,
244
245 #[serde(rename = "J")]
246 j: Option<u32>,
247
248 #[serde(rename = "Y")]
249 y: Option<u32>,
250
251 #[serde(rename = "INFO_FORMAT")]
252 info_format: Option<String>,
253}
254
255pub async fn get_capabilities(
257 State(state): State<Arc<WmsState>>,
258 Query(params): Query<GetCapabilitiesParams>,
259) -> Result<Response, WmsError> {
260 debug!("WMS GetCapabilities request");
261
262 if let Some(ref service) = params.service {
264 if service.to_uppercase() != "WMS" {
265 return Err(WmsError::InvalidParameter(format!(
266 "Invalid SERVICE: {}",
267 service
268 )));
269 }
270 }
271
272 let layers = state.registry.list_layers()?;
274
275 let xml = generate_capabilities_xml(&state, &layers)?;
277
278 Ok((
279 StatusCode::OK,
280 [(header::CONTENT_TYPE, "application/vnd.ogc.wms_xml")],
281 xml,
282 )
283 .into_response())
284}
285
286fn generate_capabilities_xml(state: &WmsState, layers: &[LayerInfo]) -> Result<String, WmsError> {
288 let mut writer = Writer::new(Cursor::new(Vec::new()));
289
290 writer
292 .write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
293 .map_err(|e| WmsError::Rendering(e.to_string()))?;
294
295 let mut root = BytesStart::new("WMS_Capabilities");
297 root.push_attribute(("version", "1.3.0"));
298 root.push_attribute(("xmlns", "http://www.opengis.net/wms"));
299 root.push_attribute(("xmlns:xlink", "http://www.w3.org/1999/xlink"));
300 writer
301 .write_event(Event::Start(root))
302 .map_err(|e| WmsError::Rendering(e.to_string()))?;
303
304 write_service_section(&mut writer, state)?;
306
307 write_capability_section(&mut writer, state, layers)?;
309
310 writer
312 .write_event(Event::End(BytesEnd::new("WMS_Capabilities")))
313 .map_err(|e| WmsError::Rendering(e.to_string()))?;
314
315 let result = writer.into_inner().into_inner();
316 String::from_utf8(result).map_err(|e| WmsError::Rendering(e.to_string()))
317}
318
319fn write_service_section(
321 writer: &mut Writer<Cursor<Vec<u8>>>,
322 state: &WmsState,
323) -> Result<(), WmsError> {
324 writer
325 .write_event(Event::Start(BytesStart::new("Service")))
326 .map_err(|e| WmsError::Rendering(e.to_string()))?;
327
328 writer
330 .write_event(Event::Start(BytesStart::new("Name")))
331 .map_err(|e| WmsError::Rendering(e.to_string()))?;
332 writer
333 .write_event(Event::Text(BytesText::new("WMS")))
334 .map_err(|e| WmsError::Rendering(e.to_string()))?;
335 writer
336 .write_event(Event::End(BytesEnd::new("Name")))
337 .map_err(|e| WmsError::Rendering(e.to_string()))?;
338
339 writer
341 .write_event(Event::Start(BytesStart::new("Title")))
342 .map_err(|e| WmsError::Rendering(e.to_string()))?;
343 writer
344 .write_event(Event::Text(BytesText::new(&state.service_title)))
345 .map_err(|e| WmsError::Rendering(e.to_string()))?;
346 writer
347 .write_event(Event::End(BytesEnd::new("Title")))
348 .map_err(|e| WmsError::Rendering(e.to_string()))?;
349
350 writer
352 .write_event(Event::Start(BytesStart::new("Abstract")))
353 .map_err(|e| WmsError::Rendering(e.to_string()))?;
354 writer
355 .write_event(Event::Text(BytesText::new(&state.service_abstract)))
356 .map_err(|e| WmsError::Rendering(e.to_string()))?;
357 writer
358 .write_event(Event::End(BytesEnd::new("Abstract")))
359 .map_err(|e| WmsError::Rendering(e.to_string()))?;
360
361 let mut online_resource = BytesStart::new("OnlineResource");
363 online_resource.push_attribute(("xmlns:xlink", "http://www.w3.org/1999/xlink"));
364 online_resource.push_attribute(("xlink:type", "simple"));
365 online_resource.push_attribute(("xlink:href", state.service_url.as_str()));
366 writer
367 .write_event(Event::Empty(online_resource))
368 .map_err(|e| WmsError::Rendering(e.to_string()))?;
369
370 writer
371 .write_event(Event::End(BytesEnd::new("Service")))
372 .map_err(|e| WmsError::Rendering(e.to_string()))?;
373
374 Ok(())
375}
376
377fn write_capability_section(
379 writer: &mut Writer<Cursor<Vec<u8>>>,
380 state: &WmsState,
381 layers: &[LayerInfo],
382) -> Result<(), WmsError> {
383 writer
384 .write_event(Event::Start(BytesStart::new("Capability")))
385 .map_err(|e| WmsError::Rendering(e.to_string()))?;
386
387 write_request_section(writer, state)?;
389
390 writer
392 .write_event(Event::Start(BytesStart::new("Exception")))
393 .map_err(|e| WmsError::Rendering(e.to_string()))?;
394 writer
395 .write_event(Event::Empty(BytesStart::new("Format")))
396 .map_err(|e| WmsError::Rendering(e.to_string()))?;
397 writer
398 .write_event(Event::End(BytesEnd::new("Exception")))
399 .map_err(|e| WmsError::Rendering(e.to_string()))?;
400
401 write_layers_section(writer, layers)?;
403
404 writer
405 .write_event(Event::End(BytesEnd::new("Capability")))
406 .map_err(|e| WmsError::Rendering(e.to_string()))?;
407
408 Ok(())
409}
410
411fn write_request_section(
413 writer: &mut Writer<Cursor<Vec<u8>>>,
414 state: &WmsState,
415) -> Result<(), WmsError> {
416 writer
417 .write_event(Event::Start(BytesStart::new("Request")))
418 .map_err(|e| WmsError::Rendering(e.to_string()))?;
419
420 write_operation(writer, "GetCapabilities", &state.service_url)?;
422
423 write_operation(writer, "GetMap", &state.service_url)?;
425
426 write_operation(writer, "GetFeatureInfo", &state.service_url)?;
428
429 writer
430 .write_event(Event::End(BytesEnd::new("Request")))
431 .map_err(|e| WmsError::Rendering(e.to_string()))?;
432
433 Ok(())
434}
435
436fn write_operation(
438 writer: &mut Writer<Cursor<Vec<u8>>>,
439 operation: &str,
440 url: &str,
441) -> Result<(), WmsError> {
442 writer
443 .write_event(Event::Start(BytesStart::new(operation)))
444 .map_err(|e| WmsError::Rendering(e.to_string()))?;
445
446 for format in &["image/png", "image/jpeg", "image/webp"] {
448 writer
449 .write_event(Event::Start(BytesStart::new("Format")))
450 .map_err(|e| WmsError::Rendering(e.to_string()))?;
451 writer
452 .write_event(Event::Text(BytesText::new(format)))
453 .map_err(|e| WmsError::Rendering(e.to_string()))?;
454 writer
455 .write_event(Event::End(BytesEnd::new("Format")))
456 .map_err(|e| WmsError::Rendering(e.to_string()))?;
457 }
458
459 writer
461 .write_event(Event::Start(BytesStart::new("DCPType")))
462 .map_err(|e| WmsError::Rendering(e.to_string()))?;
463 writer
464 .write_event(Event::Start(BytesStart::new("HTTP")))
465 .map_err(|e| WmsError::Rendering(e.to_string()))?;
466 writer
467 .write_event(Event::Start(BytesStart::new("Get")))
468 .map_err(|e| WmsError::Rendering(e.to_string()))?;
469
470 let mut online_resource = BytesStart::new("OnlineResource");
471 online_resource.push_attribute(("xmlns:xlink", "http://www.w3.org/1999/xlink"));
472 online_resource.push_attribute(("xlink:type", "simple"));
473 online_resource.push_attribute(("xlink:href", url));
474 writer
475 .write_event(Event::Empty(online_resource))
476 .map_err(|e| WmsError::Rendering(e.to_string()))?;
477
478 writer
479 .write_event(Event::End(BytesEnd::new("Get")))
480 .map_err(|e| WmsError::Rendering(e.to_string()))?;
481 writer
482 .write_event(Event::End(BytesEnd::new("HTTP")))
483 .map_err(|e| WmsError::Rendering(e.to_string()))?;
484 writer
485 .write_event(Event::End(BytesEnd::new("DCPType")))
486 .map_err(|e| WmsError::Rendering(e.to_string()))?;
487
488 writer
489 .write_event(Event::End(BytesEnd::new(operation)))
490 .map_err(|e| WmsError::Rendering(e.to_string()))?;
491
492 Ok(())
493}
494
495fn write_layers_section(
497 writer: &mut Writer<Cursor<Vec<u8>>>,
498 layers: &[LayerInfo],
499) -> Result<(), WmsError> {
500 for layer in layers {
501 write_layer(writer, layer)?;
502 }
503
504 Ok(())
505}
506
507fn write_layer(writer: &mut Writer<Cursor<Vec<u8>>>, layer: &LayerInfo) -> Result<(), WmsError> {
509 writer
510 .write_event(Event::Start(BytesStart::new("Layer")))
511 .map_err(|e| WmsError::Rendering(e.to_string()))?;
512
513 writer
515 .write_event(Event::Start(BytesStart::new("Name")))
516 .map_err(|e| WmsError::Rendering(e.to_string()))?;
517 writer
518 .write_event(Event::Text(BytesText::new(&layer.name)))
519 .map_err(|e| WmsError::Rendering(e.to_string()))?;
520 writer
521 .write_event(Event::End(BytesEnd::new("Name")))
522 .map_err(|e| WmsError::Rendering(e.to_string()))?;
523
524 writer
526 .write_event(Event::Start(BytesStart::new("Title")))
527 .map_err(|e| WmsError::Rendering(e.to_string()))?;
528 writer
529 .write_event(Event::Text(BytesText::new(&layer.title)))
530 .map_err(|e| WmsError::Rendering(e.to_string()))?;
531 writer
532 .write_event(Event::End(BytesEnd::new("Title")))
533 .map_err(|e| WmsError::Rendering(e.to_string()))?;
534
535 writer
537 .write_event(Event::Start(BytesStart::new("Abstract")))
538 .map_err(|e| WmsError::Rendering(e.to_string()))?;
539 writer
540 .write_event(Event::Text(BytesText::new(&layer.abstract_)))
541 .map_err(|e| WmsError::Rendering(e.to_string()))?;
542 writer
543 .write_event(Event::End(BytesEnd::new("Abstract")))
544 .map_err(|e| WmsError::Rendering(e.to_string()))?;
545
546 if let Some((min_x, min_y, max_x, max_y)) = layer.metadata.bbox {
548 let mut bbox = BytesStart::new("BoundingBox");
549 bbox.push_attribute(("CRS", "EPSG:4326"));
550 bbox.push_attribute(("minx", min_x.to_string().as_str()));
551 bbox.push_attribute(("miny", min_y.to_string().as_str()));
552 bbox.push_attribute(("maxx", max_x.to_string().as_str()));
553 bbox.push_attribute(("maxy", max_y.to_string().as_str()));
554 writer
555 .write_event(Event::Empty(bbox))
556 .map_err(|e| WmsError::Rendering(e.to_string()))?;
557 }
558
559 writer
560 .write_event(Event::End(BytesEnd::new("Layer")))
561 .map_err(|e| WmsError::Rendering(e.to_string()))?;
562
563 Ok(())
564}
565
566pub async fn get_map(
568 State(state): State<Arc<WmsState>>,
569 Query(params): Query<GetMapParams>,
570) -> Result<Response, WmsError> {
571 debug!("WMS GetMap request: layers={}", params.layers);
572
573 let layer_name = params
575 .layers
576 .split(',')
577 .next()
578 .ok_or_else(|| WmsError::InvalidParameter("LAYERS parameter is empty".to_string()))?;
579
580 let format = parse_format(¶ms.format)?;
581 let bbox = parse_bbox(¶ms.bbox)?;
582
583 if params.width == 0 || params.height == 0 {
585 return Err(WmsError::InvalidParameter(
586 "WIDTH and HEIGHT must be greater than 0".to_string(),
587 ));
588 }
589
590 if params.width > 4096 || params.height > 4096 {
591 return Err(WmsError::InvalidParameter(
592 "WIDTH and HEIGHT must be <= 4096".to_string(),
593 ));
594 }
595
596 let layer = state.registry.get_layer(layer_name)?;
598 let dataset = state.registry.get_dataset(layer_name)?;
599
600 let image_data = render_map(
602 &dataset,
603 bbox,
604 params.width,
605 params.height,
606 format,
607 params.transparent.unwrap_or(false),
608 layer.config.style.as_ref(),
609 )?;
610
611 Ok((
612 StatusCode::OK,
613 [(header::CONTENT_TYPE, format.mime_type())],
614 image_data,
615 )
616 .into_response())
617}
618
619fn parse_format(format_str: &str) -> Result<ImageFormat, WmsError> {
621 match format_str.to_lowercase().as_str() {
622 "image/png" => Ok(ImageFormat::Png),
623 "image/jpeg" | "image/jpg" => Ok(ImageFormat::Jpeg),
624 "image/webp" => Ok(ImageFormat::Webp),
625 _ => Err(WmsError::UnsupportedFormat(format_str.to_string())),
626 }
627}
628
629fn parse_bbox(bbox_str: &str) -> Result<(f64, f64, f64, f64), WmsError> {
631 let parts: Vec<&str> = bbox_str.split(',').collect();
632 if parts.len() != 4 {
633 return Err(WmsError::InvalidBbox("BBOX must have 4 values".to_string()));
634 }
635
636 let min_x = parts[0]
637 .parse::<f64>()
638 .map_err(|_| WmsError::InvalidBbox(format!("Invalid minx: {}", parts[0])))?;
639 let min_y = parts[1]
640 .parse::<f64>()
641 .map_err(|_| WmsError::InvalidBbox(format!("Invalid miny: {}", parts[1])))?;
642 let max_x = parts[2]
643 .parse::<f64>()
644 .map_err(|_| WmsError::InvalidBbox(format!("Invalid maxx: {}", parts[2])))?;
645 let max_y = parts[3]
646 .parse::<f64>()
647 .map_err(|_| WmsError::InvalidBbox(format!("Invalid maxy: {}", parts[3])))?;
648
649 if min_x >= max_x || min_y >= max_y {
650 return Err(WmsError::InvalidBbox(
651 "Invalid bbox: min must be < max".to_string(),
652 ));
653 }
654
655 Ok((min_x, min_y, max_x, max_y))
656}
657
658fn render_map(
667 dataset: &Dataset,
668 bbox: (f64, f64, f64, f64),
669 width: u32,
670 height: u32,
671 format: ImageFormat,
672 transparent: bool,
673 style: Option<&crate::config::StyleConfig>,
674) -> Result<Bytes, WmsError> {
675 let (req_min_x, req_min_y, req_max_x, req_max_y) = bbox;
676
677 debug!(
678 "Rendering map: bbox=({},{},{},{}), size={}x{}, format={:?}",
679 req_min_x, req_min_y, req_max_x, req_max_y, width, height, format
680 );
681
682 let geo_transform = dataset.geo_transform_obj().ok_or_else(|| {
684 WmsError::Rendering("Dataset has no geotransform - cannot map coordinates".to_string())
685 })?;
686
687 let ds_width = dataset.width();
688 let ds_height = dataset.height();
689 let band_count = dataset.raster_count();
690
691 let (px_min_x, px_min_y) = geo_transform
693 .world_to_pixel(req_min_x, req_max_y)
694 .map_err(|e| WmsError::Rendering(format!("Coordinate transform error: {}", e)))?;
695 let (px_max_x, px_max_y) = geo_transform
696 .world_to_pixel(req_max_x, req_min_y)
697 .map_err(|e| WmsError::Rendering(format!("Coordinate transform error: {}", e)))?;
698
699 let src_x = (px_min_x.min(px_max_x).floor().max(0.0)) as u64;
701 let src_y = (px_min_y.min(px_max_y).floor().max(0.0)) as u64;
702 let src_end_x = (px_min_x.max(px_max_x).ceil().max(0.0) as u64).min(ds_width);
703 let src_end_y = (px_min_y.max(px_max_y).ceil().max(0.0) as u64).min(ds_height);
704
705 let src_width = if src_end_x > src_x {
706 src_end_x - src_x
707 } else {
708 1
709 };
710 let src_height = if src_end_y > src_y {
711 src_end_y - src_y
712 } else {
713 1
714 };
715
716 debug!(
717 "Source window: offset=({}, {}), size={}x{}, dataset={}x{}, bands={}",
718 src_x, src_y, src_width, src_height, ds_width, ds_height, band_count
719 );
720
721 let overview_level =
723 select_overview_level(dataset, src_width, src_height, width as u64, height as u64);
724
725 let render_style = if let Some(style_cfg) = style {
727 RenderStyle::from_config(style_cfg)
728 } else {
729 let mut s = RenderStyle::default();
730 if !transparent {
731 s.alpha = 1.0;
732 } else {
733 s.alpha = 1.0; }
735 s
736 };
737
738 let rgba_data = if band_count >= 3 {
740 render_rgb_bands(
742 dataset,
743 overview_level,
744 SourceRegion::new(src_x, src_y, src_width, src_height),
745 TargetDimensions::new(width as u64, height as u64),
746 &render_style,
747 )?
748 } else {
749 render_single_band(
751 dataset,
752 overview_level,
753 SourceRegion::new(src_x, src_y, src_width, src_height),
754 TargetDimensions::new(width as u64, height as u64),
755 &render_style,
756 )?
757 };
758
759 let final_rgba = if !transparent {
762 let mut data = rgba_data;
763 for chunk in data.chunks_exact_mut(4) {
765 if chunk[3] > 0 {
766 chunk[3] = 255;
767 }
768 }
769 data
770 } else {
771 rgba_data
772 };
773
774 encode_image(&final_rgba, width, height, format).map_err(|e| WmsError::Rendering(e.to_string()))
776}
777
778fn select_overview_level(
784 dataset: &Dataset,
785 src_width: u64,
786 src_height: u64,
787 target_width: u64,
788 target_height: u64,
789) -> usize {
790 let overview_count = dataset.overview_count();
791 if overview_count == 0 {
792 return 0;
793 }
794
795 let ratio_x = if target_width > 0 {
797 src_width as f64 / target_width as f64
798 } else {
799 1.0
800 };
801 let ratio_y = if target_height > 0 {
802 src_height as f64 / target_height as f64
803 } else {
804 1.0
805 };
806 let request_ratio = ratio_x.max(ratio_y);
807
808 if request_ratio <= 1.0 {
809 return 0;
811 }
812
813 let mut best_level = 0;
816 for level in 1..=overview_count {
817 let overview_factor = (1u64 << level) as f64;
818 if overview_factor <= request_ratio * 1.5 {
819 best_level = level;
821 } else {
822 break;
823 }
824 }
825
826 best_level
827}
828
829fn render_single_band(
831 dataset: &Dataset,
832 _overview_level: usize,
833 source: SourceRegion,
834 target: TargetDimensions,
835 style: &RenderStyle,
836) -> Result<Vec<u8>, WmsError> {
837 let src_buffer = dataset
839 .read_window(source.x, source.y, source.width, source.height)
840 .map_err(|e| WmsError::Rendering(format!("Failed to read window: {}", e)))?;
841
842 let resampled = if src_buffer.width() != target.width || src_buffer.height() != target.height {
844 RasterRenderer::resample(&src_buffer, target.width, target.height, style.resampling)
845 .map_err(|e| WmsError::Rendering(format!("Resampling failed: {}", e)))?
846 } else {
847 src_buffer
848 };
849
850 RasterRenderer::render_to_rgba(&resampled, style)
852 .map_err(|e| WmsError::Rendering(format!("Rendering failed: {}", e)))
853}
854
855fn render_rgb_bands(
857 dataset: &Dataset,
858 _overview_level: usize,
859 source: SourceRegion,
860 target: TargetDimensions,
861 style: &RenderStyle,
862) -> Result<Vec<u8>, WmsError> {
863 let band_0 = dataset
866 .read_window(source.x, source.y, source.width, source.height)
867 .map_err(|e| WmsError::Rendering(format!("Failed to read red band: {}", e)))?;
868
869 let green_buffer =
877 build_band_window_from_full(dataset, 1, source.x, source.y, source.width, source.height);
878 let blue_buffer =
879 build_band_window_from_full(dataset, 2, source.x, source.y, source.width, source.height);
880
881 let (green_buf, blue_buf) = match (green_buffer, blue_buffer) {
882 (Ok(g), Ok(b)) => (g, b),
883 _ => {
884 let gray = band_0.clone();
886 (gray.clone(), gray)
887 }
888 };
889
890 let resample_method = style.resampling;
892 let r_resampled = if band_0.width() != target.width || band_0.height() != target.height {
893 RasterRenderer::resample(&band_0, target.width, target.height, resample_method)
894 .map_err(|e| WmsError::Rendering(format!("Red resample failed: {}", e)))?
895 } else {
896 band_0
897 };
898 let g_resampled = if green_buf.width() != target.width || green_buf.height() != target.height {
899 RasterRenderer::resample(&green_buf, target.width, target.height, resample_method)
900 .map_err(|e| WmsError::Rendering(format!("Green resample failed: {}", e)))?
901 } else {
902 green_buf
903 };
904 let b_resampled = if blue_buf.width() != target.width || blue_buf.height() != target.height {
905 RasterRenderer::resample(&blue_buf, target.width, target.height, resample_method)
906 .map_err(|e| WmsError::Rendering(format!("Blue resample failed: {}", e)))?
907 } else {
908 blue_buf
909 };
910
911 RasterRenderer::render_rgb_to_rgba(&r_resampled, &g_resampled, &b_resampled, style)
913 .map_err(|e| WmsError::Rendering(format!("RGB rendering failed: {}", e)))
914}
915
916fn build_band_window_from_full(
920 dataset: &Dataset,
921 band: usize,
922 src_x: u64,
923 src_y: u64,
924 src_width: u64,
925 src_height: u64,
926) -> Result<RasterBuffer, WmsError> {
927 let band_data = dataset
928 .read_band(0, band)
929 .map_err(|e| WmsError::Rendering(format!("Failed to read band {}: {}", band, e)))?;
930
931 let ds_width = dataset.width();
932 let ds_height = dataset.height();
933 let data_type = dataset.data_type();
934 let nodata = dataset.nodata();
935
936 let full_buffer = RasterBuffer::new(band_data, ds_width, ds_height, data_type, nodata)
937 .map_err(|e| WmsError::Rendering(format!("Buffer creation error: {}", e)))?;
938
939 let mut window = RasterBuffer::zeros(src_width, src_height, data_type);
941 for dy in 0..src_height {
942 for dx in 0..src_width {
943 let gx = src_x + dx;
944 let gy = src_y + dy;
945 if gx < ds_width && gy < ds_height {
946 if let Ok(val) = full_buffer.get_pixel(gx, gy) {
947 let _ = window.set_pixel(dx, dy, val);
948 }
949 }
950 }
951 }
952
953 Ok(window)
954}
955
956pub async fn get_feature_info(
963 State(state): State<Arc<WmsState>>,
964 Query(params): Query<GetFeatureInfoParams>,
965) -> Result<Response, WmsError> {
966 debug!("WMS GetFeatureInfo request");
967
968 let layer_name =
970 params.query_layers.split(',').next().ok_or_else(|| {
971 WmsError::InvalidParameter("QUERY_LAYERS parameter is empty".to_string())
972 })?;
973
974 let screen_x = params
976 .i
977 .or(params.x)
978 .ok_or_else(|| WmsError::MissingParameter("I or X parameter required".to_string()))?;
979
980 let screen_y = params
981 .j
982 .or(params.y)
983 .ok_or_else(|| WmsError::MissingParameter("J or Y parameter required".to_string()))?;
984
985 if screen_x >= params.width || screen_y >= params.height {
987 return Err(WmsError::InvalidParameter(format!(
988 "Query point ({}, {}) is outside image dimensions ({}x{})",
989 screen_x, screen_y, params.width, params.height
990 )));
991 }
992
993 let bbox = parse_bbox(¶ms.bbox)?;
995
996 let layer = state.registry.get_layer(layer_name)?;
998 let dataset = state.registry.get_dataset(layer_name)?;
999
1000 let info = query_pixel_info(
1002 &dataset,
1003 &layer,
1004 bbox,
1005 params.width,
1006 params.height,
1007 screen_x,
1008 screen_y,
1009 )?;
1010
1011 let info_format = params.info_format.as_deref().unwrap_or("text/plain");
1013
1014 let response_text = match info_format {
1015 "application/json" | "text/json" => {
1016 format_feature_info_json(layer_name, screen_x, screen_y, &info)
1017 }
1018 "text/xml" | "application/xml" | "application/vnd.ogc.gml" => {
1019 format_feature_info_xml(layer_name, screen_x, screen_y, &info)
1020 }
1021 "text/html" => format_feature_info_html(layer_name, screen_x, screen_y, &info),
1022 _ => {
1023 format_feature_info_text(layer_name, screen_x, screen_y, &info)
1025 }
1026 };
1027
1028 let content_type = match info_format {
1029 "application/json" | "text/json" => "application/json",
1030 "text/xml" | "application/xml" | "application/vnd.ogc.gml" => "application/xml",
1031 "text/html" => "text/html",
1032 _ => "text/plain",
1033 };
1034
1035 Ok((
1036 StatusCode::OK,
1037 [(header::CONTENT_TYPE, content_type)],
1038 response_text,
1039 )
1040 .into_response())
1041}
1042
1043struct PixelQueryResult {
1045 world_x: f64,
1047 world_y: f64,
1049 pixel_x: u64,
1051 pixel_y: u64,
1053 band_values: Vec<(usize, f64, bool)>,
1055 data_type: String,
1057}
1058
1059fn query_pixel_info(
1065 dataset: &Dataset,
1066 _layer: &LayerInfo,
1067 bbox: (f64, f64, f64, f64),
1068 screen_width: u32,
1069 screen_height: u32,
1070 screen_x: u32,
1071 screen_y: u32,
1072) -> Result<PixelQueryResult, WmsError> {
1073 let (req_min_x, req_min_y, req_max_x, req_max_y) = bbox;
1074
1075 let world_x = req_min_x + (screen_x as f64 / screen_width as f64) * (req_max_x - req_min_x);
1078 let world_y = req_max_y - (screen_y as f64 / screen_height as f64) * (req_max_y - req_min_y);
1079
1080 debug!(
1081 "GetFeatureInfo: screen=({}, {}), world=({}, {})",
1082 screen_x, screen_y, world_x, world_y
1083 );
1084
1085 let geo_transform = dataset
1087 .geo_transform_obj()
1088 .ok_or_else(|| WmsError::Rendering("Dataset has no geotransform".to_string()))?;
1089
1090 let (px_f, py_f) = geo_transform
1091 .world_to_pixel(world_x, world_y)
1092 .map_err(|e| WmsError::Rendering(format!("Coordinate transform error: {}", e)))?;
1093
1094 let pixel_x = px_f.floor() as i64;
1095 let pixel_y = py_f.floor() as i64;
1096
1097 let ds_width = dataset.width() as i64;
1099 let ds_height = dataset.height() as i64;
1100
1101 if pixel_x < 0 || pixel_y < 0 || pixel_x >= ds_width || pixel_y >= ds_height {
1102 return Ok(PixelQueryResult {
1104 world_x,
1105 world_y,
1106 pixel_x: pixel_x.max(0) as u64,
1107 pixel_y: pixel_y.max(0) as u64,
1108 band_values: Vec::new(),
1109 data_type: format!("{:?}", dataset.data_type()),
1110 });
1111 }
1112
1113 let px = pixel_x as u64;
1114 let py = pixel_y as u64;
1115
1116 let band_count = dataset.raster_count();
1118 let nodata = dataset.nodata();
1119 let mut band_values = Vec::with_capacity(band_count);
1120
1121 for band_idx in 0..band_count {
1122 match dataset.get_pixel(px, py) {
1124 Ok(value) => {
1125 let is_nodata = nodata
1126 .as_f64()
1127 .is_some_and(|nd| (value - nd).abs() < f64::EPSILON);
1128 band_values.push((band_idx + 1, value, is_nodata));
1129 }
1130 Err(e) => {
1131 debug!(
1132 "Failed to read pixel at ({}, {}) band {}: {}",
1133 px, py, band_idx, e
1134 );
1135 band_values.push((band_idx + 1, f64::NAN, true));
1136 }
1137 }
1138 }
1139
1140 Ok(PixelQueryResult {
1141 world_x,
1142 world_y,
1143 pixel_x: px,
1144 pixel_y: py,
1145 band_values,
1146 data_type: format!("{:?}", dataset.data_type()),
1147 })
1148}
1149
1150fn format_feature_info_text(
1152 layer_name: &str,
1153 screen_x: u32,
1154 screen_y: u32,
1155 result: &PixelQueryResult,
1156) -> String {
1157 let mut text = format!(
1158 "Layer: {}\nQuery Point: ({}, {})\nWorld Coordinates: ({:.6}, {:.6})\nPixel Coordinates: ({}, {})\nData Type: {}\n",
1159 layer_name,
1160 screen_x,
1161 screen_y,
1162 result.world_x,
1163 result.world_y,
1164 result.pixel_x,
1165 result.pixel_y,
1166 result.data_type
1167 );
1168
1169 if result.band_values.is_empty() {
1170 text.push_str("Values: (outside dataset bounds)\n");
1171 } else {
1172 text.push_str("Band Values:\n");
1173 for (band, value, is_nodata) in &result.band_values {
1174 if *is_nodata {
1175 text.push_str(&format!(" Band {}: NoData\n", band));
1176 } else {
1177 text.push_str(&format!(" Band {}: {:.6}\n", band, value));
1178 }
1179 }
1180 }
1181
1182 text
1183}
1184
1185fn format_feature_info_json(
1187 layer_name: &str,
1188 screen_x: u32,
1189 screen_y: u32,
1190 result: &PixelQueryResult,
1191) -> String {
1192 let mut bands_json = String::from("[");
1193 for (i, (band, value, is_nodata)) in result.band_values.iter().enumerate() {
1194 if i > 0 {
1195 bands_json.push(',');
1196 }
1197 if *is_nodata {
1198 bands_json.push_str(&format!(
1199 r#"{{"band":{},"value":null,"nodata":true}}"#,
1200 band
1201 ));
1202 } else {
1203 bands_json.push_str(&format!(
1204 r#"{{"band":{},"value":{},"nodata":false}}"#,
1205 band, value
1206 ));
1207 }
1208 }
1209 bands_json.push(']');
1210
1211 format!(
1212 r#"{{"type":"FeatureInfo","layer":"{}","query_point":{{"x":{},"y":{}}},"world_coords":{{"x":{},"y":{}}},"pixel_coords":{{"x":{},"y":{}}},"data_type":"{}","bands":{}}}"#,
1213 layer_name,
1214 screen_x,
1215 screen_y,
1216 result.world_x,
1217 result.world_y,
1218 result.pixel_x,
1219 result.pixel_y,
1220 result.data_type,
1221 bands_json
1222 )
1223}
1224
1225fn format_feature_info_xml(
1227 layer_name: &str,
1228 screen_x: u32,
1229 screen_y: u32,
1230 result: &PixelQueryResult,
1231) -> String {
1232 let mut xml = String::from(r#"<?xml version="1.0" encoding="UTF-8"?>"#);
1233 xml.push('\n');
1234 xml.push_str("<FeatureInfoResponse>\n");
1235 xml.push_str(&format!(" <Layer name=\"{}\">\n", layer_name));
1236 xml.push_str(&format!(
1237 " <QueryPoint x=\"{}\" y=\"{}\"/>\n",
1238 screen_x, screen_y
1239 ));
1240 xml.push_str(&format!(
1241 " <WorldCoords x=\"{:.6}\" y=\"{:.6}\"/>\n",
1242 result.world_x, result.world_y
1243 ));
1244 xml.push_str(&format!(
1245 " <PixelCoords x=\"{}\" y=\"{}\"/>\n",
1246 result.pixel_x, result.pixel_y
1247 ));
1248
1249 for (band, value, is_nodata) in &result.band_values {
1250 if *is_nodata {
1251 xml.push_str(&format!(" <Band index=\"{}\" nodata=\"true\"/>\n", band));
1252 } else {
1253 xml.push_str(&format!(
1254 " <Band index=\"{}\">{:.6}</Band>\n",
1255 band, value
1256 ));
1257 }
1258 }
1259
1260 xml.push_str(" </Layer>\n");
1261 xml.push_str("</FeatureInfoResponse>");
1262
1263 xml
1264}
1265
1266fn format_feature_info_html(
1268 layer_name: &str,
1269 screen_x: u32,
1270 screen_y: u32,
1271 result: &PixelQueryResult,
1272) -> String {
1273 let mut html =
1274 String::from("<!DOCTYPE html>\n<html>\n<head><title>Feature Info</title></head>\n<body>\n");
1275 html.push_str(&format!("<h3>Layer: {}</h3>\n", layer_name));
1276 html.push_str("<table border=\"1\">\n");
1277 html.push_str(&format!(
1278 "<tr><td>Query Point</td><td>({}, {})</td></tr>\n",
1279 screen_x, screen_y
1280 ));
1281 html.push_str(&format!(
1282 "<tr><td>World Coordinates</td><td>({:.6}, {:.6})</td></tr>\n",
1283 result.world_x, result.world_y
1284 ));
1285 html.push_str(&format!(
1286 "<tr><td>Pixel Coordinates</td><td>({}, {})</td></tr>\n",
1287 result.pixel_x, result.pixel_y
1288 ));
1289
1290 for (band, value, is_nodata) in &result.band_values {
1291 if *is_nodata {
1292 html.push_str(&format!("<tr><td>Band {}</td><td>NoData</td></tr>\n", band));
1293 } else {
1294 html.push_str(&format!(
1295 "<tr><td>Band {}</td><td>{:.6}</td></tr>\n",
1296 band, value
1297 ));
1298 }
1299 }
1300
1301 html.push_str("</table>\n</body>\n</html>");
1302
1303 html
1304}