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 {
160 zoom,
161 feature_state,
162 }
163 }
164
165 pub fn to_base(&self) -> StyleEvalContext {
167 StyleEvalContext { zoom: self.zoom }
168 }
169
170 pub fn get_feature_state(&self, key: &str) -> Option<&PropertyValue> {
172 self.feature_state.get(key)
173 }
174
175 pub fn feature_state_bool(&self, key: &str) -> bool {
177 self.feature_state
178 .get(key)
179 .and_then(|v| v.as_bool())
180 .unwrap_or(false)
181 }
182
183 pub fn feature_state_f64(&self, key: &str, default: f64) -> f64 {
185 self.feature_state
186 .get(key)
187 .and_then(|v| v.as_f64())
188 .unwrap_or(default)
189 }
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
194pub enum StyleProjection {
195 #[default]
197 Mercator,
198 Equirectangular,
200 Globe,
202 VerticalPerspective,
204}
205
206impl StyleProjection {
207 pub fn to_camera_projection(self) -> CameraProjection {
209 match self {
210 StyleProjection::Mercator => CameraProjection::WebMercator,
211 StyleProjection::Equirectangular => CameraProjection::Equirectangular,
212 StyleProjection::Globe => CameraProjection::Globe,
213 StyleProjection::VerticalPerspective => {
214 CameraProjection::vertical_perspective(GeoCoord::default(), 10_000_000.0)
215 }
216 }
217 }
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
233pub enum LightingMode {
234 #[default]
238 Default,
239 Flat,
242}
243
244#[derive(Debug, Clone, PartialEq)]
249pub struct AmbientLight {
250 pub color: [f32; 3],
252 pub intensity: f32,
254}
255
256impl Default for AmbientLight {
257 fn default() -> Self {
258 Self {
259 color: [1.0, 1.0, 1.0],
260 intensity: 0.5,
261 }
262 }
263}
264
265#[derive(Debug, Clone, PartialEq)]
270pub struct DirectionalLight {
271 pub direction: [f32; 2],
279 pub color: [f32; 3],
281 pub intensity: f32,
283 pub cast_shadows: bool,
286}
287
288impl Default for DirectionalLight {
289 fn default() -> Self {
290 Self {
291 direction: [210.0, 45.0],
292 color: [1.0, 1.0, 1.0],
293 intensity: 0.5,
294 cast_shadows: false,
295 }
296 }
297}
298
299#[derive(Debug, Clone, PartialEq, Default)]
309pub struct LightConfig {
310 pub mode: LightingMode,
312 pub ambient: AmbientLight,
314 pub directional: DirectionalLight,
316 pub shadow: ShadowConfig,
319}
320
321#[derive(Debug, Clone, Copy, PartialEq)]
327pub struct ComputedLighting {
328 pub ambient_color: [f32; 3],
330 pub directional_dir: [f32; 3],
332 pub directional_color: [f32; 3],
334 pub lighting_enabled: f32,
336 pub shadows_enabled: bool,
338}
339
340impl Default for ComputedLighting {
341 fn default() -> Self {
342 compute_lighting(&LightConfig::default())
343 }
344}
345
346pub fn compute_lighting(config: &LightConfig) -> ComputedLighting {
348 let enabled = match config.mode {
349 LightingMode::Default => 1.0,
350 LightingMode::Flat => 0.0,
351 };
352
353 let ambient_color = [
354 config.ambient.color[0] * config.ambient.intensity,
355 config.ambient.color[1] * config.ambient.intensity,
356 config.ambient.color[2] * config.ambient.intensity,
357 ];
358
359 let [azimuth_deg, altitude_deg] = config.directional.direction;
360 let azimuth = azimuth_deg.to_radians();
361 let altitude = altitude_deg.to_radians();
362 let cos_alt = altitude.cos();
363 let dir = [
366 azimuth.sin() * cos_alt,
367 azimuth.cos() * cos_alt,
368 altitude.sin(),
369 ];
370 let len = (dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]).sqrt();
371 let directional_dir = if len > 1e-6 {
372 [dir[0] / len, dir[1] / len, dir[2] / len]
373 } else {
374 [0.0, 0.0, 1.0]
375 };
376
377 let directional_color = [
378 config.directional.color[0] * config.directional.intensity,
379 config.directional.color[1] * config.directional.intensity,
380 config.directional.color[2] * config.directional.intensity,
381 ];
382
383 ComputedLighting {
384 ambient_color,
385 directional_dir,
386 directional_color,
387 lighting_enabled: enabled,
388 shadows_enabled: enabled > 0.5 && config.directional.cast_shadows,
389 }
390}
391
392#[derive(Debug, Clone, Copy, PartialEq)]
401pub struct ShadowConfig {
402 pub cascade_count: u32,
404 pub map_resolution: u32,
406 pub intensity: f32,
409 pub normal_offset: f32,
411}
412
413impl Default for ShadowConfig {
414 fn default() -> Self {
415 Self {
416 cascade_count: 2,
417 map_resolution: 2048,
418 intensity: 0.8,
419 normal_offset: 3.0,
420 }
421 }
422}
423
424#[derive(Debug, Clone, Copy, PartialEq)]
430pub struct ComputedShadow {
431 pub enabled: bool,
433 pub light_matrices: [[[f32; 4]; 4]; 4],
437 pub cascade_count: u32,
439 pub map_resolution: u32,
441 pub intensity: f32,
443 pub texel_size: f32,
445 pub normal_offset: f32,
447 pub cascade_split: f32,
449}
450
451impl Default for ComputedShadow {
452 fn default() -> Self {
453 Self {
454 enabled: false,
455 light_matrices: [[[0.0; 4]; 4]; 4],
456 cascade_count: 2,
457 map_resolution: 2048,
458 intensity: 0.0,
459 texel_size: 1.0 / 2048.0,
460 normal_offset: 3.0,
461 cascade_split: 0.0,
462 }
463 }
464}
465
466pub fn compute_shadow_cascades(
483 view_proj: &glam::DMat4,
484 light_dir: [f32; 3],
485 camera_distance: f64,
486 config: &ShadowConfig,
487) -> ComputedShadow {
488 use glam::{DMat4, DVec3};
489
490 let cascade_count = config.cascade_count.clamp(1, 4) as usize;
491 let resolution = config.map_resolution.max(256);
492
493 let split0 = camera_distance * 1.5;
496 let splits: [f64; 4] = [split0, split0 * 2.0, split0 * 4.0, split0 * 8.0];
497
498 let inv_vp = view_proj.inverse();
500
501 let light = DVec3::new(
503 light_dir[0] as f64,
504 light_dir[1] as f64,
505 light_dir[2] as f64,
506 )
507 .normalize_or_zero();
508 if light.length_squared() < 0.5 {
509 return ComputedShadow::default();
510 }
511
512 let up_hint = if light.z.abs() > 0.99 {
514 DVec3::new(0.0, 1.0, 0.0)
515 } else {
516 DVec3::new(0.0, 0.0, 1.0)
517 };
518 let light_right = light.cross(up_hint).normalize();
519 let light_up = light_right.cross(light).normalize();
520
521 let light_view_rot = DMat4::from_cols(
523 glam::DVec4::new(light_right.x, light_up.x, light.x, 0.0),
524 glam::DVec4::new(light_right.y, light_up.y, light.y, 0.0),
525 glam::DVec4::new(light_right.z, light_up.z, light.z, 0.0),
526 glam::DVec4::new(0.0, 0.0, 0.0, 1.0),
527 );
528
529 let mut result = ComputedShadow {
530 enabled: true,
531 light_matrices: [[[0.0; 4]; 4]; 4],
532 cascade_count: cascade_count as u32,
533 map_resolution: resolution,
534 intensity: config.intensity.clamp(0.0, 1.0),
535 texel_size: 1.0 / resolution as f32,
536 normal_offset: config.normal_offset,
537 cascade_split: splits[0] as f32,
538 };
539
540 let ndc_corners: [[f64; 3]; 8] = [
542 [-1.0, -1.0, 0.0],
543 [1.0, -1.0, 0.0],
544 [-1.0, 1.0, 0.0],
545 [1.0, 1.0, 0.0],
546 [-1.0, -1.0, 1.0],
547 [1.0, -1.0, 1.0],
548 [-1.0, 1.0, 1.0],
549 [1.0, 1.0, 1.0],
550 ];
551
552 for cascade in 0..cascade_count {
553 let near_frac = if cascade == 0 {
555 0.0
556 } else {
557 splits[cascade - 1] / splits[cascade_count - 1]
558 };
559 let far_frac = splits[cascade] / splits[cascade_count - 1];
560
561 let mut world_corners = [DVec3::ZERO; 8];
563 for (i, ndc) in ndc_corners.iter().enumerate() {
564 let z = if i < 4 { near_frac } else { far_frac };
566 let ndc_pos = glam::DVec4::new(ndc[0], ndc[1], z, 1.0);
567 let world_h = inv_vp * ndc_pos;
568 world_corners[i] = DVec3::new(
569 world_h.x / world_h.w,
570 world_h.y / world_h.w,
571 world_h.z / world_h.w,
572 );
573 }
574
575 let center: DVec3 = world_corners.iter().copied().sum::<DVec3>() / 8.0;
577
578 let radius = world_corners
580 .iter()
581 .map(|c| (*c - center).length())
582 .fold(0.0_f64, f64::max);
583
584 let texel_size = (radius * 2.0) / resolution as f64;
586 let light_center = light_view_rot * glam::DVec4::new(center.x, center.y, center.z, 1.0);
587 let snapped_x = (light_center.x / texel_size).floor() * texel_size;
588 let snapped_y = (light_center.y / texel_size).floor() * texel_size;
589
590 let snap_offset_x = snapped_x - light_center.x;
592 let snap_offset_y = snapped_y - light_center.y;
593 let snapped_center = center + DVec3::new(snap_offset_x, snap_offset_y, 0.0);
594
595 let light_pos = snapped_center - light * radius * 2.0;
597 let light_view = DMat4::look_at_rh(light_pos, snapped_center, light_up);
598
599 let view_center = light_view * glam::DVec4::new(center.x, center.y, center.z, 1.0);
601 let snap_offset_x = (view_center.x / texel_size).fract() * texel_size;
602 let snap_offset_y = (view_center.y / texel_size).fract() * texel_size;
603 let snap = DMat4::from_translation(DVec3::new(-snap_offset_x, -snap_offset_y, 0.0));
604 let snapped_view = snap * light_view;
605
606 let ortho = DMat4::orthographic_rh(-radius, radius, -radius, radius, 0.0, radius * 4.0);
609
610 let light_vp = ortho * snapped_view;
611
612 let m32 = light_vp.as_mat4();
614 result.light_matrices[cascade] = m32.to_cols_array_2d();
615 }
616
617 result
618}
619
620#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
628pub enum SkyType {
629 #[default]
631 Atmosphere,
632 Gradient,
634}
635
636#[derive(Debug, Clone, PartialEq)]
646pub struct SkyConfig {
647 pub sky_type: SkyType,
649
650 pub sun_position: Option<[f32; 2]>,
658
659 pub sun_intensity: f32,
661
662 pub atmosphere_color: [f32; 3],
664
665 pub halo_color: [f32; 3],
667
668 pub opacity: f32,
672}
673
674impl Default for SkyConfig {
675 fn default() -> Self {
676 Self {
677 sky_type: SkyType::default(),
678 sun_position: None,
679 sun_intensity: 10.0,
680 atmosphere_color: [1.0, 1.0, 1.0],
681 halo_color: [1.0, 1.0, 1.0],
682 opacity: 1.0,
683 }
684 }
685}
686
687#[derive(Debug, Clone, Copy, PartialEq)]
692pub struct ComputedSky {
693 pub sun_direction: [f32; 3],
695 pub sun_intensity: f32,
697 pub rayleigh_color: [f32; 3],
699 pub mie_color: [f32; 3],
701 pub sky_enabled: f32,
703}
704
705impl Default for ComputedSky {
706 fn default() -> Self {
707 Self {
708 sun_direction: [0.0, 0.0, 1.0],
709 sun_intensity: 0.0,
710 rayleigh_color: [1.0, 1.0, 1.0],
711 mie_color: [1.0, 1.0, 1.0],
712 sky_enabled: 0.0, }
714 }
715}
716
717pub fn compute_sky(config: &SkyConfig, fallback_sun: [f32; 2]) -> ComputedSky {
722 let enabled = if config.opacity > 0.0 { 1.0 } else { 0.0 };
723
724 let [azimuth_deg, altitude_deg] = config.sun_position.unwrap_or(fallback_sun);
725 let azimuth = azimuth_deg.to_radians();
726 let altitude = altitude_deg.to_radians();
727 let cos_alt = altitude.cos();
728 let dir = [
729 azimuth.sin() * cos_alt,
730 azimuth.cos() * cos_alt,
731 altitude.sin(),
732 ];
733 let len = (dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]).sqrt();
734 let sun_direction = if len > 1e-6 {
735 [dir[0] / len, dir[1] / len, dir[2] / len]
736 } else {
737 [0.0, 0.0, 1.0]
738 };
739
740 ComputedSky {
741 sun_direction,
742 sun_intensity: config.sun_intensity,
743 rayleigh_color: config.atmosphere_color,
744 mie_color: config.halo_color,
745 sky_enabled: enabled,
746 }
747}
748
749#[derive(Debug, Clone, PartialEq, Default)]
762pub struct FogConfig {
763 pub color: Option<[f32; 4]>,
768
769 pub range: Option<[f32; 2]>,
774
775 pub density: Option<f32>,
780
781 pub horizon_color: Option<[f32; 4]>,
787
788 pub horizon_blend: Option<f32>,
793}
794
795#[derive(Debug, Clone, Copy, PartialEq)]
802pub struct ComputedFog {
803 pub fog_color: [f32; 4],
805 pub fog_start: f32,
807 pub fog_end: f32,
809 pub fog_density: f32,
811 pub clear_color: [f32; 4],
813}
814
815impl Default for ComputedFog {
816 fn default() -> Self {
817 Self {
818 fog_color: [1.0; 4],
819 fog_start: 10_000.0,
820 fog_end: 20_000.0,
821 fog_density: 0.0,
822 clear_color: [1.0; 4],
823 }
824 }
825}
826
827pub fn atmospheric_clear_color(base: [f32; 4], pitch: f64) -> [f32; 4] {
833 let t = (((pitch - 0.25) / 1.0).clamp(0.0, 1.0)) as f32;
834 let horizon = [
835 (base[0] * 0.92 + 0.05).clamp(0.0, 1.0),
836 (base[1] * 0.95 + 0.06).clamp(0.0, 1.0),
837 (base[2] * 0.98 + 0.08).clamp(0.0, 1.0),
838 base[3],
839 ];
840 [
841 base[0] * (1.0 - t) + horizon[0] * t,
842 base[1] * (1.0 - t) + horizon[1] * t,
843 base[2] * (1.0 - t) + horizon[2] * t,
844 base[3],
845 ]
846}
847
848pub fn compute_fog(
853 pitch: f64,
854 camera_distance: f64,
855 background_color: [f32; 4],
856 config: Option<&FogConfig>,
857) -> ComputedFog {
858 let auto_clear = atmospheric_clear_color(background_color, pitch);
859
860 let auto_density = (((pitch - 0.70) / 0.55).clamp(0.0, 1.0) as f32) * 0.9;
862
863 let visible_range = camera_distance / pitch.cos().max(0.05);
865 let auto_start = (visible_range * 0.55) as f32;
866 let auto_end = (visible_range * 1.05) as f32;
867
868 let (fog_start, fog_end) = match config.and_then(|c| c.range) {
869 Some([s, e]) => ((visible_range as f32) * s, (visible_range as f32) * e),
870 None => (auto_start, auto_end),
871 };
872
873 let fog_density = config.and_then(|c| c.density).unwrap_or(auto_density);
874
875 let fog_color = config.and_then(|c| c.color).unwrap_or(auto_clear);
876
877 let clear_color = match config.and_then(|c| c.horizon_color) {
878 Some(horizon) => {
879 let blend = config
880 .and_then(|c| c.horizon_blend)
881 .unwrap_or_else(|| ((pitch - 0.25) / 1.0).clamp(0.0, 1.0) as f32);
882 [
883 background_color[0] * (1.0 - blend) + horizon[0] * blend,
884 background_color[1] * (1.0 - blend) + horizon[1] * blend,
885 background_color[2] * (1.0 - blend) + horizon[2] * blend,
886 background_color[3],
887 ]
888 }
889 None => auto_clear,
890 };
891
892 ComputedFog {
893 fog_color,
894 fog_start,
895 fog_end,
896 fog_density,
897 clear_color,
898 }
899}
900
901pub trait StyleInterpolatable: Clone + FromFeatureStateProperty {
907 fn interpolate(a: &Self, b: &Self, t: f32) -> Self;
909}
910
911impl StyleInterpolatable for f32 {
912 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
913 *a + (*b - *a) * t.clamp(0.0, 1.0)
914 }
915}
916
917impl StyleInterpolatable for [f32; 4] {
918 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
919 let t = t.clamp(0.0, 1.0);
920 [
921 a[0] + (b[0] - a[0]) * t,
922 a[1] + (b[1] - a[1]) * t,
923 a[2] + (b[2] - a[2]) * t,
924 a[3] + (b[3] - a[3]) * t,
925 ]
926 }
927}
928
929impl StyleInterpolatable for bool {
930 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
931 if t < 1.0 {
932 *a
933 } else {
934 *b
935 }
936 }
937}
938
939impl StyleInterpolatable for String {
940 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
941 if t < 1.0 {
942 a.clone()
943 } else {
944 b.clone()
945 }
946 }
947}
948
949impl StyleInterpolatable for SymbolTextJustify {
950 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
951 if t < 1.0 {
952 *a
953 } else {
954 *b
955 }
956 }
957}
958
959impl StyleInterpolatable for SymbolTextTransform {
960 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
961 if t < 1.0 {
962 *a
963 } else {
964 *b
965 }
966 }
967}
968
969impl StyleInterpolatable for SymbolIconTextFit {
970 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
971 if t < 1.0 {
972 *a
973 } else {
974 *b
975 }
976 }
977}
978
979#[derive(Debug, Clone, Copy, PartialEq)]
993pub struct TransitionSpec {
994 pub duration: f32,
996 pub delay: f32,
998}
999
1000impl Default for TransitionSpec {
1001 fn default() -> Self {
1002 Self {
1003 duration: 0.3,
1004 delay: 0.0,
1005 }
1006 }
1007}
1008
1009impl TransitionSpec {
1010 pub const INSTANT: Self = Self {
1012 duration: 0.0,
1013 delay: 0.0,
1014 };
1015
1016 pub fn is_active(&self) -> bool {
1018 self.duration > 0.0 || self.delay > 0.0
1019 }
1020}
1021
1022fn ease_cubic_in_out(t: f32) -> f32 {
1026 let t = t.clamp(0.0, 1.0);
1027 if t < 0.5 {
1028 4.0 * t * t * t
1029 } else {
1030 let f = 2.0 * t - 2.0;
1031 0.5 * f * f * f + 1.0
1032 }
1033}
1034
1035#[derive(Debug, Clone)]
1041pub struct Transitioning<T: StyleInterpolatable> {
1042 prior: T,
1045 target: T,
1047 begin: f64,
1049 end: f64,
1051}
1052
1053impl<T: StyleInterpolatable> Transitioning<T> {
1054 pub fn new(prior: T, target: T, now: f64, spec: &TransitionSpec) -> Self {
1061 let begin = now + spec.delay as f64;
1062 let end = begin + spec.duration as f64;
1063 Self {
1064 prior,
1065 target,
1066 begin,
1067 end,
1068 }
1069 }
1070
1071 pub fn settled(value: T) -> Self {
1073 Self {
1074 prior: value.clone(),
1075 target: value,
1076 begin: 0.0,
1077 end: 0.0,
1078 }
1079 }
1080
1081 pub fn resolve(&self, now: f64) -> T {
1083 if now >= self.end {
1084 return self.target.clone();
1085 }
1086 if now < self.begin {
1087 return self.prior.clone();
1088 }
1089 let duration = self.end - self.begin;
1090 if duration <= 0.0 {
1091 return self.target.clone();
1092 }
1093 let t = ((now - self.begin) / duration) as f32;
1094 T::interpolate(&self.prior, &self.target, ease_cubic_in_out(t))
1095 }
1096
1097 pub fn is_active(&self, now: f64) -> bool {
1099 now < self.end
1100 }
1101
1102 pub fn target(&self) -> &T {
1104 &self.target
1105 }
1106
1107 pub fn retarget(&mut self, new_target: T, now: f64, spec: &TransitionSpec) {
1110 let current = self.resolve(now);
1111 self.prior = current;
1112 self.target = new_target;
1113 self.begin = now + spec.delay as f64;
1114 self.end = self.begin + spec.duration as f64;
1115 }
1116}
1117
1118#[derive(Debug, Clone)]
1124pub struct LayerTransitionState {
1125 pub spec: TransitionSpec,
1127 pub opacity: Transitioning<f32>,
1129 pub color: Transitioning<[f32; 4]>,
1131 pub secondary_color: Transitioning<[f32; 4]>,
1133 pub width: Transitioning<f32>,
1135 pub height: Transitioning<f32>,
1137 pub base: Transitioning<f32>,
1139}
1140
1141impl LayerTransitionState {
1142 pub fn from_initial(
1144 spec: TransitionSpec,
1145 opacity: f32,
1146 color: [f32; 4],
1147 secondary_color: [f32; 4],
1148 width: f32,
1149 height: f32,
1150 base: f32,
1151 ) -> Self {
1152 Self {
1153 spec,
1154 opacity: Transitioning::settled(opacity),
1155 color: Transitioning::settled(color),
1156 secondary_color: Transitioning::settled(secondary_color),
1157 width: Transitioning::settled(width),
1158 height: Transitioning::settled(height),
1159 base: Transitioning::settled(base),
1160 }
1161 }
1162
1163 fn update_f32(trans: &mut Transitioning<f32>, new_val: f32, now: f64, spec: &TransitionSpec) {
1165 if (trans.target() - new_val).abs() > 1e-6 {
1166 trans.retarget(new_val, now, spec);
1167 }
1168 }
1169
1170 fn update_color(
1171 trans: &mut Transitioning<[f32; 4]>,
1172 new_val: [f32; 4],
1173 now: f64,
1174 spec: &TransitionSpec,
1175 ) {
1176 let old = trans.target();
1177 let diff = (old[0] - new_val[0]).abs()
1178 + (old[1] - new_val[1]).abs()
1179 + (old[2] - new_val[2]).abs()
1180 + (old[3] - new_val[3]).abs();
1181 if diff > 1e-5 {
1182 trans.retarget(new_val, now, spec);
1183 }
1184 }
1185
1186 #[allow(clippy::too_many_arguments)]
1188 pub fn update(
1189 &mut self,
1190 now: f64,
1191 opacity: f32,
1192 color: [f32; 4],
1193 secondary_color: [f32; 4],
1194 width: f32,
1195 height: f32,
1196 base: f32,
1197 ) {
1198 let spec = self.spec;
1199 Self::update_f32(&mut self.opacity, opacity, now, &spec);
1200 Self::update_color(&mut self.color, color, now, &spec);
1201 Self::update_color(&mut self.secondary_color, secondary_color, now, &spec);
1202 Self::update_f32(&mut self.width, width, now, &spec);
1203 Self::update_f32(&mut self.height, height, now, &spec);
1204 Self::update_f32(&mut self.base, base, now, &spec);
1205 }
1206
1207 pub fn resolve(&self, now: f64) -> ResolvedTransitions {
1209 ResolvedTransitions {
1210 opacity: self.opacity.resolve(now),
1211 color: self.color.resolve(now),
1212 secondary_color: self.secondary_color.resolve(now),
1213 width: self.width.resolve(now),
1214 height: self.height.resolve(now),
1215 base: self.base.resolve(now),
1216 }
1217 }
1218
1219 pub fn has_active_transitions(&self, now: f64) -> bool {
1221 self.opacity.is_active(now)
1222 || self.color.is_active(now)
1223 || self.secondary_color.is_active(now)
1224 || self.width.is_active(now)
1225 || self.height.is_active(now)
1226 || self.base.is_active(now)
1227 }
1228}
1229
1230#[derive(Debug, Clone, Copy, PartialEq)]
1232pub struct ResolvedTransitions {
1233 pub opacity: f32,
1235 pub color: [f32; 4],
1237 pub secondary_color: [f32; 4],
1239 pub width: f32,
1241 pub height: f32,
1243 pub base: f32,
1245}
1246
1247pub type RasterSourceFactory = Arc<dyn Fn() -> Box<dyn TileSource> + Send + Sync>;
1249
1250pub type VectorTileSourceFactory = Arc<dyn Fn() -> Box<dyn TileSource> + Send + Sync>;
1252
1253pub type TerrainSourceFactory = Arc<dyn Fn() -> Box<dyn ElevationSource> + Send + Sync>;
1255
1256#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1258pub enum StyleSourceKind {
1259 Raster,
1261 Terrain,
1263 GeoJson,
1265 VectorTile,
1267 Image,
1269 Video,
1271 Canvas,
1273 Model,
1275}
1276
1277impl StyleSourceKind {
1278 pub fn as_str(self) -> &'static str {
1280 match self {
1281 StyleSourceKind::Raster => "raster",
1282 StyleSourceKind::Terrain => "terrain",
1283 StyleSourceKind::GeoJson => "geojson",
1284 StyleSourceKind::VectorTile => "vector",
1285 StyleSourceKind::Image => "image",
1286 StyleSourceKind::Video => "video",
1287 StyleSourceKind::Canvas => "canvas",
1288 StyleSourceKind::Model => "model",
1289 }
1290 }
1291}
1292
1293#[derive(Clone)]
1295pub enum StyleSource {
1296 Raster(RasterSource),
1298 Terrain(TerrainSource),
1300 GeoJson(GeoJsonSource),
1302 VectorTile(VectorTileSource),
1304 Image(ImageSource),
1306 Video(VideoSource),
1308 Canvas(CanvasSource),
1310 Model(ModelSource),
1312}
1313
1314impl StyleSource {
1315 pub fn kind_name(&self) -> &'static str {
1317 self.kind().as_str()
1318 }
1319
1320 pub fn kind(&self) -> StyleSourceKind {
1322 match self {
1323 StyleSource::Raster(_) => StyleSourceKind::Raster,
1324 StyleSource::Terrain(_) => StyleSourceKind::Terrain,
1325 StyleSource::GeoJson(_) => StyleSourceKind::GeoJson,
1326 StyleSource::VectorTile(_) => StyleSourceKind::VectorTile,
1327 StyleSource::Image(_) => StyleSourceKind::Image,
1328 StyleSource::Video(_) => StyleSourceKind::Video,
1329 StyleSource::Canvas(_) => StyleSourceKind::Canvas,
1330 StyleSource::Model(_) => StyleSourceKind::Model,
1331 }
1332 }
1333}
1334
1335impl fmt::Debug for StyleSource {
1336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1337 match self {
1338 StyleSource::Raster(src) => f.debug_tuple("Raster").field(src).finish(),
1339 StyleSource::Terrain(src) => f.debug_tuple("Terrain").field(src).finish(),
1340 StyleSource::GeoJson(src) => f.debug_tuple("GeoJson").field(src).finish(),
1341 StyleSource::VectorTile(src) => f.debug_tuple("VectorTile").field(src).finish(),
1342 StyleSource::Image(src) => f.debug_tuple("Image").field(src).finish(),
1343 StyleSource::Video(src) => f.debug_tuple("Video").field(src).finish(),
1344 StyleSource::Canvas(src) => f.debug_tuple("Canvas").field(src).finish(),
1345 StyleSource::Model(src) => f.debug_tuple("Model").field(src).finish(),
1346 }
1347 }
1348}
1349
1350#[derive(Clone)]
1352pub struct RasterSource {
1353 pub cache_capacity: usize,
1355 pub selection: TileSelectionConfig,
1357 pub factory: RasterSourceFactory,
1359}
1360
1361impl RasterSource {
1362 pub fn new(factory: impl Fn() -> Box<dyn TileSource> + Send + Sync + 'static) -> Self {
1364 Self {
1365 cache_capacity: 256,
1366 selection: TileSelectionConfig::default(),
1367 factory: Arc::new(factory),
1368 }
1369 }
1370
1371 pub fn with_cache_capacity(mut self, cache_capacity: usize) -> Self {
1373 self.cache_capacity = cache_capacity;
1374 self
1375 }
1376
1377 pub fn with_selection(mut self, selection: TileSelectionConfig) -> Self {
1379 self.selection = selection;
1380 self
1381 }
1382}
1383
1384impl fmt::Debug for RasterSource {
1385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1386 f.debug_struct("RasterSource")
1387 .field("cache_capacity", &self.cache_capacity)
1388 .field("selection", &self.selection)
1389 .finish_non_exhaustive()
1390 }
1391}
1392
1393#[derive(Clone)]
1395pub struct TerrainSource {
1396 pub enabled: bool,
1398 pub vertical_exaggeration: f64,
1400 pub mesh_resolution: u16,
1402 pub skirt_depth: f64,
1404 pub cache_capacity: usize,
1406 pub factory: TerrainSourceFactory,
1408}
1409
1410impl TerrainSource {
1411 pub fn new(factory: impl Fn() -> Box<dyn ElevationSource> + Send + Sync + 'static) -> Self {
1413 Self {
1414 enabled: true,
1415 vertical_exaggeration: 1.0,
1416 mesh_resolution: 64,
1417 skirt_depth: 100.0,
1418 cache_capacity: 256,
1419 factory: Arc::new(factory),
1420 }
1421 }
1422
1423 pub fn with_cache_capacity(mut self, cache_capacity: usize) -> Self {
1425 self.cache_capacity = cache_capacity;
1426 self
1427 }
1428
1429 pub fn to_terrain_config(&self) -> TerrainConfig {
1431 TerrainConfig {
1432 enabled: self.enabled,
1433 vertical_exaggeration: self.vertical_exaggeration,
1434 mesh_resolution: self.mesh_resolution,
1435 skirt_depth: self.skirt_depth,
1436 source_max_zoom: TerrainConfig::default().source_max_zoom,
1437 source: (self.factory)(),
1438 }
1439 }
1440}
1441
1442impl fmt::Debug for TerrainSource {
1443 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1444 f.debug_struct("TerrainSource")
1445 .field("enabled", &self.enabled)
1446 .field("vertical_exaggeration", &self.vertical_exaggeration)
1447 .field("mesh_resolution", &self.mesh_resolution)
1448 .field("skirt_depth", &self.skirt_depth)
1449 .field("cache_capacity", &self.cache_capacity)
1450 .finish_non_exhaustive()
1451 }
1452}
1453
1454#[derive(Clone)]
1456pub struct GeoJsonSource {
1457 pub data: FeatureCollection,
1459 cluster_index: Option<Arc<PointCluster>>,
1462}
1463
1464impl fmt::Debug for GeoJsonSource {
1465 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1466 f.debug_struct("GeoJsonSource")
1467 .field("features", &self.data.len())
1468 .field("clustered", &self.cluster_index.is_some())
1469 .finish()
1470 }
1471}
1472
1473impl GeoJsonSource {
1474 pub fn new(data: FeatureCollection) -> Self {
1476 Self {
1477 data,
1478 cluster_index: None,
1479 }
1480 }
1481
1482 pub fn with_clustering(mut self, options: ClusterOptions) -> Self {
1488 let mut cluster = PointCluster::new(options);
1489 cluster.load(&self.data);
1490 self.cluster_index = Some(Arc::new(cluster));
1491 self
1492 }
1493
1494 pub fn is_clustered(&self) -> bool {
1496 self.cluster_index.is_some()
1497 }
1498
1499 pub fn features_at_zoom(&self, zoom: u8) -> Cow<'_, FeatureCollection> {
1504 if let Some(ref cluster) = self.cluster_index {
1505 Cow::Owned(cluster.get_clusters_for_zoom(zoom))
1506 } else {
1507 Cow::Borrowed(&self.data)
1508 }
1509 }
1510}
1511
1512#[derive(Clone)]
1514pub struct VectorTileSource {
1515 pub data: FeatureCollection,
1520 pub source_layers: HashMap<String, FeatureCollection>,
1526 pub factory: Option<VectorTileSourceFactory>,
1533 pub cache_capacity: usize,
1535 pub selection: TileSelectionConfig,
1537}
1538
1539impl fmt::Debug for VectorTileSource {
1540 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1541 f.debug_struct("VectorTileSource")
1542 .field("feature_count", &self.data.len())
1543 .field("source_layer_count", &self.source_layers.len())
1544 .field("streamed", &self.factory.is_some())
1545 .field("cache_capacity", &self.cache_capacity)
1546 .field("selection", &self.selection)
1547 .finish()
1548 }
1549}
1550
1551impl VectorTileSource {
1552 pub fn new(data: FeatureCollection) -> Self {
1554 Self {
1555 data,
1556 source_layers: HashMap::new(),
1557 factory: None,
1558 cache_capacity: 256,
1559 selection: TileSelectionConfig::default(),
1560 }
1561 }
1562
1563 pub fn streamed(factory: impl Fn() -> Box<dyn TileSource> + Send + Sync + 'static) -> Self {
1565 Self {
1566 data: FeatureCollection::default(),
1567 source_layers: HashMap::new(),
1568 factory: Some(Arc::new(factory)),
1569 cache_capacity: 256,
1570 selection: TileSelectionConfig::default(),
1571 }
1572 }
1573
1574 pub fn from_source_layers(source_layers: HashMap<String, FeatureCollection>) -> Self {
1576 let mut data = FeatureCollection::default();
1577 for features in source_layers.values() {
1578 data.features.extend(features.features.iter().cloned());
1579 }
1580 Self {
1581 data,
1582 source_layers,
1583 factory: None,
1584 cache_capacity: 256,
1585 selection: TileSelectionConfig::default(),
1586 }
1587 }
1588
1589 pub fn with_source_layer(mut self, name: impl Into<String>, data: FeatureCollection) -> Self {
1591 self.source_layers.insert(name.into(), data);
1592 self.rebuild_flattened_data();
1593 self
1594 }
1595
1596 pub fn source_layer(&self, name: &str) -> Option<&FeatureCollection> {
1598 self.source_layers.get(name)
1599 }
1600
1601 pub fn has_source_layers(&self) -> bool {
1603 !self.source_layers.is_empty()
1604 }
1605
1606 pub fn is_streamed(&self) -> bool {
1608 self.factory.is_some()
1609 }
1610
1611 pub fn with_cache_capacity(mut self, cache_capacity: usize) -> Self {
1613 self.cache_capacity = cache_capacity;
1614 self
1615 }
1616
1617 pub fn with_selection(mut self, selection: TileSelectionConfig) -> Self {
1619 self.selection = selection;
1620 self
1621 }
1622
1623 pub fn make_tile_source(&self) -> Option<Box<dyn TileSource>> {
1625 self.factory.as_ref().map(|factory| (factory)())
1626 }
1627
1628 fn rebuild_flattened_data(&mut self) {
1629 let mut data = FeatureCollection::default();
1630 for features in self.source_layers.values() {
1631 data.features.extend(features.features.iter().cloned());
1632 }
1633 self.data = data;
1634 }
1635}
1636
1637#[derive(Clone)]
1639pub struct ImageSource {
1640 pub cache_capacity: usize,
1642 pub selection: TileSelectionConfig,
1644 pub factory: RasterSourceFactory,
1646}
1647
1648impl ImageSource {
1649 pub fn new(factory: impl Fn() -> Box<dyn TileSource> + Send + Sync + 'static) -> Self {
1651 Self {
1652 cache_capacity: 16,
1653 selection: TileSelectionConfig::default(),
1654 factory: Arc::new(factory),
1655 }
1656 }
1657
1658 pub fn with_cache_capacity(mut self, cache_capacity: usize) -> Self {
1660 self.cache_capacity = cache_capacity;
1661 self
1662 }
1663
1664 pub fn with_selection(mut self, selection: TileSelectionConfig) -> Self {
1666 self.selection = selection;
1667 self
1668 }
1669}
1670
1671impl fmt::Debug for ImageSource {
1672 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1673 f.debug_struct("ImageSource")
1674 .field("cache_capacity", &self.cache_capacity)
1675 .field("selection", &self.selection)
1676 .finish_non_exhaustive()
1677 }
1678}
1679
1680#[derive(Clone)]
1693pub struct VideoSource {
1694 pub coordinates: [GeoCoord; 4],
1696 pub factory: FrameProviderFactory,
1698}
1699
1700impl VideoSource {
1701 pub fn new(
1705 coordinates: [GeoCoord; 4],
1706 factory: impl Fn() -> Box<dyn crate::layers::FrameProvider> + Send + Sync + 'static,
1707 ) -> Self {
1708 Self {
1709 coordinates,
1710 factory: Arc::new(factory),
1711 }
1712 }
1713}
1714
1715impl fmt::Debug for VideoSource {
1716 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1717 f.debug_struct("VideoSource")
1718 .field("coordinates", &self.coordinates)
1719 .finish_non_exhaustive()
1720 }
1721}
1722
1723#[derive(Clone)]
1736pub struct CanvasSource {
1737 pub coordinates: [GeoCoord; 4],
1739 pub factory: FrameProviderFactory,
1741 pub animate: bool,
1744}
1745
1746impl CanvasSource {
1747 pub fn new(
1751 coordinates: [GeoCoord; 4],
1752 factory: impl Fn() -> Box<dyn crate::layers::FrameProvider> + Send + Sync + 'static,
1753 ) -> Self {
1754 Self {
1755 coordinates,
1756 factory: Arc::new(factory),
1757 animate: true,
1758 }
1759 }
1760
1761 pub fn with_animate(mut self, animate: bool) -> Self {
1763 self.animate = animate;
1764 self
1765 }
1766}
1767
1768impl fmt::Debug for CanvasSource {
1769 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1770 f.debug_struct("CanvasSource")
1771 .field("coordinates", &self.coordinates)
1772 .field("animate", &self.animate)
1773 .finish_non_exhaustive()
1774 }
1775}
1776
1777#[derive(Debug, Clone, Default)]
1779pub struct ModelSource {
1780 pub instances: Vec<ModelInstance>,
1782}
1783
1784impl ModelSource {
1785 pub fn new(instances: Vec<ModelInstance>) -> Self {
1787 Self { instances }
1788 }
1789}
1790
1791#[derive(Debug, Default)]
1793pub struct StyleDocument {
1794 sources: HashMap<StyleSourceId, StyleSource>,
1795 layers: Vec<StyleLayer>,
1796 terrain_source: Option<StyleSourceId>,
1797 projection: StyleProjection,
1798 fog: Option<FogConfig>,
1799 lights: Option<LightConfig>,
1800 sky: Option<SkyConfig>,
1801 transition: TransitionSpec,
1806}
1807
1808pub type StyleValue<T> = crate::expression::Expression<T>;
1819
1820pub trait FromFeatureStateProperty: Sized {
1831 fn from_feature_state_property(prop: &PropertyValue) -> Option<Self>;
1834}
1835
1836impl FromFeatureStateProperty for f32 {
1837 fn from_feature_state_property(prop: &PropertyValue) -> Option<Self> {
1838 prop.as_f64().map(|v| v as f32)
1839 }
1840}
1841
1842impl FromFeatureStateProperty for [f32; 4] {
1843 fn from_feature_state_property(_prop: &PropertyValue) -> Option<Self> {
1845 None
1846 }
1847}
1848
1849impl FromFeatureStateProperty for bool {
1850 fn from_feature_state_property(prop: &PropertyValue) -> Option<Self> {
1851 prop.as_bool()
1852 }
1853}
1854
1855impl FromFeatureStateProperty for String {
1856 fn from_feature_state_property(prop: &PropertyValue) -> Option<Self> {
1857 prop.as_str().map(|s| s.to_owned())
1858 }
1859}
1860
1861impl FromFeatureStateProperty for SymbolTextJustify {
1862 fn from_feature_state_property(_prop: &PropertyValue) -> Option<Self> {
1863 None
1864 }
1865}
1866
1867impl FromFeatureStateProperty for SymbolTextTransform {
1868 fn from_feature_state_property(_prop: &PropertyValue) -> Option<Self> {
1869 None
1870 }
1871}
1872
1873impl FromFeatureStateProperty for SymbolIconTextFit {
1874 fn from_feature_state_property(_prop: &PropertyValue) -> Option<Self> {
1875 None
1876 }
1877}
1878
1879#[derive(Debug, Clone)]
1881pub struct StyleLayerMeta {
1882 pub id: StyleLayerId,
1884 pub name: String,
1886 pub visible: StyleValue<bool>,
1888 pub opacity: StyleValue<f32>,
1890 pub min_zoom: Option<f32>,
1892 pub max_zoom: Option<f32>,
1894 pub transition: TransitionSpec,
1896}
1897
1898impl StyleLayerMeta {
1899 pub fn new(id: impl Into<String>) -> Self {
1901 let id = id.into();
1902 Self {
1903 name: id.clone(),
1904 id,
1905 visible: StyleValue::Constant(true),
1906 opacity: StyleValue::Constant(1.0),
1907 min_zoom: None,
1908 max_zoom: None,
1909 transition: TransitionSpec::default(),
1910 }
1911 }
1912
1913 fn visible_in_context(&self, ctx: StyleEvalContext) -> bool {
1914 if let Some(min_zoom) = self.min_zoom {
1915 if ctx.zoom < min_zoom {
1916 return false;
1917 }
1918 }
1919 if let Some(max_zoom) = self.max_zoom {
1920 if ctx.zoom > max_zoom {
1921 return false;
1922 }
1923 }
1924 self.visible.evaluate_with_context(ctx)
1925 }
1926}
1927
1928#[allow(missing_docs)]
1930#[derive(Debug, Clone)]
1931pub struct BackgroundStyleLayer {
1932 pub meta: StyleLayerMeta,
1933 pub color: StyleValue<[f32; 4]>,
1934}
1935
1936impl BackgroundStyleLayer {
1937 pub fn new(id: impl Into<String>, color: impl Into<StyleValue<[f32; 4]>>) -> Self {
1939 Self {
1940 meta: StyleLayerMeta::new(id),
1941 color: color.into(),
1942 }
1943 }
1944}
1945
1946#[allow(missing_docs)]
1948#[derive(Debug, Clone)]
1949pub struct HillshadeStyleLayer {
1950 pub meta: StyleLayerMeta,
1951 pub highlight_color: StyleValue<[f32; 4]>,
1952 pub shadow_color: StyleValue<[f32; 4]>,
1953 pub accent_color: StyleValue<[f32; 4]>,
1954 pub illumination_direction_deg: StyleValue<f32>,
1955 pub illumination_altitude_deg: StyleValue<f32>,
1956 pub exaggeration: StyleValue<f32>,
1957}
1958
1959impl HillshadeStyleLayer {
1960 pub fn new(id: impl Into<String>) -> Self {
1962 Self {
1963 meta: StyleLayerMeta::new(id),
1964 highlight_color: [1.0, 1.0, 1.0, 1.0].into(),
1965 shadow_color: [0.0, 0.0, 0.0, 1.0].into(),
1966 accent_color: [0.42, 0.48, 0.42, 1.0].into(),
1967 illumination_direction_deg: 335.0.into(),
1968 illumination_altitude_deg: 45.0.into(),
1969 exaggeration: 1.0.into(),
1970 }
1971 }
1972}
1973
1974#[allow(missing_docs)]
1976#[derive(Debug, Clone)]
1977pub struct RasterStyleLayer {
1978 pub meta: StyleLayerMeta,
1979 pub source: StyleSourceId,
1980}
1981
1982impl RasterStyleLayer {
1983 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
1985 Self {
1986 meta: StyleLayerMeta::new(id),
1987 source: source.into(),
1988 }
1989 }
1990}
1991
1992#[allow(missing_docs)]
1994#[derive(Debug, Clone)]
1995pub struct VectorStyleLayer {
1996 pub meta: StyleLayerMeta,
1997 pub source: StyleSourceId,
1998 pub source_layer: Option<String>,
2000 pub fill_color: StyleValue<[f32; 4]>,
2001 pub stroke_color: StyleValue<[f32; 4]>,
2002 pub stroke_width: StyleValue<f32>,
2003}
2004
2005impl VectorStyleLayer {
2006 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
2008 Self {
2009 meta: StyleLayerMeta::new(id),
2010 source: source.into(),
2011 source_layer: None,
2012 fill_color: VectorStyle::default().fill_color.into(),
2013 stroke_color: VectorStyle::default().stroke_color.into(),
2014 stroke_width: VectorStyle::default().stroke_width.into(),
2015 }
2016 }
2017}
2018
2019#[allow(missing_docs)]
2021#[derive(Debug, Clone)]
2022pub struct FillStyleLayer {
2023 pub meta: StyleLayerMeta,
2024 pub source: StyleSourceId,
2025 pub source_layer: Option<String>,
2027 pub fill_color: StyleValue<[f32; 4]>,
2028 pub outline_color: StyleValue<[f32; 4]>,
2029 pub outline_width: StyleValue<f32>,
2030 pub fill_pattern: Option<std::sync::Arc<crate::PatternImage>>,
2035}
2036
2037impl FillStyleLayer {
2038 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
2040 let style = VectorStyle::default();
2041 Self {
2042 meta: StyleLayerMeta::new(id),
2043 source: source.into(),
2044 source_layer: None,
2045 fill_color: style.fill_color.into(),
2046 outline_color: style.stroke_color.into(),
2047 outline_width: style.stroke_width.into(),
2048 fill_pattern: None,
2049 }
2050 }
2051}
2052
2053#[allow(missing_docs)]
2055#[derive(Debug, Clone)]
2056pub struct LineStyleLayer {
2057 pub meta: StyleLayerMeta,
2058 pub source: StyleSourceId,
2059 pub source_layer: Option<String>,
2061 pub color: StyleValue<[f32; 4]>,
2062 pub width: StyleValue<f32>,
2063 pub line_cap: LineCap,
2065 pub line_join: LineJoin,
2067 pub miter_limit: f32,
2069 pub dash_array: Option<Vec<f32>>,
2071 pub line_gradient: Option<crate::visualization::ColorRamp>,
2078 pub line_pattern: Option<std::sync::Arc<crate::PatternImage>>,
2084}
2085
2086impl LineStyleLayer {
2087 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
2089 let style = VectorStyle::default();
2090 Self {
2091 meta: StyleLayerMeta::new(id),
2092 source: source.into(),
2093 source_layer: None,
2094 color: style.stroke_color.into(),
2095 width: style.stroke_width.into(),
2096 line_cap: LineCap::default(),
2097 line_join: LineJoin::default(),
2098 miter_limit: 2.0,
2099 dash_array: None,
2100 line_gradient: None,
2101 line_pattern: None,
2102 }
2103 }
2104}
2105
2106#[allow(missing_docs)]
2108#[derive(Debug, Clone)]
2109pub struct CircleStyleLayer {
2110 pub meta: StyleLayerMeta,
2111 pub source: StyleSourceId,
2112 pub source_layer: Option<String>,
2114 pub color: StyleValue<[f32; 4]>,
2115 pub radius: StyleValue<f32>,
2116 pub stroke_color: StyleValue<[f32; 4]>,
2117 pub stroke_width: StyleValue<f32>,
2118}
2119
2120impl CircleStyleLayer {
2121 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
2123 let style = VectorStyle::default();
2124 Self {
2125 meta: StyleLayerMeta::new(id),
2126 source: source.into(),
2127 source_layer: None,
2128 color: style.fill_color.into(),
2129 radius: style.point_radius.into(),
2130 stroke_color: style.stroke_color.into(),
2131 stroke_width: style.stroke_width.into(),
2132 }
2133 }
2134}
2135
2136#[allow(missing_docs)]
2138#[derive(Debug, Clone)]
2139pub struct HeatmapStyleLayer {
2140 pub meta: StyleLayerMeta,
2141 pub source: StyleSourceId,
2142 pub source_layer: Option<String>,
2144 pub color: StyleValue<[f32; 4]>,
2145 pub radius: StyleValue<f32>,
2146 pub intensity: StyleValue<f32>,
2147}
2148
2149impl HeatmapStyleLayer {
2150 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
2152 let style = VectorStyle::default();
2153 Self {
2154 meta: StyleLayerMeta::new(id),
2155 source: source.into(),
2156 source_layer: None,
2157 color: style.fill_color.into(),
2158 radius: style.heatmap_radius.into(),
2159 intensity: style.heatmap_intensity.into(),
2160 }
2161 }
2162}
2163
2164#[allow(missing_docs)]
2166#[derive(Debug, Clone)]
2167pub struct FillExtrusionStyleLayer {
2168 pub meta: StyleLayerMeta,
2169 pub source: StyleSourceId,
2170 pub source_layer: Option<String>,
2172 pub color: StyleValue<[f32; 4]>,
2173 pub base: StyleValue<f32>,
2174 pub height: StyleValue<f32>,
2175}
2176
2177impl FillExtrusionStyleLayer {
2178 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
2180 let style = VectorStyle::default();
2181 Self {
2182 meta: StyleLayerMeta::new(id),
2183 source: source.into(),
2184 source_layer: None,
2185 color: style.fill_color.into(),
2186 base: style.extrusion_base.into(),
2187 height: style.extrusion_height.into(),
2188 }
2189 }
2190}
2191
2192#[allow(missing_docs)]
2194#[derive(Debug, Clone)]
2195pub struct SymbolStyleLayer {
2196 pub meta: StyleLayerMeta,
2197 pub source: StyleSourceId,
2198 pub source_layer: Option<String>,
2200 pub color: StyleValue<[f32; 4]>,
2201 pub halo_color: StyleValue<[f32; 4]>,
2202 pub size: StyleValue<f32>,
2203 pub text_field: Option<StyleValue<String>>,
2204 pub icon_image: Option<StyleValue<String>>,
2205 pub font_stack: StyleValue<String>,
2206 pub padding: StyleValue<f32>,
2207 pub allow_overlap: StyleValue<bool>,
2209 pub text_allow_overlap: Option<StyleValue<bool>>,
2211 pub icon_allow_overlap: Option<StyleValue<bool>>,
2213 pub text_optional: Option<StyleValue<bool>>,
2215 pub icon_optional: Option<StyleValue<bool>>,
2217 pub text_ignore_placement: Option<StyleValue<bool>>,
2219 pub icon_ignore_placement: Option<StyleValue<bool>>,
2221 pub radial_offset: Option<StyleValue<f32>>,
2223 pub variable_anchor_offsets: Option<Vec<(SymbolAnchor, [f32; 2])>>,
2225 pub anchor: SymbolAnchor,
2227 pub justify: StyleValue<SymbolTextJustify>,
2229 pub transform: StyleValue<SymbolTextTransform>,
2231 pub max_width: Option<StyleValue<f32>>,
2233 pub line_height: Option<StyleValue<f32>>,
2235 pub letter_spacing: Option<StyleValue<f32>>,
2237 pub icon_text_fit: StyleValue<SymbolIconTextFit>,
2239 pub icon_text_fit_padding: [f32; 4],
2241 pub sort_key: Option<StyleValue<f32>>,
2243 pub placement: SymbolPlacement,
2245 pub spacing: StyleValue<f32>,
2247 pub max_angle: StyleValue<f32>,
2249 pub keep_upright: StyleValue<bool>,
2251 pub variable_anchors: Vec<SymbolAnchor>,
2252 pub writing_mode: SymbolWritingMode,
2253 pub offset: [f32; 2],
2254}
2255
2256impl SymbolStyleLayer {
2257 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
2259 let style = VectorStyle::default();
2260 Self {
2261 meta: StyleLayerMeta::new(id),
2262 source: source.into(),
2263 source_layer: None,
2264 color: style.fill_color.into(),
2265 halo_color: style.symbol_halo_color.into(),
2266 size: style.symbol_size.into(),
2267 text_field: None,
2268 icon_image: None,
2269 font_stack: style.symbol_font_stack.into(),
2270 padding: style.symbol_padding.into(),
2271 allow_overlap: style.symbol_allow_overlap.into(),
2272 text_allow_overlap: None,
2273 icon_allow_overlap: None,
2274 text_optional: None,
2275 icon_optional: None,
2276 text_ignore_placement: None,
2277 icon_ignore_placement: None,
2278 radial_offset: None,
2279 variable_anchor_offsets: None,
2280 anchor: style.symbol_text_anchor,
2281 justify: style.symbol_text_justify.into(),
2282 transform: style.symbol_text_transform.into(),
2283 max_width: None,
2284 line_height: None,
2285 letter_spacing: None,
2286 icon_text_fit: style.symbol_icon_text_fit.into(),
2287 icon_text_fit_padding: style.symbol_icon_text_fit_padding,
2288 sort_key: None,
2289 placement: style.symbol_placement,
2290 spacing: style.symbol_spacing.into(),
2291 max_angle: style.symbol_max_angle.into(),
2292 keep_upright: style.symbol_keep_upright.into(),
2293 variable_anchors: style.symbol_anchors.clone(),
2294 writing_mode: style.symbol_writing_mode,
2295 offset: style.symbol_offset,
2296 }
2297 }
2298}
2299
2300#[allow(missing_docs)]
2302#[derive(Debug, Clone)]
2303pub struct ModelStyleLayer {
2304 pub meta: StyleLayerMeta,
2305 pub source: StyleSourceId,
2306}
2307
2308impl ModelStyleLayer {
2309 pub fn new(id: impl Into<String>, source: impl Into<String>) -> Self {
2311 Self {
2312 meta: StyleLayerMeta::new(id),
2313 source: source.into(),
2314 }
2315 }
2316}
2317
2318#[derive(Debug, Clone)]
2320#[allow(clippy::large_enum_variant)]
2321pub enum StyleLayer {
2322 Background(BackgroundStyleLayer),
2324 Hillshade(HillshadeStyleLayer),
2326 Raster(RasterStyleLayer),
2328 Vector(VectorStyleLayer),
2330 Fill(FillStyleLayer),
2332 Line(LineStyleLayer),
2334 Circle(CircleStyleLayer),
2336 Heatmap(HeatmapStyleLayer),
2338 FillExtrusion(FillExtrusionStyleLayer),
2340 Symbol(SymbolStyleLayer),
2342 Model(ModelStyleLayer),
2344}
2345
2346impl StyleLayer {
2347 pub fn id(&self) -> &str {
2349 self.meta().id.as_str()
2350 }
2351
2352 pub fn meta(&self) -> &StyleLayerMeta {
2354 match self {
2355 StyleLayer::Background(layer) => &layer.meta,
2356 StyleLayer::Hillshade(layer) => &layer.meta,
2357 StyleLayer::Raster(layer) => &layer.meta,
2358 StyleLayer::Vector(layer) => &layer.meta,
2359 StyleLayer::Fill(layer) => &layer.meta,
2360 StyleLayer::Line(layer) => &layer.meta,
2361 StyleLayer::Circle(layer) => &layer.meta,
2362 StyleLayer::Heatmap(layer) => &layer.meta,
2363 StyleLayer::FillExtrusion(layer) => &layer.meta,
2364 StyleLayer::Symbol(layer) => &layer.meta,
2365 StyleLayer::Model(layer) => &layer.meta,
2366 }
2367 }
2368
2369 pub fn meta_mut(&mut self) -> &mut StyleLayerMeta {
2371 match self {
2372 StyleLayer::Background(layer) => &mut layer.meta,
2373 StyleLayer::Hillshade(layer) => &mut layer.meta,
2374 StyleLayer::Raster(layer) => &mut layer.meta,
2375 StyleLayer::Vector(layer) => &mut layer.meta,
2376 StyleLayer::Fill(layer) => &mut layer.meta,
2377 StyleLayer::Line(layer) => &mut layer.meta,
2378 StyleLayer::Circle(layer) => &mut layer.meta,
2379 StyleLayer::Heatmap(layer) => &mut layer.meta,
2380 StyleLayer::FillExtrusion(layer) => &mut layer.meta,
2381 StyleLayer::Symbol(layer) => &mut layer.meta,
2382 StyleLayer::Model(layer) => &mut layer.meta,
2383 }
2384 }
2385
2386 pub fn to_runtime_layer_with_context(
2388 &self,
2389 sources: &HashMap<StyleSourceId, StyleSource>,
2390 ctx: StyleEvalContext,
2391 ) -> Result<Box<dyn Layer>, StyleError> {
2392 match self {
2393 StyleLayer::Background(layer) => Ok(Box::new(evaluate_background_layer(layer, ctx))),
2394 StyleLayer::Hillshade(layer) => Ok(Box::new(evaluate_hillshade_layer(layer, ctx))),
2395 StyleLayer::Raster(layer) => evaluate_raster_layer(layer, sources, ctx),
2396 StyleLayer::Vector(layer) => evaluate_vector_layer(layer, sources, ctx),
2397 StyleLayer::Fill(layer) => evaluate_fill_layer(layer, sources, ctx),
2398 StyleLayer::Line(layer) => evaluate_line_layer(layer, sources, ctx),
2399 StyleLayer::Circle(layer) => evaluate_circle_layer(layer, sources, ctx),
2400 StyleLayer::Heatmap(layer) => evaluate_heatmap_layer(layer, sources, ctx),
2401 StyleLayer::FillExtrusion(layer) => evaluate_fill_extrusion_layer(layer, sources, ctx),
2402 StyleLayer::Symbol(layer) => evaluate_symbol_layer(layer, sources, ctx),
2403 StyleLayer::Model(layer) => evaluate_model_layer(layer, sources, ctx),
2404 }
2405 }
2406
2407 pub fn apply_to_runtime_layer_with_context(
2409 &self,
2410 runtime: &mut dyn Layer,
2411 sources: &HashMap<StyleSourceId, StyleSource>,
2412 ctx: StyleEvalContext,
2413 ) -> Result<(), StyleError> {
2414 match self {
2415 StyleLayer::Background(layer) => apply_background_to_runtime(layer, runtime, ctx),
2416 StyleLayer::Hillshade(layer) => apply_hillshade_to_runtime(layer, runtime, ctx),
2417 StyleLayer::Raster(layer) => apply_raster_to_runtime(layer, runtime, sources, ctx),
2418 StyleLayer::Vector(layer) => apply_vector_to_runtime(layer, runtime, sources, ctx),
2419 StyleLayer::Fill(layer) => apply_fill_to_runtime(layer, runtime, sources, ctx),
2420 StyleLayer::Line(layer) => apply_line_to_runtime(layer, runtime, sources, ctx),
2421 StyleLayer::Circle(layer) => apply_circle_to_runtime(layer, runtime, sources, ctx),
2422 StyleLayer::Heatmap(layer) => apply_heatmap_to_runtime(layer, runtime, sources, ctx),
2423 StyleLayer::FillExtrusion(layer) => {
2424 apply_fill_extrusion_to_runtime(layer, runtime, sources, ctx)
2425 }
2426 StyleLayer::Symbol(layer) => apply_symbol_to_runtime(layer, runtime, sources, ctx),
2427 StyleLayer::Model(layer) => apply_model_to_runtime(layer, runtime, sources, ctx),
2428 }
2429 }
2430
2431 pub fn source_id(&self) -> Option<&str> {
2433 match self {
2434 StyleLayer::Background(_) | StyleLayer::Hillshade(_) => None,
2435 StyleLayer::Raster(layer) => Some(layer.source.as_str()),
2436 StyleLayer::Vector(layer) => Some(layer.source.as_str()),
2437 StyleLayer::Fill(layer) => Some(layer.source.as_str()),
2438 StyleLayer::Line(layer) => Some(layer.source.as_str()),
2439 StyleLayer::Circle(layer) => Some(layer.source.as_str()),
2440 StyleLayer::Heatmap(layer) => Some(layer.source.as_str()),
2441 StyleLayer::FillExtrusion(layer) => Some(layer.source.as_str()),
2442 StyleLayer::Symbol(layer) => Some(layer.source.as_str()),
2443 StyleLayer::Model(layer) => Some(layer.source.as_str()),
2444 }
2445 }
2446
2447 pub fn source_layer(&self) -> Option<&str> {
2450 match self {
2451 StyleLayer::Vector(layer) => layer.source_layer.as_deref(),
2452 StyleLayer::Fill(layer) => layer.source_layer.as_deref(),
2453 StyleLayer::Line(layer) => layer.source_layer.as_deref(),
2454 StyleLayer::Circle(layer) => layer.source_layer.as_deref(),
2455 StyleLayer::Heatmap(layer) => layer.source_layer.as_deref(),
2456 StyleLayer::FillExtrusion(layer) => layer.source_layer.as_deref(),
2457 StyleLayer::Symbol(layer) => layer.source_layer.as_deref(),
2458 _ => None,
2459 }
2460 }
2461
2462 pub fn uses_source(&self, source_id: &str) -> bool {
2464 self.source_id() == Some(source_id)
2465 }
2466
2467 pub fn has_feature_state_driven_paint(&self) -> bool {
2472 match self {
2473 StyleLayer::Fill(l) => {
2474 l.fill_color.is_feature_state_driven()
2475 || l.outline_color.is_feature_state_driven()
2476 || l.outline_width.is_feature_state_driven()
2477 }
2478 StyleLayer::Line(l) => {
2479 l.color.is_feature_state_driven() || l.width.is_feature_state_driven()
2480 }
2481 StyleLayer::Circle(l) => {
2482 l.color.is_feature_state_driven()
2483 || l.radius.is_feature_state_driven()
2484 || l.stroke_color.is_feature_state_driven()
2485 || l.stroke_width.is_feature_state_driven()
2486 }
2487 StyleLayer::Heatmap(l) => {
2488 l.color.is_feature_state_driven()
2489 || l.radius.is_feature_state_driven()
2490 || l.intensity.is_feature_state_driven()
2491 }
2492 StyleLayer::FillExtrusion(l) => {
2493 l.color.is_feature_state_driven()
2494 || l.base.is_feature_state_driven()
2495 || l.height.is_feature_state_driven()
2496 }
2497 StyleLayer::Symbol(l) => {
2498 l.color.is_feature_state_driven()
2499 || l.halo_color.is_feature_state_driven()
2500 || l.size.is_feature_state_driven()
2501 }
2502 StyleLayer::Vector(l) => {
2503 l.fill_color.is_feature_state_driven()
2504 || l.stroke_color.is_feature_state_driven()
2505 || l.stroke_width.is_feature_state_driven()
2506 }
2507 StyleLayer::Background(_)
2509 | StyleLayer::Hillshade(_)
2510 | StyleLayer::Raster(_)
2511 | StyleLayer::Model(_) => false,
2512 }
2513 }
2514
2515 pub fn resolve_style_with_feature_state(
2527 &self,
2528 ctx: &StyleEvalContextFull<'_>,
2529 ) -> Option<VectorStyle> {
2530 match self {
2531 StyleLayer::Fill(l) => Some(fill_style_with_state(l, ctx)),
2532 StyleLayer::Line(l) => Some(line_style_with_state(l, ctx)),
2533 StyleLayer::Circle(l) => Some(circle_style_with_state(l, ctx)),
2534 StyleLayer::Heatmap(l) => Some(heatmap_style_with_state(l, ctx)),
2535 StyleLayer::FillExtrusion(l) => Some(fill_extrusion_style_with_state(l, ctx)),
2536 StyleLayer::Symbol(l) => Some(symbol_style_with_state(l, ctx)),
2537 StyleLayer::Vector(l) => Some(vector_style_with_state(l, ctx)),
2538 StyleLayer::Background(_)
2540 | StyleLayer::Hillshade(_)
2541 | StyleLayer::Raster(_)
2542 | StyleLayer::Model(_) => None,
2543 }
2544 }
2545}
2546
2547impl StyleDocument {
2548 pub fn new() -> Self {
2550 Self::default()
2551 }
2552
2553 pub fn add_source(
2555 &mut self,
2556 id: impl Into<String>,
2557 source: StyleSource,
2558 ) -> Result<(), StyleError> {
2559 let id = id.into();
2560 if self.sources.contains_key(&id) {
2561 return Err(StyleError::DuplicateSourceId(id));
2562 }
2563 self.sources.insert(id, source);
2564 Ok(())
2565 }
2566
2567 pub fn set_source(&mut self, id: impl Into<String>, source: StyleSource) {
2569 self.sources.insert(id.into(), source);
2570 }
2571
2572 pub fn remove_source(&mut self, id: &str) -> Option<StyleSource> {
2574 if self.terrain_source.as_deref() == Some(id) {
2575 self.terrain_source = None;
2576 }
2577 self.sources.remove(id)
2578 }
2579
2580 pub fn source(&self, id: &str) -> Option<&StyleSource> {
2582 self.sources.get(id)
2583 }
2584
2585 pub fn sources(&self) -> impl Iterator<Item = (&str, &StyleSource)> {
2587 self.sources
2588 .iter()
2589 .map(|(id, source)| (id.as_str(), source))
2590 }
2591
2592 pub fn set_terrain_source(&mut self, source_id: Option<impl Into<String>>) {
2594 self.terrain_source = source_id.map(Into::into);
2595 }
2596
2597 pub fn terrain_source(&self) -> Option<&str> {
2599 self.terrain_source.as_deref()
2600 }
2601
2602 pub fn set_projection(&mut self, projection: StyleProjection) {
2604 self.projection = projection;
2605 }
2606
2607 pub fn projection(&self) -> StyleProjection {
2609 self.projection
2610 }
2611
2612 pub fn set_fog(&mut self, fog: Option<FogConfig>) {
2614 self.fog = fog;
2615 }
2616
2617 pub fn fog(&self) -> Option<&FogConfig> {
2619 self.fog.as_ref()
2620 }
2621
2622 pub fn set_lights(&mut self, lights: Option<LightConfig>) {
2624 self.lights = lights;
2625 }
2626
2627 pub fn lights(&self) -> Option<&LightConfig> {
2629 self.lights.as_ref()
2630 }
2631
2632 pub fn set_sky(&mut self, sky: Option<SkyConfig>) {
2634 self.sky = sky;
2635 }
2636
2637 pub fn sky(&self) -> Option<&SkyConfig> {
2639 self.sky.as_ref()
2640 }
2641
2642 pub fn set_transition(&mut self, spec: TransitionSpec) {
2644 self.transition = spec;
2645 }
2646
2647 pub fn transition(&self) -> TransitionSpec {
2649 self.transition
2650 }
2651
2652 pub fn add_layer(&mut self, layer: StyleLayer) -> Result<(), StyleError> {
2654 if self
2655 .layers
2656 .iter()
2657 .any(|existing| existing.id() == layer.id())
2658 {
2659 return Err(StyleError::DuplicateLayerId(layer.id().to_owned()));
2660 }
2661 self.layers.push(layer);
2662 Ok(())
2663 }
2664
2665 pub fn insert_layer_before(
2667 &mut self,
2668 before_id: &str,
2669 layer: StyleLayer,
2670 ) -> Result<(), StyleError> {
2671 if self
2672 .layers
2673 .iter()
2674 .any(|existing| existing.id() == layer.id())
2675 {
2676 return Err(StyleError::DuplicateLayerId(layer.id().to_owned()));
2677 }
2678 if let Some(index) = self.layer_index(before_id) {
2679 self.layers.insert(index, layer);
2680 } else {
2681 self.layers.push(layer);
2682 }
2683 Ok(())
2684 }
2685
2686 pub fn move_layer_before(&mut self, layer_id: &str, before_id: &str) -> bool {
2688 let Some(from) = self.layer_index(layer_id) else {
2689 return false;
2690 };
2691 let layer = self.layers.remove(from);
2692 let to = self.layer_index(before_id).unwrap_or(self.layers.len());
2693 self.layers.insert(to, layer);
2694 true
2695 }
2696
2697 pub fn remove_layer(&mut self, layer_id: &str) -> Option<StyleLayer> {
2699 self.layer_index(layer_id)
2700 .map(|index| self.layers.remove(index))
2701 }
2702
2703 pub fn layer(&self, layer_id: &str) -> Option<&StyleLayer> {
2705 self.layers.iter().find(|layer| layer.id() == layer_id)
2706 }
2707
2708 pub fn layer_mut(&mut self, layer_id: &str) -> Option<&mut StyleLayer> {
2710 self.layers.iter_mut().find(|layer| layer.id() == layer_id)
2711 }
2712
2713 pub fn layers(&self) -> &[StyleLayer] {
2715 &self.layers
2716 }
2717
2718 pub fn to_runtime_layers(&self) -> Result<Vec<Box<dyn Layer>>, StyleError> {
2720 self.to_runtime_layers_with_context(StyleEvalContext::default())
2721 }
2722
2723 pub fn to_runtime_layers_with_context(
2725 &self,
2726 ctx: StyleEvalContext,
2727 ) -> Result<Vec<Box<dyn Layer>>, StyleError> {
2728 self.layers
2729 .iter()
2730 .map(|layer| layer.to_runtime_layer_with_context(&self.sources, ctx))
2731 .collect()
2732 }
2733
2734 pub fn to_terrain_config(&self) -> Result<Option<(TerrainConfig, usize)>, StyleError> {
2736 let Some(source_id) = self.terrain_source.as_deref() else {
2737 return Ok(None);
2738 };
2739 let Some(source) = self.sources.get(source_id) else {
2740 return Err(StyleError::MissingSource(source_id.to_owned()));
2741 };
2742 match source {
2743 StyleSource::Terrain(terrain) => {
2744 Ok(Some((terrain.to_terrain_config(), terrain.cache_capacity)))
2745 }
2746 other => Err(StyleError::SourceKindMismatch {
2747 layer_id: "<terrain>".to_owned(),
2748 source_id: source_id.to_owned(),
2749 expected: "terrain",
2750 actual: other.kind_name(),
2751 }),
2752 }
2753 }
2754
2755 #[allow(dead_code)]
2756 pub(crate) fn apply_runtime_layers_with_context(
2757 &self,
2758 layers: &mut crate::layers::LayerStack,
2759 ctx: StyleEvalContext,
2760 ) -> Result<(), StyleError> {
2761 if layers.len() != self.layers.len() {
2762 return Ok(());
2763 }
2764 for (style_layer, runtime_layer) in self.layers.iter().zip(layers.iter_mut()) {
2765 style_layer.apply_to_runtime_layer_with_context(
2766 runtime_layer.as_mut(),
2767 &self.sources,
2768 ctx,
2769 )?;
2770 }
2771 Ok(())
2772 }
2773
2774 fn layer_index(&self, layer_id: &str) -> Option<usize> {
2775 self.layers.iter().position(|layer| layer.id() == layer_id)
2776 }
2777
2778 pub fn source_is_used(&self, source_id: &str) -> bool {
2781 self.terrain_source.as_deref() == Some(source_id)
2782 || self.layers.iter().any(|layer| layer.uses_source(source_id))
2783 }
2784
2785 pub fn layer_ids_using_source(&self, source_id: &str) -> Vec<&str> {
2788 self.layers
2789 .iter()
2790 .filter(|layer| layer.uses_source(source_id))
2791 .map(|layer| layer.id())
2792 .collect()
2793 }
2794}
2795
2796fn evaluate_background_layer(
2797 layer: &BackgroundStyleLayer,
2798 ctx: StyleEvalContext,
2799) -> BackgroundLayer {
2800 let mut runtime = BackgroundLayer::new(
2801 layer.meta.name.clone(),
2802 layer.color.evaluate_with_context(ctx),
2803 );
2804 apply_shared_meta(&mut runtime, &layer.meta, ctx);
2805 runtime
2806}
2807
2808fn evaluate_hillshade_layer(layer: &HillshadeStyleLayer, ctx: StyleEvalContext) -> HillshadeLayer {
2809 let mut runtime = HillshadeLayer::new(layer.meta.name.clone());
2810 apply_shared_meta(&mut runtime, &layer.meta, ctx);
2811 runtime.set_highlight_color(layer.highlight_color.evaluate_with_context(ctx));
2812 runtime.set_shadow_color(layer.shadow_color.evaluate_with_context(ctx));
2813 runtime.set_accent_color(layer.accent_color.evaluate_with_context(ctx));
2814 runtime.set_illumination_direction_deg(
2815 layer.illumination_direction_deg.evaluate_with_context(ctx),
2816 );
2817 runtime
2818 .set_illumination_altitude_deg(layer.illumination_altitude_deg.evaluate_with_context(ctx));
2819 runtime.set_exaggeration(layer.exaggeration.evaluate_with_context(ctx));
2820 runtime
2821}
2822
2823fn evaluate_raster_layer(
2824 layer: &RasterStyleLayer,
2825 sources: &HashMap<StyleSourceId, StyleSource>,
2826 ctx: StyleEvalContext,
2827) -> Result<Box<dyn Layer>, StyleError> {
2828 if let Some(result) =
2831 try_dynamic_overlay_from_source(&layer.meta.name, &layer.source, sources, ctx)
2832 {
2833 return result.map(|mut runtime| {
2834 apply_shared_meta(runtime.as_mut(), &layer.meta, ctx);
2835 runtime
2836 });
2837 }
2838
2839 let (factory, cache_capacity, selection) =
2840 require_raster_source(&layer.meta.id, &layer.source, sources)?;
2841 let mut runtime = TileLayer::new_with_selection_config(
2842 layer.meta.name.clone(),
2843 (factory)(),
2844 cache_capacity,
2845 selection.clone(),
2846 );
2847 apply_shared_meta(&mut runtime, &layer.meta, ctx);
2848 Ok(Box::new(runtime))
2849}
2850
2851fn evaluate_vector_runtime_layer(
2852 meta: &StyleLayerMeta,
2853 source_id: &str,
2854 source_layer: Option<&str>,
2855 layer_id: &str,
2856 style: VectorStyle,
2857 sources: &HashMap<StyleSourceId, StyleSource>,
2858 ctx: StyleEvalContext,
2859) -> Result<Box<dyn Layer>, StyleError> {
2860 let features = match sources.get(source_id) {
2861 Some(StyleSource::VectorTile(source)) if source.is_streamed() => {
2862 FeatureCollection::default()
2863 }
2864 _ => require_vector_source(layer_id, source_id, source_layer, sources, ctx.zoom as u8)?
2865 .into_owned(),
2866 };
2867 let mut runtime = VectorLayer::new(meta.name.clone(), features, style)
2868 .with_query_metadata(Some(layer_id.to_owned()), Some(source_id.to_owned()))
2869 .with_source_layer(source_layer.map(str::to_owned));
2870 apply_shared_meta(&mut runtime, meta, ctx);
2871 Ok(Box::new(runtime))
2872}
2873
2874fn evaluate_vector_layer(
2875 layer: &VectorStyleLayer,
2876 sources: &HashMap<StyleSourceId, StyleSource>,
2877 ctx: StyleEvalContext,
2878) -> Result<Box<dyn Layer>, StyleError> {
2879 evaluate_vector_runtime_layer(
2880 &layer.meta,
2881 &layer.source,
2882 layer.source_layer.as_deref(),
2883 &layer.meta.id,
2884 vector_style_from_vector_layer(layer, ctx),
2885 sources,
2886 ctx,
2887 )
2888}
2889
2890fn evaluate_fill_layer(
2891 layer: &FillStyleLayer,
2892 sources: &HashMap<StyleSourceId, StyleSource>,
2893 ctx: StyleEvalContext,
2894) -> Result<Box<dyn Layer>, StyleError> {
2895 evaluate_vector_runtime_layer(
2896 &layer.meta,
2897 &layer.source,
2898 layer.source_layer.as_deref(),
2899 &layer.meta.id,
2900 vector_style_from_fill_layer(layer, ctx),
2901 sources,
2902 ctx,
2903 )
2904}
2905
2906fn evaluate_line_layer(
2907 layer: &LineStyleLayer,
2908 sources: &HashMap<StyleSourceId, StyleSource>,
2909 ctx: StyleEvalContext,
2910) -> Result<Box<dyn Layer>, StyleError> {
2911 evaluate_vector_runtime_layer(
2912 &layer.meta,
2913 &layer.source,
2914 layer.source_layer.as_deref(),
2915 &layer.meta.id,
2916 vector_style_from_line_layer(layer, ctx),
2917 sources,
2918 ctx,
2919 )
2920}
2921
2922fn evaluate_circle_layer(
2923 layer: &CircleStyleLayer,
2924 sources: &HashMap<StyleSourceId, StyleSource>,
2925 ctx: StyleEvalContext,
2926) -> Result<Box<dyn Layer>, StyleError> {
2927 evaluate_vector_runtime_layer(
2928 &layer.meta,
2929 &layer.source,
2930 layer.source_layer.as_deref(),
2931 &layer.meta.id,
2932 vector_style_from_circle_layer(layer, ctx),
2933 sources,
2934 ctx,
2935 )
2936}
2937
2938fn evaluate_heatmap_layer(
2939 layer: &HeatmapStyleLayer,
2940 sources: &HashMap<StyleSourceId, StyleSource>,
2941 ctx: StyleEvalContext,
2942) -> Result<Box<dyn Layer>, StyleError> {
2943 evaluate_vector_runtime_layer(
2944 &layer.meta,
2945 &layer.source,
2946 layer.source_layer.as_deref(),
2947 &layer.meta.id,
2948 vector_style_from_heatmap_layer(layer, ctx),
2949 sources,
2950 ctx,
2951 )
2952}
2953
2954fn evaluate_fill_extrusion_layer(
2955 layer: &FillExtrusionStyleLayer,
2956 sources: &HashMap<StyleSourceId, StyleSource>,
2957 ctx: StyleEvalContext,
2958) -> Result<Box<dyn Layer>, StyleError> {
2959 evaluate_vector_runtime_layer(
2960 &layer.meta,
2961 &layer.source,
2962 layer.source_layer.as_deref(),
2963 &layer.meta.id,
2964 vector_style_from_fill_extrusion_layer(layer, ctx),
2965 sources,
2966 ctx,
2967 )
2968}
2969
2970fn evaluate_symbol_layer(
2971 layer: &SymbolStyleLayer,
2972 sources: &HashMap<StyleSourceId, StyleSource>,
2973 ctx: StyleEvalContext,
2974) -> Result<Box<dyn Layer>, StyleError> {
2975 evaluate_vector_runtime_layer(
2976 &layer.meta,
2977 &layer.source,
2978 layer.source_layer.as_deref(),
2979 &layer.meta.id,
2980 vector_style_from_symbol_layer(layer, ctx),
2981 sources,
2982 ctx,
2983 )
2984}
2985
2986fn evaluate_model_layer(
2987 layer: &ModelStyleLayer,
2988 sources: &HashMap<StyleSourceId, StyleSource>,
2989 ctx: StyleEvalContext,
2990) -> Result<Box<dyn Layer>, StyleError> {
2991 let model = require_model_source(&layer.meta.id, &layer.source, sources)?;
2992 let mut runtime = ModelLayer::new(layer.meta.name.clone())
2993 .with_query_metadata(Some(layer.meta.id.clone()), Some(layer.source.clone()));
2994 apply_shared_meta(&mut runtime, &layer.meta, ctx);
2995 runtime.instances.extend(model.instances.iter().cloned());
2996 Ok(Box::new(runtime))
2997}
2998
2999fn apply_background_to_runtime(
3000 layer: &BackgroundStyleLayer,
3001 runtime: &mut dyn Layer,
3002 ctx: StyleEvalContext,
3003) -> Result<(), StyleError> {
3004 let background = runtime
3005 .as_any_mut()
3006 .downcast_mut::<BackgroundLayer>()
3007 .expect("style/runtime layer mismatch: expected BackgroundLayer");
3008 apply_shared_meta(background, &layer.meta, ctx);
3009 background.set_color(layer.color.evaluate_with_context(ctx));
3010 Ok(())
3011}
3012
3013fn apply_hillshade_to_runtime(
3014 layer: &HillshadeStyleLayer,
3015 runtime: &mut dyn Layer,
3016 ctx: StyleEvalContext,
3017) -> Result<(), StyleError> {
3018 let hillshade = runtime
3019 .as_any_mut()
3020 .downcast_mut::<HillshadeLayer>()
3021 .expect("style/runtime layer mismatch: expected HillshadeLayer");
3022 apply_shared_meta(hillshade, &layer.meta, ctx);
3023 hillshade.set_highlight_color(layer.highlight_color.evaluate_with_context(ctx));
3024 hillshade.set_shadow_color(layer.shadow_color.evaluate_with_context(ctx));
3025 hillshade.set_accent_color(layer.accent_color.evaluate_with_context(ctx));
3026 hillshade.set_illumination_direction_deg(
3027 layer.illumination_direction_deg.evaluate_with_context(ctx),
3028 );
3029 hillshade
3030 .set_illumination_altitude_deg(layer.illumination_altitude_deg.evaluate_with_context(ctx));
3031 hillshade.set_exaggeration(layer.exaggeration.evaluate_with_context(ctx));
3032 Ok(())
3033}
3034
3035fn apply_raster_to_runtime(
3036 layer: &RasterStyleLayer,
3037 runtime: &mut dyn Layer,
3038 sources: &HashMap<StyleSourceId, StyleSource>,
3039 ctx: StyleEvalContext,
3040) -> Result<(), StyleError> {
3041 let _ = require_raster_source(&layer.meta.id, &layer.source, sources)?;
3042 let tile = runtime
3043 .as_any_mut()
3044 .downcast_mut::<TileLayer>()
3045 .expect("style/runtime layer mismatch: expected TileLayer");
3046 apply_shared_meta(tile, &layer.meta, ctx);
3047 Ok(())
3048}
3049
3050#[allow(clippy::too_many_arguments)]
3051fn apply_vector_style_to_runtime(
3052 runtime: &mut dyn Layer,
3053 meta: &StyleLayerMeta,
3054 source_id: &str,
3055 source_layer: Option<&str>,
3056 layer_id: &str,
3057 style: VectorStyle,
3058 sources: &HashMap<StyleSourceId, StyleSource>,
3059 ctx: StyleEvalContext,
3060) -> Result<(), StyleError> {
3061 let vector = require_vector_source(layer_id, source_id, source_layer, sources, ctx.zoom as u8)?;
3062 let layer = runtime
3063 .as_any_mut()
3064 .downcast_mut::<VectorLayer>()
3065 .expect("style/runtime layer mismatch: expected VectorLayer");
3066 apply_shared_meta(layer, meta, ctx);
3067 layer.style = style;
3068 layer.set_query_metadata(Some(layer_id.to_owned()), Some(source_id.to_owned()));
3069 if layer.features.len() != vector.len() {
3070 layer.features = vector.into_owned();
3071 }
3072 Ok(())
3073}
3074
3075fn apply_vector_to_runtime(
3076 layer: &VectorStyleLayer,
3077 runtime: &mut dyn Layer,
3078 sources: &HashMap<StyleSourceId, StyleSource>,
3079 ctx: StyleEvalContext,
3080) -> Result<(), StyleError> {
3081 apply_vector_style_to_runtime(
3082 runtime,
3083 &layer.meta,
3084 &layer.source,
3085 layer.source_layer.as_deref(),
3086 &layer.meta.id,
3087 vector_style_from_vector_layer(layer, ctx),
3088 sources,
3089 ctx,
3090 )
3091}
3092
3093fn apply_fill_to_runtime(
3094 layer: &FillStyleLayer,
3095 runtime: &mut dyn Layer,
3096 sources: &HashMap<StyleSourceId, StyleSource>,
3097 ctx: StyleEvalContext,
3098) -> Result<(), StyleError> {
3099 apply_vector_style_to_runtime(
3100 runtime,
3101 &layer.meta,
3102 &layer.source,
3103 layer.source_layer.as_deref(),
3104 &layer.meta.id,
3105 vector_style_from_fill_layer(layer, ctx),
3106 sources,
3107 ctx,
3108 )
3109}
3110
3111fn apply_line_to_runtime(
3112 layer: &LineStyleLayer,
3113 runtime: &mut dyn Layer,
3114 sources: &HashMap<StyleSourceId, StyleSource>,
3115 ctx: StyleEvalContext,
3116) -> Result<(), StyleError> {
3117 apply_vector_style_to_runtime(
3118 runtime,
3119 &layer.meta,
3120 &layer.source,
3121 layer.source_layer.as_deref(),
3122 &layer.meta.id,
3123 vector_style_from_line_layer(layer, ctx),
3124 sources,
3125 ctx,
3126 )
3127}
3128
3129fn apply_circle_to_runtime(
3130 layer: &CircleStyleLayer,
3131 runtime: &mut dyn Layer,
3132 sources: &HashMap<StyleSourceId, StyleSource>,
3133 ctx: StyleEvalContext,
3134) -> Result<(), StyleError> {
3135 apply_vector_style_to_runtime(
3136 runtime,
3137 &layer.meta,
3138 &layer.source,
3139 layer.source_layer.as_deref(),
3140 &layer.meta.id,
3141 vector_style_from_circle_layer(layer, ctx),
3142 sources,
3143 ctx,
3144 )
3145}
3146
3147fn apply_heatmap_to_runtime(
3148 layer: &HeatmapStyleLayer,
3149 runtime: &mut dyn Layer,
3150 sources: &HashMap<StyleSourceId, StyleSource>,
3151 ctx: StyleEvalContext,
3152) -> Result<(), StyleError> {
3153 apply_vector_style_to_runtime(
3154 runtime,
3155 &layer.meta,
3156 &layer.source,
3157 layer.source_layer.as_deref(),
3158 &layer.meta.id,
3159 vector_style_from_heatmap_layer(layer, ctx),
3160 sources,
3161 ctx,
3162 )
3163}
3164
3165fn apply_fill_extrusion_to_runtime(
3166 layer: &FillExtrusionStyleLayer,
3167 runtime: &mut dyn Layer,
3168 sources: &HashMap<StyleSourceId, StyleSource>,
3169 ctx: StyleEvalContext,
3170) -> Result<(), StyleError> {
3171 apply_vector_style_to_runtime(
3172 runtime,
3173 &layer.meta,
3174 &layer.source,
3175 layer.source_layer.as_deref(),
3176 &layer.meta.id,
3177 vector_style_from_fill_extrusion_layer(layer, ctx),
3178 sources,
3179 ctx,
3180 )
3181}
3182
3183fn apply_symbol_to_runtime(
3184 layer: &SymbolStyleLayer,
3185 runtime: &mut dyn Layer,
3186 sources: &HashMap<StyleSourceId, StyleSource>,
3187 ctx: StyleEvalContext,
3188) -> Result<(), StyleError> {
3189 apply_vector_style_to_runtime(
3190 runtime,
3191 &layer.meta,
3192 &layer.source,
3193 layer.source_layer.as_deref(),
3194 &layer.meta.id,
3195 vector_style_from_symbol_layer(layer, ctx),
3196 sources,
3197 ctx,
3198 )
3199}
3200
3201fn apply_model_to_runtime(
3202 layer: &ModelStyleLayer,
3203 runtime: &mut dyn Layer,
3204 sources: &HashMap<StyleSourceId, StyleSource>,
3205 ctx: StyleEvalContext,
3206) -> Result<(), StyleError> {
3207 let model = require_model_source(&layer.meta.id, &layer.source, sources)?;
3208 let runtime = runtime
3209 .as_any_mut()
3210 .downcast_mut::<ModelLayer>()
3211 .expect("style/runtime layer mismatch: expected ModelLayer");
3212 apply_shared_meta(runtime, &layer.meta, ctx);
3213 runtime.set_query_metadata(Some(layer.meta.id.clone()), Some(layer.source.clone()));
3214 runtime.instances.clear();
3215 runtime.instances.extend(model.instances.iter().cloned());
3216 Ok(())
3217}
3218
3219fn vector_style_from_vector_layer(layer: &VectorStyleLayer, ctx: StyleEvalContext) -> VectorStyle {
3220 VectorStyle {
3221 render_mode: VectorRenderMode::Generic,
3222 fill_color: layer.fill_color.evaluate_with_context(ctx),
3223 stroke_color: layer.stroke_color.evaluate_with_context(ctx),
3224 stroke_width: layer.stroke_width.evaluate_with_context(ctx),
3225 ..VectorStyle::default()
3226 }
3227}
3228
3229fn vector_style_from_fill_layer(layer: &FillStyleLayer, ctx: StyleEvalContext) -> VectorStyle {
3230 let mut style = VectorStyle::fill(
3231 layer.fill_color.evaluate_with_context(ctx),
3232 layer.outline_color.evaluate_with_context(ctx),
3233 layer.outline_width.evaluate_with_context(ctx),
3234 );
3235 style.fill_pattern = layer.fill_pattern.clone();
3236 style
3237}
3238
3239fn vector_style_from_line_layer(layer: &LineStyleLayer, ctx: StyleEvalContext) -> VectorStyle {
3240 let mut style = VectorStyle::line_styled(
3241 layer.color.evaluate_with_context(ctx),
3242 layer.width.evaluate_with_context(ctx),
3243 layer.line_cap,
3244 layer.line_join,
3245 layer.miter_limit,
3246 layer.dash_array.clone(),
3247 );
3248 if layer.width.is_data_driven() {
3250 style.width_expr = Some(layer.width.clone());
3251 }
3252 if layer.color.is_data_driven() {
3253 style.stroke_color_expr = Some(layer.color.clone());
3254 }
3255 style.eval_zoom = ctx.zoom;
3256 style.line_gradient = layer.line_gradient.clone();
3257 style.line_pattern = layer.line_pattern.clone();
3258 style
3259}
3260
3261fn vector_style_from_circle_layer(layer: &CircleStyleLayer, ctx: StyleEvalContext) -> VectorStyle {
3262 VectorStyle::circle(
3263 layer.color.evaluate_with_context(ctx),
3264 layer.radius.evaluate_with_context(ctx),
3265 layer.stroke_color.evaluate_with_context(ctx),
3266 layer.stroke_width.evaluate_with_context(ctx),
3267 )
3268}
3269
3270fn vector_style_from_heatmap_layer(
3271 layer: &HeatmapStyleLayer,
3272 ctx: StyleEvalContext,
3273) -> VectorStyle {
3274 VectorStyle::heatmap(
3275 layer.color.evaluate_with_context(ctx),
3276 layer.radius.evaluate_with_context(ctx),
3277 layer.intensity.evaluate_with_context(ctx),
3278 )
3279}
3280
3281fn vector_style_from_fill_extrusion_layer(
3282 layer: &FillExtrusionStyleLayer,
3283 ctx: StyleEvalContext,
3284) -> VectorStyle {
3285 VectorStyle::fill_extrusion(
3286 layer.color.evaluate_with_context(ctx),
3287 layer.base.evaluate_with_context(ctx),
3288 layer.height.evaluate_with_context(ctx),
3289 )
3290}
3291
3292fn vector_style_from_symbol_layer(layer: &SymbolStyleLayer, ctx: StyleEvalContext) -> VectorStyle {
3293 let mut style = VectorStyle::symbol(
3294 layer.color.evaluate_with_context(ctx),
3295 layer.halo_color.evaluate_with_context(ctx),
3296 layer.size.evaluate_with_context(ctx),
3297 );
3298 style.symbol_text_field = layer
3299 .text_field
3300 .as_ref()
3301 .map(|value| value.evaluate_with_context(ctx));
3302 style.symbol_icon_image = layer
3303 .icon_image
3304 .as_ref()
3305 .map(|value| value.evaluate_with_context(ctx));
3306 style.symbol_font_stack = layer.font_stack.evaluate_with_context(ctx);
3307 style.symbol_padding = layer.padding.evaluate_with_context(ctx);
3308 let shared_overlap = layer.allow_overlap.evaluate_with_context(ctx);
3309 style.symbol_allow_overlap = shared_overlap;
3310 style.symbol_text_allow_overlap = layer
3311 .text_allow_overlap
3312 .as_ref()
3313 .map(|value| value.evaluate_with_context(ctx))
3314 .unwrap_or(shared_overlap);
3315 style.symbol_icon_allow_overlap = layer
3316 .icon_allow_overlap
3317 .as_ref()
3318 .map(|value| value.evaluate_with_context(ctx))
3319 .unwrap_or(shared_overlap);
3320 style.symbol_text_optional = layer
3321 .text_optional
3322 .as_ref()
3323 .map(|value| value.evaluate_with_context(ctx))
3324 .unwrap_or(false);
3325 style.symbol_icon_optional = layer
3326 .icon_optional
3327 .as_ref()
3328 .map(|value| value.evaluate_with_context(ctx))
3329 .unwrap_or(false);
3330 style.symbol_text_ignore_placement = layer
3331 .text_ignore_placement
3332 .as_ref()
3333 .map(|value| value.evaluate_with_context(ctx))
3334 .unwrap_or(false);
3335 style.symbol_icon_ignore_placement = layer
3336 .icon_ignore_placement
3337 .as_ref()
3338 .map(|value| value.evaluate_with_context(ctx))
3339 .unwrap_or(false);
3340 style.symbol_text_radial_offset = layer
3341 .radial_offset
3342 .as_ref()
3343 .map(|value| value.evaluate_with_context(ctx));
3344 style.symbol_variable_anchor_offsets = layer.variable_anchor_offsets.clone();
3345 style.symbol_text_anchor = layer.anchor;
3346 style.symbol_text_justify =
3347 effective_symbol_text_justify(layer.justify.evaluate_with_context(ctx), layer.anchor);
3348 style.symbol_text_transform = layer.transform.evaluate_with_context(ctx);
3349 style.symbol_text_max_width = layer
3350 .max_width
3351 .as_ref()
3352 .map(|value| value.evaluate_with_context(ctx));
3353 style.symbol_text_line_height = layer
3354 .line_height
3355 .as_ref()
3356 .map(|value| value.evaluate_with_context(ctx));
3357 style.symbol_text_letter_spacing = layer
3358 .letter_spacing
3359 .as_ref()
3360 .map(|value| value.evaluate_with_context(ctx));
3361 style.symbol_icon_text_fit = layer.icon_text_fit.evaluate_with_context(ctx);
3362 style.symbol_icon_text_fit_padding = layer.icon_text_fit_padding;
3363 style.symbol_sort_key = layer
3364 .sort_key
3365 .as_ref()
3366 .map(|value| value.evaluate_with_context(ctx));
3367 style.symbol_placement = layer.placement;
3368 style.symbol_spacing = layer.spacing.evaluate_with_context(ctx);
3369 style.symbol_max_angle = layer.max_angle.evaluate_with_context(ctx);
3370 style.symbol_keep_upright = layer.keep_upright.evaluate_with_context(ctx);
3371 style.symbol_anchors = effective_symbol_anchor_order(
3372 layer.anchor,
3373 &layer.variable_anchors,
3374 layer.variable_anchor_offsets.as_deref(),
3375 );
3376 style.symbol_writing_mode = layer.writing_mode;
3377 style.symbol_offset = layer.offset;
3378 style
3379}
3380
3381fn effective_symbol_anchor_order(
3382 anchor: SymbolAnchor,
3383 variable_anchors: &[SymbolAnchor],
3384 variable_anchor_offsets: Option<&[(SymbolAnchor, [f32; 2])]>,
3385) -> Vec<SymbolAnchor> {
3386 if let Some(anchor_offsets) = variable_anchor_offsets {
3387 return anchor_offsets.iter().map(|(anchor, _)| *anchor).collect();
3388 }
3389 if variable_anchors.is_empty() {
3390 vec![anchor]
3391 } else {
3392 variable_anchors.to_vec()
3393 }
3394}
3395
3396pub fn fill_style_with_state(
3411 layer: &FillStyleLayer,
3412 ctx: &StyleEvalContextFull<'_>,
3413) -> VectorStyle {
3414 let mut style = VectorStyle::fill(
3415 layer.fill_color.evaluate_with_full_context(ctx),
3416 layer.outline_color.evaluate_with_full_context(ctx),
3417 layer.outline_width.evaluate_with_full_context(ctx),
3418 );
3419 style.fill_pattern = layer.fill_pattern.clone();
3420 style
3421}
3422
3423pub fn line_style_with_state(
3425 layer: &LineStyleLayer,
3426 ctx: &StyleEvalContextFull<'_>,
3427) -> VectorStyle {
3428 let mut style = VectorStyle::line_styled(
3429 layer.color.evaluate_with_full_context(ctx),
3430 layer.width.evaluate_with_full_context(ctx),
3431 layer.line_cap,
3432 layer.line_join,
3433 layer.miter_limit,
3434 layer.dash_array.clone(),
3435 );
3436 style.line_gradient = layer.line_gradient.clone();
3437 style.line_pattern = layer.line_pattern.clone();
3438 style
3439}
3440
3441pub fn circle_style_with_state(
3443 layer: &CircleStyleLayer,
3444 ctx: &StyleEvalContextFull<'_>,
3445) -> VectorStyle {
3446 VectorStyle::circle(
3447 layer.color.evaluate_with_full_context(ctx),
3448 layer.radius.evaluate_with_full_context(ctx),
3449 layer.stroke_color.evaluate_with_full_context(ctx),
3450 layer.stroke_width.evaluate_with_full_context(ctx),
3451 )
3452}
3453
3454pub fn heatmap_style_with_state(
3456 layer: &HeatmapStyleLayer,
3457 ctx: &StyleEvalContextFull<'_>,
3458) -> VectorStyle {
3459 VectorStyle::heatmap(
3460 layer.color.evaluate_with_full_context(ctx),
3461 layer.radius.evaluate_with_full_context(ctx),
3462 layer.intensity.evaluate_with_full_context(ctx),
3463 )
3464}
3465
3466pub fn fill_extrusion_style_with_state(
3468 layer: &FillExtrusionStyleLayer,
3469 ctx: &StyleEvalContextFull<'_>,
3470) -> VectorStyle {
3471 VectorStyle::fill_extrusion(
3472 layer.color.evaluate_with_full_context(ctx),
3473 layer.base.evaluate_with_full_context(ctx),
3474 layer.height.evaluate_with_full_context(ctx),
3475 )
3476}
3477
3478pub fn vector_style_with_state(
3480 layer: &VectorStyleLayer,
3481 ctx: &StyleEvalContextFull<'_>,
3482) -> VectorStyle {
3483 VectorStyle {
3484 render_mode: VectorRenderMode::Generic,
3485 fill_color: layer.fill_color.evaluate_with_full_context(ctx),
3486 stroke_color: layer.stroke_color.evaluate_with_full_context(ctx),
3487 stroke_width: layer.stroke_width.evaluate_with_full_context(ctx),
3488 ..VectorStyle::default()
3489 }
3490}
3491
3492pub fn symbol_style_with_state(
3498 layer: &SymbolStyleLayer,
3499 ctx: &StyleEvalContextFull<'_>,
3500) -> VectorStyle {
3501 let base = ctx.to_base();
3502 let mut style = VectorStyle::symbol(
3503 layer.color.evaluate_with_full_context(ctx),
3504 layer.halo_color.evaluate_with_full_context(ctx),
3505 layer.size.evaluate_with_full_context(ctx),
3506 );
3507 style.symbol_text_field = layer
3510 .text_field
3511 .as_ref()
3512 .map(|v| v.evaluate_with_full_context(ctx));
3513 style.symbol_icon_image = layer
3514 .icon_image
3515 .as_ref()
3516 .map(|v| v.evaluate_with_full_context(ctx));
3517 style.symbol_font_stack = layer.font_stack.evaluate_with_full_context(ctx);
3518 style.symbol_padding = layer.padding.evaluate_with_full_context(ctx);
3519 let shared_overlap = layer.allow_overlap.evaluate_with_full_context(ctx);
3520 style.symbol_allow_overlap = shared_overlap;
3521 style.symbol_text_allow_overlap = layer
3522 .text_allow_overlap
3523 .as_ref()
3524 .map(|v| v.evaluate_with_full_context(ctx))
3525 .unwrap_or(shared_overlap);
3526 style.symbol_icon_allow_overlap = layer
3527 .icon_allow_overlap
3528 .as_ref()
3529 .map(|v| v.evaluate_with_full_context(ctx))
3530 .unwrap_or(shared_overlap);
3531 style.symbol_text_optional = layer
3532 .text_optional
3533 .as_ref()
3534 .map(|v| v.evaluate_with_full_context(ctx))
3535 .unwrap_or(false);
3536 style.symbol_icon_optional = layer
3537 .icon_optional
3538 .as_ref()
3539 .map(|v| v.evaluate_with_full_context(ctx))
3540 .unwrap_or(false);
3541 style.symbol_text_ignore_placement = layer
3542 .text_ignore_placement
3543 .as_ref()
3544 .map(|v| v.evaluate_with_full_context(ctx))
3545 .unwrap_or(false);
3546 style.symbol_icon_ignore_placement = layer
3547 .icon_ignore_placement
3548 .as_ref()
3549 .map(|v| v.evaluate_with_full_context(ctx))
3550 .unwrap_or(false);
3551 style.symbol_text_radial_offset = layer
3552 .radial_offset
3553 .as_ref()
3554 .map(|v| v.evaluate_with_full_context(ctx));
3555 style.symbol_variable_anchor_offsets = layer.variable_anchor_offsets.clone();
3556 style.symbol_text_anchor = layer.anchor;
3557 style.symbol_text_justify =
3558 effective_symbol_text_justify(layer.justify.evaluate_with_context(base), layer.anchor);
3559 style.symbol_text_transform = layer.transform.evaluate_with_full_context(ctx);
3560 style.symbol_text_max_width = layer
3561 .max_width
3562 .as_ref()
3563 .map(|v| v.evaluate_with_full_context(ctx));
3564 style.symbol_text_line_height = layer
3565 .line_height
3566 .as_ref()
3567 .map(|v| v.evaluate_with_full_context(ctx));
3568 style.symbol_text_letter_spacing = layer
3569 .letter_spacing
3570 .as_ref()
3571 .map(|v| v.evaluate_with_full_context(ctx));
3572 style.symbol_icon_text_fit = layer.icon_text_fit.evaluate_with_full_context(ctx);
3573 style.symbol_icon_text_fit_padding = layer.icon_text_fit_padding;
3574 style.symbol_sort_key = layer
3575 .sort_key
3576 .as_ref()
3577 .map(|v| v.evaluate_with_full_context(ctx));
3578 style.symbol_placement = layer.placement;
3579 style.symbol_spacing = layer.spacing.evaluate_with_full_context(ctx);
3580 style.symbol_max_angle = layer.max_angle.evaluate_with_full_context(ctx);
3581 style.symbol_keep_upright = layer.keep_upright.evaluate_with_full_context(ctx);
3582 style.symbol_anchors = effective_symbol_anchor_order(
3583 layer.anchor,
3584 &layer.variable_anchors,
3585 layer.variable_anchor_offsets.as_deref(),
3586 );
3587 style.symbol_writing_mode = layer.writing_mode;
3588 style.symbol_offset = layer.offset;
3589 style
3590}
3591
3592fn effective_symbol_text_justify(
3597 justify: SymbolTextJustify,
3598 anchor: SymbolAnchor,
3599) -> SymbolTextJustify {
3600 match justify {
3601 SymbolTextJustify::Auto => anchor_justification(anchor),
3602 explicit => explicit,
3603 }
3604}
3605
3606fn anchor_justification(anchor: SymbolAnchor) -> SymbolTextJustify {
3607 match anchor {
3608 SymbolAnchor::Left | SymbolAnchor::TopLeft | SymbolAnchor::BottomLeft => {
3609 SymbolTextJustify::Left
3610 }
3611 SymbolAnchor::Right | SymbolAnchor::TopRight | SymbolAnchor::BottomRight => {
3612 SymbolTextJustify::Right
3613 }
3614 _ => SymbolTextJustify::Center,
3615 }
3616}
3617
3618#[derive(Debug, Default)]
3620pub struct MapStyle {
3621 document: StyleDocument,
3622}
3623
3624impl MapStyle {
3625 pub fn new() -> Self {
3627 Self::default()
3628 }
3629
3630 pub fn from_document(document: StyleDocument) -> Self {
3632 Self { document }
3633 }
3634
3635 pub fn document(&self) -> &StyleDocument {
3637 &self.document
3638 }
3639
3640 pub fn document_mut(&mut self) -> &mut StyleDocument {
3642 &mut self.document
3643 }
3644
3645 pub fn into_document(self) -> StyleDocument {
3647 self.document
3648 }
3649}
3650
3651fn apply_shared_meta(layer: &mut dyn Layer, meta: &StyleLayerMeta, ctx: StyleEvalContext) {
3652 layer.set_visible(meta.visible_in_context(ctx));
3653 layer.set_opacity(meta.opacity.evaluate_with_context(ctx));
3654}
3655
3656fn require_raster_source<'a>(
3657 layer_id: &str,
3658 source_id: &str,
3659 sources: &'a HashMap<StyleSourceId, StyleSource>,
3660) -> Result<(&'a RasterSourceFactory, usize, &'a TileSelectionConfig), StyleError> {
3661 let Some(source) = sources.get(source_id) else {
3662 return Err(StyleError::MissingSource(source_id.to_owned()));
3663 };
3664 match source {
3665 StyleSource::Raster(raster) => {
3666 Ok((&raster.factory, raster.cache_capacity, &raster.selection))
3667 }
3668 StyleSource::Image(image) => Ok((&image.factory, image.cache_capacity, &image.selection)),
3669 other => Err(StyleError::SourceKindMismatch {
3670 layer_id: layer_id.to_owned(),
3671 source_id: source_id.to_owned(),
3672 expected: "raster|image",
3673 actual: other.kind_name(),
3674 }),
3675 }
3676}
3677
3678fn try_dynamic_overlay_from_source(
3684 layer_name: &str,
3685 source_id: &str,
3686 sources: &HashMap<StyleSourceId, StyleSource>,
3687 ctx: StyleEvalContext,
3688) -> Option<Result<Box<dyn Layer>, StyleError>> {
3689 let source = sources.get(source_id)?;
3690 match source {
3691 StyleSource::Video(video) => {
3692 let provider = (video.factory)();
3693 let mut layer =
3694 DynamicImageOverlayLayer::new(layer_name.to_owned(), video.coordinates, provider);
3695 layer.set_opacity(ctx.zoom.fract()); Some(Ok(Box::new(layer)))
3697 }
3698 StyleSource::Canvas(canvas) => {
3699 let provider = (canvas.factory)();
3700 let mut layer =
3701 DynamicImageOverlayLayer::new(layer_name.to_owned(), canvas.coordinates, provider);
3702 layer.set_opacity(ctx.zoom.fract());
3703 Some(Ok(Box::new(layer)))
3704 }
3705 _ => None,
3706 }
3707}
3708
3709fn require_vector_source<'a>(
3710 layer_id: &str,
3711 source_id: &str,
3712 source_layer: Option<&str>,
3713 sources: &'a HashMap<StyleSourceId, StyleSource>,
3714 zoom: u8,
3715) -> Result<Cow<'a, FeatureCollection>, StyleError> {
3716 let Some(source) = sources.get(source_id) else {
3717 return Err(StyleError::MissingSource(source_id.to_owned()));
3718 };
3719 match source {
3720 StyleSource::GeoJson(source) => Ok(source.features_at_zoom(zoom)),
3721 StyleSource::VectorTile(source) => {
3722 if let Some(source_layer) = source_layer {
3723 source
3724 .source_layer(source_layer)
3725 .map(Cow::Borrowed)
3726 .ok_or_else(|| StyleError::MissingSourceLayer {
3727 layer_id: layer_id.to_owned(),
3728 source_id: source_id.to_owned(),
3729 source_layer: source_layer.to_owned(),
3730 })
3731 } else {
3732 Ok(Cow::Borrowed(&source.data))
3733 }
3734 }
3735 other => Err(StyleError::SourceKindMismatch {
3736 layer_id: layer_id.to_owned(),
3737 source_id: source_id.to_owned(),
3738 expected: "geojson|vector",
3739 actual: other.kind_name(),
3740 }),
3741 }
3742}
3743
3744fn require_model_source<'a>(
3745 layer_id: &str,
3746 source_id: &str,
3747 sources: &'a HashMap<StyleSourceId, StyleSource>,
3748) -> Result<&'a ModelSource, StyleError> {
3749 let Some(source) = sources.get(source_id) else {
3750 return Err(StyleError::MissingSource(source_id.to_owned()));
3751 };
3752 match source {
3753 StyleSource::Model(model) => Ok(model),
3754 other => Err(StyleError::SourceKindMismatch {
3755 layer_id: layer_id.to_owned(),
3756 source_id: source_id.to_owned(),
3757 expected: "model",
3758 actual: other.kind_name(),
3759 }),
3760 }
3761}
3762
3763#[cfg(test)]
3764mod tests {
3765 use super::*;
3766 use crate::geometry::{Feature, FeatureCollection, Geometry, Point};
3767 use crate::tile_source::{TileError, TileResponse, TileSource};
3768 use std::collections::HashMap;
3769
3770 struct EmptyTileSource;
3771
3772 impl TileSource for EmptyTileSource {
3773 fn request(&self, _id: rustial_math::TileId) {}
3774
3775 fn poll(&self) -> Vec<(rustial_math::TileId, Result<TileResponse, TileError>)> {
3776 Vec::new()
3777 }
3778 }
3779
3780 fn feature_at(lat: f64, lon: f64) -> Feature {
3781 Feature {
3782 geometry: Geometry::Point(Point {
3783 coord: GeoCoord::from_lat_lon(lat, lon),
3784 }),
3785 properties: HashMap::new(),
3786 }
3787 }
3788
3789 fn collection_with_point(lat: f64, lon: f64) -> FeatureCollection {
3790 FeatureCollection {
3791 features: vec![feature_at(lat, lon)],
3792 }
3793 }
3794
3795 #[test]
3796 fn vector_tile_source_can_be_partitioned_by_source_layer() {
3797 let mut source_layers = HashMap::new();
3798 source_layers.insert("roads".to_string(), collection_with_point(1.0, 2.0));
3799 source_layers.insert("water".to_string(), collection_with_point(3.0, 4.0));
3800
3801 let source = VectorTileSource::from_source_layers(source_layers);
3802 assert!(source.has_source_layers());
3803 assert_eq!(source.source_layer("roads").map(|fc| fc.len()), Some(1));
3804 assert_eq!(source.source_layer("water").map(|fc| fc.len()), Some(1));
3805 assert_eq!(source.data.len(), 2);
3806 }
3807
3808 #[test]
3809 fn vector_style_layer_resolves_requested_source_layer() {
3810 let mut document = StyleDocument::new();
3811 let mut source_layers = HashMap::new();
3812 source_layers.insert("roads".to_string(), collection_with_point(10.0, 20.0));
3813 source_layers.insert("water".to_string(), collection_with_point(30.0, 40.0));
3814 document
3815 .add_source(
3816 "vector",
3817 StyleSource::VectorTile(VectorTileSource::from_source_layers(source_layers)),
3818 )
3819 .expect("source added");
3820
3821 let mut layer = LineStyleLayer::new("roads-line", "vector");
3822 layer.source_layer = Some("roads".to_string());
3823 document
3824 .add_layer(StyleLayer::Line(layer))
3825 .expect("layer added");
3826
3827 let runtime = document.to_runtime_layers().expect("runtime layers");
3828 let vector = runtime[0]
3829 .as_any()
3830 .downcast_ref::<VectorLayer>()
3831 .expect("vector runtime layer");
3832 assert_eq!(vector.features.len(), 1);
3833 match &vector.features.features[0].geometry {
3834 Geometry::Point(point) => {
3835 assert!((point.coord.lat - 10.0).abs() < 1e-9);
3836 assert!((point.coord.lon - 20.0).abs() < 1e-9);
3837 }
3838 other => panic!("expected point geometry, got {other:?}"),
3839 }
3840 }
3841
3842 #[test]
3843 fn missing_source_layer_returns_style_error() {
3844 let mut document = StyleDocument::new();
3845 document
3846 .add_source(
3847 "vector",
3848 StyleSource::VectorTile(VectorTileSource::new(collection_with_point(0.0, 0.0))),
3849 )
3850 .expect("source added");
3851
3852 let mut layer = FillStyleLayer::new("water-fill", "vector");
3853 layer.source_layer = Some("water".to_string());
3854 document
3855 .add_layer(StyleLayer::Fill(layer))
3856 .expect("layer added");
3857
3858 let err = document
3859 .to_runtime_layers()
3860 .expect_err("missing source-layer should fail");
3861 assert!(matches!(err, StyleError::MissingSourceLayer { .. }));
3862 }
3863
3864 #[test]
3865 fn streamed_vector_source_allows_runtime_layer_creation_without_resolved_features() {
3866 let mut document = StyleDocument::new();
3867 document
3868 .add_source(
3869 "vector",
3870 StyleSource::VectorTile(
3871 VectorTileSource::streamed(|| Box::new(EmptyTileSource)).with_cache_capacity(8),
3872 ),
3873 )
3874 .expect("source added");
3875
3876 let mut layer = CircleStyleLayer::new("labels", "vector");
3877 layer.source_layer = Some("poi".to_string());
3878 document
3879 .add_layer(StyleLayer::Circle(layer))
3880 .expect("layer added");
3881
3882 let runtime = document.to_runtime_layers().expect("runtime layers");
3883 let vector = runtime[0]
3884 .as_any()
3885 .downcast_ref::<VectorLayer>()
3886 .expect("vector runtime layer");
3887 assert!(vector.features.is_empty());
3888 assert_eq!(vector.query_source_layer.as_deref(), Some("poi"));
3889 }
3890
3891 #[test]
3892 fn style_document_reports_source_usage() {
3893 let mut document = StyleDocument::new();
3894 document
3895 .add_source(
3896 "places",
3897 StyleSource::GeoJson(GeoJsonSource::new(collection_with_point(0.0, 0.0))),
3898 )
3899 .expect("source added");
3900 document
3901 .add_source(
3902 "labels",
3903 StyleSource::VectorTile(VectorTileSource::new(collection_with_point(1.0, 1.0))),
3904 )
3905 .expect("source added");
3906 document.set_terrain_source(Some("labels"));
3907
3908 document
3909 .add_layer(StyleLayer::Fill(FillStyleLayer::new("fill", "places")))
3910 .expect("fill layer added");
3911 document
3912 .add_layer(StyleLayer::Line(LineStyleLayer::new("line", "labels")))
3913 .expect("line layer added");
3914
3915 assert!(document.source_is_used("places"));
3916 assert!(document.source_is_used("labels"));
3917 assert!(!document.source_is_used("missing"));
3918
3919 let layer_ids = document.layer_ids_using_source("labels");
3920 assert_eq!(layer_ids, vec!["line"]);
3921 }
3922
3923 #[test]
3928 fn feature_state_value_returns_fallback_with_zoom_only_context() {
3929 let value = StyleValue::<f32>::feature_state_key("opacity", 0.5);
3932 let result = value.evaluate_with_context(StyleEvalContext::new(10.0));
3933 assert!((result - 0.5).abs() < f32::EPSILON);
3934 }
3935
3936 #[test]
3937 fn feature_state_value_resolves_with_full_context() {
3938 let mut state = HashMap::new();
3941 state.insert("opacity".to_string(), PropertyValue::Number(0.8));
3942
3943 let value = StyleValue::<f32>::feature_state_key("opacity", 0.5);
3944 let ctx = StyleEvalContext::new(10.0).with_feature_state(&state);
3945 let result = value.evaluate_with_full_context(&ctx);
3946 assert!((result - 0.8).abs() < f32::EPSILON);
3947 }
3948
3949 #[test]
3950 fn feature_state_value_falls_back_when_key_absent() {
3951 let state: FeatureState = HashMap::new();
3954
3955 let value = StyleValue::<f32>::feature_state_key("opacity", 0.5);
3956 let ctx = StyleEvalContext::new(10.0).with_feature_state(&state);
3957 let result = value.evaluate_with_full_context(&ctx);
3958 assert!((result - 0.5).abs() < f32::EPSILON);
3959 }
3960
3961 #[test]
3962 fn feature_state_bool_resolves_hover_flag() {
3963 let mut state = HashMap::new();
3966 state.insert("hover".to_string(), PropertyValue::Bool(true));
3967
3968 let value = StyleValue::<bool>::feature_state_key("hover", false);
3969 let ctx = StyleEvalContext::new(14.0).with_feature_state(&state);
3970 assert!(value.evaluate_with_full_context(&ctx));
3971 }
3972
3973 #[test]
3974 fn feature_state_color_array_always_returns_fallback() {
3975 let mut state = HashMap::new();
3978 state.insert("color".to_string(), PropertyValue::Number(1.0));
3979
3980 let fallback = [0.1, 0.2, 0.3, 1.0];
3981 let value = StyleValue::<[f32; 4]>::feature_state_key("color", fallback);
3982 let ctx = StyleEvalContext::new(14.0).with_feature_state(&state);
3983 assert_eq!(value.evaluate_with_full_context(&ctx), fallback);
3984 }
3985
3986 #[test]
3987 fn is_feature_state_driven_flag() {
3988 let constant: StyleValue<f32> = StyleValue::Constant(1.0);
3989 assert!(!constant.is_feature_state_driven());
3990
3991 let driven: StyleValue<f32> = StyleValue::feature_state_key("opacity", 1.0);
3992 assert!(driven.is_feature_state_driven());
3993 }
3994
3995 #[test]
3996 fn constant_and_zoom_stops_unchanged_with_full_context() {
3997 let state: FeatureState = HashMap::new();
4000 let ctx = StyleEvalContext::new(5.0).with_feature_state(&state);
4001
4002 let constant = StyleValue::Constant(42.0_f32);
4003 assert!((constant.evaluate_with_full_context(&ctx) - 42.0).abs() < f32::EPSILON);
4004
4005 let stops = StyleValue::ZoomStops(vec![(0.0, 0.0_f32), (10.0, 100.0)]);
4006 let result = stops.evaluate_with_full_context(&ctx);
4007 assert!((result - 50.0).abs() < f32::EPSILON);
4008 }
4009
4010 #[test]
4011 fn full_context_helpers_return_expected_values() {
4012 let mut state = HashMap::new();
4013 state.insert("hover".to_string(), PropertyValue::Bool(true));
4014 state.insert("width".to_string(), PropertyValue::Number(3.5));
4015
4016 let ctx = StyleEvalContextFull::new(14.0, &state);
4017 assert!(ctx.feature_state_bool("hover"));
4018 assert!(!ctx.feature_state_bool("missing"));
4019 assert!((ctx.feature_state_f64("width", 1.0) - 3.5).abs() < f64::EPSILON);
4020 assert!((ctx.feature_state_f64("missing", 1.0) - 1.0).abs() < f64::EPSILON);
4021 }
4022
4023 #[test]
4028 fn fill_layer_resolves_with_feature_state() {
4029 let mut layer = FillStyleLayer::new("buildings", "source");
4033 layer.outline_width = StyleValue::feature_state_key("width", 1.0);
4035
4036 let empty_state: FeatureState = HashMap::new();
4038 let ctx = StyleEvalContext::new(14.0).with_feature_state(&empty_state);
4039 let style = fill_style_with_state(&layer, &ctx);
4040 assert!((style.stroke_width - 1.0).abs() < f32::EPSILON);
4041
4042 let mut hover_state = HashMap::new();
4044 hover_state.insert("width".to_string(), PropertyValue::Number(4.0));
4045 let ctx = StyleEvalContext::new(14.0).with_feature_state(&hover_state);
4046 let style = fill_style_with_state(&layer, &ctx);
4047 assert!((style.stroke_width - 4.0).abs() < f32::EPSILON);
4048 }
4049
4050 #[test]
4051 fn line_layer_resolves_with_feature_state() {
4052 let mut layer = LineStyleLayer::new("roads", "source");
4053 layer.width = StyleValue::feature_state_key("highlight_width", 2.0);
4054
4055 let mut state = HashMap::new();
4056 state.insert("highlight_width".to_string(), PropertyValue::Number(6.0));
4057 let ctx = StyleEvalContext::new(10.0).with_feature_state(&state);
4058 let style = line_style_with_state(&layer, &ctx);
4059 assert!((style.stroke_width - 6.0).abs() < f32::EPSILON);
4060 }
4061
4062 #[test]
4063 fn circle_layer_resolves_with_feature_state() {
4064 let mut layer = CircleStyleLayer::new("points", "source");
4065 layer.radius = StyleValue::feature_state_key("size", 5.0);
4066
4067 let mut state = HashMap::new();
4068 state.insert("size".to_string(), PropertyValue::Number(12.0));
4069 let ctx = StyleEvalContext::new(10.0).with_feature_state(&state);
4070 let style = circle_style_with_state(&layer, &ctx);
4071 assert!((style.point_radius - 12.0).abs() < f32::EPSILON);
4072 }
4073
4074 #[test]
4075 fn has_feature_state_driven_paint_detects_driven_fields() {
4076 let mut fill = FillStyleLayer::new("buildings", "source");
4077 let fill_layer = StyleLayer::Fill(fill.clone());
4078 assert!(!fill_layer.has_feature_state_driven_paint());
4079
4080 fill.outline_width = StyleValue::feature_state_key("width", 1.0);
4082 let fill_layer = StyleLayer::Fill(fill);
4083 assert!(fill_layer.has_feature_state_driven_paint());
4084 }
4085
4086 #[test]
4087 fn has_feature_state_driven_paint_false_for_non_vector_layers() {
4088 let bg = BackgroundStyleLayer::new("bg", [0.0, 0.0, 0.0, 1.0]);
4089 assert!(!StyleLayer::Background(bg).has_feature_state_driven_paint());
4090 }
4091
4092 #[test]
4093 fn resolve_style_with_feature_state_returns_none_for_background() {
4094 let bg = BackgroundStyleLayer::new("bg", [0.0, 0.0, 0.0, 1.0]);
4095 let state: FeatureState = HashMap::new();
4096 let ctx = StyleEvalContext::new(10.0).with_feature_state(&state);
4097 assert!(StyleLayer::Background(bg)
4098 .resolve_style_with_feature_state(&ctx)
4099 .is_none());
4100 }
4101
4102 #[test]
4103 fn resolve_style_with_feature_state_dispatches_fill() {
4104 let mut fill = FillStyleLayer::new("buildings", "source");
4105 fill.outline_width = StyleValue::feature_state_key("width", 1.0);
4106
4107 let mut state = HashMap::new();
4108 state.insert("width".to_string(), PropertyValue::Number(5.0));
4109 let ctx = StyleEvalContext::new(14.0).with_feature_state(&state);
4110
4111 let style = StyleLayer::Fill(fill)
4112 .resolve_style_with_feature_state(&ctx)
4113 .expect("fill layer should produce VectorStyle");
4114 assert!((style.stroke_width - 5.0).abs() < f32::EPSILON);
4115 }
4116
4117 #[test]
4118 fn non_driven_fields_unchanged_through_full_context() {
4119 let layer = FillStyleLayer::new("buildings", "source");
4122 let state: FeatureState = HashMap::new();
4123 let full_ctx = StyleEvalContext::new(14.0).with_feature_state(&state);
4124 let zoom_ctx = StyleEvalContext::new(14.0);
4125
4126 let via_full = fill_style_with_state(&layer, &full_ctx);
4127 let via_zoom = vector_style_from_fill_layer(&layer, zoom_ctx);
4128 assert_eq!(via_full.fill_color, via_zoom.fill_color);
4129 assert_eq!(via_full.stroke_color, via_zoom.stroke_color);
4130 assert!((via_full.stroke_width - via_zoom.stroke_width).abs() < f32::EPSILON);
4131 }
4132
4133 #[test]
4138 fn geojson_source_with_clustering_returns_clustered_features_at_low_zoom() {
4139 let features = FeatureCollection {
4141 features: (0..10)
4142 .map(|i| feature_at(48.858 + i as f64 * 0.0001, 2.294))
4143 .collect(),
4144 };
4145 let source = GeoJsonSource::new(features).with_clustering(ClusterOptions {
4146 radius: 80.0,
4147 max_zoom: 16,
4148 min_points: 2,
4149 ..Default::default()
4150 });
4151 assert!(source.is_clustered());
4152
4153 let clustered = source.features_at_zoom(2);
4155 assert!(
4156 clustered.len() < 10,
4157 "Expected fewer features at zoom 2 (got {})",
4158 clustered.len(),
4159 );
4160
4161 let unclustered = source.features_at_zoom(20);
4163 assert_eq!(unclustered.len(), 10);
4164 }
4165
4166 #[test]
4167 fn geojson_source_without_clustering_returns_raw_data() {
4168 let source = GeoJsonSource::new(FeatureCollection {
4169 features: vec![feature_at(51.5, -0.12), feature_at(51.51, -0.13)],
4170 });
4171 assert!(!source.is_clustered());
4172 let result = source.features_at_zoom(5);
4173 assert_eq!(result.len(), 2);
4174 }
4175
4176 #[test]
4177 fn clustered_geojson_circle_layer_resolves_to_runtime_layer() {
4178 let features = FeatureCollection {
4179 features: (0..20)
4180 .map(|i| feature_at(48.858 + i as f64 * 0.0001, 2.294))
4181 .collect(),
4182 };
4183 let source = GeoJsonSource::new(features).with_clustering(Default::default());
4184
4185 let mut doc = StyleDocument::new();
4186 doc.add_source("points", StyleSource::GeoJson(source))
4187 .expect("source added");
4188 doc.add_layer(StyleLayer::Circle(CircleStyleLayer::new("dots", "points")))
4189 .expect("layer added");
4190
4191 let ctx = StyleEvalContext::new(3.0);
4193 let layers = doc.to_runtime_layers_with_context(ctx).expect("layers ok");
4194 assert_eq!(layers.len(), 1, "expected 1 circle layer");
4195 }
4196
4197 #[test]
4198 fn video_source_produces_dynamic_image_overlay_layer() {
4199 use crate::layers::{FrameData, FrameProvider};
4200
4201 struct TestProvider;
4202 impl FrameProvider for TestProvider {
4203 fn next_frame(&mut self) -> Option<FrameData> {
4204 Some(FrameData {
4205 width: 4,
4206 height: 4,
4207 data: vec![255; 64],
4208 })
4209 }
4210 }
4211
4212 let corners = [
4213 GeoCoord::from_lat_lon(40.0, -74.0),
4214 GeoCoord::from_lat_lon(40.0, -73.0),
4215 GeoCoord::from_lat_lon(39.0, -73.0),
4216 GeoCoord::from_lat_lon(39.0, -74.0),
4217 ];
4218 let source = VideoSource::new(corners, || Box::new(TestProvider));
4219
4220 let mut doc = StyleDocument::new();
4221 doc.add_source("video", StyleSource::Video(source))
4222 .expect("source added");
4223 doc.add_layer(StyleLayer::Raster(RasterStyleLayer::new(
4224 "video-layer",
4225 "video",
4226 )))
4227 .expect("layer added");
4228
4229 let layers = doc.to_runtime_layers().expect("runtime layers");
4230 assert_eq!(layers.len(), 1);
4231 assert!(
4232 layers[0]
4233 .as_any()
4234 .downcast_ref::<DynamicImageOverlayLayer>()
4235 .is_some(),
4236 "video source should produce a DynamicImageOverlayLayer"
4237 );
4238 }
4239
4240 #[test]
4241 fn canvas_source_produces_dynamic_image_overlay_layer() {
4242 use crate::layers::{FrameData, FrameProvider};
4243
4244 struct StaticCanvas;
4245 impl FrameProvider for StaticCanvas {
4246 fn next_frame(&mut self) -> Option<FrameData> {
4247 Some(FrameData {
4248 width: 8,
4249 height: 8,
4250 data: vec![128; 256],
4251 })
4252 }
4253 fn is_animating(&self) -> bool {
4254 false
4255 }
4256 }
4257
4258 let corners = [
4259 GeoCoord::from_lat_lon(51.0, -1.0),
4260 GeoCoord::from_lat_lon(51.0, 0.0),
4261 GeoCoord::from_lat_lon(50.0, 0.0),
4262 GeoCoord::from_lat_lon(50.0, -1.0),
4263 ];
4264 let source = CanvasSource::new(corners, || Box::new(StaticCanvas)).with_animate(false);
4265
4266 let mut doc = StyleDocument::new();
4267 doc.add_source("canvas", StyleSource::Canvas(source))
4268 .expect("source added");
4269 doc.add_layer(StyleLayer::Raster(RasterStyleLayer::new(
4270 "canvas-layer",
4271 "canvas",
4272 )))
4273 .expect("layer added");
4274
4275 let layers = doc.to_runtime_layers().expect("runtime layers");
4276 assert_eq!(layers.len(), 1);
4277 let dyn_layer = layers[0]
4278 .as_any()
4279 .downcast_ref::<DynamicImageOverlayLayer>()
4280 .expect("canvas source should produce a DynamicImageOverlayLayer");
4281 assert!(!dyn_layer.provider().is_animating());
4283 }
4284
4285 #[test]
4290 fn compute_lighting_defaults() {
4291 let config = LightConfig::default();
4292 let lit = compute_lighting(&config);
4293 assert!((lit.lighting_enabled - 1.0).abs() < f32::EPSILON);
4295 assert!((lit.ambient_color[0] - 0.5).abs() < 0.01);
4297 assert!((lit.ambient_color[1] - 0.5).abs() < 0.01);
4298 assert!((lit.ambient_color[2] - 0.5).abs() < 0.01);
4299 assert!((lit.directional_color[0] - 0.5).abs() < 0.01);
4301 assert!((lit.directional_color[1] - 0.5).abs() < 0.01);
4302 assert!((lit.directional_color[2] - 0.5).abs() < 0.01);
4303 let len = (lit.directional_dir[0].powi(2)
4305 + lit.directional_dir[1].powi(2)
4306 + lit.directional_dir[2].powi(2))
4307 .sqrt();
4308 assert!((len - 1.0).abs() < 0.01);
4309 }
4310
4311 #[test]
4312 fn compute_lighting_flat_mode() {
4313 let config = LightConfig {
4314 mode: LightingMode::Flat,
4315 ..Default::default()
4316 };
4317 let lit = compute_lighting(&config);
4318 assert!((lit.lighting_enabled - 0.0).abs() < f32::EPSILON);
4319 }
4320
4321 #[test]
4322 fn compute_lighting_custom_ambient() {
4323 let config = LightConfig {
4324 ambient: AmbientLight {
4325 color: [1.0, 0.0, 0.0],
4326 intensity: 0.8,
4327 },
4328 ..Default::default()
4329 };
4330 let lit = compute_lighting(&config);
4331 assert!((lit.ambient_color[0] - 0.8).abs() < 0.01);
4332 assert!((lit.ambient_color[1] - 0.0).abs() < 0.01);
4333 assert!((lit.ambient_color[2] - 0.0).abs() < 0.01);
4334 }
4335
4336 #[test]
4337 fn compute_lighting_direction_north_overhead() {
4338 let config = LightConfig {
4339 directional: DirectionalLight {
4340 direction: [0.0, 90.0], ..Default::default()
4342 },
4343 ..Default::default()
4344 };
4345 let lit = compute_lighting(&config);
4346 assert!((lit.directional_dir[2] - 1.0).abs() < 0.01);
4348 assert!(lit.directional_dir[0].abs() < 0.01);
4349 assert!(lit.directional_dir[1].abs() < 0.01);
4350 }
4351
4352 #[test]
4357 fn compute_sky_defaults_disabled() {
4358 let sky = ComputedSky::default();
4359 assert!((sky.sky_enabled - 0.0).abs() < f32::EPSILON);
4360 }
4361
4362 #[test]
4363 fn compute_sky_enabled_with_config() {
4364 let config = SkyConfig::default();
4365 let fallback = [210.0, 45.0];
4366 let sky = compute_sky(&config, fallback);
4367 assert!((sky.sky_enabled - 1.0).abs() < f32::EPSILON);
4368 assert!((sky.sun_intensity - 10.0).abs() < 0.01);
4369 }
4370
4371 #[test]
4372 fn compute_sky_inherits_sun_from_fallback() {
4373 let config = SkyConfig {
4374 sun_position: None,
4375 ..Default::default()
4376 };
4377 let fallback = [0.0, 90.0]; let sky = compute_sky(&config, fallback);
4379 assert!((sky.sun_direction[2] - 1.0).abs() < 0.01);
4381 assert!(sky.sun_direction[0].abs() < 0.01);
4382 assert!(sky.sun_direction[1].abs() < 0.01);
4383 }
4384
4385 #[test]
4386 fn compute_sky_explicit_sun_position() {
4387 let config = SkyConfig {
4388 sun_position: Some([90.0, 45.0]), ..Default::default()
4390 };
4391 let sky = compute_sky(&config, [0.0, 0.0]);
4392 assert!(sky.sun_direction[0] > 0.5);
4394 assert!((sky.sun_direction[2] - 0.707).abs() < 0.02);
4396 }
4397
4398 #[test]
4399 fn compute_sky_zero_opacity_disables() {
4400 let config = SkyConfig {
4401 opacity: 0.0,
4402 ..Default::default()
4403 };
4404 let sky = compute_sky(&config, [210.0, 45.0]);
4405 assert!((sky.sky_enabled - 0.0).abs() < f32::EPSILON);
4406 }
4407
4408 #[test]
4413 fn transition_spec_default_is_300ms() {
4414 let spec = TransitionSpec::default();
4415 assert!((spec.duration - 0.3).abs() < 1e-6);
4416 assert!((spec.delay - 0.0).abs() < 1e-6);
4417 assert!(spec.is_active());
4418 }
4419
4420 #[test]
4421 fn transition_spec_instant_is_zero() {
4422 let spec = TransitionSpec::INSTANT;
4423 assert!((spec.duration - 0.0).abs() < 1e-6);
4424 assert!(!spec.is_active());
4425 }
4426
4427 #[test]
4428 fn transitioning_settled_resolves_immediately() {
4429 let t = Transitioning::settled(0.5_f32);
4430 assert!((t.resolve(0.0) - 0.5).abs() < 1e-6);
4431 assert!((t.resolve(100.0) - 0.5).abs() < 1e-6);
4432 assert!(!t.is_active(0.0));
4433 }
4434
4435 #[test]
4436 fn transitioning_interpolates_over_duration() {
4437 let spec = TransitionSpec {
4438 duration: 1.0,
4439 delay: 0.0,
4440 };
4441 let t = Transitioning::new(0.0_f32, 1.0, 0.0, &spec);
4442
4443 assert!((t.resolve(0.0) - 0.0).abs() < 1e-6);
4445
4446 let mid = t.resolve(0.5);
4448 assert!((mid - 0.5).abs() < 0.05);
4449
4450 assert!((t.resolve(1.0) - 1.0).abs() < 1e-6);
4452 assert!((t.resolve(2.0) - 1.0).abs() < 1e-6);
4453 }
4454
4455 #[test]
4456 fn transitioning_respects_delay() {
4457 let spec = TransitionSpec {
4458 duration: 1.0,
4459 delay: 0.5,
4460 };
4461 let t = Transitioning::new(0.0_f32, 1.0, 0.0, &spec);
4462
4463 assert!((t.resolve(0.0) - 0.0).abs() < 1e-6);
4465 assert!((t.resolve(0.25) - 0.0).abs() < 1e-6);
4466 assert!((t.resolve(0.5) - 0.0).abs() < 1e-6);
4467
4468 assert!(t.resolve(1.0) > 0.1);
4470 assert!((t.resolve(1.5) - 1.0).abs() < 1e-6);
4472 }
4473
4474 #[test]
4475 fn transitioning_retarget_starts_from_current() {
4476 let spec = TransitionSpec {
4477 duration: 1.0,
4478 delay: 0.0,
4479 };
4480 let mut t = Transitioning::new(0.0_f32, 1.0, 0.0, &spec);
4481
4482 let mid = t.resolve(0.5);
4484 assert!(mid > 0.3 && mid < 0.7);
4485
4486 t.retarget(2.0, 0.5, &spec);
4488
4489 let after_retarget = t.resolve(0.5);
4491 assert!((after_retarget - mid).abs() < 0.05);
4492
4493 assert!((t.resolve(1.5) - 2.0).abs() < 1e-6);
4495 }
4496
4497 #[test]
4498 fn transitioning_color_interpolation() {
4499 let spec = TransitionSpec {
4500 duration: 1.0,
4501 delay: 0.0,
4502 };
4503 let red: [f32; 4] = [1.0, 0.0, 0.0, 1.0];
4504 let blue: [f32; 4] = [0.0, 0.0, 1.0, 1.0];
4505 let t = Transitioning::new(red, blue, 0.0, &spec);
4506
4507 let mid = t.resolve(0.5);
4508 assert!(mid[0] > 0.3 && mid[0] < 0.7);
4510 assert!(mid[2] > 0.3 && mid[2] < 0.7);
4511
4512 let end = t.resolve(1.0);
4514 assert!((end[0] - 0.0).abs() < 1e-6);
4515 assert!((end[2] - 1.0).abs() < 1e-6);
4516 }
4517
4518 #[test]
4519 fn layer_transition_state_detects_changes() {
4520 let spec = TransitionSpec {
4521 duration: 0.5,
4522 delay: 0.0,
4523 };
4524 let mut state = LayerTransitionState::from_initial(
4525 spec,
4526 1.0,
4527 [1.0, 0.0, 0.0, 1.0],
4528 [0.0; 4],
4529 2.0,
4530 0.0,
4531 0.0,
4532 );
4533
4534 assert!(!state.has_active_transitions(0.0));
4536
4537 state.update(1.0, 0.5, [1.0, 0.0, 0.0, 1.0], [0.0; 4], 2.0, 0.0, 0.0);
4539 assert!(state.has_active_transitions(1.0));
4540
4541 let resolved = state.resolve(1.25);
4543 assert!(resolved.opacity > 0.5 && resolved.opacity < 1.0);
4544
4545 let resolved = state.resolve(1.5);
4547 assert!((resolved.opacity - 0.5).abs() < 1e-6);
4548 }
4549
4550 #[test]
4551 fn ease_cubic_in_out_boundary_values() {
4552 assert!((super::ease_cubic_in_out(0.0) - 0.0).abs() < 1e-6);
4553 assert!((super::ease_cubic_in_out(0.5) - 0.5).abs() < 1e-6);
4554 assert!((super::ease_cubic_in_out(1.0) - 1.0).abs() < 1e-6);
4555 let v1 = super::ease_cubic_in_out(0.25);
4557 let v2 = super::ease_cubic_in_out(0.5);
4558 let v3 = super::ease_cubic_in_out(0.75);
4559 assert!(v1 < v2);
4560 assert!(v2 < v3);
4561 }
4562
4563 #[test]
4564 fn style_document_global_transition() {
4565 let mut doc = StyleDocument::new();
4566 assert!(doc.transition().is_active());
4568 assert!((doc.transition().duration - 0.3).abs() < 1e-6);
4569 doc.set_transition(TransitionSpec {
4570 duration: 0.5,
4571 delay: 0.1,
4572 });
4573 assert!((doc.transition().duration - 0.5).abs() < 1e-6);
4574 assert!((doc.transition().delay - 0.1).abs() < 1e-6);
4575 }
4576
4577 #[test]
4578 fn style_layer_meta_has_transition() {
4579 let meta = StyleLayerMeta::new("test");
4580 assert!((meta.transition.duration - 0.3).abs() < 1e-6);
4581 }
4582
4583 #[test]
4588 fn shadow_config_defaults() {
4589 let cfg = ShadowConfig::default();
4590 assert_eq!(cfg.cascade_count, 2);
4591 assert_eq!(cfg.map_resolution, 2048);
4592 assert!((cfg.intensity - 0.8).abs() < 1e-6);
4593 assert!((cfg.normal_offset - 3.0).abs() < 1e-6);
4594 }
4595
4596 #[test]
4597 fn computed_shadow_default_is_disabled() {
4598 let s = ComputedShadow::default();
4599 assert!(!s.enabled);
4600 assert_eq!(s.cascade_count, 2);
4603 }
4604
4605 #[test]
4606 fn shadow_cascade_identity_vp_produces_valid_output() {
4607 let vp = glam::DMat4::IDENTITY;
4608 let light_dir = [0.0_f32, -0.707, 0.707];
4609 let config = ShadowConfig::default();
4610 let shadow = compute_shadow_cascades(&vp, light_dir, 500.0, &config);
4611 assert!(shadow.enabled);
4612 assert_eq!(shadow.cascade_count, 2);
4613 assert_eq!(shadow.map_resolution, 2048);
4614 let has_nonzero = shadow.light_matrices[0]
4616 .iter()
4617 .flat_map(|r| r.iter())
4618 .any(|v| v.abs() > 1e-10);
4619 assert!(has_nonzero, "cascade 0 matrix should not be all-zero");
4620 }
4621
4622 #[test]
4623 fn shadow_cascade_count_clamped_to_four() {
4624 let vp = glam::DMat4::IDENTITY;
4625 let light_dir = [0.0_f32, -0.707, 0.707];
4626 let config = ShadowConfig {
4627 cascade_count: 10,
4628 ..Default::default()
4629 };
4630 let shadow = compute_shadow_cascades(&vp, light_dir, 500.0, &config);
4631 assert!(shadow.cascade_count <= 4);
4633 }
4634
4635 #[test]
4636 fn shadow_disabled_when_light_dir_zero() {
4637 let vp = glam::DMat4::IDENTITY;
4638 let light_dir = [0.0_f32, 0.0, 0.0];
4639 let config = ShadowConfig::default();
4640 let shadow = compute_shadow_cascades(&vp, light_dir, 500.0, &config);
4641 assert!(!shadow.enabled);
4642 }
4643
4644 #[test]
4645 fn shadows_enabled_flag_from_lighting() {
4646 let config = LightConfig::default();
4649 let lit = compute_lighting(&config);
4650 assert!(!lit.shadows_enabled);
4651
4652 let with_shadows = LightConfig {
4654 directional: DirectionalLight {
4655 cast_shadows: true,
4656 ..Default::default()
4657 },
4658 ..Default::default()
4659 };
4660 let lit_on = compute_lighting(&with_shadows);
4661 assert!(lit_on.shadows_enabled);
4662
4663 let flat = LightConfig {
4666 mode: LightingMode::Flat,
4667 directional: DirectionalLight {
4668 cast_shadows: true,
4669 ..Default::default()
4670 },
4671 ..Default::default()
4672 };
4673 let lit_flat = compute_lighting(&flat);
4674 assert!(!lit_flat.shadows_enabled);
4675 }
4676
4677 #[test]
4678 fn shadow_texel_size_matches_resolution() {
4679 let vp = glam::DMat4::IDENTITY;
4680 let light_dir = [0.0_f32, -0.707, 0.707];
4681 let config = ShadowConfig {
4682 map_resolution: 1024,
4683 ..Default::default()
4684 };
4685 let shadow = compute_shadow_cascades(&vp, light_dir, 500.0, &config);
4686 assert!((shadow.texel_size - 1.0 / 1024.0).abs() < 1e-6);
4687 }
4688}