1use 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#[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]>, pub zoom: Option<f64>,
51 pub pitch: Option<f64>,
52}
53
54impl Default for Style {
56 fn default() -> Self {
57 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 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 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}