1use std::{
4 collections::HashMap,
5 hash::{Hash, Hasher},
6};
7
8use cint::{Alpha, EncodedSrgb};
9use csscolorparser::Color;
10use serde::{Deserialize, Deserializer, Serialize};
11
12#[derive(Serialize, Deserialize, Debug, Clone)]
13#[serde(untagged)]
14pub enum StyleProperty<T> {
15 Constant(T),
16 Expression(serde_json::Value),
17}
18
19impl<T: std::str::FromStr + Clone> StyleProperty<T> {
20 pub fn evaluate(&self, feature_properties: &HashMap<String, String>) -> Option<T> {
21 match self {
22 StyleProperty::Constant(value) => Some(value.clone()),
23 StyleProperty::Expression(expr) => {
24 if let Some(arr) = expr.as_array() {
25 if let Some(op) = arr.get(0).and_then(|v| v.as_str()) {
26 if op == "match" && arr.len() > 3 {
27 if let Some(get_arr) = arr.get(1).and_then(|v| v.as_array()) {
29 if get_arr.get(0).and_then(|v| v.as_str()) == Some("get") {
30 if let Some(prop_name) = get_arr.get(1).and_then(|v| v.as_str())
31 {
32 let feature_val_opt = feature_properties.get(prop_name);
33
34 if feature_val_opt.is_none() {
36 if let Some(fallback) =
37 arr.last().and_then(|v| v.as_str())
38 {
39 return fallback.parse::<T>().ok();
40 }
41 return None;
42 }
43
44 let feature_val = feature_val_opt.unwrap();
45
46 let mut i = 2;
48 while i < arr.len() - 1 {
49 if let Some(match_keys) =
50 arr.get(i).and_then(|v| v.as_array())
51 {
52 let matches = match_keys.iter().any(|k| {
54 k.as_str() == Some(feature_val.as_str())
55 });
56 if matches {
57 if let Some(color_str) =
58 arr.get(i + 1).and_then(|v| v.as_str())
59 {
60 return color_str.parse::<T>().ok();
61 }
62 }
63 }
64 i += 2;
65 }
66 if i == arr.len() - 1 {
68 if let Some(fallback) =
69 arr.get(i).and_then(|v| v.as_str())
70 {
71 return fallback.parse::<T>().ok();
72 }
73 }
74 }
75 }
76 }
77 }
78 }
79 }
80 None
81 }
82 }
83 }
84
85 pub fn deserialize_color_or_none<'de, D>(
86 deserializer: D,
87 ) -> Result<Option<StyleProperty<T>>, D::Error>
88 where
89 D: Deserializer<'de>,
90 {
91 let v = serde_json::Value::deserialize(deserializer).map_err(serde::de::Error::custom)?;
93 if let Some(s) = v.as_str() {
94 if let Ok(color) = s.parse::<T>() {
95 return Ok(Some(StyleProperty::Constant(color)));
96 }
97 }
98 if v.is_array() {
100 return Ok(Some(StyleProperty::Expression(v)));
101 }
102 Ok(None)
103 }
104}
105
106impl StyleProperty<f32> {
107 pub fn deserialize_f32_or_none<'de, D>(
108 deserializer: D,
109 ) -> Result<Option<StyleProperty<f32>>, D::Error>
110 where
111 D: Deserializer<'de>,
112 {
113 let v = serde_json::Value::deserialize(deserializer).map_err(serde::de::Error::custom)?;
114 if let Some(f) = v.as_f64() {
115 return Ok(Some(StyleProperty::Constant(f as f32)));
116 }
117 if v.is_array() {
118 return Ok(Some(StyleProperty::Expression(v)));
119 }
120 if v.is_object() {
122 return Ok(Some(StyleProperty::Expression(v)));
123 }
124 Ok(None)
125 }
126
127 pub fn evaluate_at_zoom(&self, zoom: f32) -> f32 {
130 match self {
131 StyleProperty::Constant(v) => *v,
132 StyleProperty::Expression(expr) => {
133 let stops = expr
134 .get("stops")
135 .and_then(|s| s.as_array())
136 .or_else(|| expr.as_array());
137 let Some(stops) = stops else {
138 return 1.0;
139 };
140 let parsed: Vec<(f32, f32)> = stops
142 .iter()
143 .filter_map(|stop| {
144 let arr = stop.as_array()?;
145 let z = arr.first()?.as_f64()? as f32;
146 let v = arr.get(1)?.as_f64()? as f32;
147 Some((z, v))
148 })
149 .collect();
150
151 if parsed.is_empty() {
152 return 1.0;
153 }
154 if zoom <= parsed[0].0 {
155 return parsed[0].1;
156 }
157 if zoom >= parsed[parsed.len() - 1].0 {
158 return parsed[parsed.len() - 1].1;
159 }
160 for window in parsed.windows(2) {
162 let (z0, v0) = window[0];
163 let (z1, v1) = window[1];
164 if zoom >= z0 && zoom <= z1 {
165 let t = (zoom - z0) / (z1 - z0);
166 return v0 + t * (v1 - v0);
167 }
168 }
169 parsed[parsed.len() - 1].1
170 }
171 }
172 }
173}
174
175#[derive(Serialize, Deserialize, Debug, Clone)]
176pub struct BackgroundPaint {
177 #[serde(rename = "background-color")]
178 #[serde(
179 default,
180 deserialize_with = "StyleProperty::<Color>::deserialize_color_or_none"
181 )]
182 #[serde(skip_serializing_if = "Option::is_none")]
183 pub background_color: Option<StyleProperty<Color>>,
184 }
186
187#[derive(Serialize, Deserialize, Debug, Clone)]
188pub struct FillPaint {
189 #[serde(rename = "fill-color")]
190 #[serde(
191 default,
192 deserialize_with = "StyleProperty::<Color>::deserialize_color_or_none"
193 )]
194 #[serde(skip_serializing_if = "Option::is_none")]
195 pub fill_color: Option<StyleProperty<Color>>,
196 }
198
199#[derive(Serialize, Deserialize, Debug, Clone)]
200pub struct LinePaint {
201 #[serde(rename = "line-color")]
202 #[serde(
203 default,
204 deserialize_with = "StyleProperty::<Color>::deserialize_color_or_none"
205 )]
206 #[serde(skip_serializing_if = "Option::is_none")]
207 pub line_color: Option<StyleProperty<Color>>,
208
209 #[serde(rename = "line-width")]
210 #[serde(
211 default,
212 deserialize_with = "StyleProperty::<f32>::deserialize_f32_or_none"
213 )]
214 pub line_width: Option<StyleProperty<f32>>,
215 }
217
218#[derive(Serialize, Deserialize, Debug, Clone)]
219pub enum RasterResampling {
220 #[serde(rename = "linear")]
221 Linear,
222 #[serde(rename = "nearest")]
223 Nearest,
224}
225
226#[derive(Serialize, Deserialize, Debug, Clone)]
228pub struct RasterPaint {
229 #[serde(rename = "raster-brightness-max")]
230 #[serde(skip_serializing_if = "Option::is_none")]
231 pub raster_brightness_max: Option<f32>,
232 #[serde(rename = "raster-brightness-min")]
233 #[serde(skip_serializing_if = "Option::is_none")]
234 pub raster_brightness_min: Option<f32>,
235 #[serde(rename = "raster-contrast")]
236 #[serde(skip_serializing_if = "Option::is_none")]
237 pub raster_contrast: Option<f32>,
238 #[serde(rename = "raster-fade-duration")]
239 #[serde(skip_serializing_if = "Option::is_none")]
240 pub raster_fade_duration: Option<u32>,
241 #[serde(rename = "raster-hue-rotate")]
242 #[serde(skip_serializing_if = "Option::is_none")]
243 pub raster_hue_rotate: Option<f32>,
244 #[serde(rename = "raster-opacity")]
245 #[serde(skip_serializing_if = "Option::is_none")]
246 pub raster_opacity: Option<f32>,
247 #[serde(rename = "raster-resampling")]
248 #[serde(skip_serializing_if = "Option::is_none")]
249 pub raster_resampling: Option<RasterResampling>,
250 #[serde(rename = "raster-saturation")]
251 #[serde(skip_serializing_if = "Option::is_none")]
252 pub raster_saturation: Option<f32>,
253}
254
255impl Default for RasterPaint {
256 fn default() -> Self {
257 RasterPaint {
258 raster_brightness_max: Some(1.0),
259 raster_brightness_min: Some(0.0),
260 raster_contrast: Some(0.0),
261 raster_fade_duration: Some(0),
262 raster_hue_rotate: Some(0.0),
263 raster_opacity: Some(1.0),
264 raster_resampling: Some(RasterResampling::Linear),
265 raster_saturation: Some(0.0),
266 }
267 }
268}
269
270#[derive(Serialize, Deserialize, Debug, Clone)]
271pub struct SymbolPaint {
272 #[serde(rename = "text-field")]
273 #[serde(skip_serializing_if = "Option::is_none")]
274 pub text_field: Option<String>,
275
276 #[serde(rename = "text-size")]
277 #[serde(
278 default,
279 deserialize_with = "StyleProperty::<f32>::deserialize_f32_or_none"
280 )]
281 #[serde(skip_serializing_if = "Option::is_none")]
282 pub text_size: Option<StyleProperty<f32>>,
283 }
285
286fn extract_text_field_property(template: &str) -> String {
289 let trimmed = template.trim();
290 if trimmed.starts_with('{') && trimmed.ends_with('}') {
291 trimmed[1..trimmed.len() - 1].to_string()
292 } else {
293 trimmed.to_string()
294 }
295}
296
297fn parse_text_field_from_layout(layout: &serde_json::Value) -> Option<String> {
302 let tf = layout.get("text-field")?;
303 if let Some(s) = tf.as_str() {
304 return Some(extract_text_field_property(s));
305 }
306 if let Some(stops) = tf.get("stops").and_then(|v| v.as_array()) {
308 if let Some(last_stop) = stops.last() {
309 if let Some(s) = last_stop.get(1).and_then(|v| v.as_str()) {
310 return Some(extract_text_field_property(s));
311 }
312 }
313 }
314 None
315}
316
317fn parse_text_size_from_layout(layout: &serde_json::Value) -> Option<StyleProperty<f32>> {
320 let ts = layout.get("text-size")?;
321 if let Some(f) = ts.as_f64() {
322 return Some(StyleProperty::Constant(f as f32));
323 }
324 if ts.is_object() || ts.is_array() {
326 return Some(StyleProperty::Expression(ts.clone()));
327 }
328 None
329}
330
331#[derive(Serialize, Deserialize, Debug, Clone)]
333#[serde(tag = "type", content = "paint")]
334pub enum LayerPaint {
335 #[serde(rename = "background")]
336 Background(BackgroundPaint),
337 #[serde(rename = "line")]
338 Line(LinePaint),
339 #[serde(rename = "fill")]
340 Fill(FillPaint),
341 #[serde(rename = "raster")]
342 Raster(RasterPaint),
343 #[serde(rename = "symbol")]
344 Symbol(SymbolPaint),
345}
346
347impl LayerPaint {
348 pub fn get_color(&self) -> Option<Alpha<EncodedSrgb<f32>>> {
349 match self {
350 LayerPaint::Background(paint) => paint.background_color.as_ref().and_then(|property| {
351 if let StyleProperty::Constant(color) = property {
352 Some(color.clone().into())
353 } else {
354 None }
356 }),
357 LayerPaint::Line(paint) => paint.line_color.as_ref().and_then(|property| {
358 if let StyleProperty::Constant(color) = property {
359 Some(color.clone().into())
360 } else {
361 None
362 }
363 }),
364 LayerPaint::Fill(paint) => paint.fill_color.as_ref().and_then(|property| {
365 if let StyleProperty::Constant(color) = property {
366 Some(color.clone().into())
367 } else {
368 None
369 }
370 }),
371 LayerPaint::Raster(_) => None,
372 LayerPaint::Symbol(_) => None,
373 }
374 }
375}
376
377#[derive(Debug, Clone)]
379pub struct StyleLayer {
380 pub index: u32,
381 pub id: String,
382 pub type_: String,
383 pub filter: Option<serde_json::Value>,
384 pub maxzoom: Option<u8>,
385 pub minzoom: Option<u8>,
386 pub metadata: Option<HashMap<String, String>>,
387 pub paint: Option<LayerPaint>,
388 pub source: Option<String>,
389 pub source_layer: Option<String>,
390}
391
392impl Serialize for StyleLayer {
393 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
394 where
395 S: serde::Serializer,
396 {
397 use serde::ser::SerializeMap;
398 let mut count = 2; if self.filter.is_some() {
401 count += 1;
402 }
403 if self.maxzoom.is_some() {
404 count += 1;
405 }
406 if self.minzoom.is_some() {
407 count += 1;
408 }
409 if self.metadata.is_some() {
410 count += 1;
411 }
412 if self.paint.is_some() {
413 count += 1;
414 }
415 if self.source.is_some() {
416 count += 1;
417 }
418 if self.source_layer.is_some() {
419 count += 1;
420 }
421 let mut map = serializer.serialize_map(Some(count))?;
422 map.serialize_entry("id", &self.id)?;
423 map.serialize_entry("type", &self.type_)?;
424 if let Some(ref filter) = self.filter {
425 map.serialize_entry("filter", filter)?;
426 }
427 if let Some(ref maxzoom) = self.maxzoom {
428 map.serialize_entry("maxzoom", maxzoom)?;
429 }
430 if let Some(ref minzoom) = self.minzoom {
431 map.serialize_entry("minzoom", minzoom)?;
432 }
433 if let Some(ref metadata) = self.metadata {
434 map.serialize_entry("metadata", metadata)?;
435 }
436 if let Some(ref paint) = self.paint {
437 match paint {
439 LayerPaint::Background(p) => map.serialize_entry("paint", p)?,
440 LayerPaint::Line(p) => map.serialize_entry("paint", p)?,
441 LayerPaint::Fill(p) => map.serialize_entry("paint", p)?,
442 LayerPaint::Raster(p) => map.serialize_entry("paint", p)?,
443 LayerPaint::Symbol(p) => map.serialize_entry("paint", p)?,
444 }
445 }
446 if let Some(ref source) = self.source {
447 map.serialize_entry("source", source)?;
448 }
449 if let Some(ref source_layer) = self.source_layer {
450 map.serialize_entry("source-layer", source_layer)?;
451 }
452 map.end()
453 }
454}
455
456#[derive(Deserialize)]
457struct StyleLayerDef {
458 id: String,
459 #[serde(rename = "type")]
460 type_: String,
461 filter: Option<serde_json::Value>,
462 maxzoom: Option<u8>,
463 minzoom: Option<u8>,
464 metadata: Option<HashMap<String, String>>,
465 source: Option<String>,
466 #[serde(rename = "source-layer")]
467 source_layer: Option<String>,
468 paint: Option<serde_json::Value>,
469 layout: Option<serde_json::Value>,
470}
471
472impl<'de> serde::Deserialize<'de> for StyleLayer {
473 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
474 where
475 D: serde::Deserializer<'de>,
476 {
477 let def = StyleLayerDef::deserialize(deserializer)?;
478
479 let paint = if let Some(p) = def.paint {
480 match def.type_.as_str() {
481 "background" => serde_json::from_value(p.clone())
482 .map(LayerPaint::Background)
483 .ok(),
484 "line" => serde_json::from_value(p.clone())
485 .map(LayerPaint::Line)
486 .map_err(|e| log::error!("line paint failed {}: {:?}", def.id, e))
487 .ok(),
488 "fill" => serde_json::from_value(p.clone())
489 .map(LayerPaint::Fill)
490 .map_err(|e| log::error!("fill paint failed {}: {:?}", def.id, e))
491 .ok(),
492 "raster" => serde_json::from_value(p.clone())
493 .map(LayerPaint::Raster)
494 .ok(),
495 "symbol" => {
496 let mut paint: Option<SymbolPaint> = serde_json::from_value(p.clone())
497 .map_err(|e| log::error!("symbol paint failed {}: {:?}", def.id, e))
498 .ok();
499 if let (Some(sp), Some(layout)) = (paint.as_mut(), def.layout.as_ref()) {
501 if sp.text_field.is_none() {
502 sp.text_field = parse_text_field_from_layout(layout);
503 }
504 if sp.text_size.is_none() {
505 sp.text_size = parse_text_size_from_layout(layout);
506 }
507 }
508 paint.map(LayerPaint::Symbol)
509 }
510 _ => None,
511 }
512 } else if def.type_ == "symbol" {
513 let text_field = def.layout.as_ref().and_then(parse_text_field_from_layout);
515 let text_size = def.layout.as_ref().and_then(parse_text_size_from_layout);
516 Some(LayerPaint::Symbol(SymbolPaint {
517 text_field,
518 text_size,
519 }))
520 } else {
521 None
522 };
523
524 Ok(StyleLayer {
525 index: 0,
526 id: def.id,
527 type_: def.type_,
528 filter: def.filter,
529 maxzoom: def.maxzoom,
530 minzoom: def.minzoom,
531 metadata: def.metadata,
532 paint,
533 source: def.source,
534 source_layer: def.source_layer,
535 })
536 }
537}
538
539impl Eq for StyleLayer {}
540impl PartialEq for StyleLayer {
541 fn eq(&self, other: &Self) -> bool {
542 self.id.eq(&other.id)
543 }
544}
545
546impl Hash for StyleLayer {
547 fn hash<H: Hasher>(&self, state: &mut H) {
548 self.id.hash(state)
549 }
550}
551
552impl Default for StyleLayer {
553 fn default() -> Self {
554 Self {
555 index: 0,
556 id: "id".to_string(),
557 type_: "background".to_string(),
558 filter: None,
559 maxzoom: None,
560 minzoom: None,
561 metadata: None,
562 paint: None,
563 source: None,
564 source_layer: Some("does not exist".to_string()),
565 }
566 }
567}
568
569#[cfg(test)]
570mod tests {
571 use super::*;
572
573 #[test]
574 fn test_evaluate_match_missing_property_returns_fallback() {
575 let json = r#"
576 [
577 "match",
578 ["get", "ADM0_A3"],
579 ["ARM", "ATG"],
580 "rgba(1, 2, 3, 1)",
581 "rgba(9, 9, 9, 1)"
582 ]
583 "#;
584 let expr: serde_json::Value = serde_json::from_str(json).unwrap();
585 let prop: StyleProperty<csscolorparser::Color> = StyleProperty::Expression(expr);
586
587 let empty_props = HashMap::new();
589 let color = prop.evaluate(&empty_props).unwrap();
590 assert_eq!(color.to_rgba8(), [9, 9, 9, 255]);
591 }
592
593 #[test]
594 fn test_evaluate_match() {
595 let json = r#"
596 [
597 "match",
598 ["get", "ADM0_A3"],
599 ["ARM", "ATG"],
600 "rgba(1, 2, 3, 1)",
601 "rgba(0, 0, 0, 1)"
602 ]
603 "#;
604 let expr: serde_json::Value = serde_json::from_str(json).unwrap();
605 let prop: StyleProperty<csscolorparser::Color> = StyleProperty::Expression(expr);
606
607 let mut feature_properties = HashMap::new();
608 feature_properties.insert("ADM0_A3".to_string(), "ARM".to_string());
609
610 let color = prop.evaluate(&feature_properties).unwrap();
611 assert_eq!(color.to_rgba8(), [1, 2, 3, 255]);
612 }
613
614 #[test]
615 fn test_symbol_text_field_from_layout() {
616 let json = r#"{
617 "id": "countries-label",
618 "type": "symbol",
619 "paint": {
620 "text-color": "rgba(8, 37, 77, 1)"
621 },
622 "layout": {
623 "text-field": "{NAME}",
624 "text-font": ["Open Sans Semibold"]
625 },
626 "source": "maplibre",
627 "source-layer": "centroids"
628 }"#;
629 let layer: StyleLayer = serde_json::from_str(json).unwrap();
630 assert_eq!(layer.type_, "symbol");
631 match &layer.paint {
632 Some(LayerPaint::Symbol(sp)) => {
633 assert_eq!(sp.text_field.as_deref(), Some("NAME"));
634 }
635 other => panic!("expected Symbol paint, got {:?}", other),
636 }
637 }
638
639 #[test]
640 fn test_symbol_text_field_zoom_dependent() {
641 let json = r#"{
642 "id": "test-label",
643 "type": "symbol",
644 "paint": {},
645 "layout": {
646 "text-field": {"stops": [[2, "{ABBREV}"], [4, "{NAME}"]]}
647 },
648 "source": "maplibre",
649 "source-layer": "centroids"
650 }"#;
651 let layer: StyleLayer = serde_json::from_str(json).unwrap();
652 match &layer.paint {
653 Some(LayerPaint::Symbol(sp)) => {
654 assert_eq!(sp.text_field.as_deref(), Some("NAME"));
656 }
657 other => panic!("expected Symbol paint, got {:?}", other),
658 }
659 }
660
661 #[test]
662 fn test_demotiles_symbol_layers_have_text_field() {
663 let style: crate::style::Style = Default::default();
664 for layer in &style.layers {
665 if layer.type_ == "symbol" {
666 match &layer.paint {
667 Some(LayerPaint::Symbol(sp)) => {
668 assert!(
669 sp.text_field.is_some(),
670 "symbol layer '{}' should have text_field parsed from layout",
671 layer.id
672 );
673 }
674 _ => panic!("symbol layer '{}' has no Symbol paint", layer.id),
675 }
676 }
677 }
678 }
679}