bbox_tile_server/datasource/
wms_http.rs

1//! HTTP tile sources like remote WMS services.
2
3use crate::config::WmsHttpSourceParamsCfg;
4use crate::datasource::{
5    wms_fcgi::HttpRequestParams, LayerInfo, SourceType, TileSource, TileSourceError,
6};
7use crate::filter_params::FilterParams;
8use crate::service::TmsExtensions;
9use async_trait::async_trait;
10use bbox_core::config::WmsHttpSourceProviderCfg;
11use bbox_core::{Format, TileResponse};
12use log::debug;
13use std::io::Cursor;
14use tile_grid::{BoundingBox, Tms, Xyz};
15use tilejson::{tilejson, TileJSON};
16
17#[derive(Clone, Debug)]
18pub struct WmsHttpSource {
19    client: reqwest::Client,
20    pub req_url: String,
21}
22
23impl WmsHttpSource {
24    pub fn from_config(
25        provider: &WmsHttpSourceProviderCfg,
26        params: &WmsHttpSourceParamsCfg,
27        srid: i32,
28    ) -> Self {
29        let client = reqwest::Client::new();
30        let req_url = format!(
31            "{}&SERVICE=WMS&REQUEST=GetMap&CRS=EPSG:{}&WIDTH={}&HEIGHT={}&LAYERS={}&STYLES=&FORMAT={}",
32            provider.baseurl,
33            srid,
34            256, //grid.width,
35            256, //grid.height,
36            params.layers,
37            provider.format,
38        );
39        WmsHttpSource { client, req_url }
40    }
41    fn get_map_request(&self, extent: &BoundingBox) -> String {
42        format!(
43            "{}&BBOX={},{},{},{}",
44            self.req_url, extent.left, extent.bottom, extent.right, extent.top
45        )
46    }
47
48    pub async fn get_map_response(
49        &self,
50        extent: &BoundingBox,
51    ) -> Result<reqwest::Response, TileSourceError> {
52        let req = self.get_map_request(extent);
53        debug!("Request {req}");
54        self.client.get(req).send().await.map_err(Into::into)
55    }
56
57    async fn bbox_request(&self, extent: &BoundingBox) -> Result<TileResponse, TileSourceError> {
58        let wms_resp = self.get_map_response(extent).await?;
59        let mut response = TileResponse::new();
60        if let Some(content_type) = wms_resp
61            .headers()
62            .get("content-type")
63            .map(|ct| ct.to_str().expect("invalid content-type"))
64        {
65            response.set_content_type(content_type);
66        }
67        let body = Box::new(Cursor::new(wms_resp.bytes().await?));
68        Ok(response.with_body(body))
69    }
70}
71
72#[async_trait]
73impl TileSource for WmsHttpSource {
74    async fn xyz_request(
75        &self,
76        tms: &Tms,
77        tile: &Xyz,
78        _filter: &FilterParams,
79        _format: &Format,
80        _request_params: HttpRequestParams<'_>,
81    ) -> Result<TileResponse, TileSourceError> {
82        let extent_info = tms.xyz_extent(tile)?;
83        self.bbox_request(&extent_info.extent).await
84    }
85    fn source_type(&self) -> SourceType {
86        SourceType::Raster
87    }
88    async fn tilejson(&self, _tms: &Tms, format: &Format) -> Result<TileJSON, TileSourceError> {
89        let mut tj = tilejson! { tiles: vec![] };
90        tj.other
91            .insert("format".to_string(), format.file_suffix().into());
92        Ok(tj)
93    }
94    async fn layers(&self) -> Result<Vec<LayerInfo>, TileSourceError> {
95        Ok(vec![LayerInfo {
96            name: "WmsHttpSource".to_string(), // TODO: unique name in tileset
97            geometry_type: None,
98            style: None,
99        }])
100    }
101}