bbox_tile_server/datasource/
mod.rs1pub mod mbtiles;
4mod mvt;
5pub mod pmtiles;
6pub mod postgis;
7mod postgis_queries;
8#[cfg(feature = "map-server")]
9pub mod wms_fcgi;
10pub mod wms_http;
11
12use crate::config::{SourceParamCfg, TileSetCfg, TilesetTmsCfg};
13use crate::filter_params::FilterParams;
14use crate::service::{TileSetGrid, TmsExtensions};
15use crate::store::mbtiles::MbtilesStore;
16use crate::store::pmtiles::PmtilesStoreReader;
17use async_trait::async_trait;
18use bbox_core::config::{error_exit, DatasourceCfg, NamedDatasourceCfg};
19use bbox_core::{Format, NamedObjectStore, TileResponse};
20use dyn_clone::{clone_trait_object, DynClone};
21use geozero::error::GeozeroError;
22use martin_mbtiles::Metadata;
23use once_cell::sync::OnceCell;
24use std::env;
25use tile_grid::{tms, RegistryError, Tms, Xyz};
26use tilejson::TileJSON;
27
28#[derive(thiserror::Error, Debug)]
29pub enum TileSourceError {
30 #[error("tileserver.source `{0}` not found")]
31 TileSourceNotFound(String),
32 #[error("tileserver.source of type {0} expected")]
33 TileSourceTypeError(String),
34 #[error("missing filter parameter")]
35 FilterParamError,
36 #[error("tile not found / out of bounds")]
37 TileXyzError,
38 #[error(transparent)]
39 RegistryError(#[from] RegistryError),
40 #[error(transparent)]
41 FcgiError(#[from] wms_fcgi::FcgiError),
42 #[error("FCGI for suffix `{0}` not found")]
43 SuffixNotFound(String),
44 #[error(transparent)]
45 DbError(#[from] sqlx::Error),
46 #[error("Source field type detection failed")]
47 TypeDetectionError,
48 #[error("Integer out of range")]
49 IntRangeError(#[from] std::num::TryFromIntError),
50 #[error(transparent)]
51 GeozeroError(#[from] GeozeroError),
52 #[error("MVT encoding error")]
53 MvtEncodeError, #[error(transparent)]
55 WmsHttpError(#[from] reqwest::Error),
56 #[error(transparent)]
57 MbtilesError(#[from] martin_mbtiles::MbtError),
58 #[error(transparent)]
59 PmtilesError(#[from] ::pmtiles::error::Error),
60}
61
62#[derive(PartialEq, Clone, Debug)]
63pub enum SourceType {
64 Vector,
65 Raster,
66}
67
68pub struct LayerInfo {
69 pub name: String,
70 pub geometry_type: Option<String>,
71 pub style: Option<serde_json::Value>,
73}
74
75#[async_trait]
76pub trait TileSource: DynClone + Send + Sync {
77 async fn xyz_request(
79 &self,
80 tms: &Tms,
81 tile: &Xyz,
82 filter: &FilterParams,
83 format: &Format,
84 request_params: wms_fcgi::HttpRequestParams<'_>,
85 ) -> Result<TileResponse, TileSourceError>;
86 fn source_type(&self) -> SourceType;
88 fn default_format(&self) -> &Format {
90 match self.source_type() {
91 SourceType::Vector => &Format::Mvt,
92 SourceType::Raster => &Format::Png, }
94 }
95 fn set_map_service(&mut self, _service: &wms_fcgi::MapService) {}
97 fn wms_metrics(&self) -> &'static wms_fcgi::WmsMetrics {
99 static DUMMY_METRICS: OnceCell<wms_fcgi::WmsMetrics> = OnceCell::new();
100 DUMMY_METRICS.get_or_init(wms_fcgi::WmsMetrics::default)
101 }
102 async fn tilejson(&self, tms: &Tms, format: &Format) -> Result<TileJSON, TileSourceError>;
104 async fn layers(&self) -> Result<Vec<LayerInfo>, TileSourceError>;
106 async fn mbtiles_metadata(
108 &self,
109 tileset: &TileSetCfg,
110 format: &Format,
111 ) -> Result<Metadata, TileSourceError> {
112 let tms = tms().lookup("WebMercatorQuad").unwrap(); Ok(Metadata {
114 id: tileset.name.clone(),
115 tile_info: martin_tile_utils::TileInfo {
116 format: martin_tile_utils::Format::parse(format.file_suffix())
117 .unwrap_or(martin_tile_utils::Format::Mvt),
118 encoding: martin_tile_utils::Encoding::Uncompressed,
119 },
120 tilejson: self.tilejson(&tms, format).await?,
121 layer_type: None,
122 json: None,
123 agg_tiles_hash: None,
124 })
125 }
126}
127
128clone_trait_object!(TileSource);
129
130#[derive(Default)]
132pub struct Datasources {
133 pg_datasources: NamedObjectStore<postgis::Datasource>,
134 config_sources: NamedObjectStore<DatasourceCfg>,
136}
137
138impl Datasources {
139 pub async fn create(datasources: &Vec<NamedDatasourceCfg>) -> Self {
141 let mut ds_handler = Datasources::default();
143 for named_ds in datasources {
144 let envar = env::var(format!("BBOX_DATASOURCE_{}", &named_ds.name.to_uppercase())).ok();
147 let ds = &named_ds.datasource;
148 match ds {
149 DatasourceCfg::Postgis(cfg) => ds_handler.pg_datasources.add(
150 &named_ds.name,
151 postgis::Datasource::from_config(cfg, envar)
152 .await
153 .unwrap_or_else(error_exit),
154 ),
155 _ => ds_handler.config_sources.add(&named_ds.name, ds.clone()),
156 }
157 }
158 ds_handler
159 }
160 pub async fn setup_tile_source(
162 &self,
163 cfg: &SourceParamCfg,
164 ts_grids: &[TileSetGrid],
165 tms_cfg: &[TilesetTmsCfg],
166 ) -> Box<dyn TileSource> {
167 match cfg {
184 SourceParamCfg::WmsHttp(cfg) => {
185 let DatasourceCfg::WmsHttp(provider) =
186 self.config_sources.get(&cfg.source).unwrap_or_else(|| {
187 error_exit(TileSourceError::TileSourceNotFound(cfg.source.clone()))
188 })
189 else {
190 error_exit(TileSourceError::TileSourceTypeError(
191 "wms_proxy".to_string(),
192 ))
193 };
194 let first_srid = ts_grids.first().expect("default grid missing").tms.srid(); Box::new(wms_http::WmsHttpSource::from_config(
196 provider, cfg, first_srid,
197 ))
198 }
199 #[cfg(feature = "map-server")]
200 SourceParamCfg::WmsFcgi(cfg) => Box::new(wms_fcgi::WmsFcgiSource::from_config(cfg)),
201 #[cfg(not(feature = "map-server"))]
202 SourceParamCfg::WmsFcgi(cfg) => {
203 bbox_core::config::config_error_exit(
204 &format!("Cannot add map service tile source with project `{}` - Map service feature is not active.", cfg.project));
205 unreachable!()
206 }
207 SourceParamCfg::Postgis(pg_cfg) => {
208 let ds = self
209 .pg_datasources
210 .get_or_default(pg_cfg.datasource.as_deref())
211 .unwrap_or_else(|| {
212 error_exit(TileSourceError::TileSourceNotFound(
213 pg_cfg
214 .datasource
215 .as_ref()
216 .unwrap_or(&"(default)".to_string())
217 .clone(),
218 ))
219 });
220 Box::new(postgis::PgSource::create(ds, pg_cfg, ts_grids, tms_cfg).await)
221 }
222 SourceParamCfg::Mbtiles(cfg) => Box::new(
223 MbtilesStore::from_config(cfg)
224 .await
225 .unwrap_or_else(error_exit),
226 ),
227 SourceParamCfg::Pmtiles(cfg) => Box::new(
228 PmtilesStoreReader::from_config(cfg)
229 .await
230 .unwrap_or_else(error_exit),
231 ),
232 }
233 }
234}
235
236pub fn source_config_from_cli_arg(file_or_url: &str) -> Option<SourceParamCfg> {
237 MbtilesStore::config_from_cli_arg(file_or_url)
238 .map(SourceParamCfg::Mbtiles)
239 .or(PmtilesStoreReader::config_from_cli_arg(file_or_url).map(SourceParamCfg::Pmtiles))
240}
241
242#[cfg(not(feature = "map-server"))]
243pub mod wms_fcgi {
244 #[derive(Default)]
246 pub struct WmsMetrics;
247 #[derive(Clone)]
248 pub struct MapService;
249 impl MapService {
250 pub fn metrics(&self) -> &'static WmsMetrics {
251 unimplemented!()
252 }
253 }
254 pub type FcgiError = std::io::Error;
255
256 pub struct HttpRequestParams<'a> {
257 pub scheme: &'a str,
258 pub host: &'a str,
259 pub req_path: &'a str,
260 pub metrics: &'a WmsMetrics,
261 }
262}