1use crate::style::{
9 BackgroundStyleLayer, CircleStyleLayer, FillExtrusionStyleLayer, FillStyleLayer,
10 HeatmapStyleLayer, HillshadeStyleLayer, LightConfig, LineStyleLayer, ModelStyleLayer,
11 RasterStyleLayer, SkyConfig, StyleDocument, StyleError, StyleLayer, StyleLayerMeta,
12 StyleProjection, StyleSource, StyleSourceId, StyleSourceKind, StyleValue, SymbolStyleLayer,
13 VectorStyleLayer,
14};
15use crate::symbols::{
16 SymbolAnchor, SymbolIconTextFit, SymbolPlacement, SymbolTextJustify, SymbolTextTransform,
17 SymbolWritingMode,
18};
19use serde::{Deserialize, Serialize};
20use serde_json::{Map, Value};
21use std::collections::HashMap;
22use std::fmt;
23
24#[derive(Debug)]
26pub enum StyleSpecError {
27 Json(serde_json::Error),
29 MissingRuntimeSource(String),
31 RuntimeSourceKindMismatch {
33 source_id: String,
35 declared: StyleSourceKind,
37 actual: StyleSourceKind,
39 },
40 MissingLayerSource(String),
42 InvalidField {
44 id: String,
46 field: &'static str,
48 expected: &'static str,
50 },
51 Runtime(StyleError),
53}
54
55impl fmt::Display for StyleSpecError {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self {
58 StyleSpecError::Json(err) => write!(f, "style JSON parse error: {err}"),
59 StyleSpecError::MissingRuntimeSource(id) => {
60 write!(f, "missing runtime source binding for style source `{id}`")
61 }
62 StyleSpecError::RuntimeSourceKindMismatch {
63 source_id,
64 declared,
65 actual,
66 } => write!(
67 f,
68 "runtime source `{source_id}` kind mismatch: declared `{}`, bound `{}`",
69 declared.as_str(),
70 actual.as_str()
71 ),
72 StyleSpecError::MissingLayerSource(id) => {
73 write!(f, "style layer `{id}` requires a `source` field")
74 }
75 StyleSpecError::InvalidField {
76 id,
77 field,
78 expected,
79 } => {
80 write!(f, "invalid field `{field}` in `{id}`, expected {expected}")
81 }
82 StyleSpecError::Runtime(err) => err.fmt(f),
83 }
84 }
85}
86
87impl std::error::Error for StyleSpecError {}
88
89impl From<serde_json::Error> for StyleSpecError {
90 fn from(value: serde_json::Error) -> Self {
91 Self::Json(value)
92 }
93}
94
95impl From<StyleError> for StyleSpecError {
96 fn from(value: StyleError) -> Self {
97 Self::Runtime(value)
98 }
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct StyleSpecDocument {
104 #[serde(default = "default_style_version")]
106 pub version: u8,
107 #[serde(default)]
109 pub name: Option<String>,
110 #[serde(default)]
112 pub sources: HashMap<StyleSourceId, StyleSpecSource>,
113 #[serde(default)]
115 pub layers: Vec<StyleSpecLayer>,
116 #[serde(default)]
118 pub terrain: Option<StyleSpecTerrain>,
119 #[serde(default)]
121 pub projection: Option<StyleSpecProjection>,
122 #[serde(default)]
124 pub lights: Option<Vec<StyleSpecLight>>,
125 #[serde(default)]
127 pub sky: Option<StyleSpecSky>,
128 #[serde(default)]
133 pub transition: Option<StyleSpecTransition>,
134}
135
136#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
146pub struct StyleSpecTransition {
147 #[serde(default = "default_transition_duration")]
149 pub duration: f32,
150 #[serde(default)]
152 pub delay: f32,
153}
154
155fn default_transition_duration() -> f32 {
156 300.0
157}
158
159impl StyleSpecTransition {
160 pub fn to_runtime(&self) -> crate::style::TransitionSpec {
162 crate::style::TransitionSpec {
163 duration: self.duration / 1000.0,
164 delay: self.delay / 1000.0,
165 }
166 }
167}
168
169impl StyleSpecDocument {
170 pub fn from_json(json: &str) -> Result<Self, StyleSpecError> {
172 Ok(serde_json::from_str(json)?)
173 }
174
175 pub fn resolve(&self, registry: &StyleSourceRegistry) -> Result<StyleDocument, StyleSpecError> {
177 let mut document = StyleDocument::new();
178
179 for (id, source_spec) in &self.sources {
180 let runtime = registry
181 .source(id)
182 .ok_or_else(|| StyleSpecError::MissingRuntimeSource(id.to_string()))?;
183 if runtime.kind() != source_spec.source_type.runtime_kind() {
184 return Err(StyleSpecError::RuntimeSourceKindMismatch {
185 source_id: id.to_string(),
186 declared: source_spec.source_type.runtime_kind(),
187 actual: runtime.kind(),
188 });
189 }
190 document.add_source(id.clone(), runtime.clone())?;
191 }
192
193 if let Some(terrain) = &self.terrain {
194 document.set_terrain_source(Some(terrain.source.clone()));
195 }
196
197 if let Some(projection) = &self.projection {
198 document.set_projection(projection.projection_type.runtime_projection());
199 }
200
201 if let Some(lights) = &self.lights {
202 document.set_lights(Some(resolve_lights(lights)));
203 }
204
205 if let Some(sky) = &self.sky {
206 document.set_sky(Some(resolve_sky(sky)));
207 }
208
209 if let Some(transition) = &self.transition {
210 document.set_transition(transition.to_runtime());
211 }
212
213 for layer in &self.layers {
214 document.add_layer(layer.to_runtime_layer()?)?;
215 }
216
217 Ok(document)
218 }
219}
220
221#[derive(Debug, Clone, Default)]
223pub struct StyleSourceRegistry {
224 sources: HashMap<StyleSourceId, StyleSource>,
225}
226
227impl StyleSourceRegistry {
228 pub fn new() -> Self {
230 Self::default()
231 }
232
233 pub fn set_source(&mut self, id: impl Into<String>, source: StyleSource) {
235 self.sources.insert(id.into(), source);
236 }
237
238 pub fn source(&self, id: &str) -> Option<&StyleSource> {
240 self.sources.get(id)
241 }
242
243 pub fn resolve_document(
245 &self,
246 spec: &StyleSpecDocument,
247 ) -> Result<StyleDocument, StyleSpecError> {
248 spec.resolve(self)
249 }
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct StyleSpecTerrain {
255 pub source: StyleSourceId,
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct StyleSpecProjection {
262 #[serde(rename = "type")]
264 pub projection_type: StyleSpecProjectionType,
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
269pub enum StyleSpecProjectionType {
270 #[serde(rename = "mercator")]
272 Mercator,
273 #[serde(rename = "equirectangular")]
275 Equirectangular,
276 #[serde(rename = "globe")]
278 Globe,
279 #[serde(rename = "vertical-perspective")]
281 VerticalPerspective,
282}
283
284impl StyleSpecProjectionType {
285 fn runtime_projection(self) -> StyleProjection {
286 match self {
287 StyleSpecProjectionType::Mercator => StyleProjection::Mercator,
288 StyleSpecProjectionType::Equirectangular => StyleProjection::Equirectangular,
289 StyleSpecProjectionType::Globe => StyleProjection::Globe,
290 StyleSpecProjectionType::VerticalPerspective => StyleProjection::VerticalPerspective,
291 }
292 }
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
309pub struct StyleSpecLight {
310 #[serde(rename = "type")]
312 pub light_type: StyleSpecLightType,
313 #[serde(default)]
315 pub properties: StyleSpecLightProperties,
316}
317
318#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
320pub enum StyleSpecLightType {
321 #[serde(rename = "ambient")]
323 Ambient,
324 #[serde(rename = "directional")]
326 Directional,
327 #[serde(rename = "flat")]
329 Flat,
330}
331
332#[derive(Debug, Clone, Default, Serialize, Deserialize)]
336pub struct StyleSpecLightProperties {
337 #[serde(default)]
342 pub color: Option<String>,
343 #[serde(default)]
345 pub intensity: Option<f32>,
346 #[serde(default)]
348 pub direction: Option<[f32; 2]>,
349 #[serde(default, rename = "cast-shadows")]
351 pub cast_shadows: Option<bool>,
352}
353
354fn resolve_lights(lights: &[StyleSpecLight]) -> LightConfig {
357 use crate::style::{AmbientLight, DirectionalLight, LightingMode};
358
359 let mut config = LightConfig::default();
360
361 for light in lights {
362 let color = light
363 .properties
364 .color
365 .as_deref()
366 .map(parse_light_color)
367 .unwrap_or([1.0, 1.0, 1.0]);
368
369 match light.light_type {
370 StyleSpecLightType::Ambient => {
371 config.ambient = AmbientLight {
372 color,
373 intensity: light.properties.intensity.unwrap_or(0.5),
374 };
375 }
376 StyleSpecLightType::Directional => {
377 config.directional = DirectionalLight {
378 direction: light.properties.direction.unwrap_or([210.0, 45.0]),
379 color,
380 intensity: light.properties.intensity.unwrap_or(0.5),
381 cast_shadows: light.properties.cast_shadows.unwrap_or(false),
382 };
383 }
384 StyleSpecLightType::Flat => {
385 config.mode = LightingMode::Flat;
386 }
387 }
388 }
389
390 config
391}
392
393fn parse_light_color(s: &str) -> [f32; 3] {
398 match s.trim().to_lowercase().as_str() {
399 "white" => [1.0, 1.0, 1.0],
400 "black" => [0.0, 0.0, 0.0],
401 hex if hex.starts_with('#') && hex.len() == 7 => {
402 let r = u8::from_str_radix(&hex[1..3], 16).unwrap_or(255);
403 let g = u8::from_str_radix(&hex[3..5], 16).unwrap_or(255);
404 let b = u8::from_str_radix(&hex[5..7], 16).unwrap_or(255);
405 [
407 (r as f32 / 255.0).powf(2.2),
408 (g as f32 / 255.0).powf(2.2),
409 (b as f32 / 255.0).powf(2.2),
410 ]
411 }
412 _ => [1.0, 1.0, 1.0],
413 }
414}
415
416#[derive(Debug, Clone, Default, Serialize, Deserialize)]
434pub struct StyleSpecSky {
435 #[serde(default, rename = "sky-type")]
437 pub sky_type: Option<String>,
438 #[serde(default, rename = "sky-atmosphere-sun")]
440 pub sun_position: Option<[f32; 2]>,
441 #[serde(default, rename = "sky-atmosphere-sun-intensity")]
443 pub sun_intensity: Option<f32>,
444 #[serde(default, rename = "sky-atmosphere-color")]
446 pub atmosphere_color: Option<String>,
447 #[serde(default, rename = "sky-atmosphere-halo-color")]
449 pub halo_color: Option<String>,
450 #[serde(default, rename = "sky-opacity")]
452 pub opacity: Option<f32>,
453}
454
455fn resolve_sky(spec: &StyleSpecSky) -> SkyConfig {
457 use crate::style::{SkyConfig, SkyType};
458
459 let sky_type = match spec.sky_type.as_deref() {
460 Some("gradient") => SkyType::Gradient,
461 _ => SkyType::Atmosphere,
462 };
463
464 SkyConfig {
465 sky_type,
466 sun_position: spec.sun_position,
467 sun_intensity: spec.sun_intensity.unwrap_or(10.0),
468 atmosphere_color: spec
469 .atmosphere_color
470 .as_deref()
471 .map(parse_light_color)
472 .unwrap_or([1.0, 1.0, 1.0]),
473 halo_color: spec
474 .halo_color
475 .as_deref()
476 .map(parse_light_color)
477 .unwrap_or([1.0, 1.0, 1.0]),
478 opacity: spec.opacity.unwrap_or(1.0),
479 }
480}
481
482#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
484pub enum StyleSpecSourceType {
485 #[serde(rename = "raster")]
487 Raster,
488 #[serde(rename = "raster-dem")]
490 RasterDem,
491 #[serde(rename = "geojson")]
493 GeoJson,
494 #[serde(rename = "vector")]
496 Vector,
497 #[serde(rename = "image")]
499 Image,
500 #[serde(rename = "video")]
502 Video,
503 #[serde(rename = "canvas")]
505 Canvas,
506 #[serde(rename = "model")]
508 Model,
509}
510
511impl StyleSpecSourceType {
512 fn runtime_kind(self) -> StyleSourceKind {
513 match self {
514 StyleSpecSourceType::Raster => StyleSourceKind::Raster,
515 StyleSpecSourceType::RasterDem => StyleSourceKind::Terrain,
516 StyleSpecSourceType::GeoJson => StyleSourceKind::GeoJson,
517 StyleSpecSourceType::Vector => StyleSourceKind::VectorTile,
518 StyleSpecSourceType::Image => StyleSourceKind::Image,
519 StyleSpecSourceType::Video => StyleSourceKind::Video,
520 StyleSpecSourceType::Canvas => StyleSourceKind::Canvas,
521 StyleSpecSourceType::Model => StyleSourceKind::Model,
522 }
523 }
524}
525
526#[derive(Debug, Clone, Serialize, Deserialize)]
528pub struct StyleSpecSource {
529 #[serde(rename = "type")]
531 pub source_type: StyleSpecSourceType,
532 #[serde(default)]
534 pub url: Option<String>,
535 #[serde(default)]
537 pub tiles: Vec<String>,
538 #[serde(default)]
540 pub metadata: HashMap<String, Value>,
541}
542
543#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
545pub enum StyleSpecLayerType {
546 #[serde(rename = "background")]
547 Background,
549 #[serde(rename = "hillshade")]
550 Hillshade,
552 #[serde(rename = "raster")]
553 Raster,
555 #[serde(rename = "vector")]
556 Vector,
558 #[serde(rename = "fill")]
559 Fill,
561 #[serde(rename = "line")]
562 Line,
564 #[serde(rename = "circle")]
565 Circle,
567 #[serde(rename = "heatmap")]
568 Heatmap,
570 #[serde(rename = "fill-extrusion")]
571 FillExtrusion,
573 #[serde(rename = "symbol")]
574 Symbol,
576 #[serde(rename = "model")]
577 Model,
579}
580
581#[derive(Debug, Clone, Serialize, Deserialize)]
583pub struct StyleSpecLayer {
584 pub id: String,
586 #[serde(rename = "type")]
588 pub layer_type: StyleSpecLayerType,
589 #[serde(default)]
591 pub source: Option<StyleSourceId>,
592 #[serde(default, rename = "source-layer")]
594 pub source_layer: Option<String>,
595 #[serde(default)]
597 pub minzoom: Option<f32>,
598 #[serde(default)]
600 pub maxzoom: Option<f32>,
601 #[serde(default)]
603 pub layout: Map<String, Value>,
604 #[serde(default)]
606 pub paint: Map<String, Value>,
607}
608
609impl StyleSpecLayer {
610 fn to_runtime_layer(&self) -> Result<StyleLayer, StyleSpecError> {
611 let meta = self.meta()?;
612 let layer = match self.layer_type {
613 StyleSpecLayerType::Background => {
614 let mut layer = BackgroundStyleLayer::new(
615 self.id.clone(),
616 paint_color(&self.paint, "background-color")?
617 .unwrap_or_else(|| [0.0, 0.0, 0.0, 1.0].into()),
618 );
619 layer.meta = meta;
620 StyleLayer::Background(layer)
621 }
622 StyleSpecLayerType::Hillshade => {
623 let mut layer = HillshadeStyleLayer::new(self.id.clone());
624 layer.meta = meta;
625 if let Some(value) = paint_color(&self.paint, "hillshade-highlight-color")? {
626 layer.highlight_color = value;
627 }
628 if let Some(value) = paint_color(&self.paint, "hillshade-shadow-color")? {
629 layer.shadow_color = value;
630 }
631 if let Some(value) = paint_color(&self.paint, "hillshade-accent-color")? {
632 layer.accent_color = value;
633 }
634 if let Some(value) = paint_f32(&self.paint, "hillshade-illumination-direction")? {
635 layer.illumination_direction_deg = value;
636 }
637 if let Some(value) = paint_f32(&self.paint, "hillshade-illumination-altitude")? {
638 layer.illumination_altitude_deg = value;
639 }
640 if let Some(value) = paint_f32(&self.paint, "hillshade-exaggeration")? {
641 layer.exaggeration = value;
642 }
643 StyleLayer::Hillshade(layer)
644 }
645 StyleSpecLayerType::Raster => {
646 let source = self.required_source()?;
647 let mut layer = RasterStyleLayer::new(self.id.clone(), source);
648 layer.meta = meta;
649 StyleLayer::Raster(layer)
650 }
651 StyleSpecLayerType::Vector => {
652 let source = self.required_source()?;
653 let mut layer = VectorStyleLayer::new(self.id.clone(), source);
654 layer.meta = meta;
655 layer.source_layer = self.source_layer.clone();
656 if let Some(value) = paint_color(&self.paint, "fill-color")? {
657 layer.fill_color = value;
658 }
659 if let Some(value) = paint_color(&self.paint, "line-color")? {
660 layer.stroke_color = value;
661 }
662 if let Some(value) = paint_f32(&self.paint, "line-width")? {
663 layer.stroke_width = value;
664 }
665 StyleLayer::Vector(layer)
666 }
667 StyleSpecLayerType::Fill => {
668 let source = self.required_source()?;
669 let mut layer = FillStyleLayer::new(self.id.clone(), source);
670 layer.meta = meta;
671 layer.source_layer = self.source_layer.clone();
672 if let Some(value) = paint_color(&self.paint, "fill-color")? {
673 layer.fill_color = value;
674 }
675 if let Some(value) = paint_color(&self.paint, "fill-outline-color")? {
676 layer.outline_color = value;
677 }
678 if let Some(value) = paint_f32(&self.paint, "fill-outline-width")? {
679 layer.outline_width = value;
680 }
681 StyleLayer::Fill(layer)
682 }
683 StyleSpecLayerType::Line => {
684 let source = self.required_source()?;
685 let mut layer = LineStyleLayer::new(self.id.clone(), source);
686 layer.meta = meta;
687 layer.source_layer = self.source_layer.clone();
688 if let Some(value) = paint_color(&self.paint, "line-color")? {
689 layer.color = value;
690 }
691 if let Some(value) = paint_f32(&self.paint, "line-width")? {
692 layer.width = value;
693 }
694 StyleLayer::Line(layer)
695 }
696 StyleSpecLayerType::Circle => {
697 let source = self.required_source()?;
698 let mut layer = CircleStyleLayer::new(self.id.clone(), source);
699 layer.meta = meta;
700 layer.source_layer = self.source_layer.clone();
701 if let Some(value) = paint_color(&self.paint, "circle-color")? {
702 layer.color = value;
703 }
704 if let Some(value) = paint_f32(&self.paint, "circle-radius")? {
705 layer.radius = value;
706 }
707 if let Some(value) = paint_color(&self.paint, "circle-stroke-color")? {
708 layer.stroke_color = value;
709 }
710 if let Some(value) = paint_f32(&self.paint, "circle-stroke-width")? {
711 layer.stroke_width = value;
712 }
713 StyleLayer::Circle(layer)
714 }
715 StyleSpecLayerType::Heatmap => {
716 let source = self.required_source()?;
717 let mut layer = HeatmapStyleLayer::new(self.id.clone(), source);
718 layer.meta = meta;
719 layer.source_layer = self.source_layer.clone();
720 if let Some(value) = paint_color(&self.paint, "heatmap-color")? {
721 layer.color = value;
722 }
723 if let Some(value) = paint_f32(&self.paint, "heatmap-radius")? {
724 layer.radius = value;
725 }
726 if let Some(value) = paint_f32(&self.paint, "heatmap-intensity")? {
727 layer.intensity = value;
728 }
729 StyleLayer::Heatmap(layer)
730 }
731 StyleSpecLayerType::FillExtrusion => {
732 let source = self.required_source()?;
733 let mut layer = FillExtrusionStyleLayer::new(self.id.clone(), source);
734 layer.meta = meta;
735 layer.source_layer = self.source_layer.clone();
736 if let Some(value) = paint_color(&self.paint, "fill-extrusion-color")? {
737 layer.color = value;
738 }
739 if let Some(value) = paint_f32(&self.paint, "fill-extrusion-base")? {
740 layer.base = value;
741 }
742 if let Some(value) = paint_f32(&self.paint, "fill-extrusion-height")? {
743 layer.height = value;
744 }
745 StyleLayer::FillExtrusion(layer)
746 }
747 StyleSpecLayerType::Symbol => {
748 let source = self.required_source()?;
749 let mut layer = SymbolStyleLayer::new(self.id.clone(), source);
750 layer.meta = meta;
751 layer.source_layer = self.source_layer.clone();
752 if let Some(value) = paint_color(&self.paint, "text-color")?
753 .or_else(|| paint_color(&self.paint, "icon-color").ok().flatten())
754 {
755 layer.color = value;
756 }
757 if let Some(value) = paint_color(&self.paint, "text-halo-color")? {
758 layer.halo_color = value;
759 }
760 if let Some(value) = paint_f32(&self.layout, "text-size")?
761 .or_else(|| paint_f32(&self.layout, "icon-size").ok().flatten())
762 {
763 layer.size = value;
764 }
765 if let Some(value) = string_value(self.layout.get("text-field"))? {
766 layer.text_field = Some(value);
767 }
768 if let Some(value) = string_value(self.layout.get("icon-image"))? {
769 layer.icon_image = Some(value);
770 }
771 if let Some(value) = string_value(self.layout.get("text-font"))
772 .or_else(|_| first_string_from_array(self.layout.get("text-font")))?
773 {
774 layer.font_stack = value;
775 }
776 if let Some(value) = paint_f32(&self.layout, "text-padding")? {
777 layer.padding = value;
778 }
779 if let Some(value) = bool_value(self.layout.get("text-allow-overlap"))? {
780 layer.text_allow_overlap = Some(value);
781 }
782 if let Some(value) = bool_value(self.layout.get("icon-allow-overlap"))? {
783 layer.icon_allow_overlap = Some(value);
784 }
785 if let Some(value) = bool_value(self.layout.get("text-optional"))? {
786 layer.text_optional = Some(value);
787 }
788 if let Some(value) = bool_value(self.layout.get("icon-optional"))? {
789 layer.icon_optional = Some(value);
790 }
791 if let Some(value) = bool_value(self.layout.get("text-ignore-placement"))? {
792 layer.text_ignore_placement = Some(value);
793 }
794 if let Some(value) = bool_value(self.layout.get("icon-ignore-placement"))? {
795 layer.icon_ignore_placement = Some(value);
796 }
797 if let Some(value) = paint_f32(&self.layout, "text-radial-offset")? {
798 layer.radial_offset = Some(value);
799 }
800 if let Some(value) =
801 symbol_anchor_offset_array(self.layout.get("text-variable-anchor-offset"))?
802 {
803 layer.variable_anchor_offsets = Some(value);
804 }
805 if let Some(value) = symbol_anchor(self.layout.get("text-anchor"))? {
806 layer.anchor = value;
807 }
808 if let Some(value) = symbol_text_justify(self.layout.get("text-justify"))? {
809 layer.justify = value;
810 }
811 if let Some(value) = symbol_text_transform(self.layout.get("text-transform"))? {
812 layer.transform = value;
813 }
814 if let Some(value) = paint_f32(&self.layout, "text-max-width")? {
815 layer.max_width = Some(value);
816 }
817 if let Some(value) = paint_f32(&self.layout, "text-line-height")? {
818 layer.line_height = Some(value);
819 }
820 if let Some(value) = paint_f32(&self.layout, "text-letter-spacing")? {
821 layer.letter_spacing = Some(value);
822 }
823 if let Some(value) = symbol_icon_text_fit(self.layout.get("icon-text-fit"))? {
824 layer.icon_text_fit = value;
825 }
826 if let Some(value) = vec4_value(
827 self.layout.get("icon-text-fit-padding"),
828 "icon-text-fit-padding",
829 )? {
830 layer.icon_text_fit_padding = value;
831 }
832 if let Some(value) = paint_f32(&self.layout, "symbol-sort-key")? {
833 layer.sort_key = Some(value);
834 }
835 if let Some(value) = symbol_placement(self.layout.get("symbol-placement"))? {
836 layer.placement = value;
837 }
838 if let Some(value) = paint_f32(&self.layout, "symbol-spacing")? {
839 layer.spacing = value;
840 }
841 if let Some(value) = paint_f32(&self.layout, "text-max-angle")? {
842 layer.max_angle = value;
843 }
844 if let Some(value) = bool_value(self.layout.get("text-keep-upright"))? {
845 layer.keep_upright = value;
846 }
847 if let Some(value) = symbol_anchor_array(self.layout.get("text-variable-anchor"))? {
848 layer.variable_anchors = value;
849 }
850 if let Some(value) = symbol_writing_mode(self.layout.get("text-writing-mode"))? {
851 layer.writing_mode = value;
852 }
853 if let Some(value) = vec2_value(self.layout.get("text-offset"))? {
854 layer.offset = value;
855 }
856 StyleLayer::Symbol(layer)
857 }
858 StyleSpecLayerType::Model => {
859 let source = self.required_source()?;
860 let mut layer = ModelStyleLayer::new(self.id.clone(), source);
861 layer.meta = meta;
862 StyleLayer::Model(layer)
863 }
864 };
865 Ok(layer)
866 }
867
868 fn meta(&self) -> Result<StyleLayerMeta, StyleSpecError> {
869 let mut meta = StyleLayerMeta::new(self.id.clone());
870 meta.min_zoom = self.minzoom;
871 meta.max_zoom = self.maxzoom;
872
873 if let Some(value) = self.opacity_value()? {
874 meta.opacity = value;
875 }
876
877 if let Some(visibility) = self.layout.get("visibility") {
878 let visible = match visibility.as_str() {
879 Some("visible") | None => true,
880 Some("none") => false,
881 Some(_) => {
882 return Err(StyleSpecError::InvalidField {
883 id: self.id.clone(),
884 field: "layout.visibility",
885 expected: "`visible` or `none`",
886 })
887 }
888 };
889 meta.visible = visible.into();
890 }
891
892 Ok(meta)
893 }
894
895 fn opacity_value(&self) -> Result<Option<StyleValue<f32>>, StyleSpecError> {
896 let keys = match self.layer_type {
897 StyleSpecLayerType::Background => &["background-opacity"][..],
898 StyleSpecLayerType::Hillshade => &["hillshade-opacity"],
899 StyleSpecLayerType::Raster => &["raster-opacity"],
900 StyleSpecLayerType::Vector => &["opacity"],
901 StyleSpecLayerType::Fill => &["fill-opacity"],
902 StyleSpecLayerType::Line => &["line-opacity"],
903 StyleSpecLayerType::Circle => &["circle-opacity"],
904 StyleSpecLayerType::Heatmap => &["heatmap-opacity"],
905 StyleSpecLayerType::FillExtrusion => &["fill-extrusion-opacity"],
906 StyleSpecLayerType::Symbol => &["text-opacity", "icon-opacity"],
907 StyleSpecLayerType::Model => &["model-opacity"],
908 };
909
910 for key in keys {
911 if let Some(value) = self.paint.get(*key) {
912 return Ok(Some(f32_value_from_json(&self.id, key, value)?));
913 }
914 }
915 Ok(None)
916 }
917
918 fn required_source(&self) -> Result<String, StyleSpecError> {
919 self.source
920 .clone()
921 .ok_or_else(|| StyleSpecError::MissingLayerSource(self.id.clone()))
922 }
923}
924
925pub fn parse_style_json(json: &str) -> Result<StyleSpecDocument, StyleSpecError> {
927 StyleSpecDocument::from_json(json)
928}
929
930fn paint_color(
931 map: &Map<String, Value>,
932 key: &'static str,
933) -> Result<Option<StyleValue<[f32; 4]>>, StyleSpecError> {
934 map.get(key)
935 .map(|value| color_value_from_json(key, key, value))
936 .transpose()
937}
938
939fn paint_f32(
940 map: &Map<String, Value>,
941 key: &'static str,
942) -> Result<Option<StyleValue<f32>>, StyleSpecError> {
943 map.get(key)
944 .map(|value| f32_value_from_json(key, key, value))
945 .transpose()
946}
947
948fn string_value(value: Option<&Value>) -> Result<Option<StyleValue<String>>, StyleSpecError> {
949 value
950 .map(|value| string_value_from_json("symbol", "text-field", value))
951 .transpose()
952}
953
954fn bool_value(value: Option<&Value>) -> Result<Option<StyleValue<bool>>, StyleSpecError> {
955 value
956 .map(|value| bool_value_from_json("symbol", "allow-overlap", value))
957 .transpose()
958}
959
960fn first_string_from_array(
961 value: Option<&Value>,
962) -> Result<Option<StyleValue<String>>, StyleSpecError> {
963 let Some(value) = value else {
964 return Ok(None);
965 };
966 let Some(array) = value.as_array() else {
967 return Err(StyleSpecError::InvalidField {
968 id: "symbol".into(),
969 field: "text-font",
970 expected: "string or string array",
971 });
972 };
973 let Some(first) = array.first() else {
974 return Ok(None);
975 };
976 Ok(Some(string_value_from_json("symbol", "text-font", first)?))
977}
978
979fn vec2_value(value: Option<&Value>) -> Result<Option<[f32; 2]>, StyleSpecError> {
980 let Some(value) = value else {
981 return Ok(None);
982 };
983 let Some(array) = value.as_array() else {
984 return Err(StyleSpecError::InvalidField {
985 id: "symbol".into(),
986 field: "text-offset",
987 expected: "two-element numeric array",
988 });
989 };
990 if array.len() != 2 {
991 return Err(StyleSpecError::InvalidField {
992 id: "symbol".into(),
993 field: "text-offset",
994 expected: "two-element numeric array",
995 });
996 }
997 Ok(Some([
998 array[0]
999 .as_f64()
1000 .ok_or_else(|| StyleSpecError::InvalidField {
1001 id: "symbol".into(),
1002 field: "text-offset",
1003 expected: "two-element numeric array",
1004 })? as f32,
1005 array[1]
1006 .as_f64()
1007 .ok_or_else(|| StyleSpecError::InvalidField {
1008 id: "symbol".into(),
1009 field: "text-offset",
1010 expected: "two-element numeric array",
1011 })? as f32,
1012 ]))
1013}
1014
1015fn symbol_anchor_array(value: Option<&Value>) -> Result<Option<Vec<SymbolAnchor>>, StyleSpecError> {
1016 let Some(value) = value else {
1017 return Ok(None);
1018 };
1019 let Some(array) = value.as_array() else {
1020 return Err(StyleSpecError::InvalidField {
1021 id: "symbol".into(),
1022 field: "text-variable-anchor",
1023 expected: "string array",
1024 });
1025 };
1026 let mut anchors = Vec::with_capacity(array.len());
1027 for value in array {
1028 let Some(text) = value.as_str() else {
1029 return Err(StyleSpecError::InvalidField {
1030 id: "symbol".into(),
1031 field: "text-variable-anchor",
1032 expected: "string array",
1033 });
1034 };
1035 anchors.push(parse_symbol_anchor(text)?);
1036 }
1037 Ok(Some(anchors))
1038}
1039
1040#[allow(clippy::type_complexity)]
1041fn symbol_anchor_offset_array(
1042 value: Option<&Value>,
1043) -> Result<Option<Vec<(SymbolAnchor, [f32; 2])>>, StyleSpecError> {
1044 let Some(value) = value else {
1045 return Ok(None);
1046 };
1047 let Some(array) = value.as_array() else {
1048 return Err(StyleSpecError::InvalidField {
1049 id: "symbol".into(),
1050 field: "text-variable-anchor-offset",
1051 expected: "alternating anchor / [x, y] array",
1052 });
1053 };
1054 if array.len() % 2 != 0 {
1055 return Err(StyleSpecError::InvalidField {
1056 id: "symbol".into(),
1057 field: "text-variable-anchor-offset",
1058 expected: "alternating anchor / [x, y] array",
1059 });
1060 }
1061
1062 let mut parsed = Vec::with_capacity(array.len() / 2);
1063 for pair in array.chunks_exact(2) {
1064 let anchor_text = pair[0]
1065 .as_str()
1066 .ok_or_else(|| StyleSpecError::InvalidField {
1067 id: "symbol".into(),
1068 field: "text-variable-anchor-offset",
1069 expected: "anchor string in alternating anchor / [x, y] array",
1070 })?;
1071 let anchor = parse_symbol_anchor(anchor_text)?;
1072 let offset = parse_vec2_array(
1073 "symbol",
1074 "text-variable-anchor-offset",
1075 pair[1]
1076 .as_array()
1077 .ok_or_else(|| StyleSpecError::InvalidField {
1078 id: "symbol".into(),
1079 field: "text-variable-anchor-offset",
1080 expected: "[x, y] offset array in alternating anchor / [x, y] array",
1081 })?,
1082 )?;
1083 parsed.push((anchor, offset));
1084 }
1085
1086 Ok(Some(parsed))
1087}
1088
1089fn symbol_writing_mode(value: Option<&Value>) -> Result<Option<SymbolWritingMode>, StyleSpecError> {
1090 let Some(value) = value else {
1091 return Ok(None);
1092 };
1093 let Some(text) = value.as_str() else {
1094 return Err(StyleSpecError::InvalidField {
1095 id: "symbol".into(),
1096 field: "text-writing-mode",
1097 expected: "string value",
1098 });
1099 };
1100 Ok(Some(parse_symbol_writing_mode(text)?))
1101}
1102
1103fn symbol_placement(value: Option<&Value>) -> Result<Option<SymbolPlacement>, StyleSpecError> {
1104 let Some(value) = value else {
1105 return Ok(None);
1106 };
1107 let Some(text) = value.as_str() else {
1108 return Err(StyleSpecError::InvalidField {
1109 id: "symbol".into(),
1110 field: "symbol-placement",
1111 expected: "string value",
1112 });
1113 };
1114 Ok(Some(parse_symbol_placement(text)?))
1115}
1116
1117fn symbol_anchor(value: Option<&Value>) -> Result<Option<SymbolAnchor>, StyleSpecError> {
1118 let Some(value) = value else {
1119 return Ok(None);
1120 };
1121 let Some(text) = value.as_str() else {
1122 return Err(StyleSpecError::InvalidField {
1123 id: "symbol".into(),
1124 field: "text-anchor",
1125 expected: "string value",
1126 });
1127 };
1128 Ok(Some(parse_symbol_anchor(text)?))
1129}
1130
1131fn symbol_text_justify(
1132 value: Option<&Value>,
1133) -> Result<Option<StyleValue<SymbolTextJustify>>, StyleSpecError> {
1134 let Some(value) = value else {
1135 return Ok(None);
1136 };
1137 Ok(Some(match value {
1138 Value::String(text) => StyleValue::Constant(parse_symbol_text_justify(text)?),
1139 Value::Object(object) => {
1140 parse_zoom_stops("symbol", "text-justify", object, |id, field, value| {
1141 let text = value.as_str().ok_or_else(|| StyleSpecError::InvalidField {
1142 id: id.to_owned(),
1143 field,
1144 expected: "string value",
1145 })?;
1146 parse_symbol_text_justify(text)
1147 })?
1148 }
1149
1150 _ => {
1151 return Err(StyleSpecError::InvalidField {
1152 id: "symbol".into(),
1153 field: "text-justify",
1154 expected: "string value or `{ stops: ... }`",
1155 })
1156 }
1157 }))
1158}
1159
1160fn symbol_icon_text_fit(
1161 value: Option<&Value>,
1162) -> Result<Option<StyleValue<SymbolIconTextFit>>, StyleSpecError> {
1163 let Some(value) = value else {
1164 return Ok(None);
1165 };
1166 Ok(Some(match value {
1167 Value::String(text) => StyleValue::Constant(parse_symbol_icon_text_fit(text)?),
1168 Value::Object(object) => {
1169 parse_zoom_stops("symbol", "icon-text-fit", object, |id, field, value| {
1170 let text = value.as_str().ok_or_else(|| StyleSpecError::InvalidField {
1171 id: id.to_owned(),
1172 field,
1173 expected: "string value",
1174 })?;
1175 parse_symbol_icon_text_fit(text)
1176 })?
1177 }
1178 _ => {
1179 return Err(StyleSpecError::InvalidField {
1180 id: "symbol".into(),
1181 field: "icon-text-fit",
1182 expected: "string value or `{ stops: ... }`",
1183 })
1184 }
1185 }))
1186}
1187
1188fn symbol_text_transform(
1189 value: Option<&Value>,
1190) -> Result<Option<StyleValue<SymbolTextTransform>>, StyleSpecError> {
1191 let Some(value) = value else {
1192 return Ok(None);
1193 };
1194 Ok(Some(match value {
1195 Value::String(text) => StyleValue::Constant(parse_symbol_text_transform(text)?),
1196 Value::Object(object) => {
1197 parse_zoom_stops("symbol", "text-transform", object, |id, field, value| {
1198 let text = value.as_str().ok_or_else(|| StyleSpecError::InvalidField {
1199 id: id.to_owned(),
1200 field,
1201 expected: "string value",
1202 })?;
1203 parse_symbol_text_transform(text)
1204 })?
1205 }
1206 _ => {
1207 return Err(StyleSpecError::InvalidField {
1208 id: "symbol".into(),
1209 field: "text-transform",
1210 expected: "string value or `{ stops: ... }`",
1211 })
1212 }
1213 }))
1214}
1215
1216fn parse_symbol_anchor(text: &str) -> Result<SymbolAnchor, StyleSpecError> {
1217 match text {
1218 "center" => Ok(SymbolAnchor::Center),
1219 "top" => Ok(SymbolAnchor::Top),
1220 "bottom" => Ok(SymbolAnchor::Bottom),
1221 "left" => Ok(SymbolAnchor::Left),
1222 "right" => Ok(SymbolAnchor::Right),
1223 "top-left" => Ok(SymbolAnchor::TopLeft),
1224 "top-right" => Ok(SymbolAnchor::TopRight),
1225 "bottom-left" => Ok(SymbolAnchor::BottomLeft),
1226 "bottom-right" => Ok(SymbolAnchor::BottomRight),
1227 _ => Err(StyleSpecError::InvalidField {
1228 id: "symbol".into(),
1229 field: "text-variable-anchor",
1230 expected: "known symbol-anchor string",
1231 }),
1232 }
1233}
1234
1235fn parse_symbol_icon_text_fit(text: &str) -> Result<SymbolIconTextFit, StyleSpecError> {
1236 match text {
1237 "none" => Ok(SymbolIconTextFit::None),
1238 "width" => Ok(SymbolIconTextFit::Width),
1239 "height" => Ok(SymbolIconTextFit::Height),
1240 "both" => Ok(SymbolIconTextFit::Both),
1241 _ => Err(StyleSpecError::InvalidField {
1242 id: "symbol".into(),
1243 field: "icon-text-fit",
1244 expected: "`none`, `width`, `height`, or `both`",
1245 }),
1246 }
1247}
1248
1249fn parse_symbol_text_transform(text: &str) -> Result<SymbolTextTransform, StyleSpecError> {
1250 match text {
1251 "none" => Ok(SymbolTextTransform::None),
1252 "uppercase" => Ok(SymbolTextTransform::Uppercase),
1253 "lowercase" => Ok(SymbolTextTransform::Lowercase),
1254 _ => Err(StyleSpecError::InvalidField {
1255 id: "symbol".into(),
1256 field: "text-transform",
1257 expected: "`none`, `uppercase`, or `lowercase`",
1258 }),
1259 }
1260}
1261
1262fn parse_symbol_text_justify(text: &str) -> Result<SymbolTextJustify, StyleSpecError> {
1263 match text {
1264 "auto" => Ok(SymbolTextJustify::Auto),
1265 "left" => Ok(SymbolTextJustify::Left),
1266 "center" => Ok(SymbolTextJustify::Center),
1267 "right" => Ok(SymbolTextJustify::Right),
1268 _ => Err(StyleSpecError::InvalidField {
1269 id: "symbol".into(),
1270 field: "text-justify",
1271 expected: "`auto`, `left`, `center`, or `right`",
1272 }),
1273 }
1274}
1275
1276fn parse_symbol_placement(text: &str) -> Result<SymbolPlacement, StyleSpecError> {
1277 match text {
1278 "point" => Ok(SymbolPlacement::Point),
1279 "line" => Ok(SymbolPlacement::Line),
1280 _ => Err(StyleSpecError::InvalidField {
1281 id: "symbol".into(),
1282 field: "symbol-placement",
1283 expected: "`point` or `line`",
1284 }),
1285 }
1286}
1287
1288fn parse_symbol_writing_mode(text: &str) -> Result<SymbolWritingMode, StyleSpecError> {
1289 match text {
1290 "horizontal" => Ok(SymbolWritingMode::Horizontal),
1291 "vertical" => Ok(SymbolWritingMode::Vertical),
1292 _ => Err(StyleSpecError::InvalidField {
1293 id: "symbol".into(),
1294 field: "text-writing-mode",
1295 expected: "`horizontal` or `vertical`",
1296 }),
1297 }
1298}
1299
1300fn bool_value_from_json(
1301 id: &str,
1302 field: &'static str,
1303 value: &Value,
1304) -> Result<StyleValue<bool>, StyleSpecError> {
1305 match value {
1306 Value::Bool(value) => Ok(StyleValue::Constant(*value)),
1307 Value::Object(object) => parse_zoom_stops(id, field, object, parse_bool_constant),
1308 Value::Array(arr) if is_expression_array(arr) => parse_expression_bool(id, field, arr),
1309 _ => Err(StyleSpecError::InvalidField {
1310 id: id.to_owned(),
1311 field,
1312 expected: "boolean value, `{ stops: ... }`, or expression array",
1313 }),
1314 }
1315}
1316
1317fn f32_value_from_json(
1318 id: &str,
1319 field: &'static str,
1320 value: &Value,
1321) -> Result<StyleValue<f32>, StyleSpecError> {
1322 match value {
1323 Value::Number(number) => {
1324 Ok(StyleValue::Constant(
1325 number
1326 .as_f64()
1327 .ok_or_else(|| StyleSpecError::InvalidField {
1328 id: id.to_owned(),
1329 field,
1330 expected: "numeric value",
1331 })? as f32,
1332 ))
1333 }
1334 Value::Object(object) => parse_zoom_stops(id, field, object, parse_f32_constant),
1335 Value::Array(arr) if is_expression_array(arr) => parse_expression_f32(id, field, arr),
1336 _ => Err(StyleSpecError::InvalidField {
1337 id: id.to_owned(),
1338 field,
1339 expected: "numeric value, `{ stops: ... }`, or expression array",
1340 }),
1341 }
1342}
1343
1344fn string_value_from_json(
1345 id: &str,
1346 field: &'static str,
1347 value: &Value,
1348) -> Result<StyleValue<String>, StyleSpecError> {
1349 match value {
1350 Value::String(text) => Ok(StyleValue::Constant(text.clone())),
1351 Value::Object(object) => parse_zoom_stops(id, field, object, parse_string_constant),
1352 Value::Array(arr) if is_expression_array(arr) => parse_expression_string(id, field, arr),
1353 _ => Err(StyleSpecError::InvalidField {
1354 id: id.to_owned(),
1355 field,
1356 expected: "string value, `{ stops: ... }`, or expression array",
1357 }),
1358 }
1359}
1360
1361fn color_value_from_json(
1362 id: &str,
1363 field: &'static str,
1364 value: &Value,
1365) -> Result<StyleValue<[f32; 4]>, StyleSpecError> {
1366 match value {
1367 Value::String(text) => Ok(StyleValue::Constant(parse_color_string(id, field, text)?)),
1368 Value::Array(array) if is_expression_array(array) => {
1369 parse_expression_color(id, field, array)
1370 }
1371 Value::Array(array) => Ok(StyleValue::Constant(parse_color_array(id, field, array)?)),
1372 Value::Object(object) => {
1373 parse_zoom_stops(id, field, object, |id, field, value| match value {
1374 Value::String(text) => parse_color_string(id, field, text),
1375 Value::Array(array) => parse_color_array(id, field, array),
1376 _ => Err(StyleSpecError::InvalidField {
1377 id: id.to_owned(),
1378 field,
1379 expected: "color string or RGBA array",
1380 }),
1381 })
1382 }
1383 _ => Err(StyleSpecError::InvalidField {
1384 id: id.to_owned(),
1385 field,
1386 expected: "color string, RGBA array, or `{ stops: ... }`",
1387 }),
1388 }
1389}
1390
1391fn parse_zoom_stops<T>(
1392 id: &str,
1393 field: &'static str,
1394 object: &Map<String, Value>,
1395 parse_value: impl Fn(&str, &'static str, &Value) -> Result<T, StyleSpecError>,
1396) -> Result<StyleValue<T>, StyleSpecError> {
1397 let Some(stops) = object.get("stops").and_then(Value::as_array) else {
1398 return Err(StyleSpecError::InvalidField {
1399 id: id.to_owned(),
1400 field,
1401 expected: "object with `stops` array",
1402 });
1403 };
1404
1405 let mut parsed = Vec::with_capacity(stops.len());
1406 for stop in stops {
1407 let pair = stop
1408 .as_array()
1409 .ok_or_else(|| StyleSpecError::InvalidField {
1410 id: id.to_owned(),
1411 field,
1412 expected: "stop tuple `[zoom, value]`",
1413 })?;
1414 if pair.len() != 2 {
1415 return Err(StyleSpecError::InvalidField {
1416 id: id.to_owned(),
1417 field,
1418 expected: "stop tuple `[zoom, value]`",
1419 });
1420 }
1421 let zoom = pair[0]
1422 .as_f64()
1423 .ok_or_else(|| StyleSpecError::InvalidField {
1424 id: id.to_owned(),
1425 field,
1426 expected: "numeric stop zoom",
1427 })? as f32;
1428 let value = parse_value(id, field, &pair[1])?;
1429 parsed.push((zoom, value));
1430 }
1431 Ok(StyleValue::ZoomStops(parsed))
1432}
1433
1434fn parse_f32_constant(id: &str, field: &'static str, value: &Value) -> Result<f32, StyleSpecError> {
1435 value
1436 .as_f64()
1437 .map(|v| v as f32)
1438 .ok_or_else(|| StyleSpecError::InvalidField {
1439 id: id.to_owned(),
1440 field,
1441 expected: "numeric value",
1442 })
1443}
1444
1445fn parse_string_constant(
1446 id: &str,
1447 field: &'static str,
1448 value: &Value,
1449) -> Result<String, StyleSpecError> {
1450 value
1451 .as_str()
1452 .map(ToOwned::to_owned)
1453 .ok_or_else(|| StyleSpecError::InvalidField {
1454 id: id.to_owned(),
1455 field,
1456 expected: "string value",
1457 })
1458}
1459
1460fn parse_bool_constant(
1461 id: &str,
1462 field: &'static str,
1463 value: &Value,
1464) -> Result<bool, StyleSpecError> {
1465 value.as_bool().ok_or_else(|| StyleSpecError::InvalidField {
1466 id: id.to_owned(),
1467 field,
1468 expected: "boolean value",
1469 })
1470}
1471
1472fn vec4_value(
1473 value: Option<&Value>,
1474 field: &'static str,
1475) -> Result<Option<[f32; 4]>, StyleSpecError> {
1476 let Some(value) = value else {
1477 return Ok(None);
1478 };
1479 let arr = value
1480 .as_array()
1481 .ok_or_else(|| StyleSpecError::InvalidField {
1482 id: "symbol".into(),
1483 field,
1484 expected: "array of 4 numbers",
1485 })?;
1486 if arr.len() != 4 {
1487 return Err(StyleSpecError::InvalidField {
1488 id: "symbol".into(),
1489 field,
1490 expected: "array of 4 numbers",
1491 });
1492 }
1493 let mut out = [0.0f32; 4];
1494 for (i, v) in arr.iter().enumerate() {
1495 out[i] = v.as_f64().ok_or_else(|| StyleSpecError::InvalidField {
1496 id: "symbol".into(),
1497 field,
1498 expected: "numeric array element",
1499 })? as f32;
1500 }
1501 Ok(Some(out))
1502}
1503
1504fn parse_vec2_array(
1505 id: &str,
1506 field: &'static str,
1507 arr: &[Value],
1508) -> Result<[f32; 2], StyleSpecError> {
1509 if arr.len() != 2 {
1510 return Err(StyleSpecError::InvalidField {
1511 id: id.to_owned(),
1512 field,
1513 expected: "[x, y] array of 2 numbers",
1514 });
1515 }
1516 let x = arr[0]
1517 .as_f64()
1518 .ok_or_else(|| StyleSpecError::InvalidField {
1519 id: id.to_owned(),
1520 field,
1521 expected: "numeric value in [x, y] array",
1522 })? as f32;
1523 let y = arr[1]
1524 .as_f64()
1525 .ok_or_else(|| StyleSpecError::InvalidField {
1526 id: id.to_owned(),
1527 field,
1528 expected: "numeric value in [x, y] array",
1529 })? as f32;
1530 Ok([x, y])
1531}
1532
1533fn parse_color_array(
1534 id: &str,
1535 field: &'static str,
1536 array: &[Value],
1537) -> Result<[f32; 4], StyleSpecError> {
1538 if !(array.len() == 3 || array.len() == 4) {
1539 return Err(StyleSpecError::InvalidField {
1540 id: id.to_owned(),
1541 field,
1542 expected: "RGB or RGBA array",
1543 });
1544 }
1545 let mut rgba = [0.0, 0.0, 0.0, 1.0];
1546 for (i, value) in array.iter().enumerate() {
1547 rgba[i] = value.as_f64().ok_or_else(|| StyleSpecError::InvalidField {
1548 id: id.to_owned(),
1549 field,
1550 expected: "numeric RGB/RGBA components",
1551 })? as f32;
1552 }
1553 Ok(rgba)
1554}
1555
1556fn parse_color_string(
1557 id: &str,
1558 field: &'static str,
1559 text: &str,
1560) -> Result<[f32; 4], StyleSpecError> {
1561 let hex = text
1562 .strip_prefix('#')
1563 .ok_or_else(|| StyleSpecError::InvalidField {
1564 id: id.to_owned(),
1565 field,
1566 expected: "hex color string like `#RRGGBB` or `#RRGGBBAA`",
1567 })?;
1568
1569 let rgba = match hex.len() {
1570 3 => {
1571 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).ok();
1572 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).ok();
1573 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).ok();
1574 [r, g, b, Some(255)]
1575 }
1576 4 => {
1577 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).ok();
1578 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).ok();
1579 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).ok();
1580 let a = u8::from_str_radix(&hex[3..4].repeat(2), 16).ok();
1581 [r, g, b, a]
1582 }
1583 6 => {
1584 let r = u8::from_str_radix(&hex[0..2], 16).ok();
1585 let g = u8::from_str_radix(&hex[2..4], 16).ok();
1586 let b = u8::from_str_radix(&hex[4..6], 16).ok();
1587 [r, g, b, Some(255)]
1588 }
1589 8 => {
1590 let r = u8::from_str_radix(&hex[0..2], 16).ok();
1591 let g = u8::from_str_radix(&hex[2..4], 16).ok();
1592 let b = u8::from_str_radix(&hex[4..6], 16).ok();
1593 let a = u8::from_str_radix(&hex[6..8], 16).ok();
1594 [r, g, b, a]
1595 }
1596 _ => [None, None, None, None],
1597 };
1598
1599 match rgba {
1600 [Some(r), Some(g), Some(b), Some(a)] => Ok([
1601 r as f32 / 255.0,
1602 g as f32 / 255.0,
1603 b as f32 / 255.0,
1604 a as f32 / 255.0,
1605 ]),
1606 _ => Err(StyleSpecError::InvalidField {
1607 id: id.to_owned(),
1608 field,
1609 expected: "hex color string like `#RRGGBB` or `#RRGGBBAA`",
1610 }),
1611 }
1612}
1613
1614fn default_style_version() -> u8 {
1615 8
1616}
1617
1618use crate::expression::{BoolExpression, Expression, NumericExpression, StringExpression};
1637
1638fn is_expression_array(arr: &[Value]) -> bool {
1640 arr.first()
1641 .and_then(Value::as_str)
1642 .map(|op| {
1643 matches!(
1644 op,
1645 "get"
1646 | "has"
1647 | "!"
1648 | "all"
1649 | "any"
1650 | "=="
1651 | "!="
1652 | ">"
1653 | ">="
1654 | "<"
1655 | "<="
1656 | "+"
1657 | "-"
1658 | "*"
1659 | "/"
1660 | "%"
1661 | "^"
1662 | "abs"
1663 | "ln"
1664 | "sqrt"
1665 | "min"
1666 | "max"
1667 | "interpolate"
1668 | "step"
1669 | "match"
1670 | "case"
1671 | "coalesce"
1672 | "zoom"
1673 | "pitch"
1674 | "concat"
1675 | "upcase"
1676 | "downcase"
1677 | "feature-state"
1678 | "to-number"
1679 | "to-string"
1680 | "to-boolean"
1681 | "literal"
1682 )
1683 })
1684 .unwrap_or(false)
1685}
1686
1687fn parse_expression_f32(
1689 id: &str,
1690 field: &'static str,
1691 arr: &[Value],
1692) -> Result<StyleValue<f32>, StyleSpecError> {
1693 let op = arr[0].as_str().unwrap_or("");
1694 match op {
1695 "literal" if arr.len() == 2 => {
1696 let v = arr[1]
1697 .as_f64()
1698 .ok_or_else(|| expr_err(id, field, "literal number"))? as f32;
1699 Ok(Expression::Constant(v))
1700 }
1701 "get" if arr.len() == 2 => {
1702 let key = arr[1]
1703 .as_str()
1704 .ok_or_else(|| expr_err(id, field, "get property key"))?;
1705 Ok(Expression::GetProperty {
1706 key: key.to_owned(),
1707 fallback: 0.0,
1708 })
1709 }
1710 "feature-state" if arr.len() == 2 => {
1711 let key = arr[1]
1712 .as_str()
1713 .ok_or_else(|| expr_err(id, field, "feature-state key"))?;
1714 Ok(Expression::FeatureState {
1715 key: key.to_owned(),
1716 fallback: 0.0,
1717 })
1718 }
1719 "zoom" => Ok(Expression::Interpolate {
1720 input: Box::new(NumericExpression::Zoom),
1721 stops: vec![],
1722 }),
1723 "interpolate" => parse_interpolate_f32(id, field, arr),
1724 "step" => parse_step_f32(id, field, arr),
1725 "case" => parse_case_f32(id, field, arr),
1726 "coalesce" => {
1727 let exprs: Result<Vec<_>, _> = arr[1..]
1728 .iter()
1729 .map(|v| match v {
1730 Value::Number(n) => Ok(Expression::Constant(n.as_f64().unwrap_or(0.0) as f32)),
1731 Value::Array(a) if is_expression_array(a) => parse_expression_f32(id, field, a),
1732 _ => Err(expr_err(id, field, "coalesce sub-expression")),
1733 })
1734 .collect();
1735 Ok(Expression::Coalesce(exprs?))
1736 }
1737 _ => Err(expr_err(
1738 id,
1739 field,
1740 &format!("f32 expression (unsupported operator `{op}`)"),
1741 )),
1742 }
1743}
1744
1745fn parse_expression_color(
1747 id: &str,
1748 field: &'static str,
1749 arr: &[Value],
1750) -> Result<StyleValue<[f32; 4]>, StyleSpecError> {
1751 let op = arr[0].as_str().unwrap_or("");
1752 match op {
1753 "literal" if arr.len() == 2 => {
1754 if let Some(a) = arr[1].as_array() {
1755 let c = parse_color_array(id, field, a)?;
1756 Ok(Expression::Constant(c))
1757 } else {
1758 Err(expr_err(id, field, "literal color array"))
1759 }
1760 }
1761 "interpolate" => parse_interpolate_color(id, field, arr),
1762 "step" => parse_step_color(id, field, arr),
1763 "match" => parse_match_color(id, field, arr),
1764 "case" => parse_case_color(id, field, arr),
1765 _ => Err(expr_err(
1766 id,
1767 field,
1768 &format!("color expression (unsupported operator `{op}`)"),
1769 )),
1770 }
1771}
1772
1773fn parse_expression_string(
1775 id: &str,
1776 field: &'static str,
1777 arr: &[Value],
1778) -> Result<StyleValue<String>, StyleSpecError> {
1779 let op = arr[0].as_str().unwrap_or("");
1780 match op {
1781 "literal" if arr.len() == 2 => {
1782 let s = arr[1]
1783 .as_str()
1784 .ok_or_else(|| expr_err(id, field, "literal string"))?;
1785 Ok(Expression::Constant(s.to_owned()))
1786 }
1787 "get" if arr.len() == 2 => {
1788 let key = arr[1]
1789 .as_str()
1790 .ok_or_else(|| expr_err(id, field, "get property key"))?;
1791 Ok(Expression::GetProperty {
1792 key: key.to_owned(),
1793 fallback: String::new(),
1794 })
1795 }
1796 "feature-state" if arr.len() == 2 => {
1797 let key = arr[1]
1798 .as_str()
1799 .ok_or_else(|| expr_err(id, field, "feature-state key"))?;
1800 Ok(Expression::FeatureState {
1801 key: key.to_owned(),
1802 fallback: String::new(),
1803 })
1804 }
1805 "concat" => {
1806 let exprs: Result<Vec<_>, _> = arr[1..]
1807 .iter()
1808 .map(|v| match v {
1809 Value::String(s) => Ok(Expression::Constant(s.clone())),
1810 Value::Array(a) if is_expression_array(a) => {
1811 parse_expression_string(id, field, a)
1812 }
1813 _ => Err(expr_err(id, field, "concat sub-expression")),
1814 })
1815 .collect();
1816 Ok(Expression::Coalesce(exprs?))
1817 }
1818 _ => Err(expr_err(
1819 id,
1820 field,
1821 &format!("string expression (unsupported operator `{op}`)"),
1822 )),
1823 }
1824}
1825
1826fn parse_expression_bool(
1828 id: &str,
1829 field: &'static str,
1830 arr: &[Value],
1831) -> Result<StyleValue<bool>, StyleSpecError> {
1832 let op = arr[0].as_str().unwrap_or("");
1833 match op {
1834 "literal" if arr.len() == 2 => {
1835 let b = arr[1]
1836 .as_bool()
1837 .ok_or_else(|| expr_err(id, field, "literal boolean"))?;
1838 Ok(Expression::Constant(b))
1839 }
1840 "get" if arr.len() == 2 => {
1841 let key = arr[1]
1842 .as_str()
1843 .ok_or_else(|| expr_err(id, field, "get property key"))?;
1844 Ok(Expression::GetProperty {
1845 key: key.to_owned(),
1846 fallback: false,
1847 })
1848 }
1849 "feature-state" if arr.len() == 2 => {
1850 let key = arr[1]
1851 .as_str()
1852 .ok_or_else(|| expr_err(id, field, "feature-state key"))?;
1853 Ok(Expression::FeatureState {
1854 key: key.to_owned(),
1855 fallback: false,
1856 })
1857 }
1858 "has" if arr.len() == 2 => {
1859 let key = arr[1]
1860 .as_str()
1861 .ok_or_else(|| expr_err(id, field, "has property key"))?;
1862 Ok(Expression::Case {
1863 branches: vec![(BoolExpression::Has(key.to_owned()), true)],
1864 fallback: false,
1865 })
1866 }
1867 _ => Err(expr_err(
1868 id,
1869 field,
1870 &format!("boolean expression (unsupported operator `{op}`)"),
1871 )),
1872 }
1873}
1874
1875fn parse_numeric_input(
1881 id: &str,
1882 field: &'static str,
1883 value: &Value,
1884) -> Result<NumericExpression, StyleSpecError> {
1885 match value {
1886 Value::Number(n) => Ok(NumericExpression::Literal(n.as_f64().unwrap_or(0.0))),
1887 Value::Array(arr) if !arr.is_empty() => {
1888 let op = arr[0].as_str().unwrap_or("");
1889 match op {
1890 "zoom" => Ok(NumericExpression::Zoom),
1891 "pitch" => Ok(NumericExpression::Pitch),
1892 "get" if arr.len() == 2 => {
1893 let key = arr[1]
1894 .as_str()
1895 .ok_or_else(|| expr_err(id, field, "get key"))?;
1896 Ok(NumericExpression::GetProperty {
1897 key: key.to_owned(),
1898 fallback: 0.0,
1899 })
1900 }
1901 "feature-state" if arr.len() == 2 => {
1902 let key = arr[1]
1903 .as_str()
1904 .ok_or_else(|| expr_err(id, field, "feature-state key"))?;
1905 Ok(NumericExpression::GetState {
1906 key: key.to_owned(),
1907 fallback: 0.0,
1908 })
1909 }
1910 "+" if arr.len() == 3 => Ok(NumericExpression::Add(
1911 Box::new(parse_numeric_input(id, field, &arr[1])?),
1912 Box::new(parse_numeric_input(id, field, &arr[2])?),
1913 )),
1914 "-" if arr.len() == 3 => Ok(NumericExpression::Sub(
1915 Box::new(parse_numeric_input(id, field, &arr[1])?),
1916 Box::new(parse_numeric_input(id, field, &arr[2])?),
1917 )),
1918 "*" if arr.len() == 3 => Ok(NumericExpression::Mul(
1919 Box::new(parse_numeric_input(id, field, &arr[1])?),
1920 Box::new(parse_numeric_input(id, field, &arr[2])?),
1921 )),
1922 "/" if arr.len() == 3 => Ok(NumericExpression::Div(
1923 Box::new(parse_numeric_input(id, field, &arr[1])?),
1924 Box::new(parse_numeric_input(id, field, &arr[2])?),
1925 )),
1926 "%" if arr.len() == 3 => Ok(NumericExpression::Mod(
1927 Box::new(parse_numeric_input(id, field, &arr[1])?),
1928 Box::new(parse_numeric_input(id, field, &arr[2])?),
1929 )),
1930 "^" if arr.len() == 3 => Ok(NumericExpression::Pow(
1931 Box::new(parse_numeric_input(id, field, &arr[1])?),
1932 Box::new(parse_numeric_input(id, field, &arr[2])?),
1933 )),
1934 "abs" if arr.len() == 2 => Ok(NumericExpression::Abs(Box::new(
1935 parse_numeric_input(id, field, &arr[1])?,
1936 ))),
1937 "ln" if arr.len() == 2 => Ok(NumericExpression::Ln(Box::new(parse_numeric_input(
1938 id, field, &arr[1],
1939 )?))),
1940 "sqrt" if arr.len() == 2 => Ok(NumericExpression::Sqrt(Box::new(
1941 parse_numeric_input(id, field, &arr[1])?,
1942 ))),
1943 "min" if arr.len() == 3 => Ok(NumericExpression::Min(
1944 Box::new(parse_numeric_input(id, field, &arr[1])?),
1945 Box::new(parse_numeric_input(id, field, &arr[2])?),
1946 )),
1947 "max" if arr.len() == 3 => Ok(NumericExpression::Max(
1948 Box::new(parse_numeric_input(id, field, &arr[1])?),
1949 Box::new(parse_numeric_input(id, field, &arr[2])?),
1950 )),
1951 _ => Err(expr_err(
1952 id,
1953 field,
1954 &format!("numeric input (unsupported `{op}`)"),
1955 )),
1956 }
1957 }
1958 _ => Err(expr_err(id, field, "numeric input")),
1959 }
1960}
1961
1962fn parse_string_input(
1964 id: &str,
1965 field: &'static str,
1966 value: &Value,
1967) -> Result<StringExpression, StyleSpecError> {
1968 match value {
1969 Value::String(s) => Ok(StringExpression::Literal(s.clone())),
1970 Value::Array(arr) if !arr.is_empty() => {
1971 let op = arr[0].as_str().unwrap_or("");
1972 match op {
1973 "get" if arr.len() == 2 => {
1974 let key = arr[1]
1975 .as_str()
1976 .ok_or_else(|| expr_err(id, field, "get key"))?;
1977 Ok(StringExpression::GetProperty {
1978 key: key.to_owned(),
1979 fallback: String::new(),
1980 })
1981 }
1982 "feature-state" if arr.len() == 2 => {
1983 let key = arr[1]
1984 .as_str()
1985 .ok_or_else(|| expr_err(id, field, "feature-state key"))?;
1986 Ok(StringExpression::GetState {
1987 key: key.to_owned(),
1988 fallback: String::new(),
1989 })
1990 }
1991 "upcase" if arr.len() == 2 => Ok(StringExpression::Upcase(Box::new(
1992 parse_string_input(id, field, &arr[1])?,
1993 ))),
1994 "downcase" if arr.len() == 2 => Ok(StringExpression::Downcase(Box::new(
1995 parse_string_input(id, field, &arr[1])?,
1996 ))),
1997 "concat" if arr.len() >= 3 => {
1998 let left = parse_string_input(id, field, &arr[1])?;
1999 let right = parse_string_input(id, field, &arr[2])?;
2000 Ok(StringExpression::Concat(Box::new(left), Box::new(right)))
2001 }
2002 _ => Err(expr_err(
2003 id,
2004 field,
2005 &format!("string input (unsupported `{op}`)"),
2006 )),
2007 }
2008 }
2009 _ => Err(expr_err(id, field, "string input")),
2010 }
2011}
2012
2013fn parse_bool_condition(
2015 id: &str,
2016 field: &'static str,
2017 value: &Value,
2018) -> Result<BoolExpression, StyleSpecError> {
2019 match value {
2020 Value::Bool(b) => Ok(BoolExpression::Literal(*b)),
2021 Value::Array(arr) if !arr.is_empty() => {
2022 let op = arr[0].as_str().unwrap_or("");
2023 match op {
2024 "has" if arr.len() == 2 => {
2025 let key = arr[1]
2026 .as_str()
2027 .ok_or_else(|| expr_err(id, field, "has key"))?;
2028 Ok(BoolExpression::Has(key.to_owned()))
2029 }
2030 "get" if arr.len() == 2 => {
2031 let key = arr[1]
2032 .as_str()
2033 .ok_or_else(|| expr_err(id, field, "get key"))?;
2034 Ok(BoolExpression::GetProperty {
2035 key: key.to_owned(),
2036 fallback: false,
2037 })
2038 }
2039 "!" if arr.len() == 2 => Ok(BoolExpression::Not(Box::new(parse_bool_condition(
2040 id, field, &arr[1],
2041 )?))),
2042 "all" => {
2043 let exprs: Result<Vec<_>, _> = arr[1..]
2044 .iter()
2045 .map(|v| parse_bool_condition(id, field, v))
2046 .collect();
2047 Ok(BoolExpression::All(exprs?))
2048 }
2049 "any" => {
2050 let exprs: Result<Vec<_>, _> = arr[1..]
2051 .iter()
2052 .map(|v| parse_bool_condition(id, field, v))
2053 .collect();
2054 Ok(BoolExpression::Any(exprs?))
2055 }
2056 "==" if arr.len() == 3 => {
2057 if let (Ok(a), Ok(b)) = (
2059 parse_numeric_input(id, field, &arr[1]),
2060 parse_numeric_input(id, field, &arr[2]),
2061 ) {
2062 Ok(BoolExpression::Eq(a, b))
2063 } else if let (Ok(a), Ok(b)) = (
2064 parse_string_input(id, field, &arr[1]),
2065 parse_string_input(id, field, &arr[2]),
2066 ) {
2067 Ok(BoolExpression::StrEq(a, b))
2068 } else {
2069 Err(expr_err(id, field, "== operands"))
2070 }
2071 }
2072 "!=" if arr.len() == 3 => {
2073 let a = parse_numeric_input(id, field, &arr[1])?;
2074 let b = parse_numeric_input(id, field, &arr[2])?;
2075 Ok(BoolExpression::Neq(a, b))
2076 }
2077 ">" if arr.len() == 3 => {
2078 let a = parse_numeric_input(id, field, &arr[1])?;
2079 let b = parse_numeric_input(id, field, &arr[2])?;
2080 Ok(BoolExpression::Gt(a, b))
2081 }
2082 ">=" if arr.len() == 3 => {
2083 let a = parse_numeric_input(id, field, &arr[1])?;
2084 let b = parse_numeric_input(id, field, &arr[2])?;
2085 Ok(BoolExpression::Gte(a, b))
2086 }
2087 "<" if arr.len() == 3 => {
2088 let a = parse_numeric_input(id, field, &arr[1])?;
2089 let b = parse_numeric_input(id, field, &arr[2])?;
2090 Ok(BoolExpression::Lt(a, b))
2091 }
2092 "<=" if arr.len() == 3 => {
2093 let a = parse_numeric_input(id, field, &arr[1])?;
2094 let b = parse_numeric_input(id, field, &arr[2])?;
2095 Ok(BoolExpression::Lte(a, b))
2096 }
2097 _ => Err(expr_err(
2098 id,
2099 field,
2100 &format!("boolean condition (unsupported `{op}`)"),
2101 )),
2102 }
2103 }
2104 _ => Err(expr_err(id, field, "boolean condition")),
2105 }
2106}
2107
2108fn parse_interpolate_f32(
2114 id: &str,
2115 field: &'static str,
2116 arr: &[Value],
2117) -> Result<StyleValue<f32>, StyleSpecError> {
2118 if arr.len() < 5 || !(arr.len() - 3).is_multiple_of(2) {
2120 return Err(expr_err(id, field, "interpolate with even stop pairs"));
2121 }
2122 let input = parse_numeric_input(id, field, &arr[2])?;
2123 let mut stops = Vec::with_capacity((arr.len() - 3) / 2);
2124 for chunk in arr[3..].chunks(2) {
2125 let z = chunk[0]
2126 .as_f64()
2127 .ok_or_else(|| expr_err(id, field, "interpolate stop zoom"))? as f32;
2128 let v = chunk[1]
2129 .as_f64()
2130 .ok_or_else(|| expr_err(id, field, "interpolate stop value"))? as f32;
2131 stops.push((z, v));
2132 }
2133 Ok(Expression::Interpolate {
2134 input: Box::new(input),
2135 stops,
2136 })
2137}
2138
2139fn parse_step_f32(
2141 id: &str,
2142 field: &'static str,
2143 arr: &[Value],
2144) -> Result<StyleValue<f32>, StyleSpecError> {
2145 if arr.len() < 3 {
2146 return Err(expr_err(id, field, "step expression"));
2147 }
2148 let input = parse_numeric_input(id, field, &arr[1])?;
2149 let default = arr[2]
2150 .as_f64()
2151 .ok_or_else(|| expr_err(id, field, "step default"))? as f32;
2152 let mut stops = Vec::new();
2153 for chunk in arr[3..].chunks(2) {
2154 if chunk.len() == 2 {
2155 let z = chunk[0]
2156 .as_f64()
2157 .ok_or_else(|| expr_err(id, field, "step threshold"))? as f32;
2158 let v = chunk[1]
2159 .as_f64()
2160 .ok_or_else(|| expr_err(id, field, "step value"))? as f32;
2161 stops.push((z, v));
2162 }
2163 }
2164 Ok(Expression::Step {
2165 input: Box::new(input),
2166 default,
2167 stops,
2168 })
2169}
2170
2171fn parse_case_f32(
2173 id: &str,
2174 field: &'static str,
2175 arr: &[Value],
2176) -> Result<StyleValue<f32>, StyleSpecError> {
2177 if arr.len() < 4 || (arr.len() - 1).is_multiple_of(2) {
2180 return Err(expr_err(id, field, "case expression"));
2181 }
2182 let fallback = arr[arr.len() - 1]
2184 .as_f64()
2185 .ok_or_else(|| expr_err(id, field, "case fallback"))? as f32;
2186 let mut branches = Vec::new();
2187 for chunk in arr[1..arr.len() - 1].chunks(2) {
2188 let cond = parse_bool_condition(id, field, &chunk[0])?;
2189 let val = chunk[1]
2190 .as_f64()
2191 .ok_or_else(|| expr_err(id, field, "case value"))? as f32;
2192 branches.push((cond, val));
2193 }
2194 Ok(Expression::Case { branches, fallback })
2195}
2196
2197fn parse_interpolate_color(
2203 id: &str,
2204 field: &'static str,
2205 arr: &[Value],
2206) -> Result<StyleValue<[f32; 4]>, StyleSpecError> {
2207 if arr.len() < 5 || !(arr.len() - 3).is_multiple_of(2) {
2208 return Err(expr_err(id, field, "interpolate with even stop pairs"));
2209 }
2210 let input = parse_numeric_input(id, field, &arr[2])?;
2211 let mut stops = Vec::with_capacity((arr.len() - 3) / 2);
2212 for chunk in arr[3..].chunks(2) {
2213 let z = chunk[0]
2214 .as_f64()
2215 .ok_or_else(|| expr_err(id, field, "interpolate stop zoom"))? as f32;
2216 let c = parse_json_color(id, field, &chunk[1])?;
2217 stops.push((z, c));
2218 }
2219 Ok(Expression::Interpolate {
2220 input: Box::new(input),
2221 stops,
2222 })
2223}
2224
2225fn parse_step_color(
2227 id: &str,
2228 field: &'static str,
2229 arr: &[Value],
2230) -> Result<StyleValue<[f32; 4]>, StyleSpecError> {
2231 if arr.len() < 3 {
2232 return Err(expr_err(id, field, "step expression"));
2233 }
2234 let input = parse_numeric_input(id, field, &arr[1])?;
2235 let default = parse_json_color(id, field, &arr[2])?;
2236 let mut stops = Vec::new();
2237 for chunk in arr[3..].chunks(2) {
2238 if chunk.len() == 2 {
2239 let z = chunk[0]
2240 .as_f64()
2241 .ok_or_else(|| expr_err(id, field, "step threshold"))? as f32;
2242 let c = parse_json_color(id, field, &chunk[1])?;
2243 stops.push((z, c));
2244 }
2245 }
2246 Ok(Expression::Step {
2247 input: Box::new(input),
2248 default,
2249 stops,
2250 })
2251}
2252
2253fn parse_match_color(
2255 id: &str,
2256 field: &'static str,
2257 arr: &[Value],
2258) -> Result<StyleValue<[f32; 4]>, StyleSpecError> {
2259 if arr.len() < 4 {
2260 return Err(expr_err(id, field, "match expression"));
2261 }
2262 let input = parse_string_input(id, field, &arr[1])?;
2263 let fallback = parse_json_color(id, field, &arr[arr.len() - 1])?;
2264 let mut cases = Vec::new();
2265 let pairs = &arr[2..arr.len() - 1];
2267 for chunk in pairs.chunks(2) {
2268 if chunk.len() == 2 {
2269 let label = chunk[0]
2270 .as_str()
2271 .ok_or_else(|| expr_err(id, field, "match label"))?;
2272 let color = parse_json_color(id, field, &chunk[1])?;
2273 cases.push((label.to_owned(), color));
2274 }
2275 }
2276 Ok(Expression::Match {
2277 input: Box::new(input),
2278 cases,
2279 fallback,
2280 })
2281}
2282
2283fn parse_case_color(
2285 id: &str,
2286 field: &'static str,
2287 arr: &[Value],
2288) -> Result<StyleValue<[f32; 4]>, StyleSpecError> {
2289 if arr.len() < 4 || (arr.len() - 1).is_multiple_of(2) {
2291 return Err(expr_err(id, field, "case expression"));
2292 }
2293 let fallback = parse_json_color(id, field, &arr[arr.len() - 1])?;
2294 let mut branches = Vec::new();
2295 for chunk in arr[1..arr.len() - 1].chunks(2) {
2296 if chunk.len() == 2 {
2297 let cond = parse_bool_condition(id, field, &chunk[0])?;
2298 let color = parse_json_color(id, field, &chunk[1])?;
2299 branches.push((cond, color));
2300 }
2301 }
2302 Ok(Expression::Case { branches, fallback })
2303}
2304
2305fn parse_json_color(
2311 id: &str,
2312 field: &'static str,
2313 value: &Value,
2314) -> Result<[f32; 4], StyleSpecError> {
2315 match value {
2316 Value::String(s) => parse_color_string(id, field, s),
2317 Value::Array(a) => parse_color_array(id, field, a),
2318 _ => Err(expr_err(id, field, "color value")),
2319 }
2320}
2321
2322fn expr_err(id: &str, field: &'static str, expected: &str) -> StyleSpecError {
2324 StyleSpecError::InvalidField {
2325 id: id.to_owned(),
2326 field,
2327 expected: Box::leak(format!("expression: {expected}").into_boxed_str()),
2328 }
2329}
2330
2331#[cfg(test)]
2332mod tests {
2333 use super::*;
2334 use crate::geometry::{Feature, FeatureCollection, Geometry, Point};
2335 use crate::models::{ModelInstance, ModelMesh};
2336 use crate::style::{GeoJsonSource, ModelSource, VectorTileSource};
2337 use crate::tile_source::{TileResponse, TileSource};
2338 use rustial_math::{GeoCoord, TileId};
2339
2340 #[derive(Clone, Default)]
2341 struct StubTileSource;
2342
2343 impl TileSource for StubTileSource {
2344 fn request(&self, _id: TileId) {}
2345 fn poll(&self) -> Vec<(TileId, Result<TileResponse, crate::TileError>)> {
2346 Vec::new()
2347 }
2348 }
2349
2350 fn sample_features() -> FeatureCollection {
2351 FeatureCollection {
2352 features: vec![Feature {
2353 geometry: Geometry::Point(Point {
2354 coord: GeoCoord::from_lat_lon(51.1, 17.0),
2355 }),
2356 properties: HashMap::new(),
2357 }],
2358 }
2359 }
2360
2361 #[test]
2362 fn parses_and_resolves_style_json() {
2363 let json = r##"
2364 {
2365 "version": 8,
2366 "projection": { "type": "equirectangular" },
2367 "terrain": { "source": "dem" },
2368 "sources": {
2369 "base": { "type": "raster" },
2370 "dem": { "type": "raster-dem" },
2371 "places": { "type": "geojson" },
2372 "labels": { "type": "vector" },
2373 "hero": { "type": "model" }
2374 },
2375 "layers": [
2376 { "id": "bg", "type": "background", "paint": { "background-color": "#112233" } },
2377 { "id": "raster", "type": "raster", "source": "base", "paint": { "raster-opacity": 0.75 } },
2378 { "id": "fill", "type": "fill", "source": "places", "paint": { "fill-color": "#ff0000" } },
2379 { "id": "symbol", "type": "symbol", "source": "labels", "layout": { "text-field": "name", "text-size": 18 }, "paint": { "text-color": "#00ff00" } },
2380 { "id": "model", "type": "model", "source": "hero" }
2381 ]
2382 }
2383 "##;
2384
2385 let spec = parse_style_json(json).expect("parse spec");
2386 let mut registry = StyleSourceRegistry::new();
2387 registry.set_source(
2388 "base",
2389 StyleSource::Raster(crate::style::RasterSource::new(|| Box::new(StubTileSource))),
2390 );
2391 registry.set_source(
2392 "dem",
2393 StyleSource::Terrain(crate::style::TerrainSource::new(|| {
2394 Box::new(crate::terrain::FlatElevationSource::new(4, 4))
2395 })),
2396 );
2397 registry.set_source(
2398 "places",
2399 StyleSource::GeoJson(GeoJsonSource::new(sample_features())),
2400 );
2401 registry.set_source(
2402 "labels",
2403 StyleSource::VectorTile(VectorTileSource::new(sample_features())),
2404 );
2405 registry.set_source(
2406 "hero",
2407 StyleSource::Model(ModelSource::new(vec![ModelInstance::new(
2408 GeoCoord::from_lat_lon(0.0, 0.0),
2409 ModelMesh {
2410 positions: vec![[0.0, 0.0, 0.0]],
2411 normals: vec![[0.0, 0.0, 1.0]],
2412 uvs: vec![[0.0, 0.0]],
2413 indices: vec![0],
2414 },
2415 )])),
2416 );
2417
2418 let document = spec.resolve(®istry).expect("resolve");
2419 assert_eq!(document.layers().len(), 5);
2420 assert_eq!(document.terrain_source(), Some("dem"));
2421 assert_eq!(document.projection(), StyleProjection::Equirectangular);
2422 }
2423
2424 #[test]
2425 fn parses_projection_from_json_without_layers() {
2426 let json = r##"
2427 {
2428 "version": 8,
2429 "projection": { "type": "globe" },
2430 "sources": {},
2431 "layers": []
2432 }
2433 "##;
2434
2435 let spec = parse_style_json(json).expect("parse spec");
2436 assert!(matches!(
2437 spec.projection,
2438 Some(StyleSpecProjection {
2439 projection_type: StyleSpecProjectionType::Globe
2440 })
2441 ));
2442
2443 let registry = StyleSourceRegistry::new();
2444 let document = spec.resolve(®istry).expect("resolve");
2445 assert_eq!(document.projection(), StyleProjection::Globe);
2446 }
2447
2448 #[test]
2449 fn parses_vertical_perspective_projection_from_json() {
2450 let json = r##"
2451 {
2452 "version": 8,
2453 "projection": { "type": "vertical-perspective" },
2454 "sources": {},
2455 "layers": []
2456 }
2457 "##;
2458
2459 let spec = parse_style_json(json).expect("parse spec");
2460 assert!(matches!(
2461 spec.projection,
2462 Some(StyleSpecProjection {
2463 projection_type: StyleSpecProjectionType::VerticalPerspective
2464 })
2465 ));
2466
2467 let registry = StyleSourceRegistry::new();
2468 let document = spec.resolve(®istry).expect("resolve");
2469 assert_eq!(document.projection(), StyleProjection::VerticalPerspective);
2470 }
2471
2472 #[test]
2473 fn parses_zoom_stop_values_from_json() {
2474 let json = r##"
2475 {
2476 "version": 8,
2477 "sources": { "places": { "type": "geojson" } },
2478 "layers": [
2479 {
2480 "id": "circles",
2481 "type": "circle",
2482 "source": "places",
2483 "paint": {
2484 "circle-radius": { "stops": [[5, 4], [10, 12]] },
2485 "circle-color": { "stops": [[5, "#000000"], [10, "#ffffff"]] }
2486 }
2487 }
2488 ]
2489 }
2490 "##;
2491
2492 let spec = parse_style_json(json).expect("parse spec");
2493 let mut registry = StyleSourceRegistry::new();
2494 registry.set_source(
2495 "places",
2496 StyleSource::GeoJson(GeoJsonSource::new(sample_features())),
2497 );
2498 let document = spec.resolve(®istry).expect("resolve");
2499
2500 match &document.layers()[0] {
2501 StyleLayer::Circle(layer) => {
2502 assert!(matches!(layer.radius, StyleValue::ZoomStops(_)));
2503 assert!(matches!(layer.color, StyleValue::ZoomStops(_)));
2504 }
2505 other => panic!("expected circle layer, got {other:?}"),
2506 }
2507 }
2508
2509 #[test]
2510 fn parses_symbol_overlap_placement_spacing_max_angle_keep_upright_writing_mode_and_offset() {
2511 let json = r##"
2512 {
2513 "version": 8,
2514 "sources": { "labels": { "type": "vector" } },
2515 "layers": [
2516 {
2517 "id": "symbol",
2518 "type": "symbol",
2519 "source": "labels",
2520 "layout": {
2521 "symbol-placement": "line",
2522 "text-allow-overlap": true,
2523 "icon-allow-overlap": false,
2524 "text-optional": true,
2525 "icon-optional": false,
2526 "text-ignore-placement": true,
2527 "icon-ignore-placement": false,
2528 "text-radial-offset": 2,
2529 "text-anchor": "top-right",
2530 "text-justify": "auto",
2531 "text-transform": "uppercase",
2532 "text-max-width": 8,
2533 "text-line-height": 1.5,
2534 "text-letter-spacing": 0.25,
2535 "icon-text-fit": "both",
2536 "icon-text-fit-padding": [1, 2, 3, 4],
2537 "text-variable-anchor-offset": ["top", [1, 2], "bottom", [3, 4]],
2538 "symbol-sort-key": 7,
2539 "symbol-spacing": 320,
2540 "text-max-angle": 90,
2541 "text-keep-upright": false,
2542 "text-field": "name",
2543 "text-variable-anchor": ["center", "top"],
2544 "text-writing-mode": "vertical",
2545 "text-offset": [1, 2]
2546 }
2547 }
2548 ]
2549 }
2550 "##;
2551
2552 let spec = parse_style_json(json).expect("parse spec");
2553 let mut registry = StyleSourceRegistry::new();
2554 registry.set_source(
2555 "labels",
2556 StyleSource::VectorTile(VectorTileSource::new(sample_features())),
2557 );
2558 let document = spec.resolve(®istry).expect("resolve");
2559
2560 match &document.layers()[0] {
2561 StyleLayer::Symbol(layer) => {
2562 assert_eq!(
2563 layer.text_allow_overlap.as_ref().map(StyleValue::evaluate),
2564 Some(true)
2565 );
2566 assert_eq!(
2567 layer.icon_allow_overlap.as_ref().map(StyleValue::evaluate),
2568 Some(false)
2569 );
2570 assert_eq!(
2571 layer.text_optional.as_ref().map(StyleValue::evaluate),
2572 Some(true)
2573 );
2574 assert_eq!(
2575 layer.icon_optional.as_ref().map(StyleValue::evaluate),
2576 Some(false)
2577 );
2578 assert_eq!(
2579 layer
2580 .text_ignore_placement
2581 .as_ref()
2582 .map(StyleValue::evaluate),
2583 Some(true)
2584 );
2585 assert_eq!(
2586 layer
2587 .icon_ignore_placement
2588 .as_ref()
2589 .map(StyleValue::evaluate),
2590 Some(false)
2591 );
2592 assert_eq!(
2593 layer.radial_offset.as_ref().map(StyleValue::evaluate),
2594 Some(2.0)
2595 );
2596 assert_eq!(layer.anchor, SymbolAnchor::TopRight);
2597 assert_eq!(layer.justify.evaluate(), SymbolTextJustify::Auto);
2598 assert_eq!(layer.transform.evaluate(), SymbolTextTransform::Uppercase);
2599 assert_eq!(
2600 layer.max_width.as_ref().map(StyleValue::evaluate),
2601 Some(8.0)
2602 );
2603 assert_eq!(
2604 layer.line_height.as_ref().map(StyleValue::evaluate),
2605 Some(1.5)
2606 );
2607 assert_eq!(
2608 layer.letter_spacing.as_ref().map(StyleValue::evaluate),
2609 Some(0.25)
2610 );
2611 assert_eq!(layer.icon_text_fit.evaluate(), SymbolIconTextFit::Both);
2612 assert_eq!(layer.icon_text_fit_padding, [1.0, 2.0, 3.0, 4.0]);
2613 assert_eq!(
2614 layer.variable_anchor_offsets,
2615 Some(vec![
2616 (SymbolAnchor::Top, [1.0, 2.0]),
2617 (SymbolAnchor::Bottom, [3.0, 4.0]),
2618 ])
2619 );
2620 assert_eq!(layer.sort_key.as_ref().map(StyleValue::evaluate), Some(7.0));
2621 assert_eq!(layer.placement, SymbolPlacement::Line);
2622 assert_eq!(layer.spacing.evaluate(), 320.0);
2623 assert_eq!(layer.max_angle.evaluate(), 90.0);
2624 assert!(!layer.keep_upright.evaluate());
2625 assert_eq!(
2626 layer.variable_anchors,
2627 vec![SymbolAnchor::Center, SymbolAnchor::Top]
2628 );
2629 assert_eq!(layer.writing_mode, SymbolWritingMode::Vertical);
2630 assert_eq!(layer.offset, [1.0, 2.0]);
2631 }
2632 other => panic!("expected symbol layer, got {other:?}"),
2633 }
2634 }
2635
2636 #[test]
2637 fn parses_source_layer_for_vector_style_layers() {
2638 let json = r##"
2639 {
2640 "version": 8,
2641 "sources": { "labels": { "type": "vector" } },
2642 "layers": [
2643 {
2644 "id": "roads",
2645 "type": "line",
2646 "source": "labels",
2647 "source-layer": "transport"
2648 }
2649 ]
2650 }
2651 "##;
2652
2653 let spec = parse_style_json(json).expect("parse spec");
2654 assert_eq!(spec.layers.len(), 1);
2655 assert_eq!(spec.layers[0].source_layer.as_deref(), Some("transport"));
2656
2657 let mut registry = StyleSourceRegistry::new();
2658 let mut source_layers = HashMap::new();
2659 source_layers.insert(
2660 "transport".to_string(),
2661 FeatureCollection {
2662 features: vec![Feature {
2663 geometry: Geometry::Point(Point {
2664 coord: GeoCoord::from_lat_lon(1.0, 2.0),
2665 }),
2666 properties: HashMap::new(),
2667 }],
2668 },
2669 );
2670 registry.set_source(
2671 "labels",
2672 StyleSource::VectorTile(VectorTileSource::from_source_layers(source_layers)),
2673 );
2674
2675 let document = spec.resolve(®istry).expect("resolve");
2676 match &document.layers()[0] {
2677 StyleLayer::Line(layer) => {
2678 assert_eq!(layer.source_layer.as_deref(), Some("transport"));
2679 }
2680 other => panic!("expected line layer, got {other:?}"),
2681 }
2682 }
2683
2684 #[test]
2689 fn f32_expression_interpolate_on_zoom() {
2690 let json = r##"
2691 {
2692 "version": 8,
2693 "sources": { "s": { "type": "vector" } },
2694 "layers": [
2695 {
2696 "id": "l",
2697 "type": "line",
2698 "source": "s",
2699 "paint": {
2700 "line-width": ["interpolate", ["linear"], ["zoom"], 0, 1, 20, 10]
2701 }
2702 }
2703 ]
2704 }
2705 "##;
2706 let spec = parse_style_json(json).expect("parse");
2707 let mut registry = StyleSourceRegistry::new();
2708 registry.set_source(
2709 "s",
2710 StyleSource::VectorTile(VectorTileSource::new(sample_features())),
2711 );
2712 let doc = spec.resolve(®istry).expect("resolve");
2713 match &doc.layers()[0] {
2714 StyleLayer::Line(layer) => {
2715 let ctx = crate::style::StyleEvalContext::new(10.0);
2716 let width = layer.width.evaluate_with_context(ctx);
2717 assert!((width - 5.5).abs() < 0.1, "expected ~5.5, got {width}");
2718 }
2719 other => panic!("expected line, got {other:?}"),
2720 }
2721 }
2722
2723 #[test]
2724 fn f32_expression_step_on_zoom() {
2725 let json = r##"
2726 {
2727 "version": 8,
2728 "sources": { "s": { "type": "vector" } },
2729 "layers": [
2730 {
2731 "id": "l",
2732 "type": "circle",
2733 "source": "s",
2734 "paint": {
2735 "circle-radius": ["step", ["zoom"], 2, 5, 4, 10, 8]
2736 }
2737 }
2738 ]
2739 }
2740 "##;
2741 let spec = parse_style_json(json).expect("parse");
2742 let mut registry = StyleSourceRegistry::new();
2743 registry.set_source(
2744 "s",
2745 StyleSource::VectorTile(VectorTileSource::new(sample_features())),
2746 );
2747 let doc = spec.resolve(®istry).expect("resolve");
2748 match &doc.layers()[0] {
2749 StyleLayer::Circle(layer) => {
2750 use crate::expression::ExprEvalContext;
2751 assert!(
2753 (layer.radius.eval_full(&ExprEvalContext::zoom_only(3.0)) - 2.0).abs()
2754 < f32::EPSILON
2755 );
2756 assert!(
2758 (layer.radius.eval_full(&ExprEvalContext::zoom_only(5.0)) - 4.0).abs()
2759 < f32::EPSILON
2760 );
2761 assert!(
2763 (layer.radius.eval_full(&ExprEvalContext::zoom_only(7.0)) - 4.0).abs()
2764 < f32::EPSILON
2765 );
2766 assert!(
2768 (layer.radius.eval_full(&ExprEvalContext::zoom_only(10.0)) - 8.0).abs()
2769 < f32::EPSILON
2770 );
2771 assert!(
2773 (layer.radius.eval_full(&ExprEvalContext::zoom_only(15.0)) - 8.0).abs()
2774 < f32::EPSILON
2775 );
2776 }
2777 other => panic!("expected circle, got {other:?}"),
2778 }
2779 }
2780
2781 #[test]
2782 fn color_expression_match_on_property() {
2783 let json = r##"
2784 {
2785 "version": 8,
2786 "sources": { "s": { "type": "vector" } },
2787 "layers": [
2788 {
2789 "id": "l",
2790 "type": "fill",
2791 "source": "s",
2792 "paint": {
2793 "fill-color": ["match", ["get", "type"], "residential", "#0000ff", "commercial", "#ff0000", "#888888"]
2794 }
2795 }
2796 ]
2797 }
2798 "##;
2799 let spec = parse_style_json(json).expect("parse");
2800 let mut registry = StyleSourceRegistry::new();
2801 registry.set_source(
2802 "s",
2803 StyleSource::VectorTile(VectorTileSource::new(sample_features())),
2804 );
2805 let doc = spec.resolve(®istry).expect("resolve");
2806 match &doc.layers()[0] {
2807 StyleLayer::Fill(layer) => {
2808 use crate::expression::ExprEvalContext;
2809 use crate::geometry::PropertyValue;
2810
2811 let mut props = std::collections::HashMap::new();
2812 props.insert(
2813 "type".to_string(),
2814 PropertyValue::String("residential".to_string()),
2815 );
2816 let ctx = ExprEvalContext::with_feature(10.0, &props);
2817 let color = layer.fill_color.eval_full(&ctx);
2818 assert!((color[0] - 0.0).abs() < 0.01);
2819 assert!((color[2] - 1.0).abs() < 0.01);
2820
2821 props.insert(
2822 "type".to_string(),
2823 PropertyValue::String("commercial".to_string()),
2824 );
2825 let ctx = ExprEvalContext::with_feature(10.0, &props);
2826 let color = layer.fill_color.eval_full(&ctx);
2827 assert!((color[0] - 1.0).abs() < 0.01);
2828 assert!((color[2] - 0.0).abs() < 0.01);
2829
2830 props.insert(
2832 "type".to_string(),
2833 PropertyValue::String("industrial".to_string()),
2834 );
2835 let ctx = ExprEvalContext::with_feature(10.0, &props);
2836 let color = layer.fill_color.eval_full(&ctx);
2837 assert!((color[0] - 0.533).abs() < 0.01);
2838 }
2839 other => panic!("expected fill, got {other:?}"),
2840 }
2841 }
2842
2843 #[test]
2844 fn f32_expression_get_property() {
2845 let json = r##"
2846 {
2847 "version": 8,
2848 "sources": { "s": { "type": "vector" } },
2849 "layers": [
2850 {
2851 "id": "l",
2852 "type": "fill-extrusion",
2853 "source": "s",
2854 "paint": {
2855 "fill-extrusion-height": ["get", "height"],
2856 "fill-extrusion-base": ["get", "base"]
2857 }
2858 }
2859 ]
2860 }
2861 "##;
2862 let spec = parse_style_json(json).expect("parse");
2863 let mut registry = StyleSourceRegistry::new();
2864 registry.set_source(
2865 "s",
2866 StyleSource::VectorTile(VectorTileSource::new(sample_features())),
2867 );
2868 let doc = spec.resolve(®istry).expect("resolve");
2869 match &doc.layers()[0] {
2870 StyleLayer::FillExtrusion(layer) => {
2871 use crate::expression::ExprEvalContext;
2872 use crate::geometry::PropertyValue;
2873 let mut props = std::collections::HashMap::new();
2874 props.insert("height".to_string(), PropertyValue::Number(50.0));
2875 props.insert("base".to_string(), PropertyValue::Number(10.0));
2876 let ctx = ExprEvalContext::with_feature(14.0, &props);
2877 assert!((layer.height.eval_full(&ctx) - 50.0).abs() < f32::EPSILON);
2878 assert!((layer.base.eval_full(&ctx) - 10.0).abs() < f32::EPSILON);
2879 }
2880 other => panic!("expected fill-extrusion, got {other:?}"),
2881 }
2882 }
2883
2884 #[test]
2885 fn f32_expression_case_with_comparison() {
2886 let json = r##"
2887 {
2888 "version": 8,
2889 "sources": { "s": { "type": "vector" } },
2890 "layers": [
2891 {
2892 "id": "l",
2893 "type": "line",
2894 "source": "s",
2895 "paint": {
2896 "line-width": ["case", [">", ["get", "lanes"], 2], 8, 2]
2897 }
2898 }
2899 ]
2900 }
2901 "##;
2902 let spec = parse_style_json(json).expect("parse");
2903 let mut registry = StyleSourceRegistry::new();
2904 registry.set_source(
2905 "s",
2906 StyleSource::VectorTile(VectorTileSource::new(sample_features())),
2907 );
2908 let doc = spec.resolve(®istry).expect("resolve");
2909 match &doc.layers()[0] {
2910 StyleLayer::Line(layer) => {
2911 use crate::expression::ExprEvalContext;
2912 use crate::geometry::PropertyValue;
2913
2914 let mut props = std::collections::HashMap::new();
2916 props.insert("lanes".to_string(), PropertyValue::Number(4.0));
2917 let ctx = ExprEvalContext::with_feature(14.0, &props);
2918 assert!((layer.width.eval_full(&ctx) - 8.0).abs() < f32::EPSILON);
2919
2920 props.insert("lanes".to_string(), PropertyValue::Number(1.0));
2922 let ctx = ExprEvalContext::with_feature(14.0, &props);
2923 assert!((layer.width.eval_full(&ctx) - 2.0).abs() < f32::EPSILON);
2924 }
2925 other => panic!("expected line, got {other:?}"),
2926 }
2927 }
2928}