1#![cfg(feature = "style-json")]
2
3use crate::style::{
11 BackgroundStyleLayer, CircleStyleLayer, FillExtrusionStyleLayer, FillStyleLayer,
12 HeatmapStyleLayer, HillshadeStyleLayer, LineStyleLayer, ModelStyleLayer, RasterStyleLayer,
13 StyleDocument, StyleError, StyleLayer, StyleLayerMeta, StyleProjection, StyleSource, StyleSourceId,
14 StyleSourceKind, StyleValue, SymbolStyleLayer, VectorStyleLayer,
15};
16use crate::symbols::{
17 SymbolAnchor, SymbolIconTextFit, SymbolPlacement, SymbolTextJustify, SymbolTextTransform,
18 SymbolWritingMode,
19};
20use serde::{Deserialize, Serialize};
21use serde_json::{Map, Value};
22use std::collections::HashMap;
23use std::fmt;
24
25#[derive(Debug)]
27pub enum StyleSpecError {
28 Json(serde_json::Error),
30 MissingRuntimeSource(String),
32 RuntimeSourceKindMismatch {
34 source_id: String,
36 declared: StyleSourceKind,
38 actual: StyleSourceKind,
40 },
41 MissingLayerSource(String),
43 InvalidField {
45 id: String,
47 field: &'static str,
49 expected: &'static str,
51 },
52 Runtime(StyleError),
54}
55
56impl fmt::Display for StyleSpecError {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 match self {
59 StyleSpecError::Json(err) => write!(f, "style JSON parse error: {err}"),
60 StyleSpecError::MissingRuntimeSource(id) => {
61 write!(f, "missing runtime source binding for style source `{id}`")
62 }
63 StyleSpecError::RuntimeSourceKindMismatch {
64 source_id,
65 declared,
66 actual,
67 } => write!(
68 f,
69 "runtime source `{source_id}` kind mismatch: declared `{}`, bound `{}`",
70 declared.as_str(),
71 actual.as_str()
72 ),
73 StyleSpecError::MissingLayerSource(id) => {
74 write!(f, "style layer `{id}` requires a `source` field")
75 }
76 StyleSpecError::InvalidField { id, field, expected } => {
77 write!(f, "invalid field `{field}` in `{id}`, expected {expected}")
78 }
79 StyleSpecError::Runtime(err) => err.fmt(f),
80 }
81 }
82}
83
84impl std::error::Error for StyleSpecError {}
85
86impl From<serde_json::Error> for StyleSpecError {
87 fn from(value: serde_json::Error) -> Self {
88 Self::Json(value)
89 }
90}
91
92impl From<StyleError> for StyleSpecError {
93 fn from(value: StyleError) -> Self {
94 Self::Runtime(value)
95 }
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct StyleSpecDocument {
101 #[serde(default = "default_style_version")]
103 pub version: u8,
104 #[serde(default)]
106 pub name: Option<String>,
107 #[serde(default)]
109 pub sources: HashMap<StyleSourceId, StyleSpecSource>,
110 #[serde(default)]
112 pub layers: Vec<StyleSpecLayer>,
113 #[serde(default)]
115 pub terrain: Option<StyleSpecTerrain>,
116 #[serde(default)]
118 pub projection: Option<StyleSpecProjection>,
119}
120
121impl StyleSpecDocument {
122 pub fn from_json(json: &str) -> Result<Self, StyleSpecError> {
124 Ok(serde_json::from_str(json)?)
125 }
126
127 pub fn resolve(&self, registry: &StyleSourceRegistry) -> Result<StyleDocument, StyleSpecError> {
129 let mut document = StyleDocument::new();
130
131 for (id, source_spec) in &self.sources {
132 let runtime = registry
133 .source(id)
134 .ok_or_else(|| StyleSpecError::MissingRuntimeSource(id.to_string()))?;
135 if runtime.kind() != source_spec.source_type.runtime_kind() {
136 return Err(StyleSpecError::RuntimeSourceKindMismatch {
137 source_id: id.to_string(),
138 declared: source_spec.source_type.runtime_kind(),
139 actual: runtime.kind(),
140 });
141 }
142 document.add_source(id.clone(), runtime.clone())?;
143 }
144
145 if let Some(terrain) = &self.terrain {
146 document.set_terrain_source(Some(terrain.source.clone()));
147 }
148
149 if let Some(projection) = &self.projection {
150 document.set_projection(projection.projection_type.runtime_projection());
151 }
152
153 for layer in &self.layers {
154 document.add_layer(layer.to_runtime_layer()?)?;
155 }
156
157 Ok(document)
158 }
159}
160
161#[derive(Debug, Clone, Default)]
163pub struct StyleSourceRegistry {
164 sources: HashMap<StyleSourceId, StyleSource>,
165}
166
167impl StyleSourceRegistry {
168 pub fn new() -> Self {
170 Self::default()
171 }
172
173 pub fn set_source(&mut self, id: impl Into<String>, source: StyleSource) {
175 self.sources.insert(id.into(), source);
176 }
177
178 pub fn source(&self, id: &str) -> Option<&StyleSource> {
180 self.sources.get(id)
181 }
182
183 pub fn resolve_document(&self, spec: &StyleSpecDocument) -> Result<StyleDocument, StyleSpecError> {
185 spec.resolve(self)
186 }
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct StyleSpecTerrain {
192 pub source: StyleSourceId,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct StyleSpecProjection {
199 #[serde(rename = "type")]
201 pub projection_type: StyleSpecProjectionType,
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
206pub enum StyleSpecProjectionType {
207 #[serde(rename = "mercator")]
209 Mercator,
210 #[serde(rename = "equirectangular")]
212 Equirectangular,
213 #[serde(rename = "globe")]
215 Globe,
216 #[serde(rename = "vertical-perspective")]
218 VerticalPerspective,
219}
220
221impl StyleSpecProjectionType {
222 fn runtime_projection(self) -> StyleProjection {
223 match self {
224 StyleSpecProjectionType::Mercator => StyleProjection::Mercator,
225 StyleSpecProjectionType::Equirectangular => StyleProjection::Equirectangular,
226 StyleSpecProjectionType::Globe => StyleProjection::Globe,
227 StyleSpecProjectionType::VerticalPerspective => StyleProjection::VerticalPerspective,
228 }
229 }
230}
231
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
234pub enum StyleSpecSourceType {
235 #[serde(rename = "raster")]
237 Raster,
238 #[serde(rename = "raster-dem")]
240 RasterDem,
241 #[serde(rename = "geojson")]
243 GeoJson,
244 #[serde(rename = "vector")]
246 Vector,
247 #[serde(rename = "image")]
249 Image,
250 #[serde(rename = "video")]
252 Video,
253 #[serde(rename = "canvas")]
255 Canvas,
256 #[serde(rename = "model")]
258 Model,
259}
260
261impl StyleSpecSourceType {
262 fn runtime_kind(self) -> StyleSourceKind {
263 match self {
264 StyleSpecSourceType::Raster => StyleSourceKind::Raster,
265 StyleSpecSourceType::RasterDem => StyleSourceKind::Terrain,
266 StyleSpecSourceType::GeoJson => StyleSourceKind::GeoJson,
267 StyleSpecSourceType::Vector => StyleSourceKind::VectorTile,
268 StyleSpecSourceType::Image => StyleSourceKind::Image,
269 StyleSpecSourceType::Video => StyleSourceKind::Video,
270 StyleSpecSourceType::Canvas => StyleSourceKind::Canvas,
271 StyleSpecSourceType::Model => StyleSourceKind::Model,
272 }
273 }
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct StyleSpecSource {
279 #[serde(rename = "type")]
281 pub source_type: StyleSpecSourceType,
282 #[serde(default)]
284 pub url: Option<String>,
285 #[serde(default)]
287 pub tiles: Vec<String>,
288 #[serde(default)]
290 pub metadata: HashMap<String, Value>,
291}
292
293#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
295pub enum StyleSpecLayerType {
296 #[serde(rename = "background")]
297 Background,
299 #[serde(rename = "hillshade")]
300 Hillshade,
302 #[serde(rename = "raster")]
303 Raster,
305 #[serde(rename = "vector")]
306 Vector,
308 #[serde(rename = "fill")]
309 Fill,
311 #[serde(rename = "line")]
312 Line,
314 #[serde(rename = "circle")]
315 Circle,
317 #[serde(rename = "heatmap")]
318 Heatmap,
320 #[serde(rename = "fill-extrusion")]
321 FillExtrusion,
323 #[serde(rename = "symbol")]
324 Symbol,
326 #[serde(rename = "model")]
327 Model,
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct StyleSpecLayer {
334 pub id: String,
336 #[serde(rename = "type")]
338 pub layer_type: StyleSpecLayerType,
339 #[serde(default)]
341 pub source: Option<StyleSourceId>,
342 #[serde(default, rename = "source-layer")]
344 pub source_layer: Option<String>,
345 #[serde(default)]
347 pub minzoom: Option<f32>,
348 #[serde(default)]
350 pub maxzoom: Option<f32>,
351 #[serde(default)]
353 pub layout: Map<String, Value>,
354 #[serde(default)]
356 pub paint: Map<String, Value>,
357}
358
359impl StyleSpecLayer {
360 fn to_runtime_layer(&self) -> Result<StyleLayer, StyleSpecError> {
361 let meta = self.meta()?;
362 let layer = match self.layer_type {
363 StyleSpecLayerType::Background => {
364 let mut layer = BackgroundStyleLayer::new(
365 self.id.clone(),
366 paint_color(&self.paint, "background-color")?
367 .unwrap_or_else(|| [0.0, 0.0, 0.0, 1.0].into()),
368 );
369 layer.meta = meta;
370 StyleLayer::Background(layer)
371 }
372 StyleSpecLayerType::Hillshade => {
373 let mut layer = HillshadeStyleLayer::new(self.id.clone());
374 layer.meta = meta;
375 if let Some(value) = paint_color(&self.paint, "hillshade-highlight-color")? {
376 layer.highlight_color = value;
377 }
378 if let Some(value) = paint_color(&self.paint, "hillshade-shadow-color")? {
379 layer.shadow_color = value;
380 }
381 if let Some(value) = paint_color(&self.paint, "hillshade-accent-color")? {
382 layer.accent_color = value;
383 }
384 if let Some(value) = paint_f32(&self.paint, "hillshade-illumination-direction")? {
385 layer.illumination_direction_deg = value;
386 }
387 if let Some(value) = paint_f32(&self.paint, "hillshade-illumination-altitude")? {
388 layer.illumination_altitude_deg = value;
389 }
390 if let Some(value) = paint_f32(&self.paint, "hillshade-exaggeration")? {
391 layer.exaggeration = value;
392 }
393 StyleLayer::Hillshade(layer)
394 }
395 StyleSpecLayerType::Raster => {
396 let source = self.required_source()?;
397 let mut layer = RasterStyleLayer::new(self.id.clone(), source);
398 layer.meta = meta;
399 StyleLayer::Raster(layer)
400 }
401 StyleSpecLayerType::Vector => {
402 let source = self.required_source()?;
403 let mut layer = VectorStyleLayer::new(self.id.clone(), source);
404 layer.meta = meta;
405 layer.source_layer = self.source_layer.clone();
406 if let Some(value) = paint_color(&self.paint, "fill-color")? {
407 layer.fill_color = value;
408 }
409 if let Some(value) = paint_color(&self.paint, "line-color")? {
410 layer.stroke_color = value;
411 }
412 if let Some(value) = paint_f32(&self.paint, "line-width")? {
413 layer.stroke_width = value;
414 }
415 StyleLayer::Vector(layer)
416 }
417 StyleSpecLayerType::Fill => {
418 let source = self.required_source()?;
419 let mut layer = FillStyleLayer::new(self.id.clone(), source);
420 layer.meta = meta;
421 layer.source_layer = self.source_layer.clone();
422 if let Some(value) = paint_color(&self.paint, "fill-color")? {
423 layer.fill_color = value;
424 }
425 if let Some(value) = paint_color(&self.paint, "fill-outline-color")? {
426 layer.outline_color = value;
427 }
428 if let Some(value) = paint_f32(&self.paint, "fill-outline-width")? {
429 layer.outline_width = value;
430 }
431 StyleLayer::Fill(layer)
432 }
433 StyleSpecLayerType::Line => {
434 let source = self.required_source()?;
435 let mut layer = LineStyleLayer::new(self.id.clone(), source);
436 layer.meta = meta;
437 layer.source_layer = self.source_layer.clone();
438 if let Some(value) = paint_color(&self.paint, "line-color")? {
439 layer.color = value;
440 }
441 if let Some(value) = paint_f32(&self.paint, "line-width")? {
442 layer.width = value;
443 }
444 StyleLayer::Line(layer)
445 }
446 StyleSpecLayerType::Circle => {
447 let source = self.required_source()?;
448 let mut layer = CircleStyleLayer::new(self.id.clone(), source);
449 layer.meta = meta;
450 layer.source_layer = self.source_layer.clone();
451 if let Some(value) = paint_color(&self.paint, "circle-color")? {
452 layer.color = value;
453 }
454 if let Some(value) = paint_f32(&self.paint, "circle-radius")? {
455 layer.radius = value;
456 }
457 if let Some(value) = paint_color(&self.paint, "circle-stroke-color")? {
458 layer.stroke_color = value;
459 }
460 if let Some(value) = paint_f32(&self.paint, "circle-stroke-width")? {
461 layer.stroke_width = value;
462 }
463 StyleLayer::Circle(layer)
464 }
465 StyleSpecLayerType::Heatmap => {
466 let source = self.required_source()?;
467 let mut layer = HeatmapStyleLayer::new(self.id.clone(), source);
468 layer.meta = meta;
469 layer.source_layer = self.source_layer.clone();
470 if let Some(value) = paint_color(&self.paint, "heatmap-color")? {
471 layer.color = value;
472 }
473 if let Some(value) = paint_f32(&self.paint, "heatmap-radius")? {
474 layer.radius = value;
475 }
476 if let Some(value) = paint_f32(&self.paint, "heatmap-intensity")? {
477 layer.intensity = value;
478 }
479 StyleLayer::Heatmap(layer)
480 }
481 StyleSpecLayerType::FillExtrusion => {
482 let source = self.required_source()?;
483 let mut layer = FillExtrusionStyleLayer::new(self.id.clone(), source);
484 layer.meta = meta;
485 layer.source_layer = self.source_layer.clone();
486 if let Some(value) = paint_color(&self.paint, "fill-extrusion-color")? {
487 layer.color = value;
488 }
489 if let Some(value) = paint_f32(&self.paint, "fill-extrusion-base")? {
490 layer.base = value;
491 }
492 if let Some(value) = paint_f32(&self.paint, "fill-extrusion-height")? {
493 layer.height = value;
494 }
495 StyleLayer::FillExtrusion(layer)
496 }
497 StyleSpecLayerType::Symbol => {
498 let source = self.required_source()?;
499 let mut layer = SymbolStyleLayer::new(self.id.clone(), source);
500 layer.meta = meta;
501 layer.source_layer = self.source_layer.clone();
502 if let Some(value) = paint_color(&self.paint, "text-color")?
503 .or_else(|| paint_color(&self.paint, "icon-color").ok().flatten())
504 {
505 layer.color = value;
506 }
507 if let Some(value) = paint_color(&self.paint, "text-halo-color")? {
508 layer.halo_color = value;
509 }
510 if let Some(value) = paint_f32(&self.layout, "text-size")?
511 .or_else(|| paint_f32(&self.layout, "icon-size").ok().flatten())
512 {
513 layer.size = value;
514 }
515 if let Some(value) = string_value(self.layout.get("text-field"))? {
516 layer.text_field = Some(value);
517 }
518 if let Some(value) = string_value(self.layout.get("icon-image"))? {
519 layer.icon_image = Some(value);
520 }
521 if let Some(value) = string_value(self.layout.get("text-font")).or_else(|_| first_string_from_array(self.layout.get("text-font")))? {
522 layer.font_stack = value;
523 }
524 if let Some(value) = paint_f32(&self.layout, "text-padding")? {
525 layer.padding = value;
526 }
527 if let Some(value) = bool_value(self.layout.get("text-allow-overlap"))? {
528 layer.text_allow_overlap = Some(value);
529 }
530 if let Some(value) = bool_value(self.layout.get("icon-allow-overlap"))? {
531 layer.icon_allow_overlap = Some(value);
532 }
533 if let Some(value) = bool_value(self.layout.get("text-optional"))? {
534 layer.text_optional = Some(value);
535 }
536 if let Some(value) = bool_value(self.layout.get("icon-optional"))? {
537 layer.icon_optional = Some(value);
538 }
539 if let Some(value) = bool_value(self.layout.get("text-ignore-placement"))? {
540 layer.text_ignore_placement = Some(value);
541 }
542 if let Some(value) = bool_value(self.layout.get("icon-ignore-placement"))? {
543 layer.icon_ignore_placement = Some(value);
544 }
545 if let Some(value) = paint_f32(&self.layout, "text-radial-offset")? {
546 layer.radial_offset = Some(value);
547 }
548 if let Some(value) = symbol_anchor_offset_array(self.layout.get("text-variable-anchor-offset"))? {
549 layer.variable_anchor_offsets = Some(value);
550 }
551 if let Some(value) = symbol_anchor(self.layout.get("text-anchor"))? {
552 layer.anchor = value;
553 }
554 if let Some(value) = symbol_text_justify(self.layout.get("text-justify"))? {
555 layer.justify = value;
556 }
557 if let Some(value) = symbol_text_transform(self.layout.get("text-transform"))? {
558 layer.transform = value;
559 }
560 if let Some(value) = paint_f32(&self.layout, "text-max-width")? {
561 layer.max_width = Some(value);
562 }
563 if let Some(value) = paint_f32(&self.layout, "text-line-height")? {
564 layer.line_height = Some(value);
565 }
566 if let Some(value) = paint_f32(&self.layout, "text-letter-spacing")? {
567 layer.letter_spacing = Some(value);
568 }
569 if let Some(value) = symbol_icon_text_fit(self.layout.get("icon-text-fit"))? {
570 layer.icon_text_fit = value;
571 }
572 if let Some(value) = vec4_value(self.layout.get("icon-text-fit-padding"), "icon-text-fit-padding")? {
573 layer.icon_text_fit_padding = value;
574 }
575 if let Some(value) = paint_f32(&self.layout, "symbol-sort-key")? {
576 layer.sort_key = Some(value);
577 }
578 if let Some(value) = symbol_placement(self.layout.get("symbol-placement"))? {
579 layer.placement = value;
580 }
581 if let Some(value) = paint_f32(&self.layout, "symbol-spacing")? {
582 layer.spacing = value;
583 }
584 if let Some(value) = paint_f32(&self.layout, "text-max-angle")? {
585 layer.max_angle = value;
586 }
587 if let Some(value) = bool_value(self.layout.get("text-keep-upright"))? {
588 layer.keep_upright = value;
589 }
590 if let Some(value) = symbol_anchor_array(self.layout.get("text-variable-anchor"))? {
591 layer.variable_anchors = value;
592 }
593 if let Some(value) = symbol_writing_mode(self.layout.get("text-writing-mode"))? {
594 layer.writing_mode = value;
595 }
596 if let Some(value) = vec2_value(self.layout.get("text-offset"))? {
597 layer.offset = value;
598 }
599 StyleLayer::Symbol(layer)
600 }
601 StyleSpecLayerType::Model => {
602 let source = self.required_source()?;
603 let mut layer = ModelStyleLayer::new(self.id.clone(), source);
604 layer.meta = meta;
605 StyleLayer::Model(layer)
606 }
607 };
608 Ok(layer)
609 }
610
611 fn meta(&self) -> Result<StyleLayerMeta, StyleSpecError> {
612 let mut meta = StyleLayerMeta::new(self.id.clone());
613 meta.min_zoom = self.minzoom;
614 meta.max_zoom = self.maxzoom;
615
616 if let Some(value) = self.opacity_value()? {
617 meta.opacity = value;
618 }
619
620 if let Some(visibility) = self.layout.get("visibility") {
621 let visible = match visibility.as_str() {
622 Some("visible") | None => true,
623 Some("none") => false,
624 Some(_) => {
625 return Err(StyleSpecError::InvalidField {
626 id: self.id.clone(),
627 field: "layout.visibility",
628 expected: "`visible` or `none`",
629 })
630 }
631 };
632 meta.visible = visible.into();
633 }
634
635 Ok(meta)
636 }
637
638 fn opacity_value(&self) -> Result<Option<StyleValue<f32>>, StyleSpecError> {
639 let keys = match self.layer_type {
640 StyleSpecLayerType::Background => &["background-opacity"][..],
641 StyleSpecLayerType::Hillshade => &["hillshade-opacity"],
642 StyleSpecLayerType::Raster => &["raster-opacity"],
643 StyleSpecLayerType::Vector => &["opacity"],
644 StyleSpecLayerType::Fill => &["fill-opacity"],
645 StyleSpecLayerType::Line => &["line-opacity"],
646 StyleSpecLayerType::Circle => &["circle-opacity"],
647 StyleSpecLayerType::Heatmap => &["heatmap-opacity"],
648 StyleSpecLayerType::FillExtrusion => &["fill-extrusion-opacity"],
649 StyleSpecLayerType::Symbol => &["text-opacity", "icon-opacity"],
650 StyleSpecLayerType::Model => &["model-opacity"],
651 };
652
653 for key in keys {
654 if let Some(value) = self.paint.get(*key) {
655 return Ok(Some(f32_value_from_json(&self.id, key, value)?));
656 }
657 }
658 Ok(None)
659 }
660
661 fn required_source(&self) -> Result<String, StyleSpecError> {
662 self.source
663 .clone()
664 .ok_or_else(|| StyleSpecError::MissingLayerSource(self.id.clone()))
665 }
666}
667
668pub fn parse_style_json(json: &str) -> Result<StyleSpecDocument, StyleSpecError> {
670 StyleSpecDocument::from_json(json)
671}
672
673fn paint_color(
674 map: &Map<String, Value>,
675 key: &'static str,
676) -> Result<Option<StyleValue<[f32; 4]>>, StyleSpecError> {
677 map.get(key)
678 .map(|value| color_value_from_json(key, key, value))
679 .transpose()
680}
681
682fn paint_f32(map: &Map<String, Value>, key: &'static str) -> Result<Option<StyleValue<f32>>, StyleSpecError> {
683 map.get(key)
684 .map(|value| f32_value_from_json(key, key, value))
685 .transpose()
686}
687
688fn string_value(value: Option<&Value>) -> Result<Option<StyleValue<String>>, StyleSpecError> {
689 value
690 .map(|value| string_value_from_json("symbol", "text-field", value))
691 .transpose()
692}
693
694fn bool_value(value: Option<&Value>) -> Result<Option<StyleValue<bool>>, StyleSpecError> {
695 value
696 .map(|value| bool_value_from_json("symbol", "allow-overlap", value))
697 .transpose()
698}
699
700fn first_string_from_array(value: Option<&Value>) -> Result<Option<StyleValue<String>>, StyleSpecError> {
701 let Some(value) = value else {
702 return Ok(None);
703 };
704 let Some(array) = value.as_array() else {
705 return Err(StyleSpecError::InvalidField {
706 id: "symbol".into(),
707 field: "text-font",
708 expected: "string or string array",
709 });
710 };
711 let Some(first) = array.first() else {
712 return Ok(None);
713 };
714 Ok(Some(string_value_from_json("symbol", "text-font", first)?))
715}
716
717fn vec2_value(value: Option<&Value>) -> Result<Option<[f32; 2]>, StyleSpecError> {
718 let Some(value) = value else {
719 return Ok(None);
720 };
721 let Some(array) = value.as_array() else {
722 return Err(StyleSpecError::InvalidField {
723 id: "symbol".into(),
724 field: "text-offset",
725 expected: "two-element numeric array",
726 });
727 };
728 if array.len() != 2 {
729 return Err(StyleSpecError::InvalidField {
730 id: "symbol".into(),
731 field: "text-offset",
732 expected: "two-element numeric array",
733 });
734 }
735 Ok(Some([
736 array[0].as_f64().ok_or_else(|| StyleSpecError::InvalidField {
737 id: "symbol".into(),
738 field: "text-offset",
739 expected: "two-element numeric array",
740 })? as f32,
741 array[1].as_f64().ok_or_else(|| StyleSpecError::InvalidField {
742 id: "symbol".into(),
743 field: "text-offset",
744 expected: "two-element numeric array",
745 })? as f32,
746 ]))
747}
748
749fn symbol_anchor_array(value: Option<&Value>) -> Result<Option<Vec<SymbolAnchor>>, StyleSpecError> {
750 let Some(value) = value else {
751 return Ok(None);
752 };
753 let Some(array) = value.as_array() else {
754 return Err(StyleSpecError::InvalidField {
755 id: "symbol".into(),
756 field: "text-variable-anchor",
757 expected: "string array",
758 });
759 };
760 let mut anchors = Vec::with_capacity(array.len());
761 for value in array {
762 let Some(text) = value.as_str() else {
763 return Err(StyleSpecError::InvalidField {
764 id: "symbol".into(),
765 field: "text-variable-anchor",
766 expected: "string array",
767 });
768 };
769 anchors.push(parse_symbol_anchor(text)?);
770 }
771 Ok(Some(anchors))
772}
773
774fn symbol_anchor_offset_array(
775 value: Option<&Value>,
776) -> Result<Option<Vec<(SymbolAnchor, [f32; 2])>>, StyleSpecError> {
777 let Some(value) = value else {
778 return Ok(None);
779 };
780 let Some(array) = value.as_array() else {
781 return Err(StyleSpecError::InvalidField {
782 id: "symbol".into(),
783 field: "text-variable-anchor-offset",
784 expected: "alternating anchor / [x, y] array",
785 });
786 };
787 if array.len() % 2 != 0 {
788 return Err(StyleSpecError::InvalidField {
789 id: "symbol".into(),
790 field: "text-variable-anchor-offset",
791 expected: "alternating anchor / [x, y] array",
792 });
793 }
794
795 let mut parsed = Vec::with_capacity(array.len() / 2);
796 for pair in array.chunks_exact(2) {
797 let anchor_text = pair[0].as_str().ok_or_else(|| StyleSpecError::InvalidField {
798 id: "symbol".into(),
799 field: "text-variable-anchor-offset",
800 expected: "anchor string in alternating anchor / [x, y] array",
801 })?;
802 let anchor = parse_symbol_anchor(anchor_text)?;
803 let offset = parse_vec2_array("symbol", "text-variable-anchor-offset", pair[1].as_array().ok_or_else(|| StyleSpecError::InvalidField {
804 id: "symbol".into(),
805 field: "text-variable-anchor-offset",
806 expected: "[x, y] offset array in alternating anchor / [x, y] array",
807 })?)?;
808 parsed.push((anchor, offset));
809 }
810
811 Ok(Some(parsed))
812}
813
814fn symbol_writing_mode(value: Option<&Value>) -> Result<Option<SymbolWritingMode>, StyleSpecError> {
815 let Some(value) = value else {
816 return Ok(None);
817 };
818 let Some(text) = value.as_str() else {
819 return Err(StyleSpecError::InvalidField {
820 id: "symbol".into(),
821 field: "text-writing-mode",
822 expected: "string value",
823 });
824 };
825 Ok(Some(parse_symbol_writing_mode(text)?))
826}
827
828fn symbol_placement(value: Option<&Value>) -> Result<Option<SymbolPlacement>, StyleSpecError> {
829 let Some(value) = value else {
830 return Ok(None);
831 };
832 let Some(text) = value.as_str() else {
833 return Err(StyleSpecError::InvalidField {
834 id: "symbol".into(),
835 field: "symbol-placement",
836 expected: "string value",
837 });
838 };
839 Ok(Some(parse_symbol_placement(text)?))
840}
841
842fn symbol_anchor(value: Option<&Value>) -> Result<Option<SymbolAnchor>, StyleSpecError> {
843 let Some(value) = value else {
844 return Ok(None);
845 };
846 let Some(text) = value.as_str() else {
847 return Err(StyleSpecError::InvalidField {
848 id: "symbol".into(),
849 field: "text-anchor",
850 expected: "string value",
851 });
852 };
853 Ok(Some(parse_symbol_anchor(text)?))
854}
855
856fn symbol_text_justify(
857 value: Option<&Value>,
858) -> Result<Option<StyleValue<SymbolTextJustify>>, StyleSpecError> {
859 let Some(value) = value else {
860 return Ok(None);
861 };
862 Ok(Some(match value {
863 Value::String(text) => StyleValue::Constant(parse_symbol_text_justify(text)?),
864 Value::Object(object) => parse_zoom_stops("symbol", "text-justify", object, |id, field, value| {
865 let text = value.as_str().ok_or_else(|| StyleSpecError::InvalidField {
866 id: id.to_owned(), field, expected: "string value",
867 })?;
868 parse_symbol_text_justify(text)
869 })?,
870
871 _ => {
872 return Err(StyleSpecError::InvalidField {
873 id: "symbol".into(),
874 field: "text-justify",
875 expected: "string value or `{ stops: ... }`",
876 })
877 }
878 }))
879}
880
881fn symbol_icon_text_fit(
882 value: Option<&Value>,
883) -> Result<Option<StyleValue<SymbolIconTextFit>>, StyleSpecError> {
884 let Some(value) = value else {
885 return Ok(None);
886 };
887 Ok(Some(match value {
888 Value::String(text) => StyleValue::Constant(parse_symbol_icon_text_fit(text)?),
889 Value::Object(object) => {
890 parse_zoom_stops("symbol", "icon-text-fit", object, |id, field, value| {
891 let text = value.as_str().ok_or_else(|| StyleSpecError::InvalidField {
892 id: id.to_owned(), field, expected: "string value",
893 })?;
894 parse_symbol_icon_text_fit(text)
895 })?
896 }
897 _ => {
898 return Err(StyleSpecError::InvalidField {
899 id: "symbol".into(),
900 field: "icon-text-fit",
901 expected: "string value or `{ stops: ... }`",
902 })
903 }
904 }))
905}
906
907fn symbol_text_transform(
908 value: Option<&Value>,
909) -> Result<Option<StyleValue<SymbolTextTransform>>, StyleSpecError> {
910 let Some(value) = value else {
911 return Ok(None);
912 };
913 Ok(Some(match value {
914 Value::String(text) => StyleValue::Constant(parse_symbol_text_transform(text)?),
915 Value::Object(object) => {
916 parse_zoom_stops("symbol", "text-transform", object, |id, field, value| {
917 let text = value.as_str().ok_or_else(|| StyleSpecError::InvalidField {
918 id: id.to_owned(), field, expected: "string value",
919 })?;
920 parse_symbol_text_transform(text)
921 })?
922 }
923 _ => {
924 return Err(StyleSpecError::InvalidField {
925 id: "symbol".into(),
926 field: "text-transform",
927 expected: "string value or `{ stops: ... }`",
928 })
929 }
930 }))
931}
932
933fn parse_symbol_anchor(text: &str) -> Result<SymbolAnchor, StyleSpecError> {
934 match text {
935 "center" => Ok(SymbolAnchor::Center),
936 "top" => Ok(SymbolAnchor::Top),
937 "bottom" => Ok(SymbolAnchor::Bottom),
938 "left" => Ok(SymbolAnchor::Left),
939 "right" => Ok(SymbolAnchor::Right),
940 "top-left" => Ok(SymbolAnchor::TopLeft),
941 "top-right" => Ok(SymbolAnchor::TopRight),
942 "bottom-left" => Ok(SymbolAnchor::BottomLeft),
943 "bottom-right" => Ok(SymbolAnchor::BottomRight),
944 _ => Err(StyleSpecError::InvalidField {
945 id: "symbol".into(),
946 field: "text-variable-anchor",
947 expected: "known symbol-anchor string",
948 }),
949 }
950}
951
952fn parse_symbol_icon_text_fit(text: &str) -> Result<SymbolIconTextFit, StyleSpecError> {
953 match text {
954 "none" => Ok(SymbolIconTextFit::None),
955 "width" => Ok(SymbolIconTextFit::Width),
956 "height" => Ok(SymbolIconTextFit::Height),
957 "both" => Ok(SymbolIconTextFit::Both),
958 _ => Err(StyleSpecError::InvalidField {
959 id: "symbol".into(),
960 field: "icon-text-fit",
961 expected: "`none`, `width`, `height`, or `both`",
962 }),
963 }
964}
965
966fn parse_symbol_text_transform(text: &str) -> Result<SymbolTextTransform, StyleSpecError> {
967 match text {
968 "none" => Ok(SymbolTextTransform::None),
969 "uppercase" => Ok(SymbolTextTransform::Uppercase),
970 "lowercase" => Ok(SymbolTextTransform::Lowercase),
971 _ => Err(StyleSpecError::InvalidField {
972 id: "symbol".into(),
973 field: "text-transform",
974 expected: "`none`, `uppercase`, or `lowercase`",
975 }),
976 }
977}
978
979fn parse_symbol_text_justify(text: &str) -> Result<SymbolTextJustify, StyleSpecError> {
980 match text {
981 "auto" => Ok(SymbolTextJustify::Auto),
982 "left" => Ok(SymbolTextJustify::Left),
983 "center" => Ok(SymbolTextJustify::Center),
984 "right" => Ok(SymbolTextJustify::Right),
985 _ => Err(StyleSpecError::InvalidField {
986 id: "symbol".into(),
987 field: "text-justify",
988 expected: "`auto`, `left`, `center`, or `right`",
989 }),
990 }
991}
992
993fn parse_symbol_placement(text: &str) -> Result<SymbolPlacement, StyleSpecError> {
994 match text {
995 "point" => Ok(SymbolPlacement::Point),
996 "line" => Ok(SymbolPlacement::Line),
997 _ => Err(StyleSpecError::InvalidField {
998 id: "symbol".into(),
999 field: "symbol-placement",
1000 expected: "`point` or `line`",
1001 }),
1002 }
1003}
1004
1005fn parse_symbol_writing_mode(text: &str) -> Result<SymbolWritingMode, StyleSpecError> {
1006 match text {
1007 "horizontal" => Ok(SymbolWritingMode::Horizontal),
1008 "vertical" => Ok(SymbolWritingMode::Vertical),
1009 _ => Err(StyleSpecError::InvalidField {
1010 id: "symbol".into(),
1011 field: "text-writing-mode",
1012 expected: "`horizontal` or `vertical`",
1013 }),
1014 }
1015}
1016
1017fn bool_value_from_json(
1018 id: &str,
1019 field: &'static str,
1020 value: &Value,
1021) -> Result<StyleValue<bool>, StyleSpecError> {
1022 match value {
1023 Value::Bool(value) => Ok(StyleValue::Constant(*value)),
1024 Value::Object(object) => parse_zoom_stops(id, field, object, parse_bool_constant),
1025 Value::Array(arr) if is_expression_array(arr) => parse_expression_bool(id, field, arr),
1026 _ => Err(StyleSpecError::InvalidField {
1027 id: id.to_owned(),
1028 field,
1029 expected: "boolean value, `{ stops: ... }`, or expression array",
1030 }),
1031 }
1032}
1033
1034fn f32_value_from_json(
1035 id: &str,
1036 field: &'static str,
1037 value: &Value,
1038) -> Result<StyleValue<f32>, StyleSpecError> {
1039 match value {
1040 Value::Number(number) => Ok(StyleValue::Constant(number.as_f64().ok_or_else(|| StyleSpecError::InvalidField {
1041 id: id.to_owned(),
1042 field,
1043 expected: "numeric value",
1044 })? as f32)),
1045 Value::Object(object) => parse_zoom_stops(id, field, object, parse_f32_constant),
1046 Value::Array(arr) if is_expression_array(arr) => parse_expression_f32(id, field, arr),
1047 _ => Err(StyleSpecError::InvalidField {
1048 id: id.to_owned(),
1049 field,
1050 expected: "numeric value, `{ stops: ... }`, or expression array",
1051 }),
1052 }
1053}
1054
1055fn string_value_from_json(
1056 id: &str,
1057 field: &'static str,
1058 value: &Value,
1059) -> Result<StyleValue<String>, StyleSpecError> {
1060 match value {
1061 Value::String(text) => Ok(StyleValue::Constant(text.clone())),
1062 Value::Object(object) => parse_zoom_stops(id, field, object, parse_string_constant),
1063 Value::Array(arr) if is_expression_array(arr) => parse_expression_string(id, field, arr),
1064 _ => Err(StyleSpecError::InvalidField {
1065 id: id.to_owned(),
1066 field,
1067 expected: "string value, `{ stops: ... }`, or expression array",
1068 }),
1069 }
1070}
1071
1072fn color_value_from_json(
1073 id: &str,
1074 field: &'static str,
1075 value: &Value,
1076) -> Result<StyleValue<[f32; 4]>, StyleSpecError> {
1077 match value {
1078 Value::String(text) => Ok(StyleValue::Constant(parse_color_string(id, field, text)?)),
1079 Value::Array(array) if is_expression_array(array) => parse_expression_color(id, field, array),
1080 Value::Array(array) => Ok(StyleValue::Constant(parse_color_array(id, field, array)?)),
1081 Value::Object(object) => parse_zoom_stops(id, field, object, |id, field, value| match value {
1082 Value::String(text) => parse_color_string(id, field, text),
1083 Value::Array(array) => parse_color_array(id, field, array),
1084 _ => Err(StyleSpecError::InvalidField {
1085 id: id.to_owned(),
1086 field,
1087 expected: "color string or RGBA array",
1088 }),
1089 }),
1090 _ => Err(StyleSpecError::InvalidField {
1091 id: id.to_owned(),
1092 field,
1093 expected: "color string, RGBA array, or `{ stops: ... }`",
1094 }),
1095 }
1096}
1097
1098fn parse_zoom_stops<T>(
1099 id: &str,
1100 field: &'static str,
1101 object: &Map<String, Value>,
1102 parse_value: impl Fn(&str, &'static str, &Value) -> Result<T, StyleSpecError>,
1103) -> Result<StyleValue<T>, StyleSpecError> {
1104 let Some(stops) = object.get("stops").and_then(Value::as_array) else {
1105 return Err(StyleSpecError::InvalidField {
1106 id: id.to_owned(),
1107 field,
1108 expected: "object with `stops` array",
1109 });
1110 };
1111
1112 let mut parsed = Vec::with_capacity(stops.len());
1113 for stop in stops {
1114 let pair = stop.as_array().ok_or_else(|| StyleSpecError::InvalidField {
1115 id: id.to_owned(),
1116 field,
1117 expected: "stop tuple `[zoom, value]`",
1118 })?;
1119 if pair.len() != 2 {
1120 return Err(StyleSpecError::InvalidField {
1121 id: id.to_owned(),
1122 field,
1123 expected: "stop tuple `[zoom, value]`",
1124 });
1125 }
1126 let zoom = pair[0].as_f64().ok_or_else(|| StyleSpecError::InvalidField {
1127 id: id.to_owned(),
1128 field,
1129 expected: "numeric stop zoom",
1130 })? as f32;
1131 let value = parse_value(id, field, &pair[1])?;
1132 parsed.push((zoom, value));
1133 }
1134 Ok(StyleValue::ZoomStops(parsed))
1135}
1136
1137fn parse_f32_constant(id: &str, field: &'static str, value: &Value) -> Result<f32, StyleSpecError> {
1138 value.as_f64().map(|v| v as f32).ok_or_else(|| StyleSpecError::InvalidField {
1139 id: id.to_owned(),
1140 field,
1141 expected: "numeric value",
1142 })
1143}
1144
1145fn parse_string_constant(id: &str, field: &'static str, value: &Value) -> Result<String, StyleSpecError> {
1146 value.as_str().map(ToOwned::to_owned).ok_or_else(|| StyleSpecError::InvalidField {
1147 id: id.to_owned(),
1148 field,
1149 expected: "string value",
1150 })
1151}
1152
1153fn parse_bool_constant(id: &str, field: &'static str, value: &Value) -> Result<bool, StyleSpecError> {
1154 value.as_bool().ok_or_else(|| StyleSpecError::InvalidField {
1155 id: id.to_owned(),
1156 field,
1157 expected: "boolean value",
1158 })
1159}
1160
1161fn vec4_value(value: Option<&Value>, field: &'static str) -> Result<Option<[f32; 4]>, StyleSpecError> {
1162 let Some(value) = value else {
1163 return Ok(None);
1164 };
1165 let arr = value.as_array().ok_or_else(|| StyleSpecError::InvalidField {
1166 id: "symbol".into(),
1167 field,
1168 expected: "array of 4 numbers",
1169 })?;
1170 if arr.len() != 4 {
1171 return Err(StyleSpecError::InvalidField {
1172 id: "symbol".into(),
1173 field,
1174 expected: "array of 4 numbers",
1175 });
1176 }
1177 let mut out = [0.0f32; 4];
1178 for (i, v) in arr.iter().enumerate() {
1179 out[i] = v.as_f64().ok_or_else(|| StyleSpecError::InvalidField {
1180 id: "symbol".into(),
1181 field,
1182 expected: "numeric array element",
1183 })? as f32;
1184 }
1185 Ok(Some(out))
1186}
1187
1188fn parse_vec2_array(id: &str, field: &'static str, arr: &[Value]) -> Result<[f32; 2], StyleSpecError> {
1189 if arr.len() != 2 {
1190 return Err(StyleSpecError::InvalidField {
1191 id: id.to_owned(),
1192 field,
1193 expected: "[x, y] array of 2 numbers",
1194 });
1195 }
1196 let x = arr[0].as_f64().ok_or_else(|| StyleSpecError::InvalidField {
1197 id: id.to_owned(),
1198 field,
1199 expected: "numeric value in [x, y] array",
1200 })? as f32;
1201 let y = arr[1].as_f64().ok_or_else(|| StyleSpecError::InvalidField {
1202 id: id.to_owned(),
1203 field,
1204 expected: "numeric value in [x, y] array",
1205 })? as f32;
1206 Ok([x, y])
1207}
1208
1209fn parse_color_array(
1210 id: &str,
1211 field: &'static str,
1212 array: &[Value],
1213) -> Result<[f32; 4], StyleSpecError> {
1214 if !(array.len() == 3 || array.len() == 4) {
1215 return Err(StyleSpecError::InvalidField {
1216 id: id.to_owned(),
1217 field,
1218 expected: "RGB or RGBA array",
1219 });
1220 }
1221 let mut rgba = [0.0, 0.0, 0.0, 1.0];
1222 for (i, value) in array.iter().enumerate() {
1223 rgba[i] = value.as_f64().ok_or_else(|| StyleSpecError::InvalidField {
1224 id: id.to_owned(),
1225 field,
1226 expected: "numeric RGB/RGBA components",
1227 })? as f32;
1228 }
1229 Ok(rgba)
1230}
1231
1232fn parse_color_string(
1233 id: &str,
1234 field: &'static str,
1235 text: &str,
1236) -> Result<[f32; 4], StyleSpecError> {
1237 let hex = text.strip_prefix('#').ok_or_else(|| StyleSpecError::InvalidField {
1238 id: id.to_owned(),
1239 field,
1240 expected: "hex color string like `#RRGGBB` or `#RRGGBBAA`",
1241 })?;
1242
1243 let rgba = match hex.len() {
1244 3 => {
1245 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).ok();
1246 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).ok();
1247 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).ok();
1248 [r, g, b, Some(255)]
1249 }
1250 4 => {
1251 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).ok();
1252 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).ok();
1253 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).ok();
1254 let a = u8::from_str_radix(&hex[3..4].repeat(2), 16).ok();
1255 [r, g, b, a]
1256 }
1257 6 => {
1258 let r = u8::from_str_radix(&hex[0..2], 16).ok();
1259 let g = u8::from_str_radix(&hex[2..4], 16).ok();
1260 let b = u8::from_str_radix(&hex[4..6], 16).ok();
1261 [r, g, b, Some(255)]
1262 }
1263 8 => {
1264 let r = u8::from_str_radix(&hex[0..2], 16).ok();
1265 let g = u8::from_str_radix(&hex[2..4], 16).ok();
1266 let b = u8::from_str_radix(&hex[4..6], 16).ok();
1267 let a = u8::from_str_radix(&hex[6..8], 16).ok();
1268 [r, g, b, a]
1269 }
1270 _ => [None, None, None, None],
1271 };
1272
1273 match rgba {
1274 [Some(r), Some(g), Some(b), Some(a)] => Ok([
1275 r as f32 / 255.0,
1276 g as f32 / 255.0,
1277 b as f32 / 255.0,
1278 a as f32 / 255.0,
1279 ]),
1280 _ => Err(StyleSpecError::InvalidField {
1281 id: id.to_owned(),
1282 field,
1283 expected: "hex color string like `#RRGGBB` or `#RRGGBBAA`",
1284 }),
1285 }
1286}
1287
1288fn default_style_version() -> u8 {
1289 8
1290}
1291
1292use crate::expression::{BoolExpression, Expression, NumericExpression, StringExpression};
1311
1312fn is_expression_array(arr: &[Value]) -> bool {
1314 arr.first()
1315 .and_then(Value::as_str)
1316 .map(|op| matches!(
1317 op,
1318 "get" | "has" | "!" | "all" | "any"
1319 | "==" | "!=" | ">" | ">=" | "<" | "<="
1320 | "+" | "-" | "*" | "/" | "%" | "^" | "abs" | "ln" | "sqrt" | "min" | "max"
1321 | "interpolate" | "step" | "match" | "case" | "coalesce"
1322 | "zoom" | "pitch"
1323 | "concat" | "upcase" | "downcase"
1324 | "feature-state"
1325 | "to-number" | "to-string" | "to-boolean"
1326 | "literal"
1327 ))
1328 .unwrap_or(false)
1329}
1330
1331fn parse_expression_f32(
1333 id: &str,
1334 field: &'static str,
1335 arr: &[Value],
1336) -> Result<StyleValue<f32>, StyleSpecError> {
1337 let op = arr[0].as_str().unwrap_or("");
1338 match op {
1339 "literal" if arr.len() == 2 => {
1340 let v = arr[1].as_f64().ok_or_else(|| expr_err(id, field, "literal number"))? as f32;
1341 Ok(Expression::Constant(v))
1342 }
1343 "get" if arr.len() == 2 => {
1344 let key = arr[1].as_str().ok_or_else(|| expr_err(id, field, "get property key"))?;
1345 Ok(Expression::GetProperty { key: key.to_owned(), fallback: 0.0 })
1346 }
1347 "feature-state" if arr.len() == 2 => {
1348 let key = arr[1].as_str().ok_or_else(|| expr_err(id, field, "feature-state key"))?;
1349 Ok(Expression::FeatureState { key: key.to_owned(), fallback: 0.0 })
1350 }
1351 "zoom" => Ok(Expression::Interpolate {
1352 input: Box::new(NumericExpression::Zoom),
1353 stops: vec![],
1354 }),
1355 "interpolate" => parse_interpolate_f32(id, field, arr),
1356 "step" => parse_step_f32(id, field, arr),
1357 "case" => parse_case_f32(id, field, arr),
1358 "coalesce" => {
1359 let exprs: Result<Vec<_>, _> = arr[1..]
1360 .iter()
1361 .map(|v| match v {
1362 Value::Number(n) => Ok(Expression::Constant(n.as_f64().unwrap_or(0.0) as f32)),
1363 Value::Array(a) if is_expression_array(a) => parse_expression_f32(id, field, a),
1364 _ => Err(expr_err(id, field, "coalesce sub-expression")),
1365 })
1366 .collect();
1367 Ok(Expression::Coalesce(exprs?))
1368 }
1369 _ => Err(expr_err(id, field, &format!("f32 expression (unsupported operator `{op}`)"))),
1370 }
1371}
1372
1373fn parse_expression_color(
1375 id: &str,
1376 field: &'static str,
1377 arr: &[Value],
1378) -> Result<StyleValue<[f32; 4]>, StyleSpecError> {
1379 let op = arr[0].as_str().unwrap_or("");
1380 match op {
1381 "literal" if arr.len() == 2 => {
1382 if let Some(a) = arr[1].as_array() {
1383 let c = parse_color_array(id, field, a)?;
1384 Ok(Expression::Constant(c))
1385 } else {
1386 Err(expr_err(id, field, "literal color array"))
1387 }
1388 }
1389 "interpolate" => parse_interpolate_color(id, field, arr),
1390 "step" => parse_step_color(id, field, arr),
1391 "match" => parse_match_color(id, field, arr),
1392 "case" => parse_case_color(id, field, arr),
1393 _ => Err(expr_err(id, field, &format!("color expression (unsupported operator `{op}`)"))),
1394 }
1395}
1396
1397fn parse_expression_string(
1399 id: &str,
1400 field: &'static str,
1401 arr: &[Value],
1402) -> Result<StyleValue<String>, StyleSpecError> {
1403 let op = arr[0].as_str().unwrap_or("");
1404 match op {
1405 "literal" if arr.len() == 2 => {
1406 let s = arr[1].as_str().ok_or_else(|| expr_err(id, field, "literal string"))?;
1407 Ok(Expression::Constant(s.to_owned()))
1408 }
1409 "get" if arr.len() == 2 => {
1410 let key = arr[1].as_str().ok_or_else(|| expr_err(id, field, "get property key"))?;
1411 Ok(Expression::GetProperty { key: key.to_owned(), fallback: String::new() })
1412 }
1413 "feature-state" if arr.len() == 2 => {
1414 let key = arr[1].as_str().ok_or_else(|| expr_err(id, field, "feature-state key"))?;
1415 Ok(Expression::FeatureState { key: key.to_owned(), fallback: String::new() })
1416 }
1417 "concat" => {
1418 let exprs: Result<Vec<_>, _> = arr[1..]
1419 .iter()
1420 .map(|v| match v {
1421 Value::String(s) => Ok(Expression::Constant(s.clone())),
1422 Value::Array(a) if is_expression_array(a) => parse_expression_string(id, field, a),
1423 _ => Err(expr_err(id, field, "concat sub-expression")),
1424 })
1425 .collect();
1426 Ok(Expression::Coalesce(exprs?))
1427 }
1428 _ => Err(expr_err(id, field, &format!("string expression (unsupported operator `{op}`)"))),
1429 }
1430}
1431
1432fn parse_expression_bool(
1434 id: &str,
1435 field: &'static str,
1436 arr: &[Value],
1437) -> Result<StyleValue<bool>, StyleSpecError> {
1438 let op = arr[0].as_str().unwrap_or("");
1439 match op {
1440 "literal" if arr.len() == 2 => {
1441 let b = arr[1].as_bool().ok_or_else(|| expr_err(id, field, "literal boolean"))?;
1442 Ok(Expression::Constant(b))
1443 }
1444 "get" if arr.len() == 2 => {
1445 let key = arr[1].as_str().ok_or_else(|| expr_err(id, field, "get property key"))?;
1446 Ok(Expression::GetProperty { key: key.to_owned(), fallback: false })
1447 }
1448 "feature-state" if arr.len() == 2 => {
1449 let key = arr[1].as_str().ok_or_else(|| expr_err(id, field, "feature-state key"))?;
1450 Ok(Expression::FeatureState { key: key.to_owned(), fallback: false })
1451 }
1452 "has" if arr.len() == 2 => {
1453 let key = arr[1].as_str().ok_or_else(|| expr_err(id, field, "has property key"))?;
1454 Ok(Expression::Case {
1455 branches: vec![(BoolExpression::Has(key.to_owned()), true)],
1456 fallback: false,
1457 })
1458 }
1459 _ => Err(expr_err(id, field, &format!("boolean expression (unsupported operator `{op}`)"))),
1460 }
1461}
1462
1463fn parse_numeric_input(
1469 id: &str,
1470 field: &'static str,
1471 value: &Value,
1472) -> Result<NumericExpression, StyleSpecError> {
1473 match value {
1474 Value::Number(n) => Ok(NumericExpression::Literal(n.as_f64().unwrap_or(0.0))),
1475 Value::Array(arr) if !arr.is_empty() => {
1476 let op = arr[0].as_str().unwrap_or("");
1477 match op {
1478 "zoom" => Ok(NumericExpression::Zoom),
1479 "pitch" => Ok(NumericExpression::Pitch),
1480 "get" if arr.len() == 2 => {
1481 let key = arr[1].as_str().ok_or_else(|| expr_err(id, field, "get key"))?;
1482 Ok(NumericExpression::GetProperty { key: key.to_owned(), fallback: 0.0 })
1483 }
1484 "feature-state" if arr.len() == 2 => {
1485 let key = arr[1].as_str().ok_or_else(|| expr_err(id, field, "feature-state key"))?;
1486 Ok(NumericExpression::GetState { key: key.to_owned(), fallback: 0.0 })
1487 }
1488 "+" if arr.len() == 3 => Ok(NumericExpression::Add(
1489 Box::new(parse_numeric_input(id, field, &arr[1])?),
1490 Box::new(parse_numeric_input(id, field, &arr[2])?),
1491 )),
1492 "-" if arr.len() == 3 => Ok(NumericExpression::Sub(
1493 Box::new(parse_numeric_input(id, field, &arr[1])?),
1494 Box::new(parse_numeric_input(id, field, &arr[2])?),
1495 )),
1496 "*" if arr.len() == 3 => Ok(NumericExpression::Mul(
1497 Box::new(parse_numeric_input(id, field, &arr[1])?),
1498 Box::new(parse_numeric_input(id, field, &arr[2])?),
1499 )),
1500 "/" if arr.len() == 3 => Ok(NumericExpression::Div(
1501 Box::new(parse_numeric_input(id, field, &arr[1])?),
1502 Box::new(parse_numeric_input(id, field, &arr[2])?),
1503 )),
1504 "%" if arr.len() == 3 => Ok(NumericExpression::Mod(
1505 Box::new(parse_numeric_input(id, field, &arr[1])?),
1506 Box::new(parse_numeric_input(id, field, &arr[2])?),
1507 )),
1508 "^" if arr.len() == 3 => Ok(NumericExpression::Pow(
1509 Box::new(parse_numeric_input(id, field, &arr[1])?),
1510 Box::new(parse_numeric_input(id, field, &arr[2])?),
1511 )),
1512 "abs" if arr.len() == 2 => Ok(NumericExpression::Abs(
1513 Box::new(parse_numeric_input(id, field, &arr[1])?),
1514 )),
1515 "ln" if arr.len() == 2 => Ok(NumericExpression::Ln(
1516 Box::new(parse_numeric_input(id, field, &arr[1])?),
1517 )),
1518 "sqrt" if arr.len() == 2 => Ok(NumericExpression::Sqrt(
1519 Box::new(parse_numeric_input(id, field, &arr[1])?),
1520 )),
1521 "min" if arr.len() == 3 => Ok(NumericExpression::Min(
1522 Box::new(parse_numeric_input(id, field, &arr[1])?),
1523 Box::new(parse_numeric_input(id, field, &arr[2])?),
1524 )),
1525 "max" if arr.len() == 3 => Ok(NumericExpression::Max(
1526 Box::new(parse_numeric_input(id, field, &arr[1])?),
1527 Box::new(parse_numeric_input(id, field, &arr[2])?),
1528 )),
1529 _ => Err(expr_err(id, field, &format!("numeric input (unsupported `{op}`)"))),
1530 }
1531 }
1532 _ => Err(expr_err(id, field, "numeric input")),
1533 }
1534}
1535
1536fn parse_string_input(
1538 id: &str,
1539 field: &'static str,
1540 value: &Value,
1541) -> Result<StringExpression, StyleSpecError> {
1542 match value {
1543 Value::String(s) => Ok(StringExpression::Literal(s.clone())),
1544 Value::Array(arr) if !arr.is_empty() => {
1545 let op = arr[0].as_str().unwrap_or("");
1546 match op {
1547 "get" if arr.len() == 2 => {
1548 let key = arr[1].as_str().ok_or_else(|| expr_err(id, field, "get key"))?;
1549 Ok(StringExpression::GetProperty { key: key.to_owned(), fallback: String::new() })
1550 }
1551 "feature-state" if arr.len() == 2 => {
1552 let key = arr[1].as_str().ok_or_else(|| expr_err(id, field, "feature-state key"))?;
1553 Ok(StringExpression::GetState { key: key.to_owned(), fallback: String::new() })
1554 }
1555 "upcase" if arr.len() == 2 => {
1556 Ok(StringExpression::Upcase(Box::new(parse_string_input(id, field, &arr[1])?)))
1557 }
1558 "downcase" if arr.len() == 2 => {
1559 Ok(StringExpression::Downcase(Box::new(parse_string_input(id, field, &arr[1])?)))
1560 }
1561 "concat" if arr.len() >= 3 => {
1562 let left = parse_string_input(id, field, &arr[1])?;
1563 let right = parse_string_input(id, field, &arr[2])?;
1564 Ok(StringExpression::Concat(Box::new(left), Box::new(right)))
1565 }
1566 _ => Err(expr_err(id, field, &format!("string input (unsupported `{op}`)"))),
1567 }
1568 }
1569 _ => Err(expr_err(id, field, "string input")),
1570 }
1571}
1572
1573fn parse_bool_condition(
1575 id: &str,
1576 field: &'static str,
1577 value: &Value,
1578) -> Result<BoolExpression, StyleSpecError> {
1579 match value {
1580 Value::Bool(b) => Ok(BoolExpression::Literal(*b)),
1581 Value::Array(arr) if !arr.is_empty() => {
1582 let op = arr[0].as_str().unwrap_or("");
1583 match op {
1584 "has" if arr.len() == 2 => {
1585 let key = arr[1].as_str().ok_or_else(|| expr_err(id, field, "has key"))?;
1586 Ok(BoolExpression::Has(key.to_owned()))
1587 }
1588 "get" if arr.len() == 2 => {
1589 let key = arr[1].as_str().ok_or_else(|| expr_err(id, field, "get key"))?;
1590 Ok(BoolExpression::GetProperty { key: key.to_owned(), fallback: false })
1591 }
1592 "!" if arr.len() == 2 => {
1593 Ok(BoolExpression::Not(Box::new(parse_bool_condition(id, field, &arr[1])?)))
1594 }
1595 "all" => {
1596 let exprs: Result<Vec<_>, _> = arr[1..]
1597 .iter()
1598 .map(|v| parse_bool_condition(id, field, v))
1599 .collect();
1600 Ok(BoolExpression::All(exprs?))
1601 }
1602 "any" => {
1603 let exprs: Result<Vec<_>, _> = arr[1..]
1604 .iter()
1605 .map(|v| parse_bool_condition(id, field, v))
1606 .collect();
1607 Ok(BoolExpression::Any(exprs?))
1608 }
1609 "==" if arr.len() == 3 => {
1610 if let (Ok(a), Ok(b)) = (
1612 parse_numeric_input(id, field, &arr[1]),
1613 parse_numeric_input(id, field, &arr[2]),
1614 ) {
1615 Ok(BoolExpression::Eq(a, b))
1616 } else if let (Ok(a), Ok(b)) = (
1617 parse_string_input(id, field, &arr[1]),
1618 parse_string_input(id, field, &arr[2]),
1619 ) {
1620 Ok(BoolExpression::StrEq(a, b))
1621 } else {
1622 Err(expr_err(id, field, "== operands"))
1623 }
1624 }
1625 "!=" if arr.len() == 3 => {
1626 let a = parse_numeric_input(id, field, &arr[1])?;
1627 let b = parse_numeric_input(id, field, &arr[2])?;
1628 Ok(BoolExpression::Neq(a, b))
1629 }
1630 ">" if arr.len() == 3 => {
1631 let a = parse_numeric_input(id, field, &arr[1])?;
1632 let b = parse_numeric_input(id, field, &arr[2])?;
1633 Ok(BoolExpression::Gt(a, b))
1634 }
1635 ">=" if arr.len() == 3 => {
1636 let a = parse_numeric_input(id, field, &arr[1])?;
1637 let b = parse_numeric_input(id, field, &arr[2])?;
1638 Ok(BoolExpression::Gte(a, b))
1639 }
1640 "<" if arr.len() == 3 => {
1641 let a = parse_numeric_input(id, field, &arr[1])?;
1642 let b = parse_numeric_input(id, field, &arr[2])?;
1643 Ok(BoolExpression::Lt(a, b))
1644 }
1645 "<=" if arr.len() == 3 => {
1646 let a = parse_numeric_input(id, field, &arr[1])?;
1647 let b = parse_numeric_input(id, field, &arr[2])?;
1648 Ok(BoolExpression::Lte(a, b))
1649 }
1650 _ => Err(expr_err(id, field, &format!("boolean condition (unsupported `{op}`)"))),
1651 }
1652 }
1653 _ => Err(expr_err(id, field, "boolean condition")),
1654 }
1655}
1656
1657fn parse_interpolate_f32(
1663 id: &str,
1664 field: &'static str,
1665 arr: &[Value],
1666) -> Result<StyleValue<f32>, StyleSpecError> {
1667 if arr.len() < 5 || (arr.len() - 3) % 2 != 0 {
1669 return Err(expr_err(id, field, "interpolate with even stop pairs"));
1670 }
1671 let input = parse_numeric_input(id, field, &arr[2])?;
1672 let mut stops = Vec::with_capacity((arr.len() - 3) / 2);
1673 for chunk in arr[3..].chunks(2) {
1674 let z = chunk[0].as_f64().ok_or_else(|| expr_err(id, field, "interpolate stop zoom"))? as f32;
1675 let v = chunk[1].as_f64().ok_or_else(|| expr_err(id, field, "interpolate stop value"))? as f32;
1676 stops.push((z, v));
1677 }
1678 Ok(Expression::Interpolate { input: Box::new(input), stops })
1679}
1680
1681fn parse_step_f32(
1683 id: &str,
1684 field: &'static str,
1685 arr: &[Value],
1686) -> Result<StyleValue<f32>, StyleSpecError> {
1687 if arr.len() < 3 {
1688 return Err(expr_err(id, field, "step expression"));
1689 }
1690 let input = parse_numeric_input(id, field, &arr[1])?;
1691 let default = arr[2].as_f64().ok_or_else(|| expr_err(id, field, "step default"))? as f32;
1692 let mut stops = Vec::new();
1693 for chunk in arr[3..].chunks(2) {
1694 if chunk.len() == 2 {
1695 let z = chunk[0].as_f64().ok_or_else(|| expr_err(id, field, "step threshold"))? as f32;
1696 let v = chunk[1].as_f64().ok_or_else(|| expr_err(id, field, "step value"))? as f32;
1697 stops.push((z, v));
1698 }
1699 }
1700 Ok(Expression::Step { input: Box::new(input), default, stops })
1701}
1702
1703fn parse_case_f32(
1705 id: &str,
1706 field: &'static str,
1707 arr: &[Value],
1708) -> Result<StyleValue<f32>, StyleSpecError> {
1709 if arr.len() < 4 || (arr.len() - 1) % 2 == 0 {
1712 return Err(expr_err(id, field, "case expression"));
1713 }
1714 let fallback = arr.last().unwrap().as_f64()
1716 .ok_or_else(|| expr_err(id, field, "case fallback"))? as f32;
1717 let mut branches = Vec::new();
1718 for chunk in arr[1..arr.len() - 1].chunks(2) {
1719 let cond = parse_bool_condition(id, field, &chunk[0])?;
1720 let val = chunk[1].as_f64().ok_or_else(|| expr_err(id, field, "case value"))? as f32;
1721 branches.push((cond, val));
1722 }
1723 Ok(Expression::Case { branches, fallback })
1724}
1725
1726fn parse_interpolate_color(
1732 id: &str,
1733 field: &'static str,
1734 arr: &[Value],
1735) -> Result<StyleValue<[f32; 4]>, StyleSpecError> {
1736 if arr.len() < 5 || (arr.len() - 3) % 2 != 0 {
1737 return Err(expr_err(id, field, "interpolate with even stop pairs"));
1738 }
1739 let input = parse_numeric_input(id, field, &arr[2])?;
1740 let mut stops = Vec::with_capacity((arr.len() - 3) / 2);
1741 for chunk in arr[3..].chunks(2) {
1742 let z = chunk[0].as_f64().ok_or_else(|| expr_err(id, field, "interpolate stop zoom"))? as f32;
1743 let c = parse_json_color(id, field, &chunk[1])?;
1744 stops.push((z, c));
1745 }
1746 Ok(Expression::Interpolate { input: Box::new(input), stops })
1747}
1748
1749fn parse_step_color(
1751 id: &str,
1752 field: &'static str,
1753 arr: &[Value],
1754) -> Result<StyleValue<[f32; 4]>, StyleSpecError> {
1755 if arr.len() < 3 {
1756 return Err(expr_err(id, field, "step expression"));
1757 }
1758 let input = parse_numeric_input(id, field, &arr[1])?;
1759 let default = parse_json_color(id, field, &arr[2])?;
1760 let mut stops = Vec::new();
1761 for chunk in arr[3..].chunks(2) {
1762 if chunk.len() == 2 {
1763 let z = chunk[0].as_f64().ok_or_else(|| expr_err(id, field, "step threshold"))? as f32;
1764 let c = parse_json_color(id, field, &chunk[1])?;
1765 stops.push((z, c));
1766 }
1767 }
1768 Ok(Expression::Step { input: Box::new(input), default, stops })
1769}
1770
1771fn parse_match_color(
1773 id: &str,
1774 field: &'static str,
1775 arr: &[Value],
1776) -> Result<StyleValue<[f32; 4]>, StyleSpecError> {
1777 if arr.len() < 4 {
1778 return Err(expr_err(id, field, "match expression"));
1779 }
1780 let input = parse_string_input(id, field, &arr[1])?;
1781 let fallback = parse_json_color(id, field, arr.last().unwrap())?;
1782 let mut cases = Vec::new();
1783 let pairs = &arr[2..arr.len() - 1];
1785 for chunk in pairs.chunks(2) {
1786 if chunk.len() == 2 {
1787 let label = chunk[0].as_str().ok_or_else(|| expr_err(id, field, "match label"))?;
1788 let color = parse_json_color(id, field, &chunk[1])?;
1789 cases.push((label.to_owned(), color));
1790 }
1791 }
1792 Ok(Expression::Match { input: Box::new(input), cases, fallback })
1793}
1794
1795fn parse_case_color(
1797 id: &str,
1798 field: &'static str,
1799 arr: &[Value],
1800) -> Result<StyleValue<[f32; 4]>, StyleSpecError> {
1801 if arr.len() < 4 || (arr.len() - 1) % 2 == 0 {
1803 return Err(expr_err(id, field, "case expression"));
1804 }
1805 let fallback = parse_json_color(id, field, arr.last().unwrap())?;
1806 let mut branches = Vec::new();
1807 for chunk in arr[1..arr.len() - 1].chunks(2) {
1808 if chunk.len() == 2 {
1809 let cond = parse_bool_condition(id, field, &chunk[0])?;
1810 let color = parse_json_color(id, field, &chunk[1])?;
1811 branches.push((cond, color));
1812 }
1813 }
1814 Ok(Expression::Case { branches, fallback })
1815}
1816
1817fn parse_json_color(
1823 id: &str,
1824 field: &'static str,
1825 value: &Value,
1826) -> Result<[f32; 4], StyleSpecError> {
1827 match value {
1828 Value::String(s) => parse_color_string(id, field, s),
1829 Value::Array(a) => parse_color_array(id, field, a),
1830 _ => Err(expr_err(id, field, "color value")),
1831 }
1832}
1833
1834fn expr_err(id: &str, field: &'static str, expected: &str) -> StyleSpecError {
1836 StyleSpecError::InvalidField {
1837 id: id.to_owned(),
1838 field,
1839 expected: Box::leak(format!("expression: {expected}").into_boxed_str()),
1840 }
1841}
1842
1843#[cfg(test)]
1844mod tests {
1845 use super::*;
1846 use crate::geometry::{Feature, FeatureCollection, Geometry, Point};
1847 use crate::models::{ModelInstance, ModelMesh};
1848 use crate::style::{CanvasSource, GeoJsonSource, ImageSource, ModelSource, VectorTileSource, VideoSource};
1849 use crate::tile_source::{TileData, TileResponse, TileSource};
1850 use rustial_math::{GeoCoord, TileId};
1851
1852 #[derive(Clone, Default)]
1853 struct StubTileSource;
1854
1855 impl TileSource for StubTileSource {
1856 fn request(&self, _id: TileId) {}
1857 fn poll(&self) -> Vec<(TileId, Result<TileResponse, crate::TileError>)> {
1858 Vec::new()
1859 }
1860 }
1861
1862 fn sample_features() -> FeatureCollection {
1863 FeatureCollection {
1864 features: vec![Feature {
1865 geometry: Geometry::Point(Point {
1866 coord: GeoCoord::from_lat_lon(51.1, 17.0),
1867 }),
1868 properties: HashMap::new(),
1869 }],
1870 }
1871 }
1872
1873 #[test]
1874 fn parses_and_resolves_style_json() {
1875 let json = r##"
1876 {
1877 "version": 8,
1878 "projection": { "type": "equirectangular" },
1879 "terrain": { "source": "dem" },
1880 "sources": {
1881 "base": { "type": "raster" },
1882 "dem": { "type": "raster-dem" },
1883 "places": { "type": "geojson" },
1884 "labels": { "type": "vector" },
1885 "hero": { "type": "model" }
1886 },
1887 "layers": [
1888 { "id": "bg", "type": "background", "paint": { "background-color": "#112233" } },
1889 { "id": "raster", "type": "raster", "source": "base", "paint": { "raster-opacity": 0.75 } },
1890 { "id": "fill", "type": "fill", "source": "places", "paint": { "fill-color": "#ff0000" } },
1891 { "id": "symbol", "type": "symbol", "source": "labels", "layout": { "text-field": "name", "text-size": 18 }, "paint": { "text-color": "#00ff00" } },
1892 { "id": "model", "type": "model", "source": "hero" }
1893 ]
1894 }
1895 "##;
1896
1897 let spec = parse_style_json(json).expect("parse spec");
1898 let mut registry = StyleSourceRegistry::new();
1899 registry.set_source("base", StyleSource::Raster(crate::style::RasterSource::new(|| Box::new(StubTileSource))));
1900 registry.set_source("dem", StyleSource::Terrain(crate::style::TerrainSource::new(|| Box::new(crate::terrain::FlatElevationSource::new(4, 4)))));
1901 registry.set_source("places", StyleSource::GeoJson(GeoJsonSource::new(sample_features())));
1902 registry.set_source("labels", StyleSource::VectorTile(VectorTileSource::new(sample_features())));
1903 registry.set_source(
1904 "hero",
1905 StyleSource::Model(ModelSource::new(vec![ModelInstance::new(
1906 GeoCoord::from_lat_lon(0.0, 0.0),
1907 ModelMesh {
1908 positions: vec![[0.0, 0.0, 0.0]],
1909 normals: vec![[0.0, 0.0, 1.0]],
1910 uvs: vec![[0.0, 0.0]],
1911 indices: vec![0],
1912 },
1913 )])),
1914 );
1915
1916 let document = spec.resolve(®istry).expect("resolve");
1917 assert_eq!(document.layers().len(), 5);
1918 assert_eq!(document.terrain_source(), Some("dem"));
1919 assert_eq!(document.projection(), StyleProjection::Equirectangular);
1920 }
1921
1922 #[test]
1923 fn parses_projection_from_json_without_layers() {
1924 let json = r##"
1925 {
1926 "version": 8,
1927 "projection": { "type": "globe" },
1928 "sources": {},
1929 "layers": []
1930 }
1931 "##;
1932
1933 let spec = parse_style_json(json).expect("parse spec");
1934 assert!(matches!(
1935 spec.projection,
1936 Some(StyleSpecProjection {
1937 projection_type: StyleSpecProjectionType::Globe
1938 })
1939 ));
1940
1941 let registry = StyleSourceRegistry::new();
1942 let document = spec.resolve(®istry).expect("resolve");
1943 assert_eq!(document.projection(), StyleProjection::Globe);
1944 }
1945
1946 #[test]
1947 fn parses_vertical_perspective_projection_from_json() {
1948 let json = r##"
1949 {
1950 "version": 8,
1951 "projection": { "type": "vertical-perspective" },
1952 "sources": {},
1953 "layers": []
1954 }
1955 "##;
1956
1957 let spec = parse_style_json(json).expect("parse spec");
1958 assert!(matches!(
1959 spec.projection,
1960 Some(StyleSpecProjection {
1961 projection_type: StyleSpecProjectionType::VerticalPerspective
1962 })
1963 ));
1964
1965 let registry = StyleSourceRegistry::new();
1966 let document = spec.resolve(®istry).expect("resolve");
1967 assert_eq!(document.projection(), StyleProjection::VerticalPerspective);
1968 }
1969
1970 #[test]
1971 fn parses_zoom_stop_values_from_json() {
1972 let json = r##"
1973 {
1974 "version": 8,
1975 "sources": { "places": { "type": "geojson" } },
1976 "layers": [
1977 {
1978 "id": "circles",
1979 "type": "circle",
1980 "source": "places",
1981 "paint": {
1982 "circle-radius": { "stops": [[5, 4], [10, 12]] },
1983 "circle-color": { "stops": [[5, "#000000"], [10, "#ffffff"]] }
1984 }
1985 }
1986 ]
1987 }
1988 "##;
1989
1990 let spec = parse_style_json(json).expect("parse spec");
1991 let mut registry = StyleSourceRegistry::new();
1992 registry.set_source("places", StyleSource::GeoJson(GeoJsonSource::new(sample_features())));
1993 let document = spec.resolve(®istry).expect("resolve");
1994
1995 match &document.layers()[0] {
1996 StyleLayer::Circle(layer) => {
1997 assert!(matches!(layer.radius, StyleValue::ZoomStops(_)));
1998 assert!(matches!(layer.color, StyleValue::ZoomStops(_)));
1999 }
2000 other => panic!("expected circle layer, got {other:?}"),
2001 }
2002 }
2003
2004 #[test]
2005 fn parses_symbol_overlap_placement_spacing_max_angle_keep_upright_writing_mode_and_offset() {
2006 let json = r##"
2007 {
2008 "version": 8,
2009 "sources": { "labels": { "type": "vector" } },
2010 "layers": [
2011 {
2012 "id": "symbol",
2013 "type": "symbol",
2014 "source": "labels",
2015 "layout": {
2016 "symbol-placement": "line",
2017 "text-allow-overlap": true,
2018 "icon-allow-overlap": false,
2019 "text-optional": true,
2020 "icon-optional": false,
2021 "text-ignore-placement": true,
2022 "icon-ignore-placement": false,
2023 "text-radial-offset": 2,
2024 "text-anchor": "top-right",
2025 "text-justify": "auto",
2026 "text-transform": "uppercase",
2027 "text-max-width": 8,
2028 "text-line-height": 1.5,
2029 "text-letter-spacing": 0.25,
2030 "icon-text-fit": "both",
2031 "icon-text-fit-padding": [1, 2, 3, 4],
2032 "text-variable-anchor-offset": ["top", [1, 2], "bottom", [3, 4]],
2033 "symbol-sort-key": 7,
2034 "symbol-spacing": 320,
2035 "text-max-angle": 90,
2036 "text-keep-upright": false,
2037 "text-field": "name",
2038 "text-variable-anchor": ["center", "top"],
2039 "text-writing-mode": "vertical",
2040 "text-offset": [1, 2]
2041 }
2042 }
2043 ]
2044 }
2045 "##;
2046
2047 let spec = parse_style_json(json).expect("parse spec");
2048 let mut registry = StyleSourceRegistry::new();
2049 registry.set_source("labels", StyleSource::VectorTile(VectorTileSource::new(sample_features())));
2050 let document = spec.resolve(®istry).expect("resolve");
2051
2052 match &document.layers()[0] {
2053 StyleLayer::Symbol(layer) => {
2054 assert_eq!(layer.text_allow_overlap.as_ref().map(StyleValue::evaluate), Some(true));
2055 assert_eq!(layer.icon_allow_overlap.as_ref().map(StyleValue::evaluate), Some(false));
2056 assert_eq!(layer.text_optional.as_ref().map(StyleValue::evaluate), Some(true));
2057 assert_eq!(layer.icon_optional.as_ref().map(StyleValue::evaluate), Some(false));
2058 assert_eq!(layer.text_ignore_placement.as_ref().map(StyleValue::evaluate), Some(true));
2059 assert_eq!(layer.icon_ignore_placement.as_ref().map(StyleValue::evaluate), Some(false));
2060 assert_eq!(layer.radial_offset.as_ref().map(StyleValue::evaluate), Some(2.0));
2061 assert_eq!(layer.anchor, SymbolAnchor::TopRight);
2062 assert_eq!(layer.justify.evaluate(), SymbolTextJustify::Auto);
2063 assert_eq!(layer.transform.evaluate(), SymbolTextTransform::Uppercase);
2064 assert_eq!(layer.max_width.as_ref().map(StyleValue::evaluate), Some(8.0));
2065 assert_eq!(layer.line_height.as_ref().map(StyleValue::evaluate), Some(1.5));
2066 assert_eq!(layer.letter_spacing.as_ref().map(StyleValue::evaluate), Some(0.25));
2067 assert_eq!(layer.icon_text_fit.evaluate(), SymbolIconTextFit::Both);
2068 assert_eq!(layer.icon_text_fit_padding, [1.0, 2.0, 3.0, 4.0]);
2069 assert_eq!(
2070 layer.variable_anchor_offsets,
2071 Some(vec![
2072 (SymbolAnchor::Top, [1.0, 2.0]),
2073 (SymbolAnchor::Bottom, [3.0, 4.0]),
2074 ])
2075 );
2076 assert_eq!(layer.sort_key.as_ref().map(StyleValue::evaluate), Some(7.0));
2077 assert_eq!(layer.placement, SymbolPlacement::Line);
2078 assert_eq!(layer.spacing.evaluate(), 320.0);
2079 assert_eq!(layer.max_angle.evaluate(), 90.0);
2080 assert!(!layer.keep_upright.evaluate());
2081 assert_eq!(layer.variable_anchors, vec![SymbolAnchor::Center, SymbolAnchor::Top]);
2082 assert_eq!(layer.writing_mode, SymbolWritingMode::Vertical);
2083 assert_eq!(layer.offset, [1.0, 2.0]);
2084 }
2085 other => panic!("expected symbol layer, got {other:?}"),
2086 }
2087 }
2088
2089 #[test]
2090 fn parses_source_layer_for_vector_style_layers() {
2091 let json = r##"
2092 {
2093 "version": 8,
2094 "sources": { "labels": { "type": "vector" } },
2095 "layers": [
2096 {
2097 "id": "roads",
2098 "type": "line",
2099 "source": "labels",
2100 "source-layer": "transport"
2101 }
2102 ]
2103 }
2104 "##;
2105
2106 let spec = parse_style_json(json).expect("parse spec");
2107 assert_eq!(spec.layers.len(), 1);
2108 assert_eq!(spec.layers[0].source_layer.as_deref(), Some("transport"));
2109
2110 let mut registry = StyleSourceRegistry::new();
2111 let mut source_layers = HashMap::new();
2112 source_layers.insert(
2113 "transport".to_string(),
2114 FeatureCollection {
2115 features: vec![Feature {
2116 geometry: Geometry::Point(Point {
2117 coord: GeoCoord::from_lat_lon(1.0, 2.0),
2118 }),
2119 properties: HashMap::new(),
2120 }],
2121 },
2122 );
2123 registry.set_source(
2124 "labels",
2125 StyleSource::VectorTile(VectorTileSource::from_source_layers(source_layers)),
2126 );
2127
2128 let document = spec.resolve(®istry).expect("resolve");
2129 match &document.layers()[0] {
2130 StyleLayer::Line(layer) => {
2131 assert_eq!(layer.source_layer.as_deref(), Some("transport"));
2132 }
2133 other => panic!("expected line layer, got {other:?}"),
2134 }
2135 }
2136
2137 #[test]
2142 fn f32_expression_interpolate_on_zoom() {
2143 let json = r##"
2144 {
2145 "version": 8,
2146 "sources": { "s": { "type": "vector" } },
2147 "layers": [
2148 {
2149 "id": "l",
2150 "type": "line",
2151 "source": "s",
2152 "paint": {
2153 "line-width": ["interpolate", ["linear"], ["zoom"], 0, 1, 20, 10]
2154 }
2155 }
2156 ]
2157 }
2158 "##;
2159 let spec = parse_style_json(json).expect("parse");
2160 let mut registry = StyleSourceRegistry::new();
2161 registry.set_source("s", StyleSource::VectorTile(VectorTileSource::new(sample_features())));
2162 let doc = spec.resolve(®istry).expect("resolve");
2163 match &doc.layers()[0] {
2164 StyleLayer::Line(layer) => {
2165 let ctx = crate::style::StyleEvalContext::new(10.0);
2166 let width = layer.width.evaluate_with_context(ctx);
2167 assert!((width - 5.5).abs() < 0.1, "expected ~5.5, got {width}");
2168 }
2169 other => panic!("expected line, got {other:?}"),
2170 }
2171 }
2172
2173 #[test]
2174 fn f32_expression_step_on_zoom() {
2175 let json = r##"
2176 {
2177 "version": 8,
2178 "sources": { "s": { "type": "vector" } },
2179 "layers": [
2180 {
2181 "id": "l",
2182 "type": "circle",
2183 "source": "s",
2184 "paint": {
2185 "circle-radius": ["step", ["zoom"], 2, 5, 4, 10, 8]
2186 }
2187 }
2188 ]
2189 }
2190 "##;
2191 let spec = parse_style_json(json).expect("parse");
2192 let mut registry = StyleSourceRegistry::new();
2193 registry.set_source("s", StyleSource::VectorTile(VectorTileSource::new(sample_features())));
2194 let doc = spec.resolve(®istry).expect("resolve");
2195 match &doc.layers()[0] {
2196 StyleLayer::Circle(layer) => {
2197 use crate::expression::ExprEvalContext;
2198 assert!((layer.radius.eval_full(&ExprEvalContext::zoom_only(3.0)) - 2.0).abs() < f32::EPSILON);
2200 assert!((layer.radius.eval_full(&ExprEvalContext::zoom_only(5.0)) - 4.0).abs() < f32::EPSILON);
2202 assert!((layer.radius.eval_full(&ExprEvalContext::zoom_only(7.0)) - 4.0).abs() < f32::EPSILON);
2204 assert!((layer.radius.eval_full(&ExprEvalContext::zoom_only(10.0)) - 8.0).abs() < f32::EPSILON);
2206 assert!((layer.radius.eval_full(&ExprEvalContext::zoom_only(15.0)) - 8.0).abs() < f32::EPSILON);
2208 }
2209 other => panic!("expected circle, got {other:?}"),
2210 }
2211 }
2212
2213 #[test]
2214 fn color_expression_match_on_property() {
2215 let json = r##"
2216 {
2217 "version": 8,
2218 "sources": { "s": { "type": "vector" } },
2219 "layers": [
2220 {
2221 "id": "l",
2222 "type": "fill",
2223 "source": "s",
2224 "paint": {
2225 "fill-color": ["match", ["get", "type"], "residential", "#0000ff", "commercial", "#ff0000", "#888888"]
2226 }
2227 }
2228 ]
2229 }
2230 "##;
2231 let spec = parse_style_json(json).expect("parse");
2232 let mut registry = StyleSourceRegistry::new();
2233 registry.set_source("s", StyleSource::VectorTile(VectorTileSource::new(sample_features())));
2234 let doc = spec.resolve(®istry).expect("resolve");
2235 match &doc.layers()[0] {
2236 StyleLayer::Fill(layer) => {
2237 use crate::expression::ExprEvalContext;
2238 use crate::geometry::PropertyValue;
2239
2240 let mut props = std::collections::HashMap::new();
2241 props.insert("type".to_string(), PropertyValue::String("residential".to_string()));
2242 let ctx = ExprEvalContext::with_feature(10.0, &props);
2243 let color = layer.fill_color.eval_full(&ctx);
2244 assert!((color[0] - 0.0).abs() < 0.01);
2245 assert!((color[2] - 1.0).abs() < 0.01);
2246
2247 props.insert("type".to_string(), PropertyValue::String("commercial".to_string()));
2248 let ctx = ExprEvalContext::with_feature(10.0, &props);
2249 let color = layer.fill_color.eval_full(&ctx);
2250 assert!((color[0] - 1.0).abs() < 0.01);
2251 assert!((color[2] - 0.0).abs() < 0.01);
2252
2253 props.insert("type".to_string(), PropertyValue::String("industrial".to_string()));
2255 let ctx = ExprEvalContext::with_feature(10.0, &props);
2256 let color = layer.fill_color.eval_full(&ctx);
2257 assert!((color[0] - 0.533).abs() < 0.01);
2258 }
2259 other => panic!("expected fill, got {other:?}"),
2260 }
2261 }
2262
2263 #[test]
2264 fn f32_expression_get_property() {
2265 let json = r##"
2266 {
2267 "version": 8,
2268 "sources": { "s": { "type": "vector" } },
2269 "layers": [
2270 {
2271 "id": "l",
2272 "type": "fill-extrusion",
2273 "source": "s",
2274 "paint": {
2275 "fill-extrusion-height": ["get", "height"],
2276 "fill-extrusion-base": ["get", "base"]
2277 }
2278 }
2279 ]
2280 }
2281 "##;
2282 let spec = parse_style_json(json).expect("parse");
2283 let mut registry = StyleSourceRegistry::new();
2284 registry.set_source("s", StyleSource::VectorTile(VectorTileSource::new(sample_features())));
2285 let doc = spec.resolve(®istry).expect("resolve");
2286 match &doc.layers()[0] {
2287 StyleLayer::FillExtrusion(layer) => {
2288 use crate::expression::ExprEvalContext;
2289 use crate::geometry::PropertyValue;
2290 let mut props = std::collections::HashMap::new();
2291 props.insert("height".to_string(), PropertyValue::Number(50.0));
2292 props.insert("base".to_string(), PropertyValue::Number(10.0));
2293 let ctx = ExprEvalContext::with_feature(14.0, &props);
2294 assert!((layer.height.eval_full(&ctx) - 50.0).abs() < f32::EPSILON);
2295 assert!((layer.base.eval_full(&ctx) - 10.0).abs() < f32::EPSILON);
2296 }
2297 other => panic!("expected fill-extrusion, got {other:?}"),
2298 }
2299 }
2300
2301 #[test]
2302 fn f32_expression_case_with_comparison() {
2303 let json = r##"
2304 {
2305 "version": 8,
2306 "sources": { "s": { "type": "vector" } },
2307 "layers": [
2308 {
2309 "id": "l",
2310 "type": "line",
2311 "source": "s",
2312 "paint": {
2313 "line-width": ["case", [">", ["get", "lanes"], 2], 8, 2]
2314 }
2315 }
2316 ]
2317 }
2318 "##;
2319 let spec = parse_style_json(json).expect("parse");
2320 let mut registry = StyleSourceRegistry::new();
2321 registry.set_source("s", StyleSource::VectorTile(VectorTileSource::new(sample_features())));
2322 let doc = spec.resolve(®istry).expect("resolve");
2323 match &doc.layers()[0] {
2324 StyleLayer::Line(layer) => {
2325 use crate::expression::ExprEvalContext;
2326 use crate::geometry::PropertyValue;
2327
2328 let mut props = std::collections::HashMap::new();
2330 props.insert("lanes".to_string(), PropertyValue::Number(4.0));
2331 let ctx = ExprEvalContext::with_feature(14.0, &props);
2332 assert!((layer.width.eval_full(&ctx) - 8.0).abs() < f32::EPSILON);
2333
2334 props.insert("lanes".to_string(), PropertyValue::Number(1.0));
2336 let ctx = ExprEvalContext::with_feature(14.0, &props);
2337 assert!((layer.width.eval_full(&ctx) - 2.0).abs() < f32::EPSILON);
2338 }
2339 other => panic!("expected line, got {other:?}"),
2340 }
2341 }
2342}