bbox_tile_server/
config_t_rex.rs

1//! t-rex compatibilty functions
2
3use serde::Deserialize;
4use std::fs::File;
5use std::io::Read;
6use tile_grid::{Extent, Grid, Origin, Unit};
7use toml::Value;
8
9// from t-rex core gridcfg.rs
10
11#[derive(Deserialize, Clone, Debug)]
12pub struct ExtentCfg {
13    pub minx: f64,
14    pub miny: f64,
15    pub maxx: f64,
16    pub maxy: f64,
17}
18
19impl From<&ExtentCfg> for Extent {
20    fn from(cfg: &ExtentCfg) -> Extent {
21        Extent {
22            minx: cfg.minx,
23            miny: cfg.miny,
24            maxx: cfg.maxx,
25            maxy: cfg.maxy,
26        }
27    }
28}
29
30pub trait FromGridCfg {
31    fn from_config(grid_cfg: &GridCfg) -> Result<Self, String>
32    where
33        Self: Sized;
34}
35
36impl FromGridCfg for Grid {
37    fn from_config(grid_cfg: &GridCfg) -> Result<Self, String> {
38        if let Some(ref gridname) = grid_cfg.predefined {
39            match gridname.as_str() {
40                "wgs84" => Ok(Grid::wgs84()),
41                "web_mercator" => Ok(Grid::web_mercator()),
42                _ => Err(format!("Unkown grid '{gridname}'")),
43            }
44        } else if let Some(ref usergrid) = grid_cfg.user {
45            let units = match &usergrid.units.to_lowercase() as &str {
46                "m" => Ok(Unit::Meters),
47                "dd" => Ok(Unit::Degrees),
48                "ft" => Ok(Unit::Feet),
49                _ => Err(format!("Unexpected enum value '{}'", usergrid.units)),
50            };
51            let origin = match &usergrid.origin as &str {
52                "TopLeft" => Ok(Origin::TopLeft),
53                "BottomLeft" => Ok(Origin::BottomLeft),
54                _ => Err(format!("Unexpected enum value '{}'", usergrid.origin)),
55            };
56            let grid = Grid::new(
57                usergrid.width,
58                usergrid.height,
59                Extent::from(&usergrid.extent),
60                usergrid.srid,
61                units?,
62                usergrid.resolutions.clone(),
63                origin?,
64            );
65            Ok(grid)
66        } else {
67            Err("Invalid grid definition".to_string())
68        }
69    }
70}
71
72// from t-rex core config.rs
73
74#[derive(Deserialize, Clone, Debug)]
75pub struct ApplicationCfg {
76    pub service: ServiceCfg,
77    pub datasource: Vec<DatasourceCfg>,
78    pub grid: GridCfg,
79    #[serde(rename = "tileset")]
80    pub tilesets: Vec<TilesetCfg>,
81    pub cache: Option<CacheCfg>,
82    pub webserver: WebserverCfg,
83}
84
85#[derive(Deserialize, Clone, Debug)]
86pub struct ServiceCfg {
87    pub mvt: ServiceMvtCfg,
88}
89
90#[derive(Deserialize, Clone, Debug)]
91pub struct ServiceMvtCfg {
92    pub viewer: bool,
93}
94
95#[derive(Deserialize, Clone, Debug)]
96pub struct DatasourceCfg {
97    pub name: Option<String>,
98    pub default: Option<bool>,
99    // Postgis
100    pub dbconn: Option<String>,
101    pub pool: Option<u16>,
102    pub connection_timeout: Option<u64>,
103    // GDAL
104    pub path: Option<String>,
105}
106
107#[derive(Deserialize, Clone, Debug)]
108pub struct GridCfg {
109    pub predefined: Option<String>,
110    pub user: Option<UserGridCfg>,
111}
112
113#[derive(Deserialize, Clone, Debug)]
114pub struct UserGridCfg {
115    /// The width and height of an individual tile, in pixels.
116    pub width: u16,
117    pub height: u16,
118    /// The geographical extent covered by the grid, in ground units (e.g. meters, degrees, feet, etc.).
119    /// Must be specified as 4 floating point numbers ordered as minx, miny, maxx, maxy.
120    /// The (minx,miny) point defines the origin of the grid, i.e. the pixel at the bottom left of the
121    /// bottom-left most tile is always placed on the (minx,miny) geographical point.
122    /// The (maxx,maxy) point is used to determine how many tiles there are for each zoom level.
123    pub extent: ExtentCfg,
124    /// Spatial reference system (PostGIS SRID).
125    pub srid: i32,
126    /// Grid units (m: meters, dd: decimal degrees, ft: feet)
127    pub units: String,
128    /// This is a list of resolutions for each of the zoom levels defined by the grid.
129    /// This must be supplied as a list of positive floating point values, ordered from largest to smallest.
130    /// The largest value will correspond to the grid’s zoom level 0. Resolutions
131    /// are expressed in “units-per-pixel”,
132    /// depending on the unit used by the grid (e.g. resolutions are in meters per
133    /// pixel for most grids used in webmapping).
134    #[serde(default)]
135    pub resolutions: Vec<f64>,
136    /// Grid origin
137    pub origin: String,
138}
139
140#[derive(Deserialize, Clone, Debug)]
141pub struct TilesetCfg {
142    pub name: String,
143    pub extent: Option<ExtentCfg>,
144    pub minzoom: Option<u8>,
145    pub maxzoom: Option<u8>,
146    pub center: Option<(f64, f64)>,
147    pub start_zoom: Option<u8>,
148    pub attribution: Option<String>,
149    #[serde(rename = "layer")]
150    pub layers: Vec<LayerCfg>,
151    // Inline style
152    pub style: Option<serde_json::Value>,
153    pub cache_limits: Option<TilesetCacheCfg>,
154}
155
156#[derive(Deserialize, Clone, Debug)]
157pub struct LayerQueryCfg {
158    #[serde(default)]
159    pub minzoom: u8,
160    pub maxzoom: Option<u8>,
161    /// Simplify geometry (override layer default setting)
162    pub simplify: Option<bool>,
163    /// Simplification tolerance (override layer default setting)
164    pub tolerance: Option<String>,
165    pub sql: Option<String>,
166}
167
168#[derive(Deserialize, Clone, Debug)]
169pub struct LayerCfg {
170    pub name: String,
171    pub datasource: Option<String>,
172    pub geometry_field: Option<String>,
173    pub geometry_type: Option<String>,
174    /// Spatial reference system (PostGIS SRID)
175    pub srid: Option<i32>,
176    /// Handle geometry like one in grid SRS
177    #[serde(default)]
178    pub no_transform: bool,
179    pub fid_field: Option<String>,
180    // Input for derived queries
181    pub table_name: Option<String>,
182    pub query_limit: Option<u32>,
183    // Explicit queries
184    #[serde(default)]
185    pub query: Vec<LayerQueryCfg>,
186    pub minzoom: Option<u8>,
187    pub maxzoom: Option<u8>,
188    /// Width and height of the tile (Default: 4096. Grid default size is 256)
189    #[serde(default = "default_tile_size")]
190    pub tile_size: u32,
191    /// Simplify geometry (lines and polygons)
192    #[serde(default)]
193    pub simplify: bool,
194    /// Simplification tolerance (default to !pixel_width!/2)
195    #[serde(default = "default_tolerance")]
196    pub tolerance: String,
197    /// Tile buffer size in pixels (None: no clipping)
198    pub buffer_size: Option<u32>,
199    /// Fix invalid geometries before clipping (lines and polygons)
200    #[serde(default)]
201    pub make_valid: bool,
202    /// Apply ST_Shift_Longitude to (transformed) bbox
203    #[serde(default)]
204    pub shift_longitude: bool,
205    // Inline style
206    pub style: Option<serde_json::Value>,
207}
208
209pub fn default_tile_size() -> u32 {
210    4096
211}
212
213pub const DEFAULT_TOLERANCE: &str = "!pixel_width!/2";
214
215pub fn default_tolerance() -> String {
216    DEFAULT_TOLERANCE.to_string()
217}
218
219#[derive(Deserialize, Clone, Debug)]
220pub struct TilesetCacheCfg {
221    #[serde(default)]
222    pub minzoom: u8,
223    pub maxzoom: Option<u8>,
224    #[serde(default)]
225    pub no_cache: bool,
226}
227
228#[derive(Deserialize, Clone, Debug)]
229pub struct CacheCfg {
230    pub file: Option<CacheFileCfg>,
231    pub s3: Option<S3CacheFileCfg>,
232}
233
234#[derive(Deserialize, Clone, Debug)]
235pub struct CacheFileCfg {
236    pub base: String,
237    pub baseurl: Option<String>,
238}
239
240#[derive(Deserialize, Clone, Debug)]
241pub struct S3CacheFileCfg {
242    pub endpoint: String,
243    pub bucket: String,
244    pub access_key: String,
245    pub secret_key: String,
246    pub region: String,
247    pub baseurl: Option<String>,
248    pub key_prefix: Option<String>,
249    pub gzip_header_enabled: Option<bool>,
250}
251
252#[derive(Deserialize, Clone, Debug)]
253pub struct WebserverCfg {
254    pub bind: Option<String>,
255    pub port: Option<u16>,
256    pub threads: Option<u8>,
257    // Cache-Control headers set by web server
258    // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Expiration
259    pub cache_control_max_age: Option<u32>,
260    #[serde(rename = "static", default)]
261    pub static_: Vec<WebserverStaticCfg>,
262}
263
264#[derive(Deserialize, Clone, Debug)]
265pub struct WebserverStaticCfg {
266    pub path: String,
267    pub dir: String,
268}
269
270/// Load and parse the config file into an config struct.
271pub fn read_config<'a, T: Deserialize<'a>>(path: &str) -> Result<T, String> {
272    let mut file = match File::open(path) {
273        Ok(file) => file,
274        Err(_) => {
275            return Err("Could not find config file!".to_string());
276        }
277    };
278    let mut config_toml = String::new();
279    if let Err(err) = file.read_to_string(&mut config_toml) {
280        return Err(format!("Error while reading config: [{}]", err));
281    };
282
283    parse_config(config_toml, path)
284}
285
286/// Parse the configuration into an config struct.
287pub fn parse_config<'a, T: Deserialize<'a>>(config_toml: String, path: &str) -> Result<T, String> {
288    // Check for old ${var} expressions
289    // let re = Regex::new(r"\$\{([[:alnum:]]+)\}").unwrap();
290    // if re.is_match(&config_toml) {
291    //     return Err(
292    //         "Replace old environment variable syntax ${VARNAME} with `{{env.VARNAME}}`".to_string(),
293    //     );
294    // }
295
296    // Parse template
297    // let mut tera = Tera::default();
298    // tera.add_raw_template(path, &config_toml)
299    //     .map_err(|e| format!("Template error: {}", e))?;
300    // let mut context = Context::new();
301    // let mut env = HashMap::new();
302    // for (key, value) in env::vars() {
303    //     env.insert(key, value);
304    // }
305    // context.insert("env", &env);
306    // let toml = tera
307    //     .render(path, &context)
308    //     .map_err(|e| format!("Template error: {}", e.source().unwrap()))?;
309    let toml = config_toml; // TODO: resolve at least env vars
310
311    toml.parse::<Value>()
312        .and_then(|cfg| cfg.try_into::<T>())
313        .map_err(|err| format!("{} - {}", path, err))
314}