maplibre/style/
mod.rs

1//! Vector tile format styling.
2
3// ----------------------
4// Demotiles
5// ----------------------
6
7// use std::collections::HashMap;
8
9// pub use cint::*;
10// use serde::{Deserialize, Serialize};
11
12// use crate::style::{layer::StyleLayer, source::Source};
13
14// pub mod layer;
15// pub mod source;
16
17// ----------------------
18// Use manual styel
19// ----------------------
20
21use std::{collections::HashMap, str::FromStr};
22
23pub use cint::*;
24use csscolorparser::Color;
25use serde::{Deserialize, Serialize};
26
27pub mod layer;
28pub mod source;
29
30use crate::style::{
31    layer::{
32        BackgroundPaint, FillPaint, LayerPaint, LinePaint, RasterPaint, StyleLayer, StyleProperty,
33        SymbolPaint,
34    },
35    source::Source,
36};
37
38/// Stores the style for a multi-layered map.
39#[derive(Serialize, Deserialize, Debug, Clone)]
40pub struct Style {
41    pub version: u16,
42    #[serde(default)]
43    pub name: Option<String>,
44    #[serde(default)]
45    pub metadata: HashMap<String, serde_json::Value>,
46    #[serde(default)]
47    pub sources: HashMap<String, Source>,
48    pub layers: Vec<StyleLayer>,
49    pub center: Option<[f64; 2]>, // TODO: Use LatLon type here
50    pub zoom: Option<f64>,
51    pub pitch: Option<f64>,
52}
53
54/// Default style for https://openmaptiles.org/schema/
55impl Default for Style {
56    fn default() -> Self {
57        // ----------------------
58        // Demotiles
59        // ----------------------
60
61        // let mut style: Style = serde_json::from_str(include_str!("../../res/demotiles.json"))
62        //     .expect("Failed to parse default demotiles.json style");
63
64        // // Ensure layers have sequential Z-indices
65        // for (i, layer) in style.layers.iter_mut().enumerate() {
66        //     layer.index = i as u32;
67        // }
68
69        // style
70
71        // ----------------------
72        // Use manual styel
73        // ----------------------
74
75        Style {
76            version: 8,
77            name: Some("Default Style".to_string()),
78            metadata: Default::default(),
79            sources: Default::default(),
80            center: Some([50.85045, 4.34878]),
81            pitch: Some(0.0),
82            zoom: Some(13.0),
83            layers: vec![
84                StyleLayer {
85                    index: 0,
86                    id: "background".to_string(),
87                    type_: "background".to_string(),
88                    filter: None,
89                    maxzoom: None,
90                    minzoom: None,
91                    metadata: None,
92                    paint: Some(LayerPaint::Background(BackgroundPaint {
93                        background_color: Some(StyleProperty::Constant(
94                            Color::from_str("#ffffff").unwrap(),
95                        )),
96                    })),
97                    source: None,
98                    source_layer: None,
99                },
100                StyleLayer {
101                    index: 1,
102                    id: "park".to_string(),
103                    type_: "fill".to_string(),
104                    filter: None,
105                    maxzoom: None,
106                    minzoom: None,
107                    metadata: None,
108                    paint: Some(LayerPaint::Fill(FillPaint {
109                        fill_color: Some(StyleProperty::Constant(
110                            Color::from_str("#c8facc").unwrap(),
111                        )),
112                    })),
113                    source: None,
114                    source_layer: Some("park".to_string()),
115                },
116                StyleLayer {
117                    index: 2,
118                    id: "landuse".to_string(),
119                    type_: "fill".to_string(),
120                    filter: None,
121                    maxzoom: None,
122                    minzoom: None,
123                    metadata: None,
124                    paint: Some(LayerPaint::Fill(FillPaint {
125                        fill_color: Some(StyleProperty::Constant(
126                            Color::from_str("#e0dfdf").unwrap(),
127                        )),
128                    })),
129                    source: None,
130                    source_layer: Some("landuse".to_string()),
131                },
132                StyleLayer {
133                    index: 3,
134                    id: "landcover".to_string(),
135                    type_: "fill".to_string(),
136                    filter: None,
137                    maxzoom: None,
138                    minzoom: None,
139                    metadata: None,
140                    paint: Some(LayerPaint::Fill(FillPaint {
141                        fill_color: Some(StyleProperty::Constant(
142                            Color::from_str("#aedfa3").unwrap(),
143                        )),
144                    })),
145                    source: None,
146                    source_layer: Some("landcover".to_string()),
147                },
148                StyleLayer {
149                    index: 4,
150                    id: "transportation".to_string(),
151                    type_: "line".to_string(),
152                    filter: None,
153                    maxzoom: None,
154                    minzoom: None,
155                    metadata: None,
156                    paint: Some(LayerPaint::Line(LinePaint {
157                        line_color: Some(StyleProperty::Constant(
158                            Color::from_str("#ffffff").unwrap(),
159                        )),
160                        line_width: None,
161                    })),
162                    source: None,
163                    source_layer: Some("transportation".to_string()),
164                },
165                StyleLayer {
166                    index: 5,
167                    id: "building".to_string(),
168                    type_: "fill".to_string(),
169                    filter: None,
170                    maxzoom: None,
171                    minzoom: None,
172                    metadata: None,
173                    paint: Some(LayerPaint::Fill(FillPaint {
174                        fill_color: Some(StyleProperty::Constant(
175                            Color::from_str("#d9d0c9").unwrap(),
176                        )),
177                    })),
178                    source: None,
179                    source_layer: Some("building".to_string()),
180                },
181                StyleLayer {
182                    index: 6,
183                    id: "water".to_string(),
184                    type_: "fill".to_string(),
185                    filter: None,
186                    maxzoom: None,
187                    minzoom: None,
188                    metadata: None,
189                    paint: Some(LayerPaint::Fill(FillPaint {
190                        fill_color: Some(StyleProperty::Constant(
191                            Color::from_str("#aad3df").unwrap(),
192                        )),
193                    })),
194                    source: None,
195                    source_layer: Some("water".to_string()),
196                },
197                StyleLayer {
198                    index: 7,
199                    id: "waterway".to_string(),
200                    type_: "fill".to_string(),
201                    filter: None,
202                    maxzoom: None,
203                    minzoom: None,
204                    metadata: None,
205                    paint: Some(LayerPaint::Fill(FillPaint {
206                        fill_color: Some(StyleProperty::Constant(
207                            Color::from_str("#aad3df").unwrap(),
208                        )),
209                    })),
210                    source: None,
211                    source_layer: Some("waterway".to_string()),
212                },
213                StyleLayer {
214                    index: 8,
215                    id: "boundary".to_string(),
216                    type_: "line".to_string(),
217                    filter: None,
218                    maxzoom: None,
219                    minzoom: None,
220                    metadata: None,
221                    paint: Some(LayerPaint::Line(LinePaint {
222                        line_color: Some(StyleProperty::Constant(
223                            Color::from_str("black").unwrap(),
224                        )),
225                        line_width: None,
226                    })),
227                    source: None,
228                    source_layer: Some("boundary".to_string()),
229                },
230                StyleLayer {
231                    index: 9,
232                    id: "raster".to_string(),
233                    type_: "raster".to_string(),
234                    filter: None,
235                    maxzoom: None,
236                    minzoom: None,
237                    metadata: None,
238                    paint: Some(LayerPaint::Raster(RasterPaint::default())),
239                    source: None,
240                    source_layer: None,
241                },
242                StyleLayer {
243                    index: 10,
244                    id: "text".to_string(),
245                    type_: "symbol".to_string(),
246                    filter: None,
247                    maxzoom: None,
248                    minzoom: None,
249                    metadata: None,
250                    paint: Some(LayerPaint::Symbol(SymbolPaint {
251                        text_field: Some("name".to_string()),
252                        text_size: None,
253                    })),
254                    source: None,
255                    source_layer: Some("place".to_string()),
256                },
257                StyleLayer {
258                    index: 11,
259                    id: "transportation_name".to_string(),
260                    type_: "symbol".to_string(),
261                    filter: None,
262                    maxzoom: None,
263                    minzoom: None,
264                    metadata: None,
265                    paint: Some(LayerPaint::Symbol(SymbolPaint {
266                        text_field: Some("name".to_string()),
267                        text_size: None,
268                    })),
269                    source: None,
270                    source_layer: Some("transportation_name-disabled".to_string()),
271                },
272            ],
273        }
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn test_reading() {
283        // language=JSON
284        let style_json_str = r##"
285        {
286          "version": 8,
287          "name": "Test Style",
288          "metadata": {},
289          "sources": {
290            "openmaptiles": {
291              "type": "vector",
292              "url": "https://maps.tuerantuer.org/europe_germany/tiles.json"
293            }
294          },
295          "layers": [
296            {
297              "id": "background",
298              "type": "background",
299              "paint": {"background-color": "rgb(239,239,239)"}
300            },
301            {
302              "id": "transportation",
303              "type": "line",
304              "source": "openmaptiles",
305              "source-layer": "transportation",
306              "paint": {
307                "line-color": "#3D3D3D"
308              }
309            },
310            {
311              "id": "boundary",
312              "type": "line",
313              "source": "openmaptiles",
314              "source-layer": "boundary",
315              "paint": {
316                "line-color": "#3D3D3D"
317              }
318            },
319            {
320              "id": "building",
321              "minzoom": 14,
322              "maxzoom": 15,
323              "type": "fill",
324              "source": "openmaptiles",
325              "source-layer": "building",
326              "paint": {
327                "line-color": "#3D3D3D"
328              }
329            }
330          ]
331        }
332        "##;
333
334        let _style: Style = serde_json::from_str(style_json_str).unwrap();
335    }
336
337    #[test]
338    fn test_style_roundtrip_serde() {
339        // Test that the default style can serialize and deserialize (required for web worker Input)
340        let style = Style::default();
341        let json = serde_json::to_string(&style).unwrap();
342        let roundtripped: Style = serde_json::from_str(&json).unwrap();
343        assert_eq!(style.layers.len(), roundtripped.layers.len());
344        for (orig, rt) in style.layers.iter().zip(roundtripped.layers.iter()) {
345            assert_eq!(orig.id, rt.id, "layer ids must match after round-trip");
346            assert_eq!(
347                orig.type_, rt.type_,
348                "layer types must match after round-trip for {}",
349                orig.id
350            );
351        }
352    }
353}