1use crate::cache::{CacheKey, TileCache};
8use crate::config::ImageFormat;
9use crate::dataset_registry::{DatasetRegistry, LayerInfo};
10use crate::handlers::rendering::{RasterRenderer, RenderStyle, encode_image, tile_to_bbox};
11use axum::{
12 extract::{Path as AxumPath, Query, State},
13 http::{StatusCode, header},
14 response::{IntoResponse, Response},
15};
16use bytes::Bytes;
17use oxigdal_algorithms::resampling::ResamplingMethod;
18use oxigdal_core::buffer::RasterBuffer;
19use quick_xml::Writer;
20use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
21use serde::Deserialize;
22use std::io::Cursor;
23use std::sync::Arc;
24use thiserror::Error;
25use tracing::{debug, trace};
26
27#[derive(Debug, Clone, Copy)]
29struct SourceRegion {
30 x: u64,
31 y: u64,
32 width: u64,
33 height: u64,
34}
35
36impl SourceRegion {
37 fn new(x: u64, y: u64, width: u64, height: u64) -> Self {
38 Self {
39 x,
40 y,
41 width,
42 height,
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy)]
49struct TileDimensions {
50 width: u64,
51 height: u64,
52}
53
54impl TileDimensions {
55 fn new(width: u64, height: u64) -> Self {
56 Self { width, height }
57 }
58}
59
60#[derive(Debug, Error)]
62pub enum WmtsError {
63 #[error("Invalid parameter: {0}")]
65 InvalidParameter(String),
66
67 #[error("Missing required parameter: {0}")]
69 MissingParameter(String),
70
71 #[error("Layer not found: {0}")]
73 LayerNotFound(String),
74
75 #[error("TileMatrixSet not found: {0}")]
77 TileMatrixSetNotFound(String),
78
79 #[error("Tile coordinates out of bounds")]
81 TileOutOfBounds,
82
83 #[error("Rendering error: {0}")]
85 Rendering(String),
86
87 #[error("Registry error: {0}")]
89 Registry(#[from] crate::dataset_registry::RegistryError),
90
91 #[error("Unsupported format: {0}")]
93 UnsupportedFormat(String),
94}
95
96impl IntoResponse for WmtsError {
97 fn into_response(self) -> Response {
98 let (status, message) = match self {
99 WmtsError::InvalidParameter(_) | WmtsError::MissingParameter(_) => {
100 (StatusCode::BAD_REQUEST, self.to_string())
101 }
102 WmtsError::LayerNotFound(_) | WmtsError::TileOutOfBounds => {
103 (StatusCode::NOT_FOUND, self.to_string())
104 }
105 _ => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
106 };
107
108 let xml = format!(
109 r#"<?xml version="1.0" encoding="UTF-8"?>
110<ExceptionReport version="1.0.0" xmlns="http://www.opengis.net/ows/1.1">
111 <Exception exceptionCode="{}">{}</Exception>
112</ExceptionReport>"#,
113 status.as_u16(),
114 message
115 );
116
117 (status, [(header::CONTENT_TYPE, "application/xml")], xml).into_response()
118 }
119}
120
121#[derive(Clone)]
123pub struct WmtsState {
124 pub registry: DatasetRegistry,
126
127 pub cache: TileCache,
129
130 pub service_url: String,
132
133 pub service_title: String,
135 pub service_abstract: String,
137}
138
139#[derive(Debug, Clone)]
141pub struct TileMatrixSet {
142 pub identifier: String,
144
145 pub crs: String,
147
148 pub matrices: Vec<TileMatrix>,
150}
151
152#[derive(Debug, Clone)]
154pub struct TileMatrix {
155 pub identifier: String,
157
158 pub scale_denominator: f64,
160
161 pub top_left_corner: (f64, f64),
163
164 pub tile_width: u32,
166
167 pub tile_height: u32,
169
170 pub matrix_width: u32,
172
173 pub matrix_height: u32,
175}
176
177impl TileMatrixSet {
178 pub fn web_mercator_quad() -> Self {
180 let mut matrices = Vec::new();
181
182 for z in 0..=18 {
183 let tiles_at_zoom = 1u32 << z;
184 let scale_denominator = 559082264.0287178 / (1u64 << z) as f64;
185
186 matrices.push(TileMatrix {
187 identifier: z.to_string(),
188 scale_denominator,
189 top_left_corner: (-20037508.34278925, 20037508.34278925),
190 tile_width: 256,
191 tile_height: 256,
192 matrix_width: tiles_at_zoom,
193 matrix_height: tiles_at_zoom,
194 });
195 }
196
197 Self {
198 identifier: "WebMercatorQuad".to_string(),
199 crs: "urn:ogc:def:crs:EPSG::3857".to_string(),
200 matrices,
201 }
202 }
203
204 pub fn world_crs84_quad() -> Self {
206 let mut matrices = Vec::new();
207
208 for z in 0..=18 {
209 let tiles_x = 2u32 << z;
210 let tiles_y = 1u32 << z;
211 let scale_denominator = 279541132.0143589 / (1u64 << z) as f64;
212
213 matrices.push(TileMatrix {
214 identifier: z.to_string(),
215 scale_denominator,
216 top_left_corner: (-180.0, 90.0),
217 tile_width: 256,
218 tile_height: 256,
219 matrix_width: tiles_x,
220 matrix_height: tiles_y,
221 });
222 }
223
224 Self {
225 identifier: "WorldCRS84Quad".to_string(),
226 crs: "urn:ogc:def:crs:OGC:1.3:CRS84".to_string(),
227 matrices,
228 }
229 }
230
231 pub fn get_matrix(&self, identifier: &str) -> Option<&TileMatrix> {
233 self.matrices.iter().find(|m| m.identifier == identifier)
234 }
235}
236
237#[derive(Debug, Deserialize)]
239#[allow(dead_code)]
240pub struct GetCapabilitiesParams {
241 #[serde(rename = "SERVICE")]
242 service: Option<String>,
243
244 #[serde(rename = "REQUEST")]
245 request: Option<String>,
246
247 #[serde(rename = "VERSION")]
248 version: Option<String>,
249}
250
251#[derive(Debug, Deserialize)]
253#[allow(dead_code)]
254pub struct GetTileParams {
255 #[serde(rename = "SERVICE")]
256 service: Option<String>,
257
258 #[serde(rename = "REQUEST")]
259 request: Option<String>,
260
261 #[serde(rename = "VERSION")]
262 version: Option<String>,
263
264 #[serde(rename = "LAYER")]
265 layer: String,
266
267 #[serde(rename = "STYLE")]
268 style: Option<String>,
269
270 #[serde(rename = "FORMAT")]
271 format: String,
272
273 #[serde(rename = "TILEMATRIXSET")]
274 tile_matrix_set: String,
275
276 #[serde(rename = "TILEMATRIX")]
277 tile_matrix: String,
278
279 #[serde(rename = "TILEROW")]
280 tile_row: u32,
281
282 #[serde(rename = "TILECOL")]
283 tile_col: u32,
284}
285
286#[derive(Debug, Deserialize)]
288pub struct GetTilePath {
289 layer: String,
290 tile_matrix_set: String,
291 tile_matrix: String,
292 tile_row: u32,
293 tile_col: u32,
294}
295
296pub async fn get_capabilities(
298 State(state): State<Arc<WmtsState>>,
299 Query(params): Query<GetCapabilitiesParams>,
300) -> Result<Response, WmtsError> {
301 debug!("WMTS GetCapabilities request");
302
303 if let Some(ref service) = params.service {
305 if service.to_uppercase() != "WMTS" {
306 return Err(WmtsError::InvalidParameter(format!(
307 "Invalid SERVICE: {}",
308 service
309 )));
310 }
311 }
312
313 let layers = state.registry.list_layers()?;
315
316 let matrix_sets = vec![
318 TileMatrixSet::web_mercator_quad(),
319 TileMatrixSet::world_crs84_quad(),
320 ];
321
322 let xml = generate_capabilities_xml(&state, &layers, &matrix_sets)?;
324
325 Ok((
326 StatusCode::OK,
327 [(header::CONTENT_TYPE, "application/xml")],
328 xml,
329 )
330 .into_response())
331}
332
333fn generate_capabilities_xml(
335 state: &WmtsState,
336 layers: &[LayerInfo],
337 matrix_sets: &[TileMatrixSet],
338) -> Result<String, WmtsError> {
339 let mut writer = Writer::new(Cursor::new(Vec::new()));
340
341 writer
343 .write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
344 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
345
346 let mut root = BytesStart::new("Capabilities");
348 root.push_attribute(("version", "1.0.0"));
349 root.push_attribute(("xmlns", "http://www.opengis.net/wmts/1.0"));
350 root.push_attribute(("xmlns:ows", "http://www.opengis.net/ows/1.1"));
351 root.push_attribute(("xmlns:xlink", "http://www.w3.org/1999/xlink"));
352 writer
353 .write_event(Event::Start(root))
354 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
355
356 write_service_identification(&mut writer, state)?;
358
359 write_contents(&mut writer, state, layers, matrix_sets)?;
361
362 writer
364 .write_event(Event::End(BytesEnd::new("Capabilities")))
365 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
366
367 let result = writer.into_inner().into_inner();
368 String::from_utf8(result).map_err(|e| WmtsError::Rendering(e.to_string()))
369}
370
371fn write_service_identification(
373 writer: &mut Writer<Cursor<Vec<u8>>>,
374 state: &WmtsState,
375) -> Result<(), WmtsError> {
376 writer
377 .write_event(Event::Start(BytesStart::new("ows:ServiceIdentification")))
378 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
379
380 writer
382 .write_event(Event::Start(BytesStart::new("ows:Title")))
383 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
384 writer
385 .write_event(Event::Text(BytesText::new(&state.service_title)))
386 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
387 writer
388 .write_event(Event::End(BytesEnd::new("ows:Title")))
389 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
390
391 writer
393 .write_event(Event::Start(BytesStart::new("ows:Abstract")))
394 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
395 writer
396 .write_event(Event::Text(BytesText::new(&state.service_abstract)))
397 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
398 writer
399 .write_event(Event::End(BytesEnd::new("ows:Abstract")))
400 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
401
402 writer
404 .write_event(Event::Start(BytesStart::new("ows:ServiceType")))
405 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
406 writer
407 .write_event(Event::Text(BytesText::new("OGC WMTS")))
408 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
409 writer
410 .write_event(Event::End(BytesEnd::new("ows:ServiceType")))
411 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
412
413 writer
415 .write_event(Event::Start(BytesStart::new("ows:ServiceTypeVersion")))
416 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
417 writer
418 .write_event(Event::Text(BytesText::new("1.0.0")))
419 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
420 writer
421 .write_event(Event::End(BytesEnd::new("ows:ServiceTypeVersion")))
422 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
423
424 writer
425 .write_event(Event::End(BytesEnd::new("ows:ServiceIdentification")))
426 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
427
428 Ok(())
429}
430
431fn write_contents(
433 writer: &mut Writer<Cursor<Vec<u8>>>,
434 state: &WmtsState,
435 layers: &[LayerInfo],
436 matrix_sets: &[TileMatrixSet],
437) -> Result<(), WmtsError> {
438 writer
439 .write_event(Event::Start(BytesStart::new("Contents")))
440 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
441
442 for layer in layers {
444 write_layer(writer, state, layer, matrix_sets)?;
445 }
446
447 for matrix_set in matrix_sets {
449 write_tile_matrix_set(writer, matrix_set)?;
450 }
451
452 writer
453 .write_event(Event::End(BytesEnd::new("Contents")))
454 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
455
456 Ok(())
457}
458
459fn write_layer(
461 writer: &mut Writer<Cursor<Vec<u8>>>,
462 state: &WmtsState,
463 layer: &LayerInfo,
464 matrix_sets: &[TileMatrixSet],
465) -> Result<(), WmtsError> {
466 writer
467 .write_event(Event::Start(BytesStart::new("Layer")))
468 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
469
470 writer
472 .write_event(Event::Start(BytesStart::new("ows:Identifier")))
473 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
474 writer
475 .write_event(Event::Text(BytesText::new(&layer.name)))
476 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
477 writer
478 .write_event(Event::End(BytesEnd::new("ows:Identifier")))
479 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
480
481 writer
483 .write_event(Event::Start(BytesStart::new("ows:Title")))
484 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
485 writer
486 .write_event(Event::Text(BytesText::new(&layer.title)))
487 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
488 writer
489 .write_event(Event::End(BytesEnd::new("ows:Title")))
490 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
491
492 for format in &layer.config.formats {
494 writer
495 .write_event(Event::Start(BytesStart::new("Format")))
496 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
497 writer
498 .write_event(Event::Text(BytesText::new(format.mime_type())))
499 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
500 writer
501 .write_event(Event::End(BytesEnd::new("Format")))
502 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
503 }
504
505 for matrix_set in matrix_sets {
507 if layer
508 .config
509 .tile_matrix_sets
510 .contains(&matrix_set.identifier)
511 {
512 writer
513 .write_event(Event::Start(BytesStart::new("TileMatrixSetLink")))
514 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
515
516 writer
517 .write_event(Event::Start(BytesStart::new("TileMatrixSet")))
518 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
519 writer
520 .write_event(Event::Text(BytesText::new(&matrix_set.identifier)))
521 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
522 writer
523 .write_event(Event::End(BytesEnd::new("TileMatrixSet")))
524 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
525
526 writer
527 .write_event(Event::End(BytesEnd::new("TileMatrixSetLink")))
528 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
529 }
530 }
531
532 let mut resource_url = BytesStart::new("ResourceURL");
534 resource_url.push_attribute(("format", "image/png"));
535 resource_url.push_attribute(("resourceType", "tile"));
536 let template = format!(
537 "{}/wmts/1.0.0/{}/{{TileMatrixSet}}/{{TileMatrix}}/{{TileRow}}/{{TileCol}}.png",
538 state.service_url, layer.name
539 );
540 resource_url.push_attribute(("template", template.as_str()));
541 writer
542 .write_event(Event::Empty(resource_url))
543 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
544
545 writer
546 .write_event(Event::End(BytesEnd::new("Layer")))
547 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
548
549 Ok(())
550}
551
552fn write_tile_matrix_set(
554 writer: &mut Writer<Cursor<Vec<u8>>>,
555 matrix_set: &TileMatrixSet,
556) -> Result<(), WmtsError> {
557 writer
558 .write_event(Event::Start(BytesStart::new("TileMatrixSet")))
559 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
560
561 writer
563 .write_event(Event::Start(BytesStart::new("ows:Identifier")))
564 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
565 writer
566 .write_event(Event::Text(BytesText::new(&matrix_set.identifier)))
567 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
568 writer
569 .write_event(Event::End(BytesEnd::new("ows:Identifier")))
570 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
571
572 writer
574 .write_event(Event::Start(BytesStart::new("ows:SupportedCRS")))
575 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
576 writer
577 .write_event(Event::Text(BytesText::new(&matrix_set.crs)))
578 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
579 writer
580 .write_event(Event::End(BytesEnd::new("ows:SupportedCRS")))
581 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
582
583 for matrix in &matrix_set.matrices {
585 write_tile_matrix(writer, matrix)?;
586 }
587
588 writer
589 .write_event(Event::End(BytesEnd::new("TileMatrixSet")))
590 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
591
592 Ok(())
593}
594
595fn write_tile_matrix(
597 writer: &mut Writer<Cursor<Vec<u8>>>,
598 matrix: &TileMatrix,
599) -> Result<(), WmtsError> {
600 writer
601 .write_event(Event::Start(BytesStart::new("TileMatrix")))
602 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
603
604 writer
606 .write_event(Event::Start(BytesStart::new("ows:Identifier")))
607 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
608 writer
609 .write_event(Event::Text(BytesText::new(&matrix.identifier)))
610 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
611 writer
612 .write_event(Event::End(BytesEnd::new("ows:Identifier")))
613 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
614
615 writer
617 .write_event(Event::Start(BytesStart::new("ScaleDenominator")))
618 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
619 writer
620 .write_event(Event::Text(BytesText::new(
621 &matrix.scale_denominator.to_string(),
622 )))
623 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
624 writer
625 .write_event(Event::End(BytesEnd::new("ScaleDenominator")))
626 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
627
628 writer
630 .write_event(Event::Start(BytesStart::new("TopLeftCorner")))
631 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
632 let corner = format!("{} {}", matrix.top_left_corner.0, matrix.top_left_corner.1);
633 writer
634 .write_event(Event::Text(BytesText::new(&corner)))
635 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
636 writer
637 .write_event(Event::End(BytesEnd::new("TopLeftCorner")))
638 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
639
640 writer
642 .write_event(Event::Start(BytesStart::new("TileWidth")))
643 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
644 writer
645 .write_event(Event::Text(BytesText::new(&matrix.tile_width.to_string())))
646 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
647 writer
648 .write_event(Event::End(BytesEnd::new("TileWidth")))
649 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
650
651 writer
653 .write_event(Event::Start(BytesStart::new("TileHeight")))
654 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
655 writer
656 .write_event(Event::Text(BytesText::new(&matrix.tile_height.to_string())))
657 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
658 writer
659 .write_event(Event::End(BytesEnd::new("TileHeight")))
660 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
661
662 writer
664 .write_event(Event::Start(BytesStart::new("MatrixWidth")))
665 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
666 writer
667 .write_event(Event::Text(BytesText::new(
668 &matrix.matrix_width.to_string(),
669 )))
670 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
671 writer
672 .write_event(Event::End(BytesEnd::new("MatrixWidth")))
673 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
674
675 writer
677 .write_event(Event::Start(BytesStart::new("MatrixHeight")))
678 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
679 writer
680 .write_event(Event::Text(BytesText::new(
681 &matrix.matrix_height.to_string(),
682 )))
683 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
684 writer
685 .write_event(Event::End(BytesEnd::new("MatrixHeight")))
686 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
687
688 writer
689 .write_event(Event::End(BytesEnd::new("TileMatrix")))
690 .map_err(|e| WmtsError::Rendering(e.to_string()))?;
691
692 Ok(())
693}
694
695pub async fn get_tile_rest(
697 State(state): State<Arc<WmtsState>>,
698 AxumPath(path): AxumPath<GetTilePath>,
699) -> Result<Response, WmtsError> {
700 debug!(
701 "WMTS GetTile (REST): layer={}, z={}, x={}, y={}",
702 path.layer, path.tile_matrix, path.tile_col, path.tile_row
703 );
704
705 get_tile_impl(
706 &state,
707 &path.layer,
708 &path.tile_matrix_set,
709 &path.tile_matrix,
710 path.tile_row,
711 path.tile_col,
712 ImageFormat::Png,
713 )
714 .await
715}
716
717pub async fn get_tile_kvp(
719 State(state): State<Arc<WmtsState>>,
720 Query(params): Query<GetTileParams>,
721) -> Result<Response, WmtsError> {
722 debug!("WMTS GetTile (KVP): layer={}", params.layer);
723
724 let format = parse_format(¶ms.format)?;
725
726 get_tile_impl(
727 &state,
728 ¶ms.layer,
729 ¶ms.tile_matrix_set,
730 ¶ms.tile_matrix,
731 params.tile_row,
732 params.tile_col,
733 format,
734 )
735 .await
736}
737
738async fn get_tile_impl(
740 state: &WmtsState,
741 layer_name: &str,
742 tile_matrix_set: &str,
743 tile_matrix: &str,
744 tile_row: u32,
745 tile_col: u32,
746 format: ImageFormat,
747) -> Result<Response, WmtsError> {
748 let zoom_level = tile_matrix.parse::<u8>().ok().unwrap_or(0);
750 let cache_key = CacheKey::new(
751 layer_name.to_string(),
752 zoom_level,
753 tile_col,
754 tile_row,
755 format.extension().to_string(),
756 );
757
758 if let Some(cached_tile) = state.cache.get(&cache_key) {
759 trace!("Cache hit for tile: {}", cache_key.to_string());
760 return Ok((
761 StatusCode::OK,
762 [(header::CONTENT_TYPE, format.mime_type())],
763 cached_tile,
764 )
765 .into_response());
766 }
767
768 let _ = state.registry.get_layer(layer_name)?;
770
771 let matrix_set_obj = get_tile_matrix_set(tile_matrix_set)?;
773 let matrix = matrix_set_obj.get_matrix(tile_matrix).ok_or_else(|| {
774 WmtsError::InvalidParameter(format!("Invalid tile matrix: {}", tile_matrix))
775 })?;
776
777 if tile_col >= matrix.matrix_width || tile_row >= matrix.matrix_height {
779 return Err(WmtsError::TileOutOfBounds);
780 }
781
782 let tile_data = render_tile(
784 &state.registry,
785 layer_name,
786 &matrix_set_obj,
787 matrix,
788 tile_row,
789 tile_col,
790 format,
791 )
792 .await?;
793
794 let _ = state.cache.put(cache_key, tile_data.clone());
796
797 Ok((
798 StatusCode::OK,
799 [(header::CONTENT_TYPE, format.mime_type())],
800 tile_data,
801 )
802 .into_response())
803}
804
805fn get_tile_matrix_set(identifier: &str) -> Result<TileMatrixSet, WmtsError> {
807 match identifier {
808 "WebMercatorQuad" => Ok(TileMatrixSet::web_mercator_quad()),
809 "WorldCRS84Quad" => Ok(TileMatrixSet::world_crs84_quad()),
810 _ => Err(WmtsError::TileMatrixSetNotFound(identifier.to_string())),
811 }
812}
813
814fn parse_format(format_str: &str) -> Result<ImageFormat, WmtsError> {
816 match format_str.to_lowercase().as_str() {
817 "image/png" => Ok(ImageFormat::Png),
818 "image/jpeg" | "image/jpg" => Ok(ImageFormat::Jpeg),
819 "image/webp" => Ok(ImageFormat::Webp),
820 _ => Err(WmtsError::UnsupportedFormat(format_str.to_string())),
821 }
822}
823
824async fn render_tile(
834 registry: &DatasetRegistry,
835 layer_name: &str,
836 matrix_set: &TileMatrixSet,
837 matrix: &TileMatrix,
838 tile_row: u32,
839 tile_col: u32,
840 format: ImageFormat,
841) -> Result<Bytes, WmtsError> {
842 let zoom: u32 = matrix.identifier.parse().map_err(|_| {
843 WmtsError::InvalidParameter(format!(
844 "Cannot parse zoom level from matrix identifier: {}",
845 matrix.identifier
846 ))
847 })?;
848
849 debug!(
850 "Rendering tile: layer={}, z={}, col={}, row={}, matrix_set={}",
851 layer_name, zoom, tile_col, tile_row, matrix_set.identifier
852 );
853
854 let dataset = registry.get_dataset(layer_name).map_err(|e| {
856 WmtsError::Rendering(format!(
857 "Failed to get dataset for layer {}: {}",
858 layer_name, e
859 ))
860 })?;
861
862 let layer_info = registry
864 .get_layer(layer_name)
865 .map_err(|e| WmtsError::Rendering(format!("Failed to get layer info: {}", e)))?;
866
867 let tile_width = matrix.tile_width as u64;
868 let tile_height = matrix.tile_height as u64;
869
870 let tile_bbox = tile_to_bbox(&matrix_set.identifier, zoom, tile_col, tile_row)
872 .map_err(|e| WmtsError::Rendering(format!("Failed to calculate tile bbox: {}", e)))?;
873
874 let geo_transform = dataset
876 .geo_transform_obj()
877 .ok_or_else(|| WmtsError::Rendering("Dataset has no geotransform".to_string()))?;
878
879 let ds_width = dataset.width();
880 let ds_height = dataset.height();
881
882 let (px_min_x, px_min_y) = geo_transform
887 .world_to_pixel(tile_bbox.min_x, tile_bbox.max_y)
888 .map_err(|e| WmtsError::Rendering(format!("Coordinate transform error: {}", e)))?;
889 let (px_max_x, px_max_y) = geo_transform
890 .world_to_pixel(tile_bbox.max_x, tile_bbox.min_y)
891 .map_err(|e| WmtsError::Rendering(format!("Coordinate transform error: {}", e)))?;
892
893 let src_x = (px_min_x.min(px_max_x).floor().max(0.0)) as u64;
895 let src_y = (px_min_y.min(px_max_y).floor().max(0.0)) as u64;
896 let src_end_x = (px_min_x.max(px_max_x).ceil().max(0.0) as u64).min(ds_width);
897 let src_end_y = (px_min_y.max(px_max_y).ceil().max(0.0) as u64).min(ds_height);
898
899 let src_width = src_end_x.saturating_sub(src_x);
900 let src_height = src_end_y.saturating_sub(src_y);
901
902 if src_width == 0 || src_height == 0 {
904 return render_empty_tile(tile_width, tile_height, format);
906 }
907
908 let overview_level =
910 select_tile_overview_level(&dataset, src_width, src_height, tile_width, tile_height);
911
912 let render_style = if let Some(ref style_cfg) = layer_info.config.style {
914 RenderStyle::from_config(style_cfg)
915 } else {
916 RenderStyle::default()
917 };
918
919 let band_count = dataset.raster_count();
920
921 let rgba_data = if band_count >= 3 {
923 render_tile_rgb(
924 &dataset,
925 overview_level,
926 SourceRegion::new(src_x, src_y, src_width, src_height),
927 TileDimensions::new(tile_width, tile_height),
928 &render_style,
929 )?
930 } else {
931 render_tile_single_band(
932 &dataset,
933 overview_level,
934 SourceRegion::new(src_x, src_y, src_width, src_height),
935 TileDimensions::new(tile_width, tile_height),
936 &render_style,
937 )?
938 };
939
940 encode_image(&rgba_data, tile_width as u32, tile_height as u32, format)
946 .map_err(|e| WmtsError::Rendering(format!("Image encoding failed: {}", e)))
947}
948
949fn render_empty_tile(width: u64, height: u64, format: ImageFormat) -> Result<Bytes, WmtsError> {
951 let rgba = vec![0u8; (width * height * 4) as usize];
953 encode_image(&rgba, width as u32, height as u32, format)
954 .map_err(|e| WmtsError::Rendering(format!("Empty tile encoding failed: {}", e)))
955}
956
957fn select_tile_overview_level(
963 dataset: &crate::dataset_registry::Dataset,
964 src_width: u64,
965 src_height: u64,
966 tile_width: u64,
967 tile_height: u64,
968) -> usize {
969 let overview_count = dataset.overview_count();
970 if overview_count == 0 {
971 return 0;
972 }
973
974 let ratio_x = if tile_width > 0 {
976 src_width as f64 / tile_width as f64
977 } else {
978 1.0
979 };
980 let ratio_y = if tile_height > 0 {
981 src_height as f64 / tile_height as f64
982 } else {
983 1.0
984 };
985 let request_ratio = ratio_x.max(ratio_y);
986
987 if request_ratio <= 1.0 {
988 return 0; }
990
991 let mut best_level = 0;
993 for level in 1..=overview_count {
994 let overview_factor = (1u64 << level) as f64;
995 if overview_factor <= request_ratio * 1.5 {
996 best_level = level;
997 } else {
998 break;
999 }
1000 }
1001
1002 best_level
1003}
1004
1005fn render_tile_single_band(
1007 dataset: &crate::dataset_registry::Dataset,
1008 _overview_level: usize,
1009 source: SourceRegion,
1010 tile: TileDimensions,
1011 style: &RenderStyle,
1012) -> Result<Vec<u8>, WmtsError> {
1013 let src_buffer = dataset
1015 .read_window(source.x, source.y, source.width, source.height)
1016 .map_err(|e| WmtsError::Rendering(format!("Failed to read window: {}", e)))?;
1017
1018 let resampled = if src_buffer.width() != tile.width || src_buffer.height() != tile.height {
1020 RasterRenderer::resample(&src_buffer, tile.width, tile.height, style.resampling)
1021 .map_err(|e| WmtsError::Rendering(format!("Resampling failed: {}", e)))?
1022 } else {
1023 src_buffer
1024 };
1025
1026 RasterRenderer::render_to_rgba(&resampled, style)
1028 .map_err(|e| WmtsError::Rendering(format!("Rendering failed: {}", e)))
1029}
1030
1031fn render_tile_rgb(
1033 dataset: &crate::dataset_registry::Dataset,
1034 _overview_level: usize,
1035 source: SourceRegion,
1036 tile: TileDimensions,
1037 style: &RenderStyle,
1038) -> Result<Vec<u8>, WmtsError> {
1039 let red_buffer = dataset
1041 .read_window(source.x, source.y, source.width, source.height)
1042 .map_err(|e| WmtsError::Rendering(format!("Failed to read red band: {}", e)))?;
1043
1044 let green_buffer =
1046 build_band_window(dataset, 1, source.x, source.y, source.width, source.height);
1047 let blue_buffer =
1048 build_band_window(dataset, 2, source.x, source.y, source.width, source.height);
1049
1050 let (green_buf, blue_buf) = match (green_buffer, blue_buffer) {
1051 (Ok(g), Ok(b)) => (g, b),
1052 _ => {
1053 let gray = red_buffer.clone();
1055 (gray.clone(), gray)
1056 }
1057 };
1058
1059 let resample_method = style.resampling;
1061 let r_resampled = resample_if_needed(&red_buffer, tile.width, tile.height, resample_method)?;
1062 let g_resampled = resample_if_needed(&green_buf, tile.width, tile.height, resample_method)?;
1063 let b_resampled = resample_if_needed(&blue_buf, tile.width, tile.height, resample_method)?;
1064
1065 RasterRenderer::render_rgb_to_rgba(&r_resampled, &g_resampled, &b_resampled, style)
1067 .map_err(|e| WmtsError::Rendering(format!("RGB rendering failed: {}", e)))
1068}
1069
1070fn resample_if_needed(
1072 buffer: &RasterBuffer,
1073 target_width: u64,
1074 target_height: u64,
1075 method: ResamplingMethod,
1076) -> Result<RasterBuffer, WmtsError> {
1077 if buffer.width() != target_width || buffer.height() != target_height {
1078 RasterRenderer::resample(buffer, target_width, target_height, method)
1079 .map_err(|e| WmtsError::Rendering(format!("Resampling failed: {}", e)))
1080 } else {
1081 Ok(buffer.clone())
1082 }
1083}
1084
1085fn build_band_window(
1087 dataset: &crate::dataset_registry::Dataset,
1088 band: usize,
1089 src_x: u64,
1090 src_y: u64,
1091 src_width: u64,
1092 src_height: u64,
1093) -> Result<RasterBuffer, WmtsError> {
1094 let band_data = dataset
1095 .read_band(0, band)
1096 .map_err(|e| WmtsError::Rendering(format!("Failed to read band {}: {}", band, e)))?;
1097
1098 let ds_width = dataset.width();
1099 let ds_height = dataset.height();
1100 let data_type = dataset.data_type();
1101 let nodata = dataset.nodata();
1102
1103 let full_buffer = RasterBuffer::new(band_data, ds_width, ds_height, data_type, nodata)
1104 .map_err(|e| WmtsError::Rendering(format!("Buffer creation error: {}", e)))?;
1105
1106 let mut window = RasterBuffer::zeros(src_width, src_height, data_type);
1108 for dy in 0..src_height {
1109 for dx in 0..src_width {
1110 let gx = src_x + dx;
1111 let gy = src_y + dy;
1112 if gx < ds_width && gy < ds_height {
1113 if let Ok(val) = full_buffer.get_pixel(gx, gy) {
1114 let _ = window.set_pixel(dx, dy, val);
1115 }
1116 }
1117 }
1118 }
1119
1120 Ok(window)
1121}