Skip to main content

egui_map_view/
config.rs

1//! Configuration for different map providers.
2
3use crate::TileId;
4
5/// Configuration for a map provider.
6pub trait MapConfig {
7    /// Returns the URL for a given tile.
8    fn tile_url(&self, tile: &TileId) -> String;
9
10    /// Returns the attribution text to be displayed on the map. If returns `None`, no attribution is shown.
11    fn attribution(&self) -> Option<&String>;
12
13    /// Returns the attribution URL to be linked from the attribution text.
14    fn attribution_url(&self) -> Option<&String>;
15
16    /// The default geographical center of the map. (longitude, latitude)
17    fn default_center(&self) -> (f64, f64);
18
19    /// The default zoom level of the map.
20    fn default_zoom(&self) -> u8;
21
22    /// Returns the minimum zoom level allowed by this provider.
23    fn min_zoom(&self) -> u8 {
24        0
25    }
26
27    /// Returns the maximum zoom level allowed by this provider.
28    fn max_zoom(&self) -> u8 {
29        19
30    }
31}
32
33/// Configuration for the OpenStreetMap tile server.
34///
35/// # Example
36///
37/// ```
38/// use egui_map_view::config::OpenStreetMapConfig;
39/// let config = OpenStreetMapConfig::default();
40/// ```
41#[cfg(feature = "openstreetmap")]
42pub struct OpenStreetMapConfig {
43    base_url: String,
44    attribution: String,
45    attribution_url: String,
46    default_center: (f64, f64),
47    default_zoom: u8,
48    min_zoom: u8,
49    max_zoom: u8,
50}
51
52#[cfg(feature = "openstreetmap")]
53impl Default for OpenStreetMapConfig {
54    fn default() -> Self {
55        Self {
56            base_url: "https://tile.openstreetmap.org".to_string(),
57            attribution: "© OpenStreetMap contributors".to_string(),
58            attribution_url: "https://www.openstreetmap.org".to_string(),
59            default_center: (24.93545, 60.16952), // Helsinki, Finland
60            default_zoom: 5,
61            min_zoom: 0,
62            max_zoom: 19,
63        }
64    }
65}
66
67#[cfg(feature = "openstreetmap")]
68impl MapConfig for OpenStreetMapConfig {
69    fn tile_url(&self, tile: &TileId) -> String {
70        format!("{}/{}/{}/{}.png", self.base_url, tile.z, tile.x, tile.y)
71    }
72
73    fn attribution(&self) -> Option<&String> {
74        Some(&self.attribution)
75    }
76
77    fn attribution_url(&self) -> Option<&String> {
78        Some(&self.attribution_url)
79    }
80
81    fn default_center(&self) -> (f64, f64) {
82        self.default_center
83    }
84
85    fn default_zoom(&self) -> u8 {
86        self.default_zoom
87    }
88
89    fn min_zoom(&self) -> u8 {
90        self.min_zoom
91    }
92
93    fn max_zoom(&self) -> u8 {
94        self.max_zoom
95    }
96}
97
98#[cfg(feature = "openstreetmap")]
99impl OpenStreetMapConfig {
100    /// Sets the minimum zoom level.
101    pub fn min_zoom(mut self, min_zoom: u8) -> Self {
102        self.min_zoom = min_zoom;
103        self
104    }
105
106    /// Sets the maximum zoom level.
107    pub fn max_zoom(mut self, max_zoom: u8) -> Self {
108        self.max_zoom = max_zoom;
109        self
110    }
111}
112
113/// Configuration for the Karttapaikka tile server.
114///
115/// # Example
116///
117/// ```
118/// use egui_map_view::config::KarttapaikkaMapConfig;
119/// let config = KarttapaikkaMapConfig::new("my-api-key".to_string());
120/// ```
121#[cfg(feature = "karttapaikka")]
122pub struct KarttapaikkaMapConfig {
123    base_url: String,
124    attribution: String,
125    attribution_url: String,
126    default_center: (f64, f64),
127    default_zoom: u8,
128    api_key: String,
129    min_zoom: u8,
130    max_zoom: u8,
131}
132
133#[cfg(feature = "karttapaikka")]
134impl Default for KarttapaikkaMapConfig {
135    fn default() -> Self {
136        Self {
137            base_url: "https://avoin-karttakuva.maanmittauslaitos.fi/avoin/wmts/1.0.0/maastokartta/default/WGS84_Pseudo-Mercator".to_string(),
138            attribution: "© Maanmittauslaitos".to_string(),
139            attribution_url: "https://www.maanmittauslaitos.fi/asioi-verkossa/karttapaikka".to_string(),
140            default_center: (24.93545, 60.16952), // Helsinki, Finland
141            default_zoom: 7,
142            api_key: "your-key-here".to_string(),
143            min_zoom: 0,
144            max_zoom: 18,
145        }
146    }
147}
148
149#[cfg(feature = "karttapaikka")]
150impl MapConfig for KarttapaikkaMapConfig {
151    fn tile_url(&self, tile: &TileId) -> String {
152        format!(
153            "{}/{}/{}/{}.png?api-key={}",
154            self.base_url, tile.z, tile.y, tile.x, self.api_key
155        )
156    }
157
158    fn attribution(&self) -> Option<&String> {
159        Some(&self.attribution)
160    }
161
162    fn attribution_url(&self) -> Option<&String> {
163        Some(&self.attribution_url)
164    }
165
166    fn default_center(&self) -> (f64, f64) {
167        self.default_center
168    }
169
170    fn default_zoom(&self) -> u8 {
171        self.default_zoom
172    }
173
174    fn min_zoom(&self) -> u8 {
175        self.min_zoom
176    }
177
178    fn max_zoom(&self) -> u8 {
179        self.max_zoom
180    }
181}
182
183#[cfg(feature = "karttapaikka")]
184impl KarttapaikkaMapConfig {
185    /// Creates a new `KarttapaikkaMapConfig` with the given API key.
186    pub fn new(api_key: String) -> Self {
187        Self {
188            api_key,
189            ..Self::default()
190        }
191    }
192
193    /// Sets the minimum zoom level.
194    pub fn min_zoom(mut self, min: u8) -> Self {
195        self.min_zoom = min;
196        self
197    }
198
199    /// Sets the maximum zoom level.
200    pub fn max_zoom(mut self, max: u8) -> Self {
201        self.max_zoom = max;
202        self
203    }
204}
205
206/// A dynamic map configuration that allows defining a custom tile URL function at runtime.
207///
208/// # Example
209///
210/// ```
211/// use egui_map_view::config::DynMapConfig;
212/// let config = DynMapConfig::new(|tile| format!("https://my-tile-server/{}/{}/{}.png", tile.z, tile.x, tile.y));
213/// ```
214pub struct DynMapConfig {
215    tile_url: Box<dyn Fn(&TileId) -> String>,
216    min_zoom: u8,
217    max_zoom: u8,
218}
219
220impl DynMapConfig {
221    /// Creates a new `DynMapConfig` with a custom tile URL function.
222    pub fn new(tile_url: impl Fn(&TileId) -> String + 'static) -> Self {
223        Self {
224            tile_url: Box::new(tile_url),
225            min_zoom: 0,
226            max_zoom: 19,
227        }
228    }
229
230    /// Sets the minimum zoom level.
231    pub fn min_zoom(mut self, min_zoom: u8) -> Self {
232        self.min_zoom = min_zoom;
233        self
234    }
235
236    /// Sets the maximum zoom level.
237    pub fn max_zoom(mut self, max_zoom: u8) -> Self {
238        self.max_zoom = max_zoom;
239        self
240    }
241}
242
243impl MapConfig for DynMapConfig {
244    fn tile_url(&self, tile: &TileId) -> String {
245        (self.tile_url)(tile)
246    }
247
248    fn attribution(&self) -> Option<&String> {
249        None
250    }
251
252    fn attribution_url(&self) -> Option<&String> {
253        None
254    }
255
256    fn default_center(&self) -> (f64, f64) {
257        (24.93545, 60.16952)
258    }
259
260    fn default_zoom(&self) -> u8 {
261        2
262    }
263
264    fn min_zoom(&self) -> u8 {
265        self.min_zoom
266    }
267
268    fn max_zoom(&self) -> u8 {
269        self.max_zoom
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276    use crate::TileId;
277
278    #[test]
279    #[cfg(feature = "openstreetmap")]
280    fn openstreetmap_config_default() {
281        let config = OpenStreetMapConfig::default();
282        assert_eq!(config.base_url, "https://tile.openstreetmap.org");
283        assert_eq!(config.attribution, "© OpenStreetMap contributors");
284        assert_eq!(config.default_center, (24.93545, 60.16952));
285        assert_eq!(config.default_zoom, 5);
286    }
287
288    #[test]
289    #[cfg(feature = "openstreetmap")]
290    fn openstreetmap_config_tile_url() {
291        let config = OpenStreetMapConfig::default();
292        let tile_id = TileId { z: 10, x: 1, y: 2 };
293        let url = config.tile_url(&tile_id);
294        assert_eq!(url, "https://tile.openstreetmap.org/10/1/2.png");
295    }
296
297    #[test]
298    #[cfg(feature = "karttapaikka")]
299    fn karttapaikka_config_new() {
300        let api_key = "test-api-key".to_string();
301        let config = KarttapaikkaMapConfig::new(api_key.clone());
302        assert_eq!(config.api_key, api_key);
303        assert_eq!(
304            config.base_url,
305            "https://avoin-karttakuva.maanmittauslaitos.fi/avoin/wmts/1.0.0/maastokartta/default/WGS84_Pseudo-Mercator"
306        );
307        assert_eq!(config.attribution, "© Maanmittauslaitos");
308        assert_eq!(config.default_center, (24.93545, 60.16952));
309        assert_eq!(config.default_zoom, 15);
310    }
311
312    #[test]
313    #[cfg(feature = "karttapaikka")]
314    fn karttapaikka_config_tile_url() {
315        let api_key = "test-api-key".to_string();
316        let config = KarttapaikkaMapConfig::new(api_key.clone());
317        let tile_id = TileId { z: 10, x: 1, y: 2 };
318        let url = config.tile_url(&tile_id);
319        assert_eq!(
320            url,
321            "https://avoin-karttakuva.maanmittauslaitos.fi/avoin/wmts/1.0.0/maastokartta/default/WGS84_Pseudo-Mercator/10/2/1.png?api-key=test-api-key"
322        );
323    }
324
325    #[test]
326    #[cfg(feature = "openstreetmap")]
327    fn test_openstreetmap_zoom_limits() {
328        let config = OpenStreetMapConfig::default();
329        assert_eq!(MapConfig::min_zoom(&config), 0);
330        assert_eq!(MapConfig::max_zoom(&config), 19);
331
332        let customized = config.min_zoom(2).max_zoom(18);
333        assert_eq!(MapConfig::min_zoom(&customized), 2);
334        assert_eq!(MapConfig::max_zoom(&customized), 18);
335    }
336
337    #[test]
338    #[cfg(feature = "karttapaikka")]
339    fn test_karttapaikka_zoom_limits() {
340        let config = KarttapaikkaMapConfig::default();
341        assert_eq!(MapConfig::min_zoom(&config), 0);
342        assert_eq!(MapConfig::max_zoom(&config), 15);
343
344        let customized = config.min_zoom(5).max_zoom(12);
345        assert_eq!(MapConfig::min_zoom(&customized), 5);
346        assert_eq!(MapConfig::max_zoom(&customized), 12);
347    }
348
349    #[test]
350    fn test_dyn_zoom_limits() {
351        let config = DynMapConfig::new(|tile| format!("url/{}", tile.z));
352        assert_eq!(MapConfig::min_zoom(&config), 0);
353        assert_eq!(MapConfig::max_zoom(&config), 19);
354
355        let customized = config.min_zoom(3).max_zoom(17);
356        assert_eq!(MapConfig::min_zoom(&customized), 3);
357        assert_eq!(MapConfig::max_zoom(&customized), 17);
358    }
359}