1use crate::camera_projection::CameraProjection;
10use crate::cluster::{ClusterOptions, PointCluster};
11use crate::geometry::{FeatureCollection, PropertyValue};
12use crate::layer::Layer;
13use crate::layers::{
14 BackgroundLayer, DynamicImageOverlayLayer, FrameProviderFactory, HillshadeLayer, LineCap,
15 LineJoin, ModelLayer, TileLayer, VectorLayer, VectorRenderMode, VectorStyle,
16};
17use crate::models::ModelInstance;
18use crate::query::FeatureState;
19use crate::symbols::{
20 SymbolAnchor, SymbolIconTextFit, SymbolPlacement, SymbolTextJustify, SymbolTextTransform,
21 SymbolWritingMode,
22};
23use crate::terrain::{ElevationSource, TerrainConfig};
24use crate::tile_manager::TileSelectionConfig;
25use crate::tile_source::TileSource;
26use rustial_math::GeoCoord;
27use std::borrow::Cow;
28use std::collections::HashMap;
29use std::fmt;
30use std::sync::Arc;
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum StyleError {
35 DuplicateSourceId(String),
37 DuplicateLayerId(String),
39 MissingSource(String),
41 SourceKindMismatch {
43 layer_id: String,
45 source_id: String,
47 expected: &'static str,
49 actual: &'static str,
51 },
52 MissingSourceLayer {
54 layer_id: String,
56 source_id: String,
58 source_layer: String,
60 },
61}
62
63impl fmt::Display for StyleError {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 match self {
66 StyleError::DuplicateSourceId(id) => write!(f, "duplicate style source id `{id}`"),
67 StyleError::DuplicateLayerId(id) => write!(f, "duplicate style layer id `{id}`"),
68 StyleError::MissingSource(id) => write!(f, "missing style source `{id}`"),
69 StyleError::SourceKindMismatch {
70 layer_id,
71 source_id,
72 expected,
73 actual,
74 } => write!(
75 f,
76 "style layer `{layer_id}` expected source `{source_id}` of kind `{expected}`, got `{actual}`"
77 ),
78 StyleError::MissingSourceLayer {
79 layer_id,
80 source_id,
81 source_layer,
82 } => write!(
83 f,
84 "style layer `{layer_id}` referenced missing source-layer `{source_layer}` on source `{source_id}`"
85 ),
86 }
87 }
88}
89
90impl std::error::Error for StyleError {}
91
92pub type StyleSourceId = String;
94
95pub type StyleLayerId = String;
97
98#[derive(Debug, Clone, Copy, PartialEq)]
105pub struct StyleEvalContext {
106 pub zoom: f32,
108}
109
110impl StyleEvalContext {
111 pub fn new(zoom: f32) -> Self {
113 Self { zoom }
114 }
115
116 pub fn with_feature_state(self, feature_state: &FeatureState) -> StyleEvalContextFull<'_> {
121 StyleEvalContextFull {
122 zoom: self.zoom,
123 feature_state,
124 }
125 }
126}
127
128impl Default for StyleEvalContext {
129 fn default() -> Self {
130 Self { zoom: 0.0 }
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq)]
149pub struct StyleEvalContextFull<'a> {
150 pub zoom: f32,
152 pub feature_state: &'a FeatureState,
154}
155
156impl<'a> StyleEvalContextFull<'a> {
157 pub fn new(zoom: f32, feature_state: &'a FeatureState) -> Self {
159 Self { zoom, feature_state }
160 }
161
162 pub fn to_base(&self) -> StyleEvalContext {
164 StyleEvalContext { zoom: self.zoom }
165 }
166
167 pub fn get_feature_state(&self, key: &str) -> Option<&PropertyValue> {
169 self.feature_state.get(key)
170 }
171
172 pub fn feature_state_bool(&self, key: &str) -> bool {
174 self.feature_state
175 .get(key)
176 .and_then(|v| v.as_bool())
177 .unwrap_or(false)
178 }
179
180 pub fn feature_state_f64(&self, key: &str, default: f64) -> f64 {
182 self.feature_state
183 .get(key)
184 .and_then(|v| v.as_f64())
185 .unwrap_or(default)
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
191pub enum StyleProjection {
192 #[default]
194 Mercator,
195 Equirectangular,
197 Globe,
199 VerticalPerspective,
201}
202
203impl StyleProjection {
204 pub fn to_camera_projection(self) -> CameraProjection {
206 match self {
207 StyleProjection::Mercator => CameraProjection::WebMercator,
208 StyleProjection::Equirectangular => CameraProjection::Equirectangular,
209 StyleProjection::Globe => CameraProjection::Globe,
210 StyleProjection::VerticalPerspective => {
211 CameraProjection::vertical_perspective(GeoCoord::default(), 10_000_000.0)
212 }
213 }
214 }
215}
216
217#[derive(Debug, Clone, PartialEq)]
230pub struct FogConfig {
231 pub color: Option<[f32; 4]>,
236
237 pub range: Option<[f32; 2]>,
242
243 pub density: Option<f32>,
248
249 pub horizon_color: Option<[f32; 4]>,
255
256 pub horizon_blend: Option<f32>,
261}
262
263impl Default for FogConfig {
264 fn default() -> Self {
265 Self {
266 color: None,
267 range: None,
268 density: None,
269 horizon_color: None,
270 horizon_blend: None,
271 }
272 }
273}
274
275#[derive(Debug, Clone, Copy, PartialEq)]
282pub struct ComputedFog {
283 pub fog_color: [f32; 4],
285 pub fog_start: f32,
287 pub fog_end: f32,
289 pub fog_density: f32,
291 pub clear_color: [f32; 4],
293}
294
295impl Default for ComputedFog {
296 fn default() -> Self {
297 Self {
298 fog_color: [1.0; 4],
299 fog_start: 10_000.0,
300 fog_end: 20_000.0,
301 fog_density: 0.0,
302 clear_color: [1.0; 4],
303 }
304 }
305}
306
307pub fn atmospheric_clear_color(base: [f32; 4], pitch: f64) -> [f32; 4] {
313 let t = (((pitch - 0.25) / 1.0).clamp(0.0, 1.0)) as f32;
314 let horizon = [
315 (base[0] * 0.92 + 0.05).clamp(0.0, 1.0),
316 (base[1] * 0.95 + 0.06).clamp(0.0, 1.0),
317 (base[2] * 0.98 + 0.08).clamp(0.0, 1.0),
318 base[3],
319 ];
320 [
321 base[0] * (1.0 - t) + horizon[0] * t,
322 base[1] * (1.0 - t) + horizon[1] * t,
323 base[2] * (1.0 - t) + horizon[2] * t,
324 base[3],
325 ]
326}
327
328pub fn compute_fog(
333 pitch: f64,
334 camera_distance: f64,
335 background_color: [f32; 4],
336 config: Option<&FogConfig>,
337) -> ComputedFog {
338 let auto_clear = atmospheric_clear_color(background_color, pitch);
339
340 let auto_density = (((pitch - 0.70) / 0.55).clamp(0.0, 1.0) as f32) * 0.9;
342
343 let visible_range = camera_distance / pitch.cos().max(0.05);
345 let auto_start = (visible_range * 0.55) as f32;
346 let auto_end = (visible_range * 1.05) as f32;
347
348 let (fog_start, fog_end) = match config.and_then(|c| c.range) {
349 Some([s, e]) => ((visible_range as f32) * s, (visible_range as f32) * e),
350 None => (auto_start, auto_end),
351 };
352
353 let fog_density = config
354 .and_then(|c| c.density)
355 .unwrap_or(auto_density);
356
357 let fog_color = config
358 .and_then(|c| c.color)
359 .unwrap_or(auto_clear);
360
361 let clear_color = match config.and_then(|c| c.horizon_color) {
362 Some(horizon) => {
363 let blend = config.and_then(|c| c.horizon_blend).unwrap_or_else(|| {
364 ((pitch - 0.25) / 1.0).clamp(0.0, 1.0) as f32
365 });
366 [
367 background_color[0] * (1.0 - blend) + horizon[0] * blend,
368 background_color[1] * (1.0 - blend) + horizon[1] * blend,
369 background_color[2] * (1.0 - blend) + horizon[2] * blend,
370 background_color[3],
371 ]
372 }
373 None => auto_clear,
374 };
375
376 ComputedFog {
377 fog_color,
378 fog_start,
379 fog_end,
380 fog_density,
381 clear_color,
382 }
383}
384
385pub trait StyleInterpolatable: Clone + FromFeatureStateProperty {
391 fn interpolate(a: &Self, b: &Self, t: f32) -> Self;
393}
394
395impl StyleInterpolatable for f32 {
396 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
397 *a + (*b - *a) * t.clamp(0.0, 1.0)
398 }
399}
400
401impl StyleInterpolatable for [f32; 4] {
402 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
403 let t = t.clamp(0.0, 1.0);
404 [
405 a[0] + (b[0] - a[0]) * t,
406 a[1] + (b[1] - a[1]) * t,
407 a[2] + (b[2] - a[2]) * t,
408 a[3] + (b[3] - a[3]) * t,
409 ]
410 }
411}
412
413impl StyleInterpolatable for bool {
414 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
415 if t < 1.0 { *a } else { *b }
416 }
417}
418
419impl StyleInterpolatable for String {
420 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
421 if t < 1.0 { a.clone() } else { b.clone() }
422 }
423}
424
425impl StyleInterpolatable for SymbolTextJustify {
426 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
427 if t < 1.0 { *a } else { *b }
428 }
429}
430
431impl StyleInterpolatable for SymbolTextTransform {
432 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
433 if t < 1.0 { *a } else { *b }
434 }
435}
436
437impl StyleInterpolatable for SymbolIconTextFit {
438 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
439 if t < 1.0 { *a } else { *b }
440 }
441}
442
443pub type RasterSourceFactory = Arc<dyn Fn() -> Box<dyn TileSource> + Send + Sync>;
445
446pub type VectorTileSourceFactory = Arc<dyn Fn() -> Box<dyn TileSource> + Send + Sync>;
448
449pub type TerrainSourceFactory = Arc<dyn Fn() -> Box<dyn ElevationSource> + Send + Sync>;
451
452#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
454pub enum StyleSourceKind {
455 Raster,
457 Terrain,
459 GeoJson,
461 VectorTile,
463 Image,
465 Video,
467 Canvas,
469 Model,
471}
472
473impl StyleSourceKind {
474 pub fn as_str(self) -> &'static str {
476 match self {
477 StyleSourceKind::Raster => "raster",
478 StyleSourceKind::Terrain => "terrain",
479 StyleSourceKind::GeoJson => "geojson",
480 StyleSourceKind::VectorTile => "vector",
481 StyleSourceKind::Image => "image",
482 StyleSourceKind::Video => "video",
483 StyleSourceKind::Canvas => "canvas",
484 StyleSourceKind::Model => "model",
485 }
486 }
487}
488
489#[derive(Clone)]
491pub enum StyleSource {
492 Raster(RasterSource),
494 Terrain(TerrainSource),
496 GeoJson(GeoJsonSource),
498 VectorTile(VectorTileSource),
500 Image(ImageSource),
502 Video(VideoSource),
504 Canvas(CanvasSource),
506 Model(ModelSource),
508}
509
510impl StyleSource {
511 pub fn kind_name(&self) -> &'static str {
513 self.kind().as_str()
514 }
515
516 pub fn kind(&self) -> StyleSourceKind {
518 match self {
519 StyleSource::Raster(_) => StyleSourceKind::Raster,
520 StyleSource::Terrain(_) => StyleSourceKind::Terrain,
521 StyleSource::GeoJson(_) => StyleSourceKind::GeoJson,
522 StyleSource::VectorTile(_) => StyleSourceKind::VectorTile,
523 StyleSource::Image(_) => StyleSourceKind::Image,
524 StyleSource::Video(_) => StyleSourceKind::Video,
525 StyleSource::Canvas(_) => StyleSourceKind::Canvas,
526 StyleSource::Model(_) => StyleSourceKind::Model,
527 }
528 }
529}
530
531impl fmt::Debug for StyleSource {
532 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
533 match self {
534 StyleSource::Raster(src) => f.debug_tuple("Raster").field(src).finish(),
535 StyleSource::Terrain(src) => f.debug_tuple("Terrain").field(src).finish(),
536 StyleSource::GeoJson(src) => f.debug_tuple("GeoJson").field(src).finish(),
537 StyleSource::VectorTile(src) => f.debug_tuple("VectorTile").field(src).finish(),
538 StyleSource::Image(src) => f.debug_tuple("Image").field(src).finish(),
539 StyleSource::Video(src) => f.debug_tuple("Video").field(src).finish(),
540 StyleSource::Canvas(src) => f.debug_tuple("Canvas").field(src).finish(),
541 StyleSource::Model(src) => f.debug_tuple("Model").field(src).finish(),
542 }
543 }
544}
545
546#[derive(Clone)]
548pub struct RasterSource {
549 pub cache_capacity: usize,
551 pub selection: TileSelectionConfig,
553 pub factory: RasterSourceFactory,
555}
556
557impl RasterSource {
558 pub fn new(factory: impl Fn() -> Box<dyn TileSource> + Send + Sync + 'static) -> Self {
560 Self {
561 cache_capacity: 256,
562 selection: TileSelectionConfig::default(),
563 factory: Arc::new(factory),
564 }
565 }
566
567 pub fn with_cache_capacity(mut self, cache_capacity: usize) -> Self {
569 self.cache_capacity = cache_capacity;
570 self
571 }
572
573 pub fn with_selection(mut self, selection: TileSelectionConfig) -> Self {
575 self.selection = selection;
576 self
577 }
578}
579
580impl fmt::Debug for RasterSource {
581 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582 f.debug_struct("RasterSource")
583 .field("cache_capacity", &self.cache_capacity)
584 .field("selection", &self.selection)
585 .finish_non_exhaustive()
586 }
587}
588
589#[derive(Clone)]
591pub struct TerrainSource {
592 pub enabled: bool,
594 pub vertical_exaggeration: f64,
596 pub mesh_resolution: u16,
598 pub skirt_depth: f64,
600 pub cache_capacity: usize,
602 pub factory: TerrainSourceFactory,
604}
605
606impl TerrainSource {
607 pub fn new(factory: impl Fn() -> Box<dyn ElevationSource> + Send + Sync + 'static) -> Self {
609 Self {
610 enabled: true,
611 vertical_exaggeration: 1.0,
612 mesh_resolution: 64,
613 skirt_depth: 100.0,
614 cache_capacity: 256,
615 factory: Arc::new(factory),
616 }
617 }
618
619 pub fn with_cache_capacity(mut self, cache_capacity: usize) -> Self {
621 self.cache_capacity = cache_capacity;
622 self
623 }
624
625 pub fn to_terrain_config(&self) -> TerrainConfig {
627 TerrainConfig {
628 enabled: self.enabled,
629 vertical_exaggeration: self.vertical_exaggeration,
630 mesh_resolution: self.mesh_resolution,
631 skirt_depth: self.skirt_depth,
632 source_max_zoom: TerrainConfig::default().source_max_zoom,
633 source: (self.factory)(),
634 }
635 }
636}
637
638impl fmt::Debug for TerrainSource {
639 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
640 f.debug_struct("TerrainSource")
641 .field("enabled", &self.enabled)
642 .field("vertical_exaggeration", &self.vertical_exaggeration)
643 .field("mesh_resolution", &self.mesh_resolution)
644 .field("skirt_depth", &self.skirt_depth)
645 .field("cache_capacity", &self.cache_capacity)
646 .finish_non_exhaustive()
647 }
648}
649
650#[derive(Clone)]
652pub struct GeoJsonSource {
653 pub data: FeatureCollection,
655 cluster_index: Option<Arc<PointCluster>>,
658}
659
660impl fmt::Debug for GeoJsonSource {
661 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
662 f.debug_struct("GeoJsonSource")
663 .field("features", &self.data.len())
664 .field("clustered", &self.cluster_index.is_some())
665 .finish()
666 }
667}
668
669impl GeoJsonSource {
670 pub fn new(data: FeatureCollection) -> Self {
672 Self {
673 data,
674 cluster_index: None,
675 }
676 }
677
678 pub fn with_clustering(mut self, options: ClusterOptions) -> Self {
684 let mut cluster = PointCluster::new(options);
685 cluster.load(&self.data);
686 self.cluster_index = Some(Arc::new(cluster));
687 self
688 }
689
690 pub fn is_clustered(&self) -> bool {
692 self.cluster_index.is_some()
693 }
694
695 pub fn features_at_zoom(&self, zoom: u8) -> Cow<'_, FeatureCollection> {
700 if let Some(ref cluster) = self.cluster_index {
701 Cow::Owned(cluster.get_clusters_for_zoom(zoom))
702 } else {
703 Cow::Borrowed(&self.data)
704 }
705 }
706}
707
708#[derive(Clone)]
710pub struct VectorTileSource {
711 pub data: FeatureCollection,
716 pub source_layers: HashMap<String, FeatureCollection>,
722 pub factory: Option<VectorTileSourceFactory>,
729 pub cache_capacity: usize,
731 pub selection: TileSelectionConfig,
733}
734
735impl fmt::Debug for VectorTileSource {
736 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
737 f.debug_struct("VectorTileSource")
738 .field("feature_count", &self.data.len())
739 .field("source_layer_count", &self.source_layers.len())
740 .field("streamed", &self.factory.is_some())
741 .field("cache_capacity", &self.cache_capacity)
742 .field("selection", &self.selection)
743 .finish()
744 }
745}
746
747impl VectorTileSource {
748 pub fn new(data: FeatureCollection) -> Self {
750 Self {
751 data,
752 source_layers: HashMap::new(),
753 factory: None,
754 cache_capacity: 256,
755 selection: TileSelectionConfig::default(),
756 }
757 }
758
759 pub fn streamed(
761 factory: impl Fn() -> Box<dyn TileSource> + Send + Sync + 'static,
762 ) -> Self {
763 Self {
764 data: FeatureCollection::default(),
765 source_layers: HashMap::new(),
766 factory: Some(Arc::new(factory)),
767 cache_capacity: 256,
768 selection: TileSelectionConfig::default(),
769 }
770 }
771
772 pub fn from_source_layers(source_layers: HashMap<String, FeatureCollection>) -> Self {
774 let mut data = FeatureCollection::default();
775 for features in source_layers.values() {
776 data.features.extend(features.features.iter().cloned());
777 }
778 Self {
779 data,
780 source_layers,
781 factory: None,
782 cache_capacity: 256,
783 selection: TileSelectionConfig::default(),
784 }
785 }
786
787 pub fn with_source_layer(
789 mut self,
790 name: impl Into<String>,
791 data: FeatureCollection,
792 ) -> Self {
793 self.source_layers.insert(name.into(), data);
794 self.rebuild_flattened_data();
795 self
796 }
797
798 pub fn source_layer(&self, name: &str) -> Option<&FeatureCollection> {
800 self.source_layers.get(name)
801 }
802
803 pub fn has_source_layers(&self) -> bool {
805 !self.source_layers.is_empty()
806 }
807
808 pub fn is_streamed(&self) -> bool {
810 self.factory.is_some()
811 }
812
813 pub fn with_cache_capacity(mut self, cache_capacity: usize) -> Self {
815 self.cache_capacity = cache_capacity;
816 self
817 }
818
819 pub fn with_selection(mut self, selection: TileSelectionConfig) -> Self {
821 self.selection = selection;
822 self
823 }
824
825 pub fn make_tile_source(&self) -> Option<Box<dyn TileSource>> {
827 self.factory.as_ref().map(|factory| (factory)())
828 }
829
830 fn rebuild_flattened_data(&mut self) {
831 let mut data = FeatureCollection::default();
832 for features in self.source_layers.values() {
833 data.features.extend(features.features.iter().cloned());
834 }
835 self.data = data;
836 }
837}
838
839#[derive(Clone)]
841pub struct ImageSource {
842 pub cache_capacity: usize,
844 pub selection: TileSelectionConfig,
846 pub factory: RasterSourceFactory,
848}
849
850impl ImageSource {
851 pub fn new(factory: impl Fn() -> Box<dyn TileSource> + Send + Sync + 'static) -> Self {
853 Self {
854 cache_capacity: 16,
855 selection: TileSelectionConfig::default(),
856 factory: Arc::new(factory),
857 }
858 }
859
860 pub fn with_cache_capacity(mut self, cache_capacity: usize) -> Self {
862 self.cache_capacity = cache_capacity;
863 self
864 }
865
866 pub fn with_selection(mut self, selection: TileSelectionConfig) -> Self {
868 self.selection = selection;
869 self
870 }
871}
872
873impl fmt::Debug for ImageSource {
874 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
875 f.debug_struct("ImageSource")
876 .field("cache_capacity", &self.cache_capacity)
877 .field("selection", &self.selection)
878 .finish_non_exhaustive()
879 }
880}
881
882#[derive(Clone)]
895pub struct VideoSource {
896 pub coordinates: [GeoCoord; 4],
898 pub factory: FrameProviderFactory,
900}
901
902impl VideoSource {
903 pub fn new(
907 coordinates: [GeoCoord; 4],
908 factory: impl Fn() -> Box<dyn crate::layers::FrameProvider> + Send + Sync + 'static,
909 ) -> Self {
910 Self {
911 coordinates,
912 factory: Arc::new(factory),
913 }
914 }
915}
916
917impl fmt::Debug for VideoSource {
918 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
919 f.debug_struct("VideoSource")
920 .field("coordinates", &self.coordinates)
921 .finish_non_exhaustive()
922 }
923}
924
925#[derive(Clone)]
938pub struct CanvasSource {
939 pub coordinates: [GeoCoord; 4],
941 pub factory: FrameProviderFactory,
943 pub animate: bool,
946}
947
948impl CanvasSource {
949 pub fn new(
953 coordinates: [GeoCoord; 4],
954 factory: impl Fn() -> Box<dyn crate::layers::FrameProvider> + Send + Sync + 'static,
955 ) -> Self {
956 Self {
957 coordinates,
958 factory: Arc::new(factory),
959 animate: true,
960 }
961 }
962
963 pub fn with_animate(mut self, animate: bool) -> Self {
965 self.animate = animate;
966 self
967 }
968}
969
970impl fmt::Debug for CanvasSource {
971 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
972 f.debug_struct("CanvasSource")
973 .field("coordinates", &self.coordinates)
974 .field("animate", &self.animate)
975 .finish_non_exhaustive()
976 }
977}
978
979#[derive(Debug, Clone, Default)]
981pub struct ModelSource {
982 pub instances: Vec<ModelInstance>,
984}
985
986impl ModelSource {
987 pub fn new(instances: Vec<ModelInstance>) -> Self {
989 Self { instances }
990 }
991}
992
993#[derive(Debug, Default)]
995pub struct StyleDocument {
996 sources: HashMap<StyleSourceId, StyleSource>,
997 layers: Vec<StyleLayer>,
998 terrain_source: Option<StyleSourceId>,
999 projection: StyleProjection,
1000 fog: Option<FogConfig>,
1001}
1002
1003pub type StyleValue<T> = crate::expression::Expression<T>;
1014
1015pub trait FromFeatureStateProperty: Sized {
1026 fn from_feature_state_property(prop: &PropertyValue) -> Option<Self>;
1029}
1030
1031impl FromFeatureStateProperty for f32 {
1032 fn from_feature_state_property(prop: &PropertyValue) -> Option<Self> {
1033 prop.as_f64().map(|v| v as f32)
1034 }
1035}
1036
1037impl FromFeatureStateProperty for [f32; 4] {
1038 fn from_feature_state_property(_prop: &PropertyValue) -> Option<Self> {
1040 None
1041 }
1042}
1043
1044impl FromFeatureStateProperty for bool {
1045 fn from_feature_state_property(prop: &PropertyValue) -> Option<Self> {
1046 prop.as_bool()
1047 }
1048}
1049
1050impl FromFeatureStateProperty for String {
1051 fn from_feature_state_property(prop: &PropertyValue) -> Option<Self> {
1052 prop.as_str().map(|s| s.to_owned())
1053 }
1054}
1055
1056impl FromFeatureStateProperty for SymbolTextJustify {
1057 fn from_feature_state_property(_prop: &PropertyValue) -> Option<Self> {
1058 None
1059 }
1060}
1061
1062impl FromFeatureStateProperty for SymbolTextTransform {
1063 fn from_feature_state_property(_prop: &PropertyValue) -> Option<Self> {
1064 None
1065 }
1066}
1067
1068impl FromFeatureStateProperty for SymbolIconTextFit {
1069 fn from_feature_state_property(_prop: &PropertyValue) -> Option<Self> {
1070 None
1071 }
1072}
1073
1074#[derive(Debug, Clone)]
1076pub struct StyleLayerMeta {
1077 pub id: StyleLayerId,
1079 pub name: String,
1081 pub visible: StyleValue<bool>,
1083 pub opacity: StyleValue<f32>,
1085 pub min_zoom: Option<f32>,
1087 pub max_zoom: Option<f32>,
1089}
1090
1091impl StyleLayerMeta {
1092 pub fn new(id: impl Into<String>) -> Self {
1094 let id = id.into();
1095 Self {
1096 name: id.clone(),
1097 id,
1098 visible: StyleValue::Constant(true),
1099 opacity: StyleValue::Constant(1.0),
1100 min_zoom: None,
1101 max_zoom: None,
1102 }
1103 }
1104
1105 fn visible_in_context(&self, ctx: StyleEvalContext) -> bool {
1106 if let Some(min_zoom) = self.min_zoom {
1107 if ctx.zoom < min_zoom {
1108 return false;
1109 }
1110 }
1111 if let Some(max_zoom) = self.max_zoom {
1112 if ctx.zoom > max_zoom {
1113 return false;
1114 }
1115 }
1116 self.visible.evaluate_with_context(ctx)
1117 }
1118}
1119
1120#[allow(missing_docs)]
1122#[derive(Debug, Clone)]
1123pub struct BackgroundStyleLayer {
1124 pub meta: StyleLayerMeta,
1125 pub color: StyleValue<[f32; 4]>,
1126}
1127
1128impl BackgroundStyleLayer {
1129 pub fn new(id: impl Into<String>, color: impl Into<StyleValue<[f32; 4]>>) -> Self {
1131 Self {
1132 meta: StyleLayerMeta::new(id),
1133 color: color.into(),
1134 }
1135 }
1136}
1137
1138#[allow(missing_docs)]
1140#[derive(Debug, Clone)]
1141pub struct HillshadeStyleLayer {
1142 pub meta: StyleLayerMeta,
1143 pub highlight_color: StyleValue<[f32; 4]>,
1144 pub shadow_color: StyleValue<[f32; 4]>,
1145 pub accent_color: StyleValue<[f32; 4]>,
1146 pub illumination_direction_deg: StyleValue<f32>,
1147 pub illumination_altitude_deg: StyleValue<f32>,
1148 pub exaggeration: StyleValue<f32>,
1149}
1150
1151impl HillshadeStyleLayer {
1152 pub fn new(id: impl Into<String>) -> Self {
1154 Self {
1155 meta: StyleLayerMeta::new(id),
1156 highlight_color: [1.0, 1.0, 1.0, 1.0].into(),
1157 shadow_color: [0.0, 0.0, 0.0, 1.0].into(),
1158 accent_color: [0.42, 0.48, 0.42, 1.0].into(),
1159 illumination_direction_deg: 335.0.into(),
1160 illumination_altitude_deg: 45.0.into(),
1161 exaggeration: 1.0.into(),
1162 }
1163 }
1164}
1165
1166#[allow(missing_docs)]
1168#[derive(Debug, Clone)]
1169pub struct RasterStyleLayer {
1170 pub meta: StyleLayerMeta,
1171 pub source: StyleSourceId,
1172}
1173
1174impl RasterStyleLayer {
1175 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
1177 Self {
1178 meta: StyleLayerMeta::new(id),
1179 source: source.into(),
1180 }
1181 }
1182}
1183
1184#[allow(missing_docs)]
1186#[derive(Debug, Clone)]
1187pub struct VectorStyleLayer {
1188 pub meta: StyleLayerMeta,
1189 pub source: StyleSourceId,
1190 pub source_layer: Option<String>,
1192 pub fill_color: StyleValue<[f32; 4]>,
1193 pub stroke_color: StyleValue<[f32; 4]>,
1194 pub stroke_width: StyleValue<f32>,
1195}
1196
1197impl VectorStyleLayer {
1198 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
1200 Self {
1201 meta: StyleLayerMeta::new(id),
1202 source: source.into(),
1203 source_layer: None,
1204 fill_color: VectorStyle::default().fill_color.into(),
1205 stroke_color: VectorStyle::default().stroke_color.into(),
1206 stroke_width: VectorStyle::default().stroke_width.into(),
1207 }
1208 }
1209}
1210
1211#[allow(missing_docs)]
1213#[derive(Debug, Clone)]
1214pub struct FillStyleLayer {
1215 pub meta: StyleLayerMeta,
1216 pub source: StyleSourceId,
1217 pub source_layer: Option<String>,
1219 pub fill_color: StyleValue<[f32; 4]>,
1220 pub outline_color: StyleValue<[f32; 4]>,
1221 pub outline_width: StyleValue<f32>,
1222 pub fill_pattern: Option<std::sync::Arc<crate::PatternImage>>,
1227}
1228
1229impl FillStyleLayer {
1230 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
1232 let style = VectorStyle::default();
1233 Self {
1234 meta: StyleLayerMeta::new(id),
1235 source: source.into(),
1236 source_layer: None,
1237 fill_color: style.fill_color.into(),
1238 outline_color: style.stroke_color.into(),
1239 outline_width: style.stroke_width.into(),
1240 fill_pattern: None,
1241 }
1242 }
1243}
1244
1245#[allow(missing_docs)]
1247#[derive(Debug, Clone)]
1248pub struct LineStyleLayer {
1249 pub meta: StyleLayerMeta,
1250 pub source: StyleSourceId,
1251 pub source_layer: Option<String>,
1253 pub color: StyleValue<[f32; 4]>,
1254 pub width: StyleValue<f32>,
1255 pub line_cap: LineCap,
1257 pub line_join: LineJoin,
1259 pub miter_limit: f32,
1261 pub dash_array: Option<Vec<f32>>,
1263 pub line_gradient: Option<crate::visualization::ColorRamp>,
1270 pub line_pattern: Option<std::sync::Arc<crate::PatternImage>>,
1276}
1277
1278impl LineStyleLayer {
1279 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
1281 let style = VectorStyle::default();
1282 Self {
1283 meta: StyleLayerMeta::new(id),
1284 source: source.into(),
1285 source_layer: None,
1286 color: style.stroke_color.into(),
1287 width: style.stroke_width.into(),
1288 line_cap: LineCap::default(),
1289 line_join: LineJoin::default(),
1290 miter_limit: 2.0,
1291 dash_array: None,
1292 line_gradient: None,
1293 line_pattern: None,
1294 }
1295 }
1296}
1297
1298#[allow(missing_docs)]
1300#[derive(Debug, Clone)]
1301pub struct CircleStyleLayer {
1302 pub meta: StyleLayerMeta,
1303 pub source: StyleSourceId,
1304 pub source_layer: Option<String>,
1306 pub color: StyleValue<[f32; 4]>,
1307 pub radius: StyleValue<f32>,
1308 pub stroke_color: StyleValue<[f32; 4]>,
1309 pub stroke_width: StyleValue<f32>,
1310}
1311
1312impl CircleStyleLayer {
1313 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
1315 let style = VectorStyle::default();
1316 Self {
1317 meta: StyleLayerMeta::new(id),
1318 source: source.into(),
1319 source_layer: None,
1320 color: style.fill_color.into(),
1321 radius: style.point_radius.into(),
1322 stroke_color: style.stroke_color.into(),
1323 stroke_width: style.stroke_width.into(),
1324 }
1325 }
1326}
1327
1328#[allow(missing_docs)]
1330#[derive(Debug, Clone)]
1331pub struct HeatmapStyleLayer {
1332 pub meta: StyleLayerMeta,
1333 pub source: StyleSourceId,
1334 pub source_layer: Option<String>,
1336 pub color: StyleValue<[f32; 4]>,
1337 pub radius: StyleValue<f32>,
1338 pub intensity: StyleValue<f32>,
1339}
1340
1341impl HeatmapStyleLayer {
1342 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
1344 let style = VectorStyle::default();
1345 Self {
1346 meta: StyleLayerMeta::new(id),
1347 source: source.into(),
1348 source_layer: None,
1349 color: style.fill_color.into(),
1350 radius: style.heatmap_radius.into(),
1351 intensity: style.heatmap_intensity.into(),
1352 }
1353 }
1354}
1355
1356#[allow(missing_docs)]
1358#[derive(Debug, Clone)]
1359pub struct FillExtrusionStyleLayer {
1360 pub meta: StyleLayerMeta,
1361 pub source: StyleSourceId,
1362 pub source_layer: Option<String>,
1364 pub color: StyleValue<[f32; 4]>,
1365 pub base: StyleValue<f32>,
1366 pub height: StyleValue<f32>,
1367}
1368
1369impl FillExtrusionStyleLayer {
1370 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
1372 let style = VectorStyle::default();
1373 Self {
1374 meta: StyleLayerMeta::new(id),
1375 source: source.into(),
1376 source_layer: None,
1377 color: style.fill_color.into(),
1378 base: style.extrusion_base.into(),
1379 height: style.extrusion_height.into(),
1380 }
1381 }
1382}
1383
1384#[allow(missing_docs)]
1386#[derive(Debug, Clone)]
1387pub struct SymbolStyleLayer {
1388 pub meta: StyleLayerMeta,
1389 pub source: StyleSourceId,
1390 pub source_layer: Option<String>,
1392 pub color: StyleValue<[f32; 4]>,
1393 pub halo_color: StyleValue<[f32; 4]>,
1394 pub size: StyleValue<f32>,
1395 pub text_field: Option<StyleValue<String>>,
1396 pub icon_image: Option<StyleValue<String>>,
1397 pub font_stack: StyleValue<String>,
1398 pub padding: StyleValue<f32>,
1399 pub allow_overlap: StyleValue<bool>,
1401 pub text_allow_overlap: Option<StyleValue<bool>>,
1403 pub icon_allow_overlap: Option<StyleValue<bool>>,
1405 pub text_optional: Option<StyleValue<bool>>,
1407 pub icon_optional: Option<StyleValue<bool>>,
1409 pub text_ignore_placement: Option<StyleValue<bool>>,
1411 pub icon_ignore_placement: Option<StyleValue<bool>>,
1413 pub radial_offset: Option<StyleValue<f32>>,
1415 pub variable_anchor_offsets: Option<Vec<(SymbolAnchor, [f32; 2])>>,
1417 pub anchor: SymbolAnchor,
1419 pub justify: StyleValue<SymbolTextJustify>,
1421 pub transform: StyleValue<SymbolTextTransform>,
1423 pub max_width: Option<StyleValue<f32>>,
1425 pub line_height: Option<StyleValue<f32>>,
1427 pub letter_spacing: Option<StyleValue<f32>>,
1429 pub icon_text_fit: StyleValue<SymbolIconTextFit>,
1431 pub icon_text_fit_padding: [f32; 4],
1433 pub sort_key: Option<StyleValue<f32>>,
1435 pub placement: SymbolPlacement,
1437 pub spacing: StyleValue<f32>,
1439 pub max_angle: StyleValue<f32>,
1441 pub keep_upright: StyleValue<bool>,
1443 pub variable_anchors: Vec<SymbolAnchor>,
1444 pub writing_mode: SymbolWritingMode,
1445 pub offset: [f32; 2],
1446}
1447
1448impl SymbolStyleLayer {
1449 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
1451 let style = VectorStyle::default();
1452 Self {
1453 meta: StyleLayerMeta::new(id),
1454 source: source.into(),
1455 source_layer: None,
1456 color: style.fill_color.into(),
1457 halo_color: style.symbol_halo_color.into(),
1458 size: style.symbol_size.into(),
1459 text_field: None,
1460 icon_image: None,
1461 font_stack: style.symbol_font_stack.into(),
1462 padding: style.symbol_padding.into(),
1463 allow_overlap: style.symbol_allow_overlap.into(),
1464 text_allow_overlap: None,
1465 icon_allow_overlap: None,
1466 text_optional: None,
1467 icon_optional: None,
1468 text_ignore_placement: None,
1469 icon_ignore_placement: None,
1470 radial_offset: None,
1471 variable_anchor_offsets: None,
1472 anchor: style.symbol_text_anchor,
1473 justify: style.symbol_text_justify.into(),
1474 transform: style.symbol_text_transform.into(),
1475 max_width: None,
1476 line_height: None,
1477 letter_spacing: None,
1478 icon_text_fit: style.symbol_icon_text_fit.into(),
1479 icon_text_fit_padding: style.symbol_icon_text_fit_padding,
1480 sort_key: None,
1481 placement: style.symbol_placement,
1482 spacing: style.symbol_spacing.into(),
1483 max_angle: style.symbol_max_angle.into(),
1484 keep_upright: style.symbol_keep_upright.into(),
1485 variable_anchors: style.symbol_anchors.clone(),
1486 writing_mode: style.symbol_writing_mode,
1487 offset: style.symbol_offset,
1488 }
1489 }
1490}
1491
1492#[allow(missing_docs)]
1494#[derive(Debug, Clone)]
1495pub struct ModelStyleLayer {
1496 pub meta: StyleLayerMeta,
1497 pub source: StyleSourceId,
1498}
1499
1500impl ModelStyleLayer {
1501 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
1503 Self {
1504 meta: StyleLayerMeta::new(id),
1505 source: source.into(),
1506 }
1507 }
1508}
1509
1510#[derive(Debug, Clone)]
1512pub enum StyleLayer {
1513 Background(BackgroundStyleLayer),
1515 Hillshade(HillshadeStyleLayer),
1517 Raster(RasterStyleLayer),
1519 Vector(VectorStyleLayer),
1521 Fill(FillStyleLayer),
1523 Line(LineStyleLayer),
1525 Circle(CircleStyleLayer),
1527 Heatmap(HeatmapStyleLayer),
1529 FillExtrusion(FillExtrusionStyleLayer),
1531 Symbol(SymbolStyleLayer),
1533 Model(ModelStyleLayer),
1535}
1536
1537impl StyleLayer {
1538 pub fn id(&self) -> &str {
1540 self.meta().id.as_str()
1541 }
1542
1543 pub fn meta(&self) -> &StyleLayerMeta {
1545 match self {
1546 StyleLayer::Background(layer) => &layer.meta,
1547 StyleLayer::Hillshade(layer) => &layer.meta,
1548 StyleLayer::Raster(layer) => &layer.meta,
1549 StyleLayer::Vector(layer) => &layer.meta,
1550 StyleLayer::Fill(layer) => &layer.meta,
1551 StyleLayer::Line(layer) => &layer.meta,
1552 StyleLayer::Circle(layer) => &layer.meta,
1553 StyleLayer::Heatmap(layer) => &layer.meta,
1554 StyleLayer::FillExtrusion(layer) => &layer.meta,
1555 StyleLayer::Symbol(layer) => &layer.meta,
1556 StyleLayer::Model(layer) => &layer.meta,
1557 }
1558 }
1559
1560 pub fn meta_mut(&mut self) -> &mut StyleLayerMeta {
1562 match self {
1563 StyleLayer::Background(layer) => &mut layer.meta,
1564 StyleLayer::Hillshade(layer) => &mut layer.meta,
1565 StyleLayer::Raster(layer) => &mut layer.meta,
1566 StyleLayer::Vector(layer) => &mut layer.meta,
1567 StyleLayer::Fill(layer) => &mut layer.meta,
1568 StyleLayer::Line(layer) => &mut layer.meta,
1569 StyleLayer::Circle(layer) => &mut layer.meta,
1570 StyleLayer::Heatmap(layer) => &mut layer.meta,
1571 StyleLayer::FillExtrusion(layer) => &mut layer.meta,
1572 StyleLayer::Symbol(layer) => &mut layer.meta,
1573 StyleLayer::Model(layer) => &mut layer.meta,
1574 }
1575 }
1576
1577 pub fn to_runtime_layer_with_context(
1579 &self,
1580 sources: &HashMap<StyleSourceId, StyleSource>,
1581 ctx: StyleEvalContext,
1582 ) -> Result<Box<dyn Layer>, StyleError> {
1583 match self {
1584 StyleLayer::Background(layer) => Ok(Box::new(evaluate_background_layer(layer, ctx))),
1585 StyleLayer::Hillshade(layer) => Ok(Box::new(evaluate_hillshade_layer(layer, ctx))),
1586 StyleLayer::Raster(layer) => evaluate_raster_layer(layer, sources, ctx),
1587 StyleLayer::Vector(layer) => evaluate_vector_layer(layer, sources, ctx),
1588 StyleLayer::Fill(layer) => evaluate_fill_layer(layer, sources, ctx),
1589 StyleLayer::Line(layer) => evaluate_line_layer(layer, sources, ctx),
1590 StyleLayer::Circle(layer) => evaluate_circle_layer(layer, sources, ctx),
1591 StyleLayer::Heatmap(layer) => evaluate_heatmap_layer(layer, sources, ctx),
1592 StyleLayer::FillExtrusion(layer) => evaluate_fill_extrusion_layer(layer, sources, ctx),
1593 StyleLayer::Symbol(layer) => evaluate_symbol_layer(layer, sources, ctx),
1594 StyleLayer::Model(layer) => evaluate_model_layer(layer, sources, ctx),
1595 }
1596 }
1597
1598 pub fn apply_to_runtime_layer_with_context(
1600 &self,
1601 runtime: &mut dyn Layer,
1602 sources: &HashMap<StyleSourceId, StyleSource>,
1603 ctx: StyleEvalContext,
1604 ) -> Result<(), StyleError> {
1605 match self {
1606 StyleLayer::Background(layer) => apply_background_to_runtime(layer, runtime, ctx),
1607 StyleLayer::Hillshade(layer) => apply_hillshade_to_runtime(layer, runtime, ctx),
1608 StyleLayer::Raster(layer) => apply_raster_to_runtime(layer, runtime, sources, ctx),
1609 StyleLayer::Vector(layer) => apply_vector_to_runtime(layer, runtime, sources, ctx),
1610 StyleLayer::Fill(layer) => apply_fill_to_runtime(layer, runtime, sources, ctx),
1611 StyleLayer::Line(layer) => apply_line_to_runtime(layer, runtime, sources, ctx),
1612 StyleLayer::Circle(layer) => apply_circle_to_runtime(layer, runtime, sources, ctx),
1613 StyleLayer::Heatmap(layer) => apply_heatmap_to_runtime(layer, runtime, sources, ctx),
1614 StyleLayer::FillExtrusion(layer) => apply_fill_extrusion_to_runtime(layer, runtime, sources, ctx),
1615 StyleLayer::Symbol(layer) => apply_symbol_to_runtime(layer, runtime, sources, ctx),
1616 StyleLayer::Model(layer) => apply_model_to_runtime(layer, runtime, sources, ctx),
1617 }
1618 }
1619
1620 pub fn source_id(&self) -> Option<&str> {
1622 match self {
1623 StyleLayer::Background(_) | StyleLayer::Hillshade(_) => None,
1624 StyleLayer::Raster(layer) => Some(layer.source.as_str()),
1625 StyleLayer::Vector(layer) => Some(layer.source.as_str()),
1626 StyleLayer::Fill(layer) => Some(layer.source.as_str()),
1627 StyleLayer::Line(layer) => Some(layer.source.as_str()),
1628 StyleLayer::Circle(layer) => Some(layer.source.as_str()),
1629 StyleLayer::Heatmap(layer) => Some(layer.source.as_str()),
1630 StyleLayer::FillExtrusion(layer) => Some(layer.source.as_str()),
1631 StyleLayer::Symbol(layer) => Some(layer.source.as_str()),
1632 StyleLayer::Model(layer) => Some(layer.source.as_str()),
1633 }
1634 }
1635
1636 pub fn source_layer(&self) -> Option<&str> {
1639 match self {
1640 StyleLayer::Vector(layer) => layer.source_layer.as_deref(),
1641 StyleLayer::Fill(layer) => layer.source_layer.as_deref(),
1642 StyleLayer::Line(layer) => layer.source_layer.as_deref(),
1643 StyleLayer::Circle(layer) => layer.source_layer.as_deref(),
1644 StyleLayer::Heatmap(layer) => layer.source_layer.as_deref(),
1645 StyleLayer::FillExtrusion(layer) => layer.source_layer.as_deref(),
1646 StyleLayer::Symbol(layer) => layer.source_layer.as_deref(),
1647 _ => None,
1648 }
1649 }
1650
1651 pub fn uses_source(&self, source_id: &str) -> bool {
1653 self.source_id() == Some(source_id)
1654 }
1655
1656 pub fn has_feature_state_driven_paint(&self) -> bool {
1661 match self {
1662 StyleLayer::Fill(l) => {
1663 l.fill_color.is_feature_state_driven()
1664 || l.outline_color.is_feature_state_driven()
1665 || l.outline_width.is_feature_state_driven()
1666 }
1667 StyleLayer::Line(l) => {
1668 l.color.is_feature_state_driven() || l.width.is_feature_state_driven()
1669 }
1670 StyleLayer::Circle(l) => {
1671 l.color.is_feature_state_driven()
1672 || l.radius.is_feature_state_driven()
1673 || l.stroke_color.is_feature_state_driven()
1674 || l.stroke_width.is_feature_state_driven()
1675 }
1676 StyleLayer::Heatmap(l) => {
1677 l.color.is_feature_state_driven()
1678 || l.radius.is_feature_state_driven()
1679 || l.intensity.is_feature_state_driven()
1680 }
1681 StyleLayer::FillExtrusion(l) => {
1682 l.color.is_feature_state_driven()
1683 || l.base.is_feature_state_driven()
1684 || l.height.is_feature_state_driven()
1685 }
1686 StyleLayer::Symbol(l) => {
1687 l.color.is_feature_state_driven()
1688 || l.halo_color.is_feature_state_driven()
1689 || l.size.is_feature_state_driven()
1690 }
1691 StyleLayer::Vector(l) => {
1692 l.fill_color.is_feature_state_driven()
1693 || l.stroke_color.is_feature_state_driven()
1694 || l.stroke_width.is_feature_state_driven()
1695 }
1696 StyleLayer::Background(_)
1698 | StyleLayer::Hillshade(_)
1699 | StyleLayer::Raster(_)
1700 | StyleLayer::Model(_) => false,
1701 }
1702 }
1703
1704 pub fn resolve_style_with_feature_state(
1716 &self,
1717 ctx: &StyleEvalContextFull<'_>,
1718 ) -> Option<VectorStyle> {
1719 match self {
1720 StyleLayer::Fill(l) => Some(fill_style_with_state(l, ctx)),
1721 StyleLayer::Line(l) => Some(line_style_with_state(l, ctx)),
1722 StyleLayer::Circle(l) => Some(circle_style_with_state(l, ctx)),
1723 StyleLayer::Heatmap(l) => Some(heatmap_style_with_state(l, ctx)),
1724 StyleLayer::FillExtrusion(l) => Some(fill_extrusion_style_with_state(l, ctx)),
1725 StyleLayer::Symbol(l) => Some(symbol_style_with_state(l, ctx)),
1726 StyleLayer::Vector(l) => Some(vector_style_with_state(l, ctx)),
1727 StyleLayer::Background(_)
1729 | StyleLayer::Hillshade(_)
1730 | StyleLayer::Raster(_)
1731 | StyleLayer::Model(_) => None,
1732 }
1733 }
1734}
1735
1736impl StyleDocument {
1737 pub fn new() -> Self {
1739 Self::default()
1740 }
1741
1742 pub fn add_source(
1744 &mut self,
1745 id: impl Into<String>,
1746 source: StyleSource,
1747 ) -> Result<(), StyleError> {
1748 let id = id.into();
1749 if self.sources.contains_key(&id) {
1750 return Err(StyleError::DuplicateSourceId(id));
1751 }
1752 self.sources.insert(id, source);
1753 Ok(())
1754 }
1755
1756 pub fn set_source(&mut self, id: impl Into<String>, source: StyleSource) {
1758 self.sources.insert(id.into(), source);
1759 }
1760
1761 pub fn remove_source(&mut self, id: &str) -> Option<StyleSource> {
1763 if self.terrain_source.as_deref() == Some(id) {
1764 self.terrain_source = None;
1765 }
1766 self.sources.remove(id)
1767 }
1768
1769 pub fn source(&self, id: &str) -> Option<&StyleSource> {
1771 self.sources.get(id)
1772 }
1773
1774 pub fn sources(&self) -> impl Iterator<Item = (&str, &StyleSource)> {
1776 self.sources.iter().map(|(id, source)| (id.as_str(), source))
1777 }
1778
1779 pub fn set_terrain_source(&mut self, source_id: Option<impl Into<String>>) {
1781 self.terrain_source = source_id.map(Into::into);
1782 }
1783
1784 pub fn terrain_source(&self) -> Option<&str> {
1786 self.terrain_source.as_deref()
1787 }
1788
1789 pub fn set_projection(&mut self, projection: StyleProjection) {
1791 self.projection = projection;
1792 }
1793
1794 pub fn projection(&self) -> StyleProjection {
1796 self.projection
1797 }
1798
1799 pub fn set_fog(&mut self, fog: Option<FogConfig>) {
1801 self.fog = fog;
1802 }
1803
1804 pub fn fog(&self) -> Option<&FogConfig> {
1806 self.fog.as_ref()
1807 }
1808
1809 pub fn add_layer(&mut self, layer: StyleLayer) -> Result<(), StyleError> {
1811 if self.layers.iter().any(|existing| existing.id() == layer.id()) {
1812 return Err(StyleError::DuplicateLayerId(layer.id().to_owned()));
1813 }
1814 self.layers.push(layer);
1815 Ok(())
1816 }
1817
1818 pub fn insert_layer_before(
1820 &mut self,
1821 before_id: &str,
1822 layer: StyleLayer,
1823 ) -> Result<(), StyleError> {
1824 if self.layers.iter().any(|existing| existing.id() == layer.id()) {
1825 return Err(StyleError::DuplicateLayerId(layer.id().to_owned()));
1826 }
1827 if let Some(index) = self.layer_index(before_id) {
1828 self.layers.insert(index, layer);
1829 } else {
1830 self.layers.push(layer);
1831 }
1832 Ok(())
1833 }
1834
1835 pub fn move_layer_before(&mut self, layer_id: &str, before_id: &str) -> bool {
1837 let Some(from) = self.layer_index(layer_id) else {
1838 return false;
1839 };
1840 let layer = self.layers.remove(from);
1841 let to = self.layer_index(before_id).unwrap_or(self.layers.len());
1842 self.layers.insert(to, layer);
1843 true
1844 }
1845
1846 pub fn remove_layer(&mut self, layer_id: &str) -> Option<StyleLayer> {
1848 self.layer_index(layer_id).map(|index| self.layers.remove(index))
1849 }
1850
1851 pub fn layer(&self, layer_id: &str) -> Option<&StyleLayer> {
1853 self.layers.iter().find(|layer| layer.id() == layer_id)
1854 }
1855
1856 pub fn layer_mut(&mut self, layer_id: &str) -> Option<&mut StyleLayer> {
1858 self.layers.iter_mut().find(|layer| layer.id() == layer_id)
1859 }
1860
1861 pub fn layers(&self) -> &[StyleLayer] {
1863 &self.layers
1864 }
1865
1866 pub fn to_runtime_layers(&self) -> Result<Vec<Box<dyn Layer>>, StyleError> {
1868 self.to_runtime_layers_with_context(StyleEvalContext::default())
1869 }
1870
1871 pub fn to_runtime_layers_with_context(
1873 &self,
1874 ctx: StyleEvalContext,
1875 ) -> Result<Vec<Box<dyn Layer>>, StyleError> {
1876 self.layers
1877 .iter()
1878 .map(|layer| layer.to_runtime_layer_with_context(&self.sources, ctx))
1879 .collect()
1880 }
1881
1882 pub fn to_terrain_config(&self) -> Result<Option<(TerrainConfig, usize)>, StyleError> {
1884 let Some(source_id) = self.terrain_source.as_deref() else {
1885 return Ok(None);
1886 };
1887 let Some(source) = self.sources.get(source_id) else {
1888 return Err(StyleError::MissingSource(source_id.to_owned()));
1889 };
1890 match source {
1891 StyleSource::Terrain(terrain) => Ok(Some((terrain.to_terrain_config(), terrain.cache_capacity))),
1892 other => Err(StyleError::SourceKindMismatch {
1893 layer_id: "<terrain>".to_owned(),
1894 source_id: source_id.to_owned(),
1895 expected: "terrain",
1896 actual: other.kind_name(),
1897 }),
1898 }
1899 }
1900
1901 #[allow(dead_code)]
1902 pub(crate) fn apply_runtime_layers_with_context(
1903 &self,
1904 layers: &mut crate::layers::LayerStack,
1905 ctx: StyleEvalContext,
1906 ) -> Result<(), StyleError> {
1907 if layers.len() != self.layers.len() {
1908 return Ok(());
1909 }
1910 for (style_layer, runtime_layer) in self.layers.iter().zip(layers.iter_mut()) {
1911 style_layer.apply_to_runtime_layer_with_context(runtime_layer.as_mut(), &self.sources, ctx)?;
1912 }
1913 Ok(())
1914 }
1915
1916 fn layer_index(&self, layer_id: &str) -> Option<usize> {
1917 self.layers.iter().position(|layer| layer.id() == layer_id)
1918 }
1919
1920 pub fn source_is_used(&self, source_id: &str) -> bool {
1923 self.terrain_source.as_deref() == Some(source_id)
1924 || self.layers.iter().any(|layer| layer.uses_source(source_id))
1925 }
1926
1927 pub fn layer_ids_using_source(&self, source_id: &str) -> Vec<&str> {
1930 self.layers
1931 .iter()
1932 .filter(|layer| layer.uses_source(source_id))
1933 .map(|layer| layer.id())
1934 .collect()
1935 }
1936}
1937
1938fn evaluate_background_layer(layer: &BackgroundStyleLayer, ctx: StyleEvalContext) -> BackgroundLayer {
1939 let mut runtime = BackgroundLayer::new(layer.meta.name.clone(), layer.color.evaluate_with_context(ctx));
1940 apply_shared_meta(&mut runtime, &layer.meta, ctx);
1941 runtime
1942}
1943
1944fn evaluate_hillshade_layer(layer: &HillshadeStyleLayer, ctx: StyleEvalContext) -> HillshadeLayer {
1945 let mut runtime = HillshadeLayer::new(layer.meta.name.clone());
1946 apply_shared_meta(&mut runtime, &layer.meta, ctx);
1947 runtime.set_highlight_color(layer.highlight_color.evaluate_with_context(ctx));
1948 runtime.set_shadow_color(layer.shadow_color.evaluate_with_context(ctx));
1949 runtime.set_accent_color(layer.accent_color.evaluate_with_context(ctx));
1950 runtime.set_illumination_direction_deg(layer.illumination_direction_deg.evaluate_with_context(ctx));
1951 runtime.set_illumination_altitude_deg(layer.illumination_altitude_deg.evaluate_with_context(ctx));
1952 runtime.set_exaggeration(layer.exaggeration.evaluate_with_context(ctx));
1953 runtime
1954}
1955
1956fn evaluate_raster_layer(
1957 layer: &RasterStyleLayer,
1958 sources: &HashMap<StyleSourceId, StyleSource>,
1959 ctx: StyleEvalContext,
1960) -> Result<Box<dyn Layer>, StyleError> {
1961 if let Some(result) = try_dynamic_overlay_from_source(&layer.meta.name, &layer.source, sources, ctx) {
1964 return result.map(|mut runtime| {
1965 apply_shared_meta(runtime.as_mut(), &layer.meta, ctx);
1966 runtime
1967 });
1968 }
1969
1970 let (factory, cache_capacity, selection) = require_raster_source(&layer.meta.id, &layer.source, sources)?;
1971 let mut runtime = TileLayer::new_with_selection_config(
1972 layer.meta.name.clone(),
1973 (factory)(),
1974 cache_capacity,
1975 selection.clone(),
1976 );
1977 apply_shared_meta(&mut runtime, &layer.meta, ctx);
1978 Ok(Box::new(runtime))
1979}
1980
1981fn evaluate_vector_runtime_layer(
1982 meta: &StyleLayerMeta,
1983 source_id: &str,
1984 source_layer: Option<&str>,
1985 layer_id: &str,
1986 style: VectorStyle,
1987 sources: &HashMap<StyleSourceId, StyleSource>,
1988 ctx: StyleEvalContext,
1989) -> Result<Box<dyn Layer>, StyleError> {
1990 let features = match sources.get(source_id) {
1991 Some(StyleSource::VectorTile(source)) if source.is_streamed() => FeatureCollection::default(),
1992 _ => require_vector_source(layer_id, source_id, source_layer, sources, ctx.zoom as u8)?.into_owned(),
1993 };
1994 let mut runtime = VectorLayer::new(meta.name.clone(), features, style)
1995 .with_query_metadata(Some(layer_id.to_owned()), Some(source_id.to_owned()))
1996 .with_source_layer(source_layer.map(str::to_owned));
1997 apply_shared_meta(&mut runtime, meta, ctx);
1998 Ok(Box::new(runtime))
1999}
2000
2001fn evaluate_vector_layer(
2002 layer: &VectorStyleLayer,
2003 sources: &HashMap<StyleSourceId, StyleSource>,
2004 ctx: StyleEvalContext,
2005) -> Result<Box<dyn Layer>, StyleError> {
2006 evaluate_vector_runtime_layer(
2007 &layer.meta,
2008 &layer.source,
2009 layer.source_layer.as_deref(),
2010 &layer.meta.id,
2011 vector_style_from_vector_layer(layer, ctx),
2012 sources,
2013 ctx,
2014 )
2015}
2016
2017fn evaluate_fill_layer(
2018 layer: &FillStyleLayer,
2019 sources: &HashMap<StyleSourceId, StyleSource>,
2020 ctx: StyleEvalContext,
2021) -> Result<Box<dyn Layer>, StyleError> {
2022 evaluate_vector_runtime_layer(
2023 &layer.meta,
2024 &layer.source,
2025 layer.source_layer.as_deref(),
2026 &layer.meta.id,
2027 vector_style_from_fill_layer(layer, ctx),
2028 sources,
2029 ctx,
2030 )
2031}
2032
2033fn evaluate_line_layer(
2034 layer: &LineStyleLayer,
2035 sources: &HashMap<StyleSourceId, StyleSource>,
2036 ctx: StyleEvalContext,
2037) -> Result<Box<dyn Layer>, StyleError> {
2038 evaluate_vector_runtime_layer(
2039 &layer.meta,
2040 &layer.source,
2041 layer.source_layer.as_deref(),
2042 &layer.meta.id,
2043 vector_style_from_line_layer(layer, ctx),
2044 sources,
2045 ctx,
2046 )
2047}
2048
2049fn evaluate_circle_layer(
2050 layer: &CircleStyleLayer,
2051 sources: &HashMap<StyleSourceId, StyleSource>,
2052 ctx: StyleEvalContext,
2053) -> Result<Box<dyn Layer>, StyleError> {
2054 evaluate_vector_runtime_layer(
2055 &layer.meta,
2056 &layer.source,
2057 layer.source_layer.as_deref(),
2058 &layer.meta.id,
2059 vector_style_from_circle_layer(layer, ctx),
2060 sources,
2061 ctx,
2062 )
2063}
2064
2065fn evaluate_heatmap_layer(
2066 layer: &HeatmapStyleLayer,
2067 sources: &HashMap<StyleSourceId, StyleSource>,
2068 ctx: StyleEvalContext,
2069) -> Result<Box<dyn Layer>, StyleError> {
2070 evaluate_vector_runtime_layer(
2071 &layer.meta,
2072 &layer.source,
2073 layer.source_layer.as_deref(),
2074 &layer.meta.id,
2075 vector_style_from_heatmap_layer(layer, ctx),
2076 sources,
2077 ctx,
2078 )
2079}
2080
2081fn evaluate_fill_extrusion_layer(
2082 layer: &FillExtrusionStyleLayer,
2083 sources: &HashMap<StyleSourceId, StyleSource>,
2084 ctx: StyleEvalContext,
2085) -> Result<Box<dyn Layer>, StyleError> {
2086 evaluate_vector_runtime_layer(
2087 &layer.meta,
2088 &layer.source,
2089 layer.source_layer.as_deref(),
2090 &layer.meta.id,
2091 vector_style_from_fill_extrusion_layer(layer, ctx),
2092 sources,
2093 ctx,
2094 )
2095}
2096
2097fn evaluate_symbol_layer(
2098 layer: &SymbolStyleLayer,
2099 sources: &HashMap<StyleSourceId, StyleSource>,
2100 ctx: StyleEvalContext,
2101) -> Result<Box<dyn Layer>, StyleError> {
2102 evaluate_vector_runtime_layer(
2103 &layer.meta,
2104 &layer.source,
2105 layer.source_layer.as_deref(),
2106 &layer.meta.id,
2107 vector_style_from_symbol_layer(layer, ctx),
2108 sources,
2109 ctx,
2110 )
2111}
2112
2113fn evaluate_model_layer(
2114 layer: &ModelStyleLayer,
2115 sources: &HashMap<StyleSourceId, StyleSource>,
2116 ctx: StyleEvalContext,
2117) -> Result<Box<dyn Layer>, StyleError> {
2118 let model = require_model_source(&layer.meta.id, &layer.source, sources)?;
2119 let mut runtime = ModelLayer::new(layer.meta.name.clone())
2120 .with_query_metadata(Some(layer.meta.id.clone()), Some(layer.source.clone()));
2121 apply_shared_meta(&mut runtime, &layer.meta, ctx);
2122 runtime.instances.extend(model.instances.iter().cloned());
2123 Ok(Box::new(runtime))
2124}
2125
2126fn apply_background_to_runtime(
2127 layer: &BackgroundStyleLayer,
2128 runtime: &mut dyn Layer,
2129 ctx: StyleEvalContext,
2130) -> Result<(), StyleError> {
2131 let background = runtime
2132 .as_any_mut()
2133 .downcast_mut::<BackgroundLayer>()
2134 .expect("style/runtime layer mismatch: expected BackgroundLayer");
2135 apply_shared_meta(background, &layer.meta, ctx);
2136 background.set_color(layer.color.evaluate_with_context(ctx));
2137 Ok(())
2138}
2139
2140fn apply_hillshade_to_runtime(
2141 layer: &HillshadeStyleLayer,
2142 runtime: &mut dyn Layer,
2143 ctx: StyleEvalContext,
2144) -> Result<(), StyleError> {
2145 let hillshade = runtime
2146 .as_any_mut()
2147 .downcast_mut::<HillshadeLayer>()
2148 .expect("style/runtime layer mismatch: expected HillshadeLayer");
2149 apply_shared_meta(hillshade, &layer.meta, ctx);
2150 hillshade.set_highlight_color(layer.highlight_color.evaluate_with_context(ctx));
2151 hillshade.set_shadow_color(layer.shadow_color.evaluate_with_context(ctx));
2152 hillshade.set_accent_color(layer.accent_color.evaluate_with_context(ctx));
2153 hillshade.set_illumination_direction_deg(layer.illumination_direction_deg.evaluate_with_context(ctx));
2154 hillshade.set_illumination_altitude_deg(layer.illumination_altitude_deg.evaluate_with_context(ctx));
2155 hillshade.set_exaggeration(layer.exaggeration.evaluate_with_context(ctx));
2156 Ok(())
2157}
2158
2159fn apply_raster_to_runtime(
2160 layer: &RasterStyleLayer,
2161 runtime: &mut dyn Layer,
2162 sources: &HashMap<StyleSourceId, StyleSource>,
2163 ctx: StyleEvalContext,
2164) -> Result<(), StyleError> {
2165 let _ = require_raster_source(&layer.meta.id, &layer.source, sources)?;
2166 let tile = runtime
2167 .as_any_mut()
2168 .downcast_mut::<TileLayer>()
2169 .expect("style/runtime layer mismatch: expected TileLayer");
2170 apply_shared_meta(tile, &layer.meta, ctx);
2171 Ok(())
2172}
2173
2174fn apply_vector_style_to_runtime(
2175 runtime: &mut dyn Layer,
2176 meta: &StyleLayerMeta,
2177 source_id: &str,
2178 source_layer: Option<&str>,
2179 layer_id: &str,
2180 style: VectorStyle,
2181 sources: &HashMap<StyleSourceId, StyleSource>,
2182 ctx: StyleEvalContext,
2183) -> Result<(), StyleError> {
2184 let vector = require_vector_source(layer_id, source_id, source_layer, sources, ctx.zoom as u8)?;
2185 let layer = runtime
2186 .as_any_mut()
2187 .downcast_mut::<VectorLayer>()
2188 .expect("style/runtime layer mismatch: expected VectorLayer");
2189 apply_shared_meta(layer, meta, ctx);
2190 layer.style = style;
2191 layer.set_query_metadata(Some(layer_id.to_owned()), Some(source_id.to_owned()));
2192 if layer.features.len() != vector.len() {
2193 layer.features = vector.into_owned();
2194 }
2195 Ok(())
2196}
2197
2198fn apply_vector_to_runtime(
2199 layer: &VectorStyleLayer,
2200 runtime: &mut dyn Layer,
2201 sources: &HashMap<StyleSourceId, StyleSource>,
2202 ctx: StyleEvalContext,
2203) -> Result<(), StyleError> {
2204 apply_vector_style_to_runtime(
2205 runtime,
2206 &layer.meta,
2207 &layer.source,
2208 layer.source_layer.as_deref(),
2209 &layer.meta.id,
2210 vector_style_from_vector_layer(layer, ctx),
2211 sources,
2212 ctx,
2213 )
2214}
2215
2216fn apply_fill_to_runtime(
2217 layer: &FillStyleLayer,
2218 runtime: &mut dyn Layer,
2219 sources: &HashMap<StyleSourceId, StyleSource>,
2220 ctx: StyleEvalContext,
2221) -> Result<(), StyleError> {
2222 apply_vector_style_to_runtime(
2223 runtime,
2224 &layer.meta,
2225 &layer.source,
2226 layer.source_layer.as_deref(),
2227 &layer.meta.id,
2228 vector_style_from_fill_layer(layer, ctx),
2229 sources,
2230 ctx,
2231 )
2232}
2233
2234fn apply_line_to_runtime(
2235 layer: &LineStyleLayer,
2236 runtime: &mut dyn Layer,
2237 sources: &HashMap<StyleSourceId, StyleSource>,
2238 ctx: StyleEvalContext,
2239) -> Result<(), StyleError> {
2240 apply_vector_style_to_runtime(
2241 runtime,
2242 &layer.meta,
2243 &layer.source,
2244 layer.source_layer.as_deref(),
2245 &layer.meta.id,
2246 vector_style_from_line_layer(layer, ctx),
2247 sources,
2248 ctx,
2249 )
2250}
2251
2252fn apply_circle_to_runtime(
2253 layer: &CircleStyleLayer,
2254 runtime: &mut dyn Layer,
2255 sources: &HashMap<StyleSourceId, StyleSource>,
2256 ctx: StyleEvalContext,
2257) -> Result<(), StyleError> {
2258 apply_vector_style_to_runtime(
2259 runtime,
2260 &layer.meta,
2261 &layer.source,
2262 layer.source_layer.as_deref(),
2263 &layer.meta.id,
2264 vector_style_from_circle_layer(layer, ctx),
2265 sources,
2266 ctx,
2267 )
2268}
2269
2270fn apply_heatmap_to_runtime(
2271 layer: &HeatmapStyleLayer,
2272 runtime: &mut dyn Layer,
2273 sources: &HashMap<StyleSourceId, StyleSource>,
2274 ctx: StyleEvalContext,
2275) -> Result<(), StyleError> {
2276 apply_vector_style_to_runtime(
2277 runtime,
2278 &layer.meta,
2279 &layer.source,
2280 layer.source_layer.as_deref(),
2281 &layer.meta.id,
2282 vector_style_from_heatmap_layer(layer, ctx),
2283 sources,
2284 ctx,
2285 )
2286}
2287
2288fn apply_fill_extrusion_to_runtime(
2289 layer: &FillExtrusionStyleLayer,
2290 runtime: &mut dyn Layer,
2291 sources: &HashMap<StyleSourceId, StyleSource>,
2292 ctx: StyleEvalContext,
2293) -> Result<(), StyleError> {
2294 apply_vector_style_to_runtime(
2295 runtime,
2296 &layer.meta,
2297 &layer.source,
2298 layer.source_layer.as_deref(),
2299 &layer.meta.id,
2300 vector_style_from_fill_extrusion_layer(layer, ctx),
2301 sources,
2302 ctx,
2303 )
2304}
2305
2306fn apply_symbol_to_runtime(
2307 layer: &SymbolStyleLayer,
2308 runtime: &mut dyn Layer,
2309 sources: &HashMap<StyleSourceId, StyleSource>,
2310 ctx: StyleEvalContext,
2311) -> Result<(), StyleError> {
2312 apply_vector_style_to_runtime(
2313 runtime,
2314 &layer.meta,
2315 &layer.source,
2316 layer.source_layer.as_deref(),
2317 &layer.meta.id,
2318 vector_style_from_symbol_layer(layer, ctx),
2319 sources,
2320 ctx,
2321 )
2322}
2323
2324fn apply_model_to_runtime(
2325 layer: &ModelStyleLayer,
2326 runtime: &mut dyn Layer,
2327 sources: &HashMap<StyleSourceId, StyleSource>,
2328 ctx: StyleEvalContext,
2329) -> Result<(), StyleError> {
2330 let model = require_model_source(&layer.meta.id, &layer.source, sources)?;
2331 let runtime = runtime
2332 .as_any_mut()
2333 .downcast_mut::<ModelLayer>()
2334 .expect("style/runtime layer mismatch: expected ModelLayer");
2335 apply_shared_meta(runtime, &layer.meta, ctx);
2336 runtime.set_query_metadata(Some(layer.meta.id.clone()), Some(layer.source.clone()));
2337 runtime.instances.clear();
2338 runtime.instances.extend(model.instances.iter().cloned());
2339 Ok(())
2340}
2341
2342fn vector_style_from_vector_layer(layer: &VectorStyleLayer, ctx: StyleEvalContext) -> VectorStyle {
2343 VectorStyle {
2344 render_mode: VectorRenderMode::Generic,
2345 fill_color: layer.fill_color.evaluate_with_context(ctx),
2346 stroke_color: layer.stroke_color.evaluate_with_context(ctx),
2347 stroke_width: layer.stroke_width.evaluate_with_context(ctx),
2348 ..VectorStyle::default()
2349 }
2350}
2351
2352fn vector_style_from_fill_layer(layer: &FillStyleLayer, ctx: StyleEvalContext) -> VectorStyle {
2353 let mut style = VectorStyle::fill(
2354 layer.fill_color.evaluate_with_context(ctx),
2355 layer.outline_color.evaluate_with_context(ctx),
2356 layer.outline_width.evaluate_with_context(ctx),
2357 );
2358 style.fill_pattern = layer.fill_pattern.clone();
2359 style
2360}
2361
2362fn vector_style_from_line_layer(layer: &LineStyleLayer, ctx: StyleEvalContext) -> VectorStyle {
2363 let mut style = VectorStyle::line_styled(
2364 layer.color.evaluate_with_context(ctx),
2365 layer.width.evaluate_with_context(ctx),
2366 layer.line_cap,
2367 layer.line_join,
2368 layer.miter_limit,
2369 layer.dash_array.clone(),
2370 );
2371 if layer.width.is_data_driven() {
2373 style.width_expr = Some(layer.width.clone());
2374 }
2375 if layer.color.is_data_driven() {
2376 style.stroke_color_expr = Some(layer.color.clone());
2377 }
2378 style.eval_zoom = ctx.zoom;
2379 style.line_gradient = layer.line_gradient.clone();
2380 style.line_pattern = layer.line_pattern.clone();
2381 style
2382}
2383
2384fn vector_style_from_circle_layer(layer: &CircleStyleLayer, ctx: StyleEvalContext) -> VectorStyle {
2385 VectorStyle::circle(
2386 layer.color.evaluate_with_context(ctx),
2387 layer.radius.evaluate_with_context(ctx),
2388 layer.stroke_color.evaluate_with_context(ctx),
2389 layer.stroke_width.evaluate_with_context(ctx),
2390 )
2391}
2392
2393fn vector_style_from_heatmap_layer(layer: &HeatmapStyleLayer, ctx: StyleEvalContext) -> VectorStyle {
2394 VectorStyle::heatmap(
2395 layer.color.evaluate_with_context(ctx),
2396 layer.radius.evaluate_with_context(ctx),
2397 layer.intensity.evaluate_with_context(ctx),
2398 )
2399}
2400
2401fn vector_style_from_fill_extrusion_layer(
2402 layer: &FillExtrusionStyleLayer,
2403 ctx: StyleEvalContext,
2404) -> VectorStyle {
2405 VectorStyle::fill_extrusion(
2406 layer.color.evaluate_with_context(ctx),
2407 layer.base.evaluate_with_context(ctx),
2408 layer.height.evaluate_with_context(ctx),
2409 )
2410}
2411
2412fn vector_style_from_symbol_layer(layer: &SymbolStyleLayer, ctx: StyleEvalContext) -> VectorStyle {
2413 let mut style = VectorStyle::symbol(
2414 layer.color.evaluate_with_context(ctx),
2415 layer.halo_color.evaluate_with_context(ctx),
2416 layer.size.evaluate_with_context(ctx),
2417 );
2418 style.symbol_text_field = layer.text_field.as_ref().map(|value| value.evaluate_with_context(ctx));
2419 style.symbol_icon_image = layer.icon_image.as_ref().map(|value| value.evaluate_with_context(ctx));
2420 style.symbol_font_stack = layer.font_stack.evaluate_with_context(ctx);
2421 style.symbol_padding = layer.padding.evaluate_with_context(ctx);
2422 let shared_overlap = layer.allow_overlap.evaluate_with_context(ctx);
2423 style.symbol_allow_overlap = shared_overlap;
2424 style.symbol_text_allow_overlap = layer
2425 .text_allow_overlap
2426 .as_ref()
2427 .map(|value| value.evaluate_with_context(ctx))
2428 .unwrap_or(shared_overlap);
2429 style.symbol_icon_allow_overlap = layer
2430 .icon_allow_overlap
2431 .as_ref()
2432 .map(|value| value.evaluate_with_context(ctx))
2433 .unwrap_or(shared_overlap);
2434 style.symbol_text_optional = layer
2435 .text_optional
2436 .as_ref()
2437 .map(|value| value.evaluate_with_context(ctx))
2438 .unwrap_or(false);
2439 style.symbol_icon_optional = layer
2440 .icon_optional
2441 .as_ref()
2442 .map(|value| value.evaluate_with_context(ctx))
2443 .unwrap_or(false);
2444 style.symbol_text_ignore_placement = layer
2445 .text_ignore_placement
2446 .as_ref()
2447 .map(|value| value.evaluate_with_context(ctx))
2448 .unwrap_or(false);
2449 style.symbol_icon_ignore_placement = layer
2450 .icon_ignore_placement
2451 .as_ref()
2452 .map(|value| value.evaluate_with_context(ctx))
2453 .unwrap_or(false);
2454 style.symbol_text_radial_offset = layer
2455 .radial_offset
2456 .as_ref()
2457 .map(|value| value.evaluate_with_context(ctx));
2458 style.symbol_variable_anchor_offsets = layer.variable_anchor_offsets.clone();
2459 style.symbol_text_anchor = layer.anchor;
2460 style.symbol_text_justify = effective_symbol_text_justify(
2461 layer.justify.evaluate_with_context(ctx),
2462 layer.anchor,
2463 );
2464 style.symbol_text_transform = layer.transform.evaluate_with_context(ctx);
2465 style.symbol_text_max_width = layer
2466 .max_width
2467 .as_ref()
2468 .map(|value| value.evaluate_with_context(ctx));
2469 style.symbol_text_line_height = layer
2470 .line_height
2471 .as_ref()
2472 .map(|value| value.evaluate_with_context(ctx));
2473 style.symbol_text_letter_spacing = layer
2474 .letter_spacing
2475 .as_ref()
2476 .map(|value| value.evaluate_with_context(ctx));
2477 style.symbol_icon_text_fit = layer.icon_text_fit.evaluate_with_context(ctx);
2478 style.symbol_icon_text_fit_padding = layer.icon_text_fit_padding;
2479 style.symbol_sort_key = layer
2480 .sort_key
2481 .as_ref()
2482 .map(|value| value.evaluate_with_context(ctx));
2483 style.symbol_placement = layer.placement;
2484 style.symbol_spacing = layer.spacing.evaluate_with_context(ctx);
2485 style.symbol_max_angle = layer.max_angle.evaluate_with_context(ctx);
2486 style.symbol_keep_upright = layer.keep_upright.evaluate_with_context(ctx);
2487 style.symbol_anchors = effective_symbol_anchor_order(
2488 layer.anchor,
2489 &layer.variable_anchors,
2490 layer.variable_anchor_offsets.as_deref(),
2491 );
2492 style.symbol_writing_mode = layer.writing_mode;
2493 style.symbol_offset = layer.offset;
2494 style
2495}
2496
2497fn effective_symbol_anchor_order(
2498 anchor: SymbolAnchor,
2499 variable_anchors: &[SymbolAnchor],
2500 variable_anchor_offsets: Option<&[(SymbolAnchor, [f32; 2])]>,
2501) -> Vec<SymbolAnchor> {
2502 if let Some(anchor_offsets) = variable_anchor_offsets {
2503 return anchor_offsets.iter().map(|(anchor, _)| *anchor).collect();
2504 }
2505 if variable_anchors.is_empty() {
2506 vec![anchor]
2507 } else {
2508 variable_anchors.to_vec()
2509 }
2510}
2511
2512pub fn fill_style_with_state(layer: &FillStyleLayer, ctx: &StyleEvalContextFull<'_>) -> VectorStyle {
2527 let mut style = VectorStyle::fill(
2528 layer.fill_color.evaluate_with_full_context(ctx),
2529 layer.outline_color.evaluate_with_full_context(ctx),
2530 layer.outline_width.evaluate_with_full_context(ctx),
2531 );
2532 style.fill_pattern = layer.fill_pattern.clone();
2533 style
2534}
2535
2536pub fn line_style_with_state(layer: &LineStyleLayer, ctx: &StyleEvalContextFull<'_>) -> VectorStyle {
2538 let mut style = VectorStyle::line_styled(
2539 layer.color.evaluate_with_full_context(ctx),
2540 layer.width.evaluate_with_full_context(ctx),
2541 layer.line_cap,
2542 layer.line_join,
2543 layer.miter_limit,
2544 layer.dash_array.clone(),
2545 );
2546 style.line_gradient = layer.line_gradient.clone();
2547 style.line_pattern = layer.line_pattern.clone();
2548 style
2549}
2550
2551pub fn circle_style_with_state(layer: &CircleStyleLayer, ctx: &StyleEvalContextFull<'_>) -> VectorStyle {
2553 VectorStyle::circle(
2554 layer.color.evaluate_with_full_context(ctx),
2555 layer.radius.evaluate_with_full_context(ctx),
2556 layer.stroke_color.evaluate_with_full_context(ctx),
2557 layer.stroke_width.evaluate_with_full_context(ctx),
2558 )
2559}
2560
2561pub fn heatmap_style_with_state(layer: &HeatmapStyleLayer, ctx: &StyleEvalContextFull<'_>) -> VectorStyle {
2563 VectorStyle::heatmap(
2564 layer.color.evaluate_with_full_context(ctx),
2565 layer.radius.evaluate_with_full_context(ctx),
2566 layer.intensity.evaluate_with_full_context(ctx),
2567 )
2568}
2569
2570pub fn fill_extrusion_style_with_state(
2572 layer: &FillExtrusionStyleLayer,
2573 ctx: &StyleEvalContextFull<'_>,
2574) -> VectorStyle {
2575 VectorStyle::fill_extrusion(
2576 layer.color.evaluate_with_full_context(ctx),
2577 layer.base.evaluate_with_full_context(ctx),
2578 layer.height.evaluate_with_full_context(ctx),
2579 )
2580}
2581
2582pub fn vector_style_with_state(layer: &VectorStyleLayer, ctx: &StyleEvalContextFull<'_>) -> VectorStyle {
2584 VectorStyle {
2585 render_mode: VectorRenderMode::Generic,
2586 fill_color: layer.fill_color.evaluate_with_full_context(ctx),
2587 stroke_color: layer.stroke_color.evaluate_with_full_context(ctx),
2588 stroke_width: layer.stroke_width.evaluate_with_full_context(ctx),
2589 ..VectorStyle::default()
2590 }
2591}
2592
2593pub fn symbol_style_with_state(layer: &SymbolStyleLayer, ctx: &StyleEvalContextFull<'_>) -> VectorStyle {
2599 let base = ctx.to_base();
2600 let mut style = VectorStyle::symbol(
2601 layer.color.evaluate_with_full_context(ctx),
2602 layer.halo_color.evaluate_with_full_context(ctx),
2603 layer.size.evaluate_with_full_context(ctx),
2604 );
2605 style.symbol_text_field = layer.text_field.as_ref().map(|v| v.evaluate_with_full_context(ctx));
2608 style.symbol_icon_image = layer.icon_image.as_ref().map(|v| v.evaluate_with_full_context(ctx));
2609 style.symbol_font_stack = layer.font_stack.evaluate_with_full_context(ctx);
2610 style.symbol_padding = layer.padding.evaluate_with_full_context(ctx);
2611 let shared_overlap = layer.allow_overlap.evaluate_with_full_context(ctx);
2612 style.symbol_allow_overlap = shared_overlap;
2613 style.symbol_text_allow_overlap = layer
2614 .text_allow_overlap.as_ref()
2615 .map(|v| v.evaluate_with_full_context(ctx))
2616 .unwrap_or(shared_overlap);
2617 style.symbol_icon_allow_overlap = layer
2618 .icon_allow_overlap.as_ref()
2619 .map(|v| v.evaluate_with_full_context(ctx))
2620 .unwrap_or(shared_overlap);
2621 style.symbol_text_optional = layer
2622 .text_optional.as_ref()
2623 .map(|v| v.evaluate_with_full_context(ctx))
2624 .unwrap_or(false);
2625 style.symbol_icon_optional = layer
2626 .icon_optional.as_ref()
2627 .map(|v| v.evaluate_with_full_context(ctx))
2628 .unwrap_or(false);
2629 style.symbol_text_ignore_placement = layer
2630 .text_ignore_placement.as_ref()
2631 .map(|v| v.evaluate_with_full_context(ctx))
2632 .unwrap_or(false);
2633 style.symbol_icon_ignore_placement = layer
2634 .icon_ignore_placement.as_ref()
2635 .map(|v| v.evaluate_with_full_context(ctx))
2636 .unwrap_or(false);
2637 style.symbol_text_radial_offset = layer
2638 .radial_offset.as_ref()
2639 .map(|v| v.evaluate_with_full_context(ctx));
2640 style.symbol_variable_anchor_offsets = layer.variable_anchor_offsets.clone();
2641 style.symbol_text_anchor = layer.anchor;
2642 style.symbol_text_justify = effective_symbol_text_justify(
2643 layer.justify.evaluate_with_context(base),
2644 layer.anchor,
2645 );
2646 style.symbol_text_transform = layer.transform.evaluate_with_full_context(ctx);
2647 style.symbol_text_max_width = layer
2648 .max_width.as_ref()
2649 .map(|v| v.evaluate_with_full_context(ctx));
2650 style.symbol_text_line_height = layer
2651 .line_height.as_ref()
2652 .map(|v| v.evaluate_with_full_context(ctx));
2653 style.symbol_text_letter_spacing = layer
2654 .letter_spacing.as_ref()
2655 .map(|v| v.evaluate_with_full_context(ctx));
2656 style.symbol_icon_text_fit = layer.icon_text_fit.evaluate_with_full_context(ctx);
2657 style.symbol_icon_text_fit_padding = layer.icon_text_fit_padding;
2658 style.symbol_sort_key = layer
2659 .sort_key.as_ref()
2660 .map(|v| v.evaluate_with_full_context(ctx));
2661 style.symbol_placement = layer.placement;
2662 style.symbol_spacing = layer.spacing.evaluate_with_full_context(ctx);
2663 style.symbol_max_angle = layer.max_angle.evaluate_with_full_context(ctx);
2664 style.symbol_keep_upright = layer.keep_upright.evaluate_with_full_context(ctx);
2665 style.symbol_anchors = effective_symbol_anchor_order(
2666 layer.anchor,
2667 &layer.variable_anchors,
2668 layer.variable_anchor_offsets.as_deref(),
2669 );
2670 style.symbol_writing_mode = layer.writing_mode;
2671 style.symbol_offset = layer.offset;
2672 style
2673}
2674
2675fn effective_symbol_text_justify(
2680 justify: SymbolTextJustify,
2681 anchor: SymbolAnchor,
2682) -> SymbolTextJustify {
2683 match justify {
2684 SymbolTextJustify::Auto => anchor_justification(anchor),
2685 explicit => explicit,
2686 }
2687}
2688
2689fn anchor_justification(anchor: SymbolAnchor) -> SymbolTextJustify {
2690 match anchor {
2691 SymbolAnchor::Left | SymbolAnchor::TopLeft | SymbolAnchor::BottomLeft => {
2692 SymbolTextJustify::Left
2693 }
2694 SymbolAnchor::Right | SymbolAnchor::TopRight | SymbolAnchor::BottomRight => {
2695 SymbolTextJustify::Right
2696 }
2697 _ => SymbolTextJustify::Center,
2698 }
2699}
2700
2701#[derive(Debug, Default)]
2703pub struct MapStyle {
2704 document: StyleDocument,
2705}
2706
2707impl MapStyle {
2708 pub fn new() -> Self {
2710 Self::default()
2711 }
2712
2713 pub fn from_document(document: StyleDocument) -> Self {
2715 Self { document }
2716 }
2717
2718 pub fn document(&self) -> &StyleDocument {
2720 &self.document
2721 }
2722
2723 pub fn document_mut(&mut self) -> &mut StyleDocument {
2725 &mut self.document
2726 }
2727
2728 pub fn into_document(self) -> StyleDocument {
2730 self.document
2731 }
2732}
2733
2734#[cfg(test)]
2735mod tests {
2736 use super::*;
2737 use crate::geometry::{Feature, FeatureCollection, Geometry, Point};
2738 use crate::tile_source::{TileError, TileResponse, TileSource};
2739 use std::collections::HashMap;
2740
2741 struct EmptyTileSource;
2742
2743 impl TileSource for EmptyTileSource {
2744 fn request(&self, _id: rustial_math::TileId) {}
2745
2746 fn poll(&self) -> Vec<(rustial_math::TileId, Result<TileResponse, TileError>)> {
2747 Vec::new()
2748 }
2749 }
2750
2751 fn feature_at(lat: f64, lon: f64) -> Feature {
2752 Feature {
2753 geometry: Geometry::Point(Point {
2754 coord: GeoCoord::from_lat_lon(lat, lon),
2755 }),
2756 properties: HashMap::new(),
2757 }
2758 }
2759
2760 fn collection_with_point(lat: f64, lon: f64) -> FeatureCollection {
2761 FeatureCollection {
2762 features: vec![feature_at(lat, lon)],
2763 }
2764 }
2765
2766 #[test]
2767 fn vector_tile_source_can_be_partitioned_by_source_layer() {
2768 let mut source_layers = HashMap::new();
2769 source_layers.insert("roads".to_string(), collection_with_point(1.0, 2.0));
2770 source_layers.insert("water".to_string(), collection_with_point(3.0, 4.0));
2771
2772 let source = VectorTileSource::from_source_layers(source_layers);
2773 assert!(source.has_source_layers());
2774 assert_eq!(source.source_layer("roads").map(|fc| fc.len()), Some(1));
2775 assert_eq!(source.source_layer("water").map(|fc| fc.len()), Some(1));
2776 assert_eq!(source.data.len(), 2);
2777 }
2778
2779 #[test]
2780 fn vector_style_layer_resolves_requested_source_layer() {
2781 let mut document = StyleDocument::new();
2782 let mut source_layers = HashMap::new();
2783 source_layers.insert("roads".to_string(), collection_with_point(10.0, 20.0));
2784 source_layers.insert("water".to_string(), collection_with_point(30.0, 40.0));
2785 document
2786 .add_source(
2787 "vector",
2788 StyleSource::VectorTile(VectorTileSource::from_source_layers(source_layers)),
2789 )
2790 .expect("source added");
2791
2792 let mut layer = LineStyleLayer::new("roads-line", "vector");
2793 layer.source_layer = Some("roads".to_string());
2794 document
2795 .add_layer(StyleLayer::Line(layer))
2796 .expect("layer added");
2797
2798 let runtime = document.to_runtime_layers().expect("runtime layers");
2799 let vector = runtime[0]
2800 .as_any()
2801 .downcast_ref::<VectorLayer>()
2802 .expect("vector runtime layer");
2803 assert_eq!(vector.features.len(), 1);
2804 match &vector.features.features[0].geometry {
2805 Geometry::Point(point) => {
2806 assert!((point.coord.lat - 10.0).abs() < 1e-9);
2807 assert!((point.coord.lon - 20.0).abs() < 1e-9);
2808 }
2809 other => panic!("expected point geometry, got {other:?}"),
2810 }
2811 }
2812
2813 #[test]
2814 fn missing_source_layer_returns_style_error() {
2815 let mut document = StyleDocument::new();
2816 document
2817 .add_source(
2818 "vector",
2819 StyleSource::VectorTile(VectorTileSource::new(collection_with_point(0.0, 0.0))),
2820 )
2821 .expect("source added");
2822
2823 let mut layer = FillStyleLayer::new("water-fill", "vector");
2824 layer.source_layer = Some("water".to_string());
2825 document
2826 .add_layer(StyleLayer::Fill(layer))
2827 .expect("layer added");
2828
2829 let err = document.to_runtime_layers().expect_err("missing source-layer should fail");
2830 assert!(matches!(err, StyleError::MissingSourceLayer { .. }));
2831 }
2832
2833 #[test]
2834 fn streamed_vector_source_allows_runtime_layer_creation_without_resolved_features() {
2835 let mut document = StyleDocument::new();
2836 document
2837 .add_source(
2838 "vector",
2839 StyleSource::VectorTile(
2840 VectorTileSource::streamed(|| Box::new(EmptyTileSource))
2841 .with_cache_capacity(8),
2842 ),
2843 )
2844 .expect("source added");
2845
2846 let mut layer = CircleStyleLayer::new("labels", "vector");
2847 layer.source_layer = Some("poi".to_string());
2848 document
2849 .add_layer(StyleLayer::Circle(layer))
2850 .expect("layer added");
2851
2852 let runtime = document.to_runtime_layers().expect("runtime layers");
2853 let vector = runtime[0]
2854 .as_any()
2855 .downcast_ref::<VectorLayer>()
2856 .expect("vector runtime layer");
2857 assert!(vector.features.is_empty());
2858 assert_eq!(vector.query_source_layer.as_deref(), Some("poi"));
2859 }
2860
2861 #[test]
2862 fn style_document_reports_source_usage() {
2863 let mut document = StyleDocument::new();
2864 document
2865 .add_source("places", StyleSource::GeoJson(GeoJsonSource::new(collection_with_point(0.0, 0.0))))
2866 .expect("source added");
2867 document
2868 .add_source(
2869 "labels",
2870 StyleSource::VectorTile(VectorTileSource::new(collection_with_point(1.0, 1.0))),
2871 )
2872 .expect("source added");
2873 document.set_terrain_source(Some("labels"));
2874
2875 document
2876 .add_layer(StyleLayer::Fill(FillStyleLayer::new("fill", "places")))
2877 .expect("fill layer added");
2878 document
2879 .add_layer(StyleLayer::Line(LineStyleLayer::new("line", "labels")))
2880 .expect("line layer added");
2881
2882 assert!(document.source_is_used("places"));
2883 assert!(document.source_is_used("labels"));
2884 assert!(!document.source_is_used("missing"));
2885
2886 let layer_ids = document.layer_ids_using_source("labels");
2887 assert_eq!(layer_ids, vec!["line"]);
2888 }
2889
2890 #[test]
2895 fn feature_state_value_returns_fallback_with_zoom_only_context() {
2896 let value = StyleValue::<f32>::feature_state_key("opacity", 0.5);
2899 let result = value.evaluate_with_context(StyleEvalContext::new(10.0));
2900 assert!((result - 0.5).abs() < f32::EPSILON);
2901 }
2902
2903 #[test]
2904 fn feature_state_value_resolves_with_full_context() {
2905 let mut state = HashMap::new();
2908 state.insert("opacity".to_string(), PropertyValue::Number(0.8));
2909
2910 let value = StyleValue::<f32>::feature_state_key("opacity", 0.5);
2911 let ctx = StyleEvalContext::new(10.0).with_feature_state(&state);
2912 let result = value.evaluate_with_full_context(&ctx);
2913 assert!((result - 0.8).abs() < f32::EPSILON);
2914 }
2915
2916 #[test]
2917 fn feature_state_value_falls_back_when_key_absent() {
2918 let state: FeatureState = HashMap::new();
2921
2922 let value = StyleValue::<f32>::feature_state_key("opacity", 0.5);
2923 let ctx = StyleEvalContext::new(10.0).with_feature_state(&state);
2924 let result = value.evaluate_with_full_context(&ctx);
2925 assert!((result - 0.5).abs() < f32::EPSILON);
2926 }
2927
2928 #[test]
2929 fn feature_state_bool_resolves_hover_flag() {
2930 let mut state = HashMap::new();
2933 state.insert("hover".to_string(), PropertyValue::Bool(true));
2934
2935 let value = StyleValue::<bool>::feature_state_key("hover", false);
2936 let ctx = StyleEvalContext::new(14.0).with_feature_state(&state);
2937 assert!(value.evaluate_with_full_context(&ctx));
2938 }
2939
2940 #[test]
2941 fn feature_state_color_array_always_returns_fallback() {
2942 let mut state = HashMap::new();
2945 state.insert("color".to_string(), PropertyValue::Number(1.0));
2946
2947 let fallback = [0.1, 0.2, 0.3, 1.0];
2948 let value = StyleValue::<[f32; 4]>::feature_state_key("color", fallback);
2949 let ctx = StyleEvalContext::new(14.0).with_feature_state(&state);
2950 assert_eq!(value.evaluate_with_full_context(&ctx), fallback);
2951 }
2952
2953 #[test]
2954 fn is_feature_state_driven_flag() {
2955 let constant: StyleValue<f32> = StyleValue::Constant(1.0);
2956 assert!(!constant.is_feature_state_driven());
2957
2958 let driven: StyleValue<f32> = StyleValue::feature_state_key("opacity", 1.0);
2959 assert!(driven.is_feature_state_driven());
2960 }
2961
2962 #[test]
2963 fn constant_and_zoom_stops_unchanged_with_full_context() {
2964 let state: FeatureState = HashMap::new();
2967 let ctx = StyleEvalContext::new(5.0).with_feature_state(&state);
2968
2969 let constant = StyleValue::Constant(42.0_f32);
2970 assert!((constant.evaluate_with_full_context(&ctx) - 42.0).abs() < f32::EPSILON);
2971
2972 let stops = StyleValue::ZoomStops(vec![(0.0, 0.0_f32), (10.0, 100.0)]);
2973 let result = stops.evaluate_with_full_context(&ctx);
2974 assert!((result - 50.0).abs() < f32::EPSILON);
2975 }
2976
2977 #[test]
2978 fn full_context_helpers_return_expected_values() {
2979 let mut state = HashMap::new();
2980 state.insert("hover".to_string(), PropertyValue::Bool(true));
2981 state.insert("width".to_string(), PropertyValue::Number(3.5));
2982
2983 let ctx = StyleEvalContextFull::new(14.0, &state);
2984 assert!(ctx.feature_state_bool("hover"));
2985 assert!(!ctx.feature_state_bool("missing"));
2986 assert!((ctx.feature_state_f64("width", 1.0) - 3.5).abs() < f64::EPSILON);
2987 assert!((ctx.feature_state_f64("missing", 1.0) - 1.0).abs() < f64::EPSILON);
2988 }
2989
2990 #[test]
2995 fn fill_layer_resolves_with_feature_state() {
2996 let mut layer = FillStyleLayer::new("buildings", "source");
3000 layer.outline_width = StyleValue::feature_state_key("width", 1.0);
3002
3003 let empty_state: FeatureState = HashMap::new();
3005 let ctx = StyleEvalContext::new(14.0).with_feature_state(&empty_state);
3006 let style = fill_style_with_state(&layer, &ctx);
3007 assert!((style.stroke_width - 1.0).abs() < f32::EPSILON);
3008
3009 let mut hover_state = HashMap::new();
3011 hover_state.insert("width".to_string(), PropertyValue::Number(4.0));
3012 let ctx = StyleEvalContext::new(14.0).with_feature_state(&hover_state);
3013 let style = fill_style_with_state(&layer, &ctx);
3014 assert!((style.stroke_width - 4.0).abs() < f32::EPSILON);
3015 }
3016
3017 #[test]
3018 fn line_layer_resolves_with_feature_state() {
3019 let mut layer = LineStyleLayer::new("roads", "source");
3020 layer.width = StyleValue::feature_state_key("highlight_width", 2.0);
3021
3022 let mut state = HashMap::new();
3023 state.insert("highlight_width".to_string(), PropertyValue::Number(6.0));
3024 let ctx = StyleEvalContext::new(10.0).with_feature_state(&state);
3025 let style = line_style_with_state(&layer, &ctx);
3026 assert!((style.stroke_width - 6.0).abs() < f32::EPSILON);
3027 }
3028
3029 #[test]
3030 fn circle_layer_resolves_with_feature_state() {
3031 let mut layer = CircleStyleLayer::new("points", "source");
3032 layer.radius = StyleValue::feature_state_key("size", 5.0);
3033
3034 let mut state = HashMap::new();
3035 state.insert("size".to_string(), PropertyValue::Number(12.0));
3036 let ctx = StyleEvalContext::new(10.0).with_feature_state(&state);
3037 let style = circle_style_with_state(&layer, &ctx);
3038 assert!((style.point_radius - 12.0).abs() < f32::EPSILON);
3039 }
3040
3041 #[test]
3042 fn has_feature_state_driven_paint_detects_driven_fields() {
3043 let mut fill = FillStyleLayer::new("buildings", "source");
3044 let fill_layer = StyleLayer::Fill(fill.clone());
3045 assert!(!fill_layer.has_feature_state_driven_paint());
3046
3047 fill.outline_width = StyleValue::feature_state_key("width", 1.0);
3049 let fill_layer = StyleLayer::Fill(fill);
3050 assert!(fill_layer.has_feature_state_driven_paint());
3051 }
3052
3053 #[test]
3054 fn has_feature_state_driven_paint_false_for_non_vector_layers() {
3055 let bg = BackgroundStyleLayer::new("bg", [0.0, 0.0, 0.0, 1.0]);
3056 assert!(!StyleLayer::Background(bg).has_feature_state_driven_paint());
3057 }
3058
3059 #[test]
3060 fn resolve_style_with_feature_state_returns_none_for_background() {
3061 let bg = BackgroundStyleLayer::new("bg", [0.0, 0.0, 0.0, 1.0]);
3062 let state: FeatureState = HashMap::new();
3063 let ctx = StyleEvalContext::new(10.0).with_feature_state(&state);
3064 assert!(StyleLayer::Background(bg).resolve_style_with_feature_state(&ctx).is_none());
3065 }
3066
3067 #[test]
3068 fn resolve_style_with_feature_state_dispatches_fill() {
3069 let mut fill = FillStyleLayer::new("buildings", "source");
3070 fill.outline_width = StyleValue::feature_state_key("width", 1.0);
3071
3072 let mut state = HashMap::new();
3073 state.insert("width".to_string(), PropertyValue::Number(5.0));
3074 let ctx = StyleEvalContext::new(14.0).with_feature_state(&state);
3075
3076 let style = StyleLayer::Fill(fill)
3077 .resolve_style_with_feature_state(&ctx)
3078 .expect("fill layer should produce VectorStyle");
3079 assert!((style.stroke_width - 5.0).abs() < f32::EPSILON);
3080 }
3081
3082 #[test]
3083 fn non_driven_fields_unchanged_through_full_context() {
3084 let layer = FillStyleLayer::new("buildings", "source");
3087 let state: FeatureState = HashMap::new();
3088 let full_ctx = StyleEvalContext::new(14.0).with_feature_state(&state);
3089 let zoom_ctx = StyleEvalContext::new(14.0);
3090
3091 let via_full = fill_style_with_state(&layer, &full_ctx);
3092 let via_zoom = vector_style_from_fill_layer(&layer, zoom_ctx);
3093 assert_eq!(via_full.fill_color, via_zoom.fill_color);
3094 assert_eq!(via_full.stroke_color, via_zoom.stroke_color);
3095 assert!((via_full.stroke_width - via_zoom.stroke_width).abs() < f32::EPSILON);
3096 }
3097
3098 #[test]
3103 fn geojson_source_with_clustering_returns_clustered_features_at_low_zoom() {
3104 let features = FeatureCollection {
3106 features: (0..10)
3107 .map(|i| feature_at(48.858 + i as f64 * 0.0001, 2.294))
3108 .collect(),
3109 };
3110 let source = GeoJsonSource::new(features).with_clustering(ClusterOptions {
3111 radius: 80.0,
3112 max_zoom: 16,
3113 min_points: 2,
3114 ..Default::default()
3115 });
3116 assert!(source.is_clustered());
3117
3118 let clustered = source.features_at_zoom(2);
3120 assert!(
3121 clustered.len() < 10,
3122 "Expected fewer features at zoom 2 (got {})",
3123 clustered.len(),
3124 );
3125
3126 let unclustered = source.features_at_zoom(20);
3128 assert_eq!(unclustered.len(), 10);
3129 }
3130
3131 #[test]
3132 fn geojson_source_without_clustering_returns_raw_data() {
3133 let source = GeoJsonSource::new(FeatureCollection {
3134 features: vec![feature_at(51.5, -0.12), feature_at(51.51, -0.13)],
3135 });
3136 assert!(!source.is_clustered());
3137 let result = source.features_at_zoom(5);
3138 assert_eq!(result.len(), 2);
3139 }
3140
3141 #[test]
3142 fn clustered_geojson_circle_layer_resolves_to_runtime_layer() {
3143 let features = FeatureCollection {
3144 features: (0..20)
3145 .map(|i| feature_at(48.858 + i as f64 * 0.0001, 2.294))
3146 .collect(),
3147 };
3148 let source = GeoJsonSource::new(features).with_clustering(Default::default());
3149
3150 let mut doc = StyleDocument::new();
3151 doc.add_source("points", StyleSource::GeoJson(source))
3152 .expect("source added");
3153 doc.add_layer(StyleLayer::Circle(CircleStyleLayer::new("dots", "points")))
3154 .expect("layer added");
3155
3156 let ctx = StyleEvalContext::new(3.0);
3158 let layers = doc.to_runtime_layers_with_context(ctx).expect("layers ok");
3159 assert_eq!(layers.len(), 1, "expected 1 circle layer");
3160 }
3161
3162 #[test]
3163 fn video_source_produces_dynamic_image_overlay_layer() {
3164 use crate::layers::{FrameData, FrameProvider};
3165
3166 struct TestProvider;
3167 impl FrameProvider for TestProvider {
3168 fn next_frame(&mut self) -> Option<FrameData> {
3169 Some(FrameData {
3170 width: 4,
3171 height: 4,
3172 data: vec![255; 64],
3173 })
3174 }
3175 }
3176
3177 let corners = [
3178 GeoCoord::from_lat_lon(40.0, -74.0),
3179 GeoCoord::from_lat_lon(40.0, -73.0),
3180 GeoCoord::from_lat_lon(39.0, -73.0),
3181 GeoCoord::from_lat_lon(39.0, -74.0),
3182 ];
3183 let source = VideoSource::new(corners, || Box::new(TestProvider));
3184
3185 let mut doc = StyleDocument::new();
3186 doc.add_source("video", StyleSource::Video(source))
3187 .expect("source added");
3188 doc.add_layer(StyleLayer::Raster(RasterStyleLayer::new(
3189 "video-layer",
3190 "video",
3191 )))
3192 .expect("layer added");
3193
3194 let layers = doc.to_runtime_layers().expect("runtime layers");
3195 assert_eq!(layers.len(), 1);
3196 assert!(
3197 layers[0].as_any().downcast_ref::<DynamicImageOverlayLayer>().is_some(),
3198 "video source should produce a DynamicImageOverlayLayer"
3199 );
3200 }
3201
3202 #[test]
3203 fn canvas_source_produces_dynamic_image_overlay_layer() {
3204 use crate::layers::{FrameData, FrameProvider};
3205
3206 struct StaticCanvas;
3207 impl FrameProvider for StaticCanvas {
3208 fn next_frame(&mut self) -> Option<FrameData> {
3209 Some(FrameData {
3210 width: 8,
3211 height: 8,
3212 data: vec![128; 256],
3213 })
3214 }
3215 fn is_animating(&self) -> bool {
3216 false
3217 }
3218 }
3219
3220 let corners = [
3221 GeoCoord::from_lat_lon(51.0, -1.0),
3222 GeoCoord::from_lat_lon(51.0, 0.0),
3223 GeoCoord::from_lat_lon(50.0, 0.0),
3224 GeoCoord::from_lat_lon(50.0, -1.0),
3225 ];
3226 let source = CanvasSource::new(corners, || Box::new(StaticCanvas)).with_animate(false);
3227
3228 let mut doc = StyleDocument::new();
3229 doc.add_source("canvas", StyleSource::Canvas(source))
3230 .expect("source added");
3231 doc.add_layer(StyleLayer::Raster(RasterStyleLayer::new(
3232 "canvas-layer",
3233 "canvas",
3234 )))
3235 .expect("layer added");
3236
3237 let layers = doc.to_runtime_layers().expect("runtime layers");
3238 assert_eq!(layers.len(), 1);
3239 let dyn_layer = layers[0]
3240 .as_any()
3241 .downcast_ref::<DynamicImageOverlayLayer>()
3242 .expect("canvas source should produce a DynamicImageOverlayLayer");
3243 assert!(!dyn_layer.provider().is_animating());
3245 }
3246}
3247
3248fn apply_shared_meta(layer: &mut dyn Layer, meta: &StyleLayerMeta, ctx: StyleEvalContext) {
3249 layer.set_visible(meta.visible_in_context(ctx));
3250 layer.set_opacity(meta.opacity.evaluate_with_context(ctx));
3251}
3252
3253fn require_raster_source<'a>(
3254 layer_id: &str,
3255 source_id: &str,
3256 sources: &'a HashMap<StyleSourceId, StyleSource>,
3257) -> Result<(&'a RasterSourceFactory, usize, &'a TileSelectionConfig), StyleError> {
3258 let Some(source) = sources.get(source_id) else {
3259 return Err(StyleError::MissingSource(source_id.to_owned()));
3260 };
3261 match source {
3262 StyleSource::Raster(raster) => Ok((&raster.factory, raster.cache_capacity, &raster.selection)),
3263 StyleSource::Image(image) => Ok((&image.factory, image.cache_capacity, &image.selection)),
3264 other => Err(StyleError::SourceKindMismatch {
3265 layer_id: layer_id.to_owned(),
3266 source_id: source_id.to_owned(),
3267 expected: "raster|image",
3268 actual: other.kind_name(),
3269 }),
3270 }
3271}
3272
3273fn try_dynamic_overlay_from_source(
3279 layer_name: &str,
3280 source_id: &str,
3281 sources: &HashMap<StyleSourceId, StyleSource>,
3282 ctx: StyleEvalContext,
3283) -> Option<Result<Box<dyn Layer>, StyleError>> {
3284 let source = sources.get(source_id)?;
3285 match source {
3286 StyleSource::Video(video) => {
3287 let provider = (video.factory)();
3288 let mut layer = DynamicImageOverlayLayer::new(
3289 layer_name.to_owned(),
3290 video.coordinates,
3291 provider,
3292 );
3293 layer.set_opacity(ctx.zoom.fract() as f32); Some(Ok(Box::new(layer)))
3295 }
3296 StyleSource::Canvas(canvas) => {
3297 let provider = (canvas.factory)();
3298 let mut layer = DynamicImageOverlayLayer::new(
3299 layer_name.to_owned(),
3300 canvas.coordinates,
3301 provider,
3302 );
3303 layer.set_opacity(ctx.zoom.fract() as f32);
3304 Some(Ok(Box::new(layer)))
3305 }
3306 _ => None,
3307 }
3308}
3309
3310fn require_vector_source<'a>(
3311 layer_id: &str,
3312 source_id: &str,
3313 source_layer: Option<&str>,
3314 sources: &'a HashMap<StyleSourceId, StyleSource>,
3315 zoom: u8,
3316) -> Result<Cow<'a, FeatureCollection>, StyleError> {
3317 let Some(source) = sources.get(source_id) else {
3318 return Err(StyleError::MissingSource(source_id.to_owned()));
3319 };
3320 match source {
3321 StyleSource::GeoJson(source) => Ok(source.features_at_zoom(zoom)),
3322 StyleSource::VectorTile(source) => {
3323 if let Some(source_layer) = source_layer {
3324 source
3325 .source_layer(source_layer)
3326 .map(Cow::Borrowed)
3327 .ok_or_else(|| StyleError::MissingSourceLayer {
3328 layer_id: layer_id.to_owned(),
3329 source_id: source_id.to_owned(),
3330 source_layer: source_layer.to_owned(),
3331 })
3332 } else {
3333 Ok(Cow::Borrowed(&source.data))
3334 }
3335 }
3336 other => Err(StyleError::SourceKindMismatch {
3337 layer_id: layer_id.to_owned(),
3338 source_id: source_id.to_owned(),
3339 expected: "geojson|vector",
3340 actual: other.kind_name(),
3341 }),
3342 }
3343}
3344
3345fn require_model_source<'a>(
3346 layer_id: &str,
3347 source_id: &str,
3348 sources: &'a HashMap<StyleSourceId, StyleSource>,
3349) -> Result<&'a ModelSource, StyleError> {
3350 let Some(source) = sources.get(source_id) else {
3351 return Err(StyleError::MissingSource(source_id.to_owned()));
3352 };
3353 match source {
3354 StyleSource::Model(model) => Ok(model),
3355 other => Err(StyleError::SourceKindMismatch {
3356 layer_id: layer_id.to_owned(),
3357 source_id: source_id.to_owned(),
3358 expected: "model",
3359 actual: other.kind_name(),
3360 }),
3361 }
3362}