bbox_tile_server/datasource/
wms_fcgi.rs

1//! FCGI tile sources like QGIS Server or UMN Mapsever.
2
3use crate::config::WmsFcgiSourceParamsCfg;
4use crate::datasource::{LayerInfo, SourceType, TileSource, TileSourceError};
5use crate::filter_params::FilterParams;
6use crate::service::{QueryExtent, TmsExtensions};
7use async_trait::async_trait;
8use bbox_core::service::OgcApiService;
9use bbox_core::{Format, TileResponse};
10use bbox_map_server::endpoints::wms_fcgi_req;
11pub use bbox_map_server::{
12    endpoints::FcgiError, endpoints::HttpRequestParams, metrics::WmsMetrics, MapService,
13};
14use once_cell::sync::OnceCell;
15use std::num::NonZeroU16;
16use tile_grid::{Tms, Xyz};
17use tilejson::{tilejson, TileJSON};
18
19#[derive(Clone)]
20pub struct WmsFcgiSource {
21    // Map service backend
22    map_service: Option<MapService>,
23    pub project: String,
24    pub suffix: String,
25    pub query: String,
26    pub tile_size: Option<NonZeroU16>,
27}
28
29impl WmsFcgiSource {
30    pub fn from_config(cfg: &WmsFcgiSourceParamsCfg) -> Self {
31        let project = cfg.project.clone();
32        let suffix = cfg.suffix.clone();
33        let query = format!(
34            "map={project}.{suffix}&SERVICE=WMS&REQUEST=GetMap&VERSION=1.3&LAYERS={}&STYLES=&{}",
35            cfg.layers,
36            cfg.params.as_ref().unwrap_or(&"".to_string()),
37        );
38        WmsFcgiSource {
39            map_service: None,
40            project,
41            suffix,
42            query,
43            tile_size: cfg.tile_size,
44        }
45    }
46
47    pub fn get_map_request(&self, extent_info: &QueryExtent, format: &Format) -> String {
48        let (width, height) = if let Some(size) = self.tile_size {
49            (size, size)
50        } else {
51            (extent_info.tile_width, extent_info.tile_height)
52        };
53        let extent = &extent_info.extent;
54        format!(
55            "{}&CRS=EPSG:{}&BBOX={},{},{},{}&WIDTH={width}&HEIGHT={height}&FORMAT={}",
56            self.query,
57            extent_info.srid,
58            extent.left,
59            extent.bottom,
60            extent.right,
61            extent.top,
62            format.content_type()
63        )
64    }
65
66    async fn bbox_request(
67        &self,
68        extent_info: &QueryExtent,
69        format: &Format,
70        request_params: HttpRequestParams<'_>,
71    ) -> Result<TileResponse, TileSourceError> {
72        let fcgi_dispatcher = self
73            .map_service
74            .as_ref()
75            .expect("not initialized")
76            .fcgi_dispatcher(&self.suffix)
77            .ok_or(TileSourceError::SuffixNotFound(self.suffix.clone()))?;
78        let fcgi_query = self.get_map_request(extent_info, format);
79        let project = &self.project;
80        let body = "".to_string();
81        wms_fcgi_req(
82            fcgi_dispatcher,
83            &fcgi_query,
84            request_params,
85            "GET",
86            body,
87            project,
88        )
89        .await
90        .map_err(Into::into)
91    }
92}
93
94#[async_trait]
95impl TileSource for WmsFcgiSource {
96    async fn xyz_request(
97        &self,
98        tms: &Tms,
99        tile: &Xyz,
100        _filter: &FilterParams,
101        format: &Format,
102        request_params: HttpRequestParams<'_>,
103    ) -> Result<TileResponse, TileSourceError> {
104        let extent_info = tms.xyz_extent(tile)?;
105        self.bbox_request(&extent_info, format, request_params)
106            .await
107    }
108    fn source_type(&self) -> SourceType {
109        SourceType::Raster
110    }
111    fn set_map_service(&mut self, service: &MapService) {
112        self.map_service = Some(service.clone());
113    }
114    fn wms_metrics(&self) -> &'static WmsMetrics {
115        static DUMMY_METRICS: OnceCell<WmsMetrics> = OnceCell::new();
116        if let Some(map_service) = &self.map_service {
117            map_service.metrics()
118        } else {
119            DUMMY_METRICS.get_or_init(WmsMetrics::default)
120        }
121    }
122    async fn tilejson(&self, _tms: &Tms, format: &Format) -> Result<TileJSON, TileSourceError> {
123        let mut tj = tilejson! { tiles: vec![] };
124        tj.other
125            .insert("format".to_string(), format.file_suffix().into());
126        Ok(tj)
127    }
128    async fn layers(&self) -> Result<Vec<LayerInfo>, TileSourceError> {
129        Ok(vec![LayerInfo {
130            name: self.project.clone(), // TODO: unique name in tileset
131            geometry_type: None,
132            style: None,
133        }])
134    }
135}