1use crate::async_data::{
66 AsyncDataPipeline, DataTaskPool, TerrainTaskInput, VectorCacheKey, VectorTaskInput,
67};
68use crate::camera::{Camera, CameraConstraints, CameraController, CameraMode};
69use crate::camera_animator::CameraAnimator;
70use crate::camera_projection::CameraProjection;
71use crate::geo_wrap::wrap_lon_180;
72use crate::geometry::{FeatureCollection, PropertyValue};
73use crate::input::InputEvent;
74use crate::layer::Layer;
75use crate::layer::LayerId;
76use crate::layers::{FeatureProvenance, LayerStack, VectorMeshData, VectorStyle};
77use crate::loading_placeholder::{LoadingPlaceholder, PlaceholderGenerator, PlaceholderStyle};
78use crate::models::ModelInstance;
79use crate::picking::{HitCategory, HitProvenance, PickHit, PickOptions, PickQuery, PickResult};
80use crate::query::{
81 feature_id_for_feature, geometry_hit_distance, FeatureState, FeatureStateId, QueriedFeature,
82 QueryOptions,
83};
84use crate::streamed_payload::{
85 collect_affected_symbol_payloads, prune_affected_symbol_payloads,
86 resolve_streamed_vector_layer_refresh, symbol_query_payloads_from_optional,
87 StreamedPayloadView, StreamedSymbolPayloadKey, StreamedVectorLayerRefreshSpec,
88 SymbolDependencyPayload, SymbolQueryPayload, TileQueryPayload, VisiblePlacedSymbolView,
89};
90use crate::style::{MapStyle, StyleDocument, StyleError};
91use crate::symbols::{PlacedSymbol, SymbolAssetRegistry, SymbolPlacementEngine};
92use crate::terrain::{PreparedHillshadeRaster, TerrainConfig, TerrainManager, TerrainMeshData};
93use crate::tile_cache::TileCacheStats;
94use crate::tile_lifecycle::TileLifecycleDiagnostics;
95use crate::tile_manager::{TileManagerCounters, TileSelectionStats};
96use crate::tile_manager::{VisibleTile, ZoomPrefetchDirection};
97use crate::tile_request_coordinator::{
98 CoordinatorConfig, CoordinatorStats, SourcePriority, TileRequestCoordinator,
99};
100use crate::tile_source::TileSourceDiagnostics;
101use rustial_math::{
102 visible_tiles, visible_tiles_flat_view_with_config, FlatTileSelectionConfig, Frustum,
103 GeoBounds, GeoCoord, TileId, WebMercator, WorldBounds, WorldCoord, MAX_ZOOM,
104};
105use std::collections::{HashMap, HashSet, VecDeque};
106use std::sync::Arc;
107
108mod async_pipeline;
109mod heavy_layers;
110mod picking;
111#[cfg(test)]
112mod tests;
113mod tile_selection;
114
115const WGS84_CIRCUMFERENCE: f64 = 2.0 * std::f64::consts::PI * 6_378_137.0;
124
125const TILE_PX: f64 = 256.0;
128
129const SPECULATIVE_PREFETCH_BUDGET_FRACTION: f64 = 0.25;
131
132const DEFAULT_SPECULATIVE_PREFETCH_REQUEST_BUDGET: usize = 8;
134
135const ZOOM_DIRECTION_PREFETCH_THRESHOLD: f64 = 0.01;
137
138fn viewport_sample_points(width: f64, height: f64) -> Vec<(f64, f64)> {
139 const FRACTIONS: [f64; 7] = [0.0, 1.0 / 6.0, 2.0 / 6.0, 0.5, 4.0 / 6.0, 5.0 / 6.0, 1.0];
140 let mut samples = Vec::with_capacity(FRACTIONS.len() * FRACTIONS.len());
141 for fy in FRACTIONS {
142 for fx in FRACTIONS {
143 samples.push((width * fx, height * fy));
144 }
145 }
146 samples
147}
148
149fn perspective_viewport_overscan(pitch: f64) -> f64 {
150 let normalized_pitch = (pitch / std::f64::consts::FRAC_PI_2).clamp(0.0, 1.0);
151 1.3 + 0.5 * normalized_pitch
152}
153
154fn terrain_base_tile_budget(required_tiles: usize) -> usize {
155 required_tiles.clamp(80, 256)
156}
157
158fn terrain_horizon_tile_budget(base_budget: usize, pitch: f64) -> usize {
159 if pitch <= 0.5 {
160 0
161 } else {
162 (base_budget / 3).clamp(24, 96)
163 }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq)]
172pub struct FitBoundsPadding {
173 pub top: f64,
175 pub bottom: f64,
177 pub left: f64,
179 pub right: f64,
181}
182
183impl Default for FitBoundsPadding {
184 fn default() -> Self {
185 Self {
186 top: 0.0,
187 bottom: 0.0,
188 left: 0.0,
189 right: 0.0,
190 }
191 }
192}
193
194impl FitBoundsPadding {
195 pub fn uniform(px: f64) -> Self {
197 Self {
198 top: px,
199 bottom: px,
200 left: px,
201 right: px,
202 }
203 }
204}
205
206#[derive(Debug, Clone)]
210pub struct FitBoundsOptions {
211 pub padding: FitBoundsPadding,
213 pub max_zoom: Option<f64>,
215 pub animate: bool,
217 pub duration: Option<f64>,
219 pub bearing: Option<f64>,
221 pub pitch: Option<f64>,
223}
224
225impl Default for FitBoundsOptions {
226 fn default() -> Self {
227 Self {
228 padding: FitBoundsPadding::default(),
229 max_zoom: None,
230 animate: true,
231 duration: None,
232 bearing: None,
233 pitch: None,
234 }
235 }
236}
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
248struct SyncVectorCacheKey {
249 layer_id: LayerId,
251 style_fingerprint: u64,
253 data_generation: u64,
255 projection: CameraProjection,
257}
258
259#[derive(Clone)]
261struct SyncVectorCacheEntry {
262 mesh: VectorMeshData,
263}
264
265#[derive(Debug, Default)]
284pub struct FrameOutput {
285 pub view_projection: glam::DMat4,
290
291 pub frustum: Option<Frustum>,
295
296 pub tiles: Arc<Vec<VisibleTile>>,
300
301 pub terrain: Arc<Vec<TerrainMeshData>>,
305
306 pub hillshade: Arc<Vec<PreparedHillshadeRaster>>,
308
309 pub vectors: Arc<Vec<VectorMeshData>>,
313
314 pub models: Arc<Vec<ModelInstance>>,
318
319 pub symbols: Arc<Vec<PlacedSymbol>>,
321
322 pub visualization: Arc<Vec<crate::visualization::VisualizationOverlay>>,
324
325 pub placeholders: Arc<Vec<LoadingPlaceholder>>,
331
332 pub image_overlays: Arc<Vec<crate::layers::ImageOverlayData>>,
338
339 pub zoom_level: u8,
341}
342
343#[derive(Debug, Clone, Default)]
345pub struct TilePipelineDiagnostics {
346 pub layer_name: String,
348 pub visible_tiles: usize,
350 pub visible_loaded_tiles: usize,
352 pub visible_fallback_tiles: usize,
354 pub visible_missing_tiles: usize,
356 pub visible_overzoomed_tiles: usize,
358 pub selection_stats: TileSelectionStats,
360 pub counters: TileManagerCounters,
362 pub cache_stats: TileCacheStats,
364 pub source_diagnostics: Option<TileSourceDiagnostics>,
366}
367
368#[derive(Debug, Clone, PartialEq)]
370pub struct CameraVelocityConfig {
371 pub sample_window: usize,
377 pub look_ahead_seconds: f64,
379}
380
381impl Default for CameraVelocityConfig {
382 fn default() -> Self {
383 Self {
384 sample_window: 6,
385 look_ahead_seconds: 0.5,
386 }
387 }
388}
389
390#[derive(Debug, Clone, Copy, PartialEq)]
391struct CameraMotionSample {
392 time_seconds: f64,
393 target_world: glam::DVec2,
394}
395
396#[derive(Debug, Clone, PartialEq)]
398pub struct CameraMotionState {
399 pub pan_velocity_world: glam::DVec2,
401 pub predicted_target_world: glam::DVec2,
404 pub predicted_viewport_bounds: WorldBounds,
406}
407
408impl Default for CameraMotionState {
409 fn default() -> Self {
410 let zero = WorldCoord::new(0.0, 0.0, 0.0);
411 Self {
412 pan_velocity_world: glam::DVec2::ZERO,
413 predicted_target_world: glam::DVec2::ZERO,
414 predicted_viewport_bounds: WorldBounds::new(zero, zero),
415 }
416 }
417}
418
419pub struct MapState {
455 camera: Camera,
458
459 constraints: CameraConstraints,
461
462 pub(crate) layers: LayerStack,
464
465 style: Option<MapStyle>,
467
468 pub(crate) terrain: TerrainManager,
470
471 animator: CameraAnimator,
473
474 data_update_interval: f64,
492
493 data_update_elapsed: f64,
495
496 async_pipeline: Option<AsyncDataPipeline>,
505
506 zoom_level: u8,
509
510 viewport_bounds: WorldBounds,
512
513 scene_viewport_bounds: WorldBounds,
515
516 frustum: Option<Frustum>,
518
519 terrain_meshes: Arc<Vec<TerrainMeshData>>,
521
522 pending_terrain_meshes: Option<Arc<Vec<TerrainMeshData>>>,
524
525 hillshade_rasters: Arc<Vec<PreparedHillshadeRaster>>,
527
528 visible_tiles: Arc<Vec<VisibleTile>>,
530
531 pub(crate) vector_meshes: Arc<Vec<VectorMeshData>>,
534
535 pending_vector_meshes: Option<Arc<Vec<VectorMeshData>>>,
537
538 pub(crate) model_instances: Arc<Vec<ModelInstance>>,
541
542 pending_model_instances: Option<Arc<Vec<ModelInstance>>>,
544
545 placed_symbols: Arc<Vec<PlacedSymbol>>,
547
548 symbol_assets: SymbolAssetRegistry,
550
551 symbol_placement: SymbolPlacementEngine,
553
554 feature_state: HashMap<FeatureStateId, FeatureState>,
556
557 streamed_vector_sources: HashMap<String, crate::layers::TileLayer>,
559
560 streamed_vector_layer_fingerprints: HashMap<String, u64>,
562
563 streamed_vector_query_payloads: HashMap<String, Vec<TileQueryPayload>>,
565
566 streamed_symbol_query_payloads: HashMap<String, Vec<SymbolQueryPayload>>,
568
569 streamed_symbol_dependency_payloads: HashMap<String, Vec<SymbolDependencyPayload>>,
571
572 dirty_streamed_symbol_layers: HashSet<String>,
574
575 dirty_streamed_symbol_tiles: HashMap<String, HashSet<TileId>>,
577
578 visualization_overlays: Arc<Vec<crate::visualization::VisualizationOverlay>>,
580
581 image_overlays: Arc<Vec<crate::layers::ImageOverlayData>>,
583
584 placeholder_style: PlaceholderStyle,
586
587 loading_placeholders: Arc<Vec<LoadingPlaceholder>>,
589
590 placeholder_time: f64,
592
593 interaction_manager: Option<crate::interaction_manager::InteractionManager>,
596
597 sync_vector_cache: HashMap<SyncVectorCacheKey, SyncVectorCacheEntry>,
603
604 request_coordinator: TileRequestCoordinator,
610
611 camera_velocity_config: CameraVelocityConfig,
613
614 camera_motion_time_seconds: f64,
616
617 camera_motion_samples: VecDeque<CameraMotionSample>,
619
620 camera_motion_state: CameraMotionState,
622
623 camera_zoom_delta: f64,
625
626 previous_fractional_zoom: Option<f64>,
628
629 prefetch_route: Option<Vec<GeoCoord>>,
635
636 event_emitter: crate::event_emitter::EventEmitter,
642
643 gesture_recognizer: crate::gesture::GestureRecognizer,
648
649 fog_config: Option<crate::style::FogConfig>,
651
652 computed_fog: crate::style::ComputedFog,
654
655 light_config: Option<crate::style::LightConfig>,
657
658 computed_lighting: crate::style::ComputedLighting,
660
661 computed_shadow: crate::style::ComputedShadow,
663
664 sky_config: Option<crate::style::SkyConfig>,
666
667 computed_sky: crate::style::ComputedSky,
669
670 style_time: f64,
673
674 layer_transitions:
676 std::collections::HashMap<crate::style::StyleLayerId, crate::style::LayerTransitionState>,
677}
678
679impl Default for MapState {
684 fn default() -> Self {
685 let zero = WorldCoord::new(0.0, 0.0, 0.0);
686 Self {
687 camera: Camera::default(),
688 constraints: CameraConstraints::default(),
689 layers: LayerStack::new(),
690 style: None,
691 terrain: TerrainManager::new(TerrainConfig::default(), 256),
692 animator: CameraAnimator::new(),
693 data_update_interval: 0.15,
694 data_update_elapsed: 0.0,
695 async_pipeline: None,
696 zoom_level: 0,
697 viewport_bounds: WorldBounds::new(zero, zero),
698 scene_viewport_bounds: WorldBounds::new(zero, zero),
699 frustum: None,
700 terrain_meshes: Arc::new(Vec::new()),
701 pending_terrain_meshes: None,
702 hillshade_rasters: Arc::new(Vec::new()),
703 visible_tiles: Arc::new(Vec::new()),
704 vector_meshes: Arc::new(Vec::new()),
705 pending_vector_meshes: None,
706 model_instances: Arc::new(Vec::new()),
707 pending_model_instances: None,
708 placed_symbols: Arc::new(Vec::new()),
709 symbol_assets: SymbolAssetRegistry::new(),
710 symbol_placement: SymbolPlacementEngine::new(),
711 feature_state: HashMap::new(),
712 streamed_vector_sources: HashMap::new(),
713 streamed_vector_layer_fingerprints: HashMap::new(),
714 streamed_vector_query_payloads: HashMap::new(),
715 streamed_symbol_query_payloads: HashMap::new(),
716 streamed_symbol_dependency_payloads: HashMap::new(),
717 dirty_streamed_symbol_layers: HashSet::new(),
718 dirty_streamed_symbol_tiles: HashMap::new(),
719 visualization_overlays: Arc::new(Vec::new()),
720 image_overlays: Arc::new(Vec::new()),
721 placeholder_style: PlaceholderStyle::default(),
722 loading_placeholders: Arc::new(Vec::new()),
723 placeholder_time: 0.0,
724 interaction_manager: None,
725 sync_vector_cache: HashMap::new(),
726 request_coordinator: TileRequestCoordinator::default(),
727 camera_velocity_config: CameraVelocityConfig::default(),
728 camera_motion_time_seconds: 0.0,
729 camera_motion_samples: VecDeque::new(),
730 camera_motion_state: CameraMotionState::default(),
731 camera_zoom_delta: 0.0,
732 previous_fractional_zoom: None,
733 prefetch_route: None,
734 event_emitter: crate::event_emitter::EventEmitter::new(),
735 gesture_recognizer: crate::gesture::GestureRecognizer::new(),
736 fog_config: None,
737 computed_fog: crate::style::ComputedFog::default(),
738 light_config: None,
739 computed_lighting: crate::style::ComputedLighting::default(),
740 computed_shadow: crate::style::ComputedShadow::default(),
741 sky_config: None,
742 computed_sky: crate::style::ComputedSky::default(),
743 style_time: 0.0,
744 layer_transitions: std::collections::HashMap::new(),
745 }
746 }
747}
748
749impl MapState {
754 pub fn new() -> Self {
756 Self::default()
757 }
758
759 pub fn with_terrain(terrain_config: TerrainConfig, terrain_cache_size: usize) -> Self {
768 let zero = WorldCoord::new(0.0, 0.0, 0.0);
769 Self {
770 camera: Camera::default(),
771 constraints: CameraConstraints::default(),
772 layers: LayerStack::new(),
773 style: None,
774 terrain: TerrainManager::new(terrain_config, terrain_cache_size),
775 animator: CameraAnimator::new(),
776 data_update_interval: 0.15,
777 data_update_elapsed: 0.0,
778 async_pipeline: None,
779 zoom_level: 0,
780 viewport_bounds: WorldBounds::new(zero, zero),
781 scene_viewport_bounds: WorldBounds::new(zero, zero),
782 frustum: None,
783 terrain_meshes: Arc::new(Vec::new()),
784 pending_terrain_meshes: None,
785 hillshade_rasters: Arc::new(Vec::new()),
786 visible_tiles: Arc::new(Vec::new()),
787 vector_meshes: Arc::new(Vec::new()),
788 pending_vector_meshes: None,
789 model_instances: Arc::new(Vec::new()),
790 pending_model_instances: None,
791 placed_symbols: Arc::new(Vec::new()),
792 symbol_assets: SymbolAssetRegistry::new(),
793 symbol_placement: SymbolPlacementEngine::new(),
794 feature_state: HashMap::new(),
795 streamed_vector_sources: HashMap::new(),
796 streamed_vector_layer_fingerprints: HashMap::new(),
797 streamed_vector_query_payloads: HashMap::new(),
798 streamed_symbol_query_payloads: HashMap::new(),
799 streamed_symbol_dependency_payloads: HashMap::new(),
800 dirty_streamed_symbol_layers: HashSet::new(),
801 dirty_streamed_symbol_tiles: HashMap::new(),
802 visualization_overlays: Arc::new(Vec::new()),
803 image_overlays: Arc::new(Vec::new()),
804 placeholder_style: PlaceholderStyle::default(),
805 loading_placeholders: Arc::new(Vec::new()),
806 placeholder_time: 0.0,
807 interaction_manager: None,
808 sync_vector_cache: HashMap::new(),
809 request_coordinator: TileRequestCoordinator::default(),
810 camera_velocity_config: CameraVelocityConfig::default(),
811 camera_motion_time_seconds: 0.0,
812 camera_motion_samples: VecDeque::new(),
813 camera_motion_state: CameraMotionState::default(),
814 camera_zoom_delta: 0.0,
815 previous_fractional_zoom: None,
816 prefetch_route: None,
817 event_emitter: crate::event_emitter::EventEmitter::new(),
818 gesture_recognizer: crate::gesture::GestureRecognizer::new(),
819 fog_config: None,
820 computed_fog: crate::style::ComputedFog::default(),
821 light_config: None,
822 computed_lighting: crate::style::ComputedLighting::default(),
823 computed_shadow: crate::style::ComputedShadow::default(),
824 sky_config: None,
825 computed_sky: crate::style::ComputedSky::default(),
826 style_time: 0.0,
827 layer_transitions: std::collections::HashMap::new(),
828 }
829 }
830
831 #[inline]
838 pub fn zoom_level(&self) -> u8 {
839 self.zoom_level
840 }
841
842 #[inline]
849 pub fn fractional_zoom(&self) -> f64 {
850 let mpp = self.camera.near_meters_per_pixel();
851 if mpp <= 0.0 || !mpp.is_finite() {
852 return MAX_ZOOM as f64;
853 }
854 (WGS84_CIRCUMFERENCE / (mpp * TILE_PX))
855 .log2()
856 .clamp(0.0, MAX_ZOOM as f64)
857 }
858
859 #[inline]
864 pub fn viewport_bounds(&self) -> &WorldBounds {
865 &self.viewport_bounds
866 }
867
868 #[inline]
870 pub fn camera_velocity_config(&self) -> &CameraVelocityConfig {
871 &self.camera_velocity_config
872 }
873
874 pub fn set_camera_velocity_config(&mut self, config: CameraVelocityConfig) {
876 self.camera_velocity_config = config;
877 self.trim_camera_motion_samples();
878 self.recompute_camera_motion_state();
879 }
880
881 #[inline]
883 pub fn camera_motion_state(&self) -> &CameraMotionState {
884 &self.camera_motion_state
885 }
886
887 #[inline]
889 pub fn predicted_viewport_bounds(&self) -> &WorldBounds {
890 &self.camera_motion_state.predicted_viewport_bounds
891 }
892
893 #[inline]
895 pub fn scene_viewport_bounds(&self) -> &WorldBounds {
896 &self.scene_viewport_bounds
897 }
898
899 #[inline]
903 pub fn frustum(&self) -> Option<&Frustum> {
904 self.frustum.as_ref()
905 }
906
907 #[inline]
912 pub fn renderer_world_origin(&self) -> glam::DVec3 {
913 let (x, y) = self.mercator_camera_world();
914 glam::DVec3::new(x, y, 0.0)
915 }
916
917 #[inline]
925 pub fn scene_world_origin(&self) -> glam::DVec3 {
926 self.camera.target_world()
927 }
928
929 #[inline]
933 pub fn terrain_meshes(&self) -> &[TerrainMeshData] {
934 &self.terrain_meshes
935 }
936
937 #[inline]
939 pub fn hillshade_rasters(&self) -> &[PreparedHillshadeRaster] {
940 &self.hillshade_rasters
941 }
942
943 #[inline]
945 pub fn visible_tiles(&self) -> &[VisibleTile] {
946 &self.visible_tiles
947 }
948
949 #[inline]
954 pub fn loading_placeholders(&self) -> &[LoadingPlaceholder] {
955 &self.loading_placeholders
956 }
957
958 #[inline]
960 pub fn placeholder_style(&self) -> &PlaceholderStyle {
961 &self.placeholder_style
962 }
963
964 pub fn set_placeholder_style(&mut self, style: PlaceholderStyle) {
966 self.placeholder_style = style;
967 }
968
969 pub fn coordinator_stats(&self) -> &CoordinatorStats {
972 self.request_coordinator.stats()
973 }
974
975 pub fn coordinator_config(&self) -> &CoordinatorConfig {
977 self.request_coordinator.config()
978 }
979
980 pub fn set_coordinator_config(&mut self, config: CoordinatorConfig) {
985 self.request_coordinator.set_config(config);
986 }
987
988 pub fn set_prefetch_route(&mut self, route: Vec<GeoCoord>) {
999 if route.len() < 2 {
1000 self.prefetch_route = None;
1001 } else {
1002 self.prefetch_route = Some(route);
1003 }
1004 }
1005
1006 pub fn clear_prefetch_route(&mut self) {
1008 self.prefetch_route = None;
1009 }
1010
1011 #[inline]
1013 pub fn prefetch_route(&self) -> Option<&[GeoCoord]> {
1014 self.prefetch_route.as_deref()
1015 }
1016
1017 pub fn tile_pipeline_diagnostics(&self) -> Option<TilePipelineDiagnostics> {
1019 use crate::layers::TileLayer;
1020
1021 for layer in self.layers.iter() {
1022 if !layer.visible() {
1023 continue;
1024 }
1025 let Some(tile_layer) = layer.as_any().downcast_ref::<TileLayer>() else {
1026 continue;
1027 };
1028
1029 let visible = tile_layer.visible_tiles();
1030 let visible_loaded_tiles = visible
1031 .tiles
1032 .iter()
1033 .filter(|tile| tile.data.is_some())
1034 .count();
1035 let visible_fallback_tiles = visible
1036 .tiles
1037 .iter()
1038 .filter(|tile| tile.is_fallback() && tile.data.is_some())
1039 .count();
1040 let visible_missing_tiles = visible
1041 .tiles
1042 .iter()
1043 .filter(|tile| tile.data.is_none())
1044 .count();
1045 let visible_overzoomed_tiles = visible
1046 .tiles
1047 .iter()
1048 .filter(|tile| tile.is_overzoomed())
1049 .count();
1050
1051 return Some(TilePipelineDiagnostics {
1052 layer_name: tile_layer.name().to_owned(),
1053 visible_tiles: visible.len(),
1054 visible_loaded_tiles,
1055 visible_fallback_tiles,
1056 visible_missing_tiles,
1057 visible_overzoomed_tiles,
1058 selection_stats: tile_layer.last_selection_stats().clone(),
1059 counters: tile_layer.counters().clone(),
1060 cache_stats: tile_layer.cache_stats(),
1061 source_diagnostics: tile_layer.source_diagnostics(),
1062 });
1063 }
1064
1065 None
1066 }
1067
1068 pub fn tile_lifecycle_diagnostics(&self) -> Option<TileLifecycleDiagnostics> {
1070 use crate::layers::TileLayer;
1071
1072 for layer in self.layers.iter() {
1073 if !layer.visible() {
1074 continue;
1075 }
1076 let Some(tile_layer) = layer.as_any().downcast_ref::<TileLayer>() else {
1077 continue;
1078 };
1079 return Some(tile_layer.lifecycle_diagnostics());
1080 }
1081
1082 None
1083 }
1084
1085 pub fn background_color(&self) -> Option<[f32; 4]> {
1092 use crate::layers::BackgroundLayer;
1093
1094 let mut color = None;
1095 for layer in self.layers.iter() {
1096 if !layer.visible() {
1097 continue;
1098 }
1099 if let Some(background) = layer.as_any().downcast_ref::<BackgroundLayer>() {
1100 color = Some(background.effective_color());
1101 }
1102 }
1103 color
1104 }
1105
1106 pub fn hillshade(&self) -> Option<crate::layers::HillshadeParams> {
1109 use crate::layers::HillshadeLayer;
1110
1111 let mut params = None;
1112 for layer in self.layers.iter() {
1113 if !layer.visible() {
1114 continue;
1115 }
1116 if let Some(hillshade) = layer.as_any().downcast_ref::<HillshadeLayer>() {
1117 params = Some(hillshade.effective_params());
1118 }
1119 }
1120 params
1121 }
1122
1123 pub fn set_fog(&mut self, config: Option<crate::style::FogConfig>) {
1131 self.fog_config = config;
1132 }
1133
1134 pub fn fog(&self) -> Option<&crate::style::FogConfig> {
1136 self.fog_config.as_ref()
1137 }
1138
1139 pub fn computed_fog(&self) -> &crate::style::ComputedFog {
1146 &self.computed_fog
1147 }
1148
1149 pub fn set_lights(&mut self, config: Option<crate::style::LightConfig>) {
1157 self.light_config = config;
1158 }
1159
1160 pub fn lights(&self) -> Option<&crate::style::LightConfig> {
1162 self.light_config.as_ref()
1163 }
1164
1165 pub fn computed_lighting(&self) -> &crate::style::ComputedLighting {
1171 &self.computed_lighting
1172 }
1173
1174 pub fn computed_shadow(&self) -> &crate::style::ComputedShadow {
1180 &self.computed_shadow
1181 }
1182
1183 pub fn set_sky(&mut self, config: Option<crate::style::SkyConfig>) {
1188 self.sky_config = config;
1189 }
1190
1191 pub fn sky(&self) -> Option<&crate::style::SkyConfig> {
1193 self.sky_config.as_ref()
1194 }
1195
1196 pub fn computed_sky(&self) -> &crate::style::ComputedSky {
1200 &self.computed_sky
1201 }
1202
1203 pub fn style_time(&self) -> f64 {
1206 self.style_time
1207 }
1208
1209 pub fn layer_transitions(
1211 &self,
1212 ) -> &std::collections::HashMap<crate::style::StyleLayerId, crate::style::LayerTransitionState>
1213 {
1214 &self.layer_transitions
1215 }
1216
1217 pub fn resolved_transitions(
1222 &self,
1223 layer_id: &str,
1224 ) -> Option<crate::style::ResolvedTransitions> {
1225 self.layer_transitions
1226 .get(layer_id)
1227 .map(|ts| ts.resolve(self.style_time))
1228 }
1229
1230 pub fn set_visible_tiles(&mut self, tiles: Vec<VisibleTile>) {
1232 self.visible_tiles = Arc::new(tiles);
1233 }
1234
1235 pub fn set_terrain_meshes(&mut self, meshes: Vec<TerrainMeshData>) {
1237 let meshes = Arc::new(meshes);
1238 self.pending_terrain_meshes = Some(meshes.clone());
1239 self.terrain_meshes = meshes;
1240 }
1241
1242 pub fn set_hillshade_rasters(&mut self, rasters: Vec<PreparedHillshadeRaster>) {
1244 self.hillshade_rasters = Arc::new(rasters);
1245 }
1246
1247 #[inline]
1251 pub fn camera(&self) -> &Camera {
1252 &self.camera
1253 }
1254
1255 pub fn set_viewport(&mut self, width: u32, height: u32) {
1257 self.camera.set_viewport(width, height);
1258 }
1259
1260 pub fn set_camera_target(&mut self, target: GeoCoord) {
1266 if !target.lat.is_finite() || !target.lon.is_finite() {
1267 return;
1268 }
1269 let wrapped = GeoCoord::from_lat_lon(target.lat, wrap_lon_180(target.lon));
1270 self.camera.set_target(wrapped);
1271 }
1272
1273 pub fn set_camera_distance(&mut self, distance: f64) {
1275 if !distance.is_finite() || distance <= 0.0 {
1276 return;
1277 }
1278 self.camera.set_distance(distance);
1279 }
1280
1281 pub fn set_camera_pitch(&mut self, pitch: f64) {
1283 if !pitch.is_finite() {
1284 return;
1285 }
1286 self.camera.set_pitch(pitch);
1287 }
1288
1289 pub fn set_camera_yaw(&mut self, yaw: f64) {
1291 if !yaw.is_finite() {
1292 return;
1293 }
1294 self.camera.set_yaw(yaw);
1295 }
1296
1297 pub fn set_camera_mode(&mut self, mode: CameraMode) {
1299 self.camera.set_mode(mode);
1300 }
1301
1302 pub fn set_camera_projection(&mut self, projection: CameraProjection) {
1304 self.camera.set_projection(projection);
1305 }
1306
1307 pub fn set_camera_fov_y(&mut self, fov_y: f64) {
1309 if !fov_y.is_finite() || fov_y <= 0.0 {
1310 return;
1311 }
1312 self.camera.set_fov_y(fov_y);
1313 }
1314
1315 #[inline]
1319 pub fn constraints(&self) -> &CameraConstraints {
1320 &self.constraints
1321 }
1322
1323 pub fn set_max_pitch(&mut self, max_pitch: f64) {
1325 if !max_pitch.is_finite() || max_pitch <= 0.0 {
1326 return;
1327 }
1328 self.constraints.max_pitch = max_pitch;
1329 }
1330
1331 pub fn set_min_distance(&mut self, min_distance: f64) {
1333 if !min_distance.is_finite() || min_distance <= 0.0 {
1334 return;
1335 }
1336 self.constraints.min_distance = min_distance;
1337 }
1338
1339 pub fn set_max_distance(&mut self, max_distance: f64) {
1341 if !max_distance.is_finite() || max_distance <= 0.0 {
1342 return;
1343 }
1344 self.constraints.max_distance = max_distance;
1345 }
1346
1347 #[inline]
1351 pub fn layers(&self) -> &LayerStack {
1352 &self.layers
1353 }
1354
1355 pub fn push_layer(&mut self, layer: Box<dyn crate::layer::Layer>) {
1357 self.layers.push(layer);
1358 self.style = None;
1359 }
1360
1361 pub fn set_grid_scalar(
1363 &mut self,
1364 name: impl Into<String>,
1365 grid: crate::visualization::GeoGrid,
1366 field: crate::visualization::ScalarField2D,
1367 ramp: crate::visualization::ColorRamp,
1368 ) {
1369 let name = name.into();
1370 if let Some(index) = self.layers.index_of(&name) {
1371 if let Some(layer) = self.layers.get_mut(index) {
1372 if let Some(existing) = layer
1373 .as_any_mut()
1374 .downcast_mut::<crate::visualization::GridScalarLayer>()
1375 {
1376 existing.grid = grid;
1377 existing.field = field;
1378 existing.ramp = ramp;
1379 self.style = None;
1380 return;
1381 }
1382 }
1383 }
1384 let layer = Box::new(crate::visualization::GridScalarLayer::new(
1385 name.clone(),
1386 grid,
1387 field,
1388 ramp,
1389 ));
1390 self.replace_or_push_named_layer(&name, layer);
1391 }
1392
1393 pub fn set_grid_extrusion(
1395 &mut self,
1396 name: impl Into<String>,
1397 grid: crate::visualization::GeoGrid,
1398 field: crate::visualization::ScalarField2D,
1399 ramp: crate::visualization::ColorRamp,
1400 params: crate::visualization::ExtrusionParams,
1401 ) {
1402 let name = name.into();
1403 if let Some(index) = self.layers.index_of(&name) {
1404 if let Some(layer) = self.layers.get_mut(index) {
1405 if let Some(existing) = layer
1406 .as_any_mut()
1407 .downcast_mut::<crate::visualization::GridExtrusionLayer>()
1408 {
1409 existing.grid = grid;
1410 existing.field = field;
1411 existing.ramp = ramp;
1412 existing.params = params;
1413 self.style = None;
1414 return;
1415 }
1416 }
1417 }
1418 let layer = Box::new(
1419 crate::visualization::GridExtrusionLayer::new(name.clone(), grid, field, ramp)
1420 .with_params(params),
1421 );
1422 self.replace_or_push_named_layer(&name, layer);
1423 }
1424
1425 pub fn set_instanced_columns(
1427 &mut self,
1428 name: impl Into<String>,
1429 columns: crate::visualization::ColumnInstanceSet,
1430 ramp: crate::visualization::ColorRamp,
1431 ) {
1432 let name = name.into();
1433 if let Some(index) = self.layers.index_of(&name) {
1434 if let Some(layer) = self.layers.get_mut(index) {
1435 if let Some(existing) = layer
1436 .as_any_mut()
1437 .downcast_mut::<crate::visualization::InstancedColumnLayer>()
1438 {
1439 existing.columns = columns;
1440 existing.ramp = ramp;
1441 self.style = None;
1442 return;
1443 }
1444 }
1445 }
1446 let layer = Box::new(crate::visualization::InstancedColumnLayer::new(
1447 name.clone(),
1448 columns,
1449 ramp,
1450 ));
1451 self.replace_or_push_named_layer(&name, layer);
1452 }
1453
1454 pub fn set_point_cloud(
1456 &mut self,
1457 name: impl Into<String>,
1458 points: crate::visualization::PointInstanceSet,
1459 ramp: crate::visualization::ColorRamp,
1460 ) {
1461 let name = name.into();
1462 if let Some(index) = self.layers.index_of(&name) {
1463 if let Some(layer) = self.layers.get_mut(index) {
1464 if let Some(existing) = layer
1465 .as_any_mut()
1466 .downcast_mut::<crate::visualization::PointCloudLayer>()
1467 {
1468 existing.points = points;
1469 existing.ramp = ramp;
1470 self.style = None;
1471 return;
1472 }
1473 }
1474 }
1475 let layer = Box::new(crate::visualization::PointCloudLayer::new(
1476 name.clone(),
1477 points,
1478 ramp,
1479 ));
1480 self.replace_or_push_named_layer(&name, layer);
1481 }
1482
1483 #[inline]
1485 pub fn style(&self) -> Option<&MapStyle> {
1486 self.style.as_ref()
1487 }
1488
1489 #[inline]
1491 pub fn style_document(&self) -> Option<&StyleDocument> {
1492 self.style.as_ref().map(MapStyle::document)
1493 }
1494
1495 pub fn set_style(&mut self, style: MapStyle) -> Result<(), StyleError> {
1497 self.apply_style_document(style.document())?;
1498 self.style = Some(style);
1499 Ok(())
1500 }
1501
1502 pub fn set_style_document(&mut self, document: StyleDocument) -> Result<(), StyleError> {
1504 self.set_style(MapStyle::from_document(document))
1505 }
1506
1507 pub fn with_style_mut<R>(
1509 &mut self,
1510 mutate: impl FnOnce(&mut StyleDocument) -> R,
1511 ) -> Result<Option<R>, StyleError> {
1512 let mut style: MapStyle = match self.style.take() {
1513 Some(s) => s,
1514 None => return Ok(None),
1515 };
1516 let result = mutate(style.document_mut());
1517 self.apply_style_document(style.document())?;
1518 self.style = Some(style);
1519 Ok(Some(result))
1520 }
1521
1522 pub fn reapply_style(&mut self) -> Result<bool, StyleError> {
1524 let style: MapStyle = match self.style.take() {
1525 Some(s) => s,
1526 None => return Ok(false),
1527 };
1528 self.apply_style_document(style.document())?;
1529 self.style = Some(style);
1530 Ok(true)
1531 }
1532
1533 #[inline]
1537 pub fn terrain(&self) -> &TerrainManager {
1538 &self.terrain
1539 }
1540
1541 pub fn set_terrain(&mut self, terrain: TerrainManager) {
1543 self.terrain = terrain;
1544 if self.style.is_some() {
1545 self.style = None;
1546 }
1547 }
1548
1549 #[inline]
1553 pub fn animator(&self) -> &CameraAnimator {
1554 &self.animator
1555 }
1556
1557 #[inline]
1559 pub fn is_animating(&self) -> bool {
1560 self.animator.is_active()
1561 }
1562
1563 pub fn set_data_update_interval(&mut self, interval: f64) {
1568 self.data_update_interval = interval.max(0.0);
1569 }
1570
1571 #[inline]
1573 pub fn data_update_interval(&self) -> f64 {
1574 self.data_update_interval
1575 }
1576
1577 pub fn set_task_pool(&mut self, pool: Arc<dyn DataTaskPool>) {
1581 self.async_pipeline = Some(AsyncDataPipeline::new(pool));
1582 }
1583
1584 pub fn clear_task_pool(&mut self) {
1586 self.async_pipeline = None;
1587 }
1588
1589 #[inline]
1591 pub fn has_async_pipeline(&self) -> bool {
1592 self.async_pipeline.is_some()
1593 }
1594
1595 #[inline]
1599 pub fn vector_meshes(&self) -> &[VectorMeshData] {
1600 &self.vector_meshes
1601 }
1602
1603 #[inline]
1605 pub fn model_instances(&self) -> &[ModelInstance] {
1606 &self.model_instances
1607 }
1608
1609 #[inline]
1611 pub fn placed_symbols(&self) -> &[PlacedSymbol] {
1612 &self.placed_symbols
1613 }
1614
1615 #[inline]
1617 pub fn symbol_assets(&self) -> &SymbolAssetRegistry {
1618 &self.symbol_assets
1619 }
1620
1621 pub fn feature_state(&self, source_id: &str, feature_id: &str) -> Option<&FeatureState> {
1623 self.feature_state
1624 .get(&FeatureStateId::new(source_id, feature_id))
1625 }
1626
1627 pub fn set_feature_state(
1629 &mut self,
1630 source_id: impl Into<String>,
1631 feature_id: impl Into<String>,
1632 state: FeatureState,
1633 ) {
1634 self.feature_state
1635 .insert(FeatureStateId::new(source_id, feature_id), state);
1636 }
1637
1638 pub fn set_feature_state_property(
1640 &mut self,
1641 source_id: impl Into<String>,
1642 feature_id: impl Into<String>,
1643 key: impl Into<String>,
1644 value: crate::geometry::PropertyValue,
1645 ) {
1646 self.feature_state
1647 .entry(FeatureStateId::new(source_id, feature_id))
1648 .or_default()
1649 .insert(key.into(), value);
1650 }
1651
1652 pub fn remove_feature_state(
1654 &mut self,
1655 source_id: &str,
1656 feature_id: &str,
1657 ) -> Option<FeatureState> {
1658 self.feature_state
1659 .remove(&FeatureStateId::new(source_id, feature_id))
1660 }
1661
1662 pub fn clear_feature_state(&mut self) {
1664 self.feature_state.clear();
1665 }
1666
1667 pub fn resolve_feature_style(
1690 &self,
1691 style_layer_id: &str,
1692 source_id: &str,
1693 feature_id: &str,
1694 ) -> Option<VectorStyle> {
1695 use crate::style::StyleEvalContextFull;
1696
1697 let document = self.style_document()?;
1698 let style_layer = document.layer(style_layer_id)?;
1699 let empty_state: FeatureState = HashMap::new();
1700 let state = self
1701 .feature_state
1702 .get(&FeatureStateId::new(source_id, feature_id))
1703 .unwrap_or(&empty_state);
1704 let ctx = StyleEvalContextFull::new(self.fractional_zoom() as f32, state);
1705 style_layer.resolve_style_with_feature_state(&ctx)
1706 }
1707
1708 pub fn set_interaction_manager(
1714 &mut self,
1715 manager: crate::interaction_manager::InteractionManager,
1716 ) {
1717 self.interaction_manager = Some(manager);
1718 }
1719
1720 pub fn take_interaction_manager(
1722 &mut self,
1723 ) -> Option<crate::interaction_manager::InteractionManager> {
1724 self.interaction_manager.take()
1725 }
1726
1727 pub fn interaction_manager(&self) -> Option<&crate::interaction_manager::InteractionManager> {
1729 self.interaction_manager.as_ref()
1730 }
1731
1732 pub fn interaction_manager_mut(
1736 &mut self,
1737 ) -> Option<&mut crate::interaction_manager::InteractionManager> {
1738 self.interaction_manager.as_mut()
1739 }
1740
1741 pub fn on<F>(
1748 &mut self,
1749 kind: crate::interaction::InteractionEventKind,
1750 callback: F,
1751 ) -> crate::event_emitter::ListenerId
1752 where
1753 F: Fn(&crate::interaction::InteractionEvent) + Send + Sync + 'static,
1754 {
1755 self.event_emitter.on(kind, callback)
1756 }
1757
1758 pub fn once<F>(
1762 &mut self,
1763 kind: crate::interaction::InteractionEventKind,
1764 callback: F,
1765 ) -> crate::event_emitter::ListenerId
1766 where
1767 F: Fn(&crate::interaction::InteractionEvent) + Send + Sync + 'static,
1768 {
1769 self.event_emitter.once(kind, callback)
1770 }
1771
1772 pub fn off(&mut self, id: crate::event_emitter::ListenerId) -> bool {
1774 self.event_emitter.off(id)
1775 }
1776
1777 pub fn dispatch_events(&mut self, events: &[crate::interaction::InteractionEvent]) {
1782 self.event_emitter.dispatch(events);
1783 }
1784
1785 pub fn event_emitter_mut(&mut self) -> &mut crate::event_emitter::EventEmitter {
1787 &mut self.event_emitter
1788 }
1789
1790 pub fn invalidate_symbol_image_dependency(&mut self, image_id: &str) -> usize {
1792 self.invalidate_symbol_dependency_tiles(|deps| deps.images.contains(image_id))
1793 }
1794
1795 pub fn invalidate_symbol_glyph_dependency(
1797 &mut self,
1798 font_stack: &str,
1799 codepoint: char,
1800 ) -> usize {
1801 let glyph = crate::symbols::GlyphKey {
1802 font_stack: font_stack.to_owned(),
1803 codepoint,
1804 };
1805 self.invalidate_symbol_dependency_tiles(|deps| deps.glyphs.contains(&glyph))
1806 }
1807
1808 pub fn set_vector_meshes(&mut self, meshes: Vec<VectorMeshData>) {
1810 let meshes = Arc::new(meshes);
1811 self.pending_vector_meshes = Some(meshes.clone());
1812 self.vector_meshes = meshes;
1813 }
1814
1815 pub fn set_model_instances(&mut self, instances: Vec<ModelInstance>) {
1817 let instances = Arc::new(instances);
1818 self.pending_model_instances = Some(instances.clone());
1819 self.model_instances = instances;
1820 }
1821
1822 pub fn set_placed_symbols(&mut self, symbols: Vec<PlacedSymbol>) {
1824 self.placed_symbols = Arc::new(symbols);
1825 self.symbol_assets
1826 .rebuild_from_symbols(&self.placed_symbols);
1827 }
1828
1829 pub fn update_with_dt(&mut self, dt: f64) {
1835 let was_flying = self.animator.is_flying() || self.animator.is_easing();
1836
1837 self.update_camera(dt);
1838
1839 let is_flying = self.animator.is_flying() || self.animator.is_easing();
1840
1841 if self.async_pipeline.is_some() {
1843 self.dispatch_data_requests();
1844 self.poll_completed_results(dt);
1845 } else {
1846 let animation_active = is_flying;
1858
1859 self.update_tile_layers();
1861
1862 if animation_active && self.data_update_interval > 0.0 {
1863 self.data_update_elapsed += dt;
1864 if self.data_update_elapsed >= self.data_update_interval {
1865 self.data_update_elapsed = 0.0;
1866 self.update_heavy_layers(dt);
1867 }
1868 } else {
1869 self.data_update_elapsed = 0.0;
1870 self.update_heavy_layers(dt);
1871 }
1872 }
1873
1874 if was_flying && !is_flying && self.async_pipeline.is_none() {
1877 self.update_tile_layers();
1878 self.update_heavy_layers(dt);
1879 }
1880
1881 self.style_time += dt.max(0.0);
1883
1884 self.placeholder_time += dt;
1886 self.loading_placeholders = Arc::new(PlaceholderGenerator::generate(
1887 &self.visible_tiles,
1888 &self.placeholder_style,
1889 self.placeholder_time,
1890 ));
1891
1892 if let Err(e) = self.sync_attached_style_runtime() {
1893 log::warn!("style sync error: {e:?}");
1894 }
1895
1896 self.apply_pending_frame_overrides();
1897
1898 {
1900 let bg = self.background_color().unwrap_or([1.0, 1.0, 1.0, 1.0]);
1901 let pitch = self.camera.pitch();
1902 let distance = self.camera.distance();
1903 let style_fog = self.style.as_ref().and_then(|s| s.document().fog());
1904 let effective_fog = self.fog_config.as_ref().or(style_fog);
1905 self.computed_fog = crate::style::compute_fog(pitch, distance, bg, effective_fog);
1906 }
1907
1908 {
1910 let style_lights = self.style.as_ref().and_then(|s| s.document().lights());
1911 let effective_lights = self.light_config.as_ref().or(style_lights);
1912 let default_lights = crate::style::LightConfig::default();
1913 let config = effective_lights.unwrap_or(&default_lights);
1914 self.computed_lighting = crate::style::compute_lighting(config);
1915
1916 if self.computed_lighting.shadows_enabled {
1918 let vp = self.camera.view_projection_matrix();
1919 let dir = self.computed_lighting.directional_dir;
1920 let cam_dist = self.camera.distance();
1921 self.computed_shadow =
1922 crate::style::compute_shadow_cascades(&vp, dir, cam_dist, &config.shadow);
1923 } else {
1924 self.computed_shadow = crate::style::ComputedShadow::default();
1925 }
1926 }
1927
1928 {
1930 let style_sky = self.style.as_ref().and_then(|s| s.document().sky());
1931 let effective_sky = self.sky_config.as_ref().or(style_sky);
1932 if let Some(sky) = effective_sky {
1933 let style_lights = self.style.as_ref().and_then(|s| s.document().lights());
1935 let effective_lights = self.light_config.as_ref().or(style_lights);
1936 let fallback_sun = effective_lights
1937 .map(|l| l.directional.direction)
1938 .unwrap_or([210.0, 45.0]);
1939 self.computed_sky = crate::style::compute_sky(sky, fallback_sun);
1940 } else {
1941 self.computed_sky = crate::style::ComputedSky::default();
1942 }
1943 }
1944 }
1945
1946 pub fn handle_input(&mut self, event: InputEvent) {
1953 if let InputEvent::Touch(contact) = event {
1954 let derived = self.gesture_recognizer.process(contact);
1955 for e in derived {
1956 CameraController::handle_event(&mut self.camera, e, &self.constraints);
1957 }
1958 return;
1959 }
1960 CameraController::handle_event(&mut self.camera, event, &self.constraints);
1961 }
1962
1963 pub fn gesture_recognizer(&self) -> &crate::gesture::GestureRecognizer {
1965 &self.gesture_recognizer
1966 }
1967
1968 pub fn update(&mut self) {
1970 self.update_with_dt(1.0 / 60.0);
1971 }
1972
1973 pub fn fly_to(&mut self, options: crate::camera_animator::FlyToOptions) {
1975 self.animator.start_fly_to(&mut self.camera, &options);
1976 }
1977
1978 pub fn ease_to(&mut self, options: crate::camera_animator::EaseToOptions) {
1980 self.animator.start_ease_to(&mut self.camera, &options);
1981 }
1982
1983 pub fn jump_to(
1985 &mut self,
1986 target: GeoCoord,
1987 distance: f64,
1988 pitch: Option<f64>,
1989 yaw: Option<f64>,
1990 ) {
1991 self.animator.cancel();
1992 self.camera.set_target(target);
1993 self.camera.set_distance(distance);
1994 if let Some(p) = pitch {
1995 self.camera.set_pitch(p);
1996 }
1997 if let Some(y) = yaw {
1998 self.camera.set_yaw(y);
1999 }
2000 }
2001
2002 pub fn update_camera(&mut self, dt: f64) {
2004 self.animator.tick(&mut self.camera, dt);
2005
2006 {
2013 let target = *self.camera.target();
2014 let wrapped_lon = wrap_lon_180(target.lon);
2015 if (wrapped_lon - target.lon).abs() > 1e-12 {
2016 self.camera
2017 .set_target(GeoCoord::from_lat_lon(target.lat, wrapped_lon));
2018 }
2019 }
2020
2021 let mpp = self.camera.meters_per_pixel();
2022 self.zoom_level = Self::mpp_to_zoom(mpp);
2023 self.viewport_bounds = self.compute_viewport_bounds();
2024 self.scene_viewport_bounds = self.compute_scene_viewport_bounds();
2025 self.frustum = Some(Frustum::from_view_projection(
2026 &self.camera.view_projection_matrix(),
2027 ));
2028 self.update_camera_motion_state(dt);
2029
2030 let fractional_zoom = self.fractional_zoom();
2031 self.camera_zoom_delta = self
2032 .previous_fractional_zoom
2033 .map_or(0.0, |previous| fractional_zoom - previous);
2034 self.previous_fractional_zoom = Some(fractional_zoom);
2035 }
2036
2037 pub fn frame_output(&self) -> FrameOutput {
2039 FrameOutput {
2040 view_projection: self.camera.view_projection_matrix(),
2041 frustum: self.frustum.clone(),
2042 tiles: Arc::clone(&self.visible_tiles),
2043 terrain: Arc::clone(&self.terrain_meshes),
2044 hillshade: Arc::clone(&self.hillshade_rasters),
2045 vectors: Arc::clone(&self.vector_meshes),
2046 models: Arc::clone(&self.model_instances),
2047 symbols: Arc::clone(&self.placed_symbols),
2048 visualization: Arc::clone(&self.visualization_overlays),
2049 placeholders: Arc::clone(&self.loading_placeholders),
2050 image_overlays: Arc::clone(&self.image_overlays),
2051 zoom_level: self.zoom_level,
2052 }
2053 }
2054
2055 pub fn elevation_at(&self, coord: &GeoCoord) -> Option<f64> {
2057 self.terrain.elevation_at(coord)
2058 }
2059
2060 pub fn screen_to_geo(&self, px: f64, py: f64) -> Option<GeoCoord> {
2062 self.camera.screen_to_geo(px, py)
2063 }
2064
2065 pub fn geo_to_screen(&self, geo: &GeoCoord) -> Option<(f64, f64)> {
2071 self.camera.geo_to_screen(geo)
2072 }
2073
2074 pub fn fit_bounds(&mut self, bounds: &GeoBounds, options: &FitBoundsOptions) {
2081 let center = bounds.center();
2082
2083 let sw_world = WebMercator::project_clamped(&bounds.sw());
2085 let ne_world = WebMercator::project_clamped(&bounds.ne());
2086
2087 let dx = (ne_world.position.x - sw_world.position.x).abs();
2088 let dy = (ne_world.position.y - sw_world.position.y).abs();
2089
2090 let vw =
2092 (self.camera.viewport_width() as f64 - options.padding.left - options.padding.right)
2093 .max(1.0);
2094 let vh =
2095 (self.camera.viewport_height() as f64 - options.padding.top - options.padding.bottom)
2096 .max(1.0);
2097
2098 let mpp_x = dx / vw;
2100 let mpp_y = dy / vh;
2101 let mpp = mpp_x.max(mpp_y);
2102
2103 let zoom = if mpp <= 0.0 || !mpp.is_finite() {
2105 MAX_ZOOM as f64
2106 } else {
2107 (WGS84_CIRCUMFERENCE / (mpp * TILE_PX))
2108 .log2()
2109 .clamp(0.0, MAX_ZOOM as f64)
2110 };
2111
2112 let zoom = match options.max_zoom {
2114 Some(mz) => zoom.min(mz),
2115 None => zoom,
2116 };
2117
2118 if options.animate {
2119 let fly = crate::camera_animator::FlyToOptions {
2120 center: Some(center),
2121 zoom: Some(zoom),
2122 bearing: options.bearing,
2123 pitch: options.pitch,
2124 duration: options.duration,
2125 ..Default::default()
2126 };
2127 self.fly_to(fly);
2128 } else {
2129 let is_perspective = matches!(self.camera.mode(), CameraMode::Perspective);
2131 let distance = {
2132 let mpp = WGS84_CIRCUMFERENCE / (2.0_f64.powf(zoom) * TILE_PX);
2133 let vis_h = mpp * self.camera.viewport_height().max(1) as f64;
2134 if is_perspective {
2135 vis_h / (2.0 * (self.camera.fov_y() / 2.0).tan())
2136 } else {
2137 vis_h / 2.0
2138 }
2139 };
2140 self.jump_to(center, distance, options.pitch, options.bearing);
2141 }
2142 }
2143
2144 pub fn ray_to_geo(&self, origin: glam::DVec3, direction: glam::DVec3) -> Option<GeoCoord> {
2146 if direction.z.abs() < 1e-12 {
2147 return None;
2148 }
2149 let t = -origin.z / direction.z;
2150 if t < 0.0 {
2151 return None;
2152 }
2153 let hit = origin + direction * t;
2154 let world = self.camera.target_world();
2155 let world_hit = WorldCoord::new(hit.x + world.x, hit.y + world.y, 0.0);
2156 Some(self.camera.projection().unproject(&world_hit))
2157 }
2158
2159 pub fn ray_to_geo_on_terrain(
2164 &self,
2165 origin: glam::DVec3,
2166 direction: glam::DVec3,
2167 ) -> Option<GeoCoord> {
2168 if !self.terrain.enabled() {
2169 return self.ray_to_geo(origin, direction);
2170 }
2171
2172 let world = self.camera.target_world();
2173 let steps = 64;
2174 let max_t = self.camera.distance() * 4.0;
2175 let step = max_t / steps as f64;
2176
2177 let mut prev_above = true;
2178 for i in 0..=steps {
2179 let t = step * i as f64;
2180 let p = origin + direction * t;
2181 let world_hit = WorldCoord::new(p.x + world.x, p.y + world.y, p.z);
2182 let geo = self.camera.projection().unproject(&world_hit);
2183 let elev = self.terrain.elevation_at(&geo).unwrap_or(0.0);
2184 let above = p.z >= elev;
2185 if !above && prev_above && i > 0 {
2186 let prev_t = step * (i - 1) as f64;
2188 let prev_p = origin + direction * prev_t;
2189 let prev_world = WorldCoord::new(prev_p.x + world.x, prev_p.y + world.y, prev_p.z);
2190 let prev_geo = self.camera.projection().unproject(&prev_world);
2191 let prev_elev = self.terrain.elevation_at(&prev_geo).unwrap_or(0.0);
2192 let prev_height = prev_p.z - prev_elev;
2193 let curr_height = p.z - elev;
2194 let frac = prev_height / (prev_height - curr_height);
2195 let hit_t = prev_t + (t - prev_t) * frac;
2196 let hit = origin + direction * hit_t;
2197 let hit_world = WorldCoord::new(hit.x + world.x, hit.y + world.y, hit.z);
2198 return Some(self.camera.projection().unproject(&hit_world));
2199 }
2200 prev_above = above;
2201 }
2202 self.ray_to_geo(origin, direction)
2204 }
2205
2206 pub fn ray_to_flat_geo(&self, origin: glam::DVec3, direction: glam::DVec3) -> Option<GeoCoord> {
2208 self.ray_to_geo(origin, direction)
2209 }
2210
2211 pub fn screen_to_geo_on_terrain(&self, px: f64, py: f64) -> Option<GeoCoord> {
2213 let (origin, direction) = self.camera.screen_to_ray(px, py);
2214 self.ray_to_geo_on_terrain(origin, direction)
2215 }
2216
2217 fn mercator_camera_world(&self) -> (f64, f64) {
2234 let w = WebMercator::project(self.camera.target());
2235 (w.position.x, w.position.y)
2236 }
2237
2238 fn update_camera_motion_state(&mut self, dt: f64) {
2239 self.camera_motion_time_seconds += dt.max(0.0);
2240
2241 let current_wrapped = self.mercator_camera_world();
2242 let current_target = if let Some(last) = self.camera_motion_samples.back() {
2243 glam::DVec2::new(
2244 last.target_world.x
2245 + heavy_layers::wrapped_world_delta(current_wrapped.0 - last.target_world.x),
2246 current_wrapped.1,
2247 )
2248 } else {
2249 glam::DVec2::new(current_wrapped.0, current_wrapped.1)
2250 };
2251
2252 self.camera_motion_samples.push_back(CameraMotionSample {
2253 time_seconds: self.camera_motion_time_seconds,
2254 target_world: current_target,
2255 });
2256 self.trim_camera_motion_samples();
2257 self.recompute_camera_motion_state();
2258 }
2259
2260 fn trim_camera_motion_samples(&mut self) {
2261 let max_samples = self.camera_velocity_config.sample_window.max(1) + 1;
2262 while self.camera_motion_samples.len() > max_samples {
2263 self.camera_motion_samples.pop_front();
2264 }
2265 }
2266
2267 fn recompute_camera_motion_state(&mut self) {
2268 let pan_velocity_world = if let (Some(first), Some(last)) = (
2269 self.camera_motion_samples.front(),
2270 self.camera_motion_samples.back(),
2271 ) {
2272 let dt = last.time_seconds - first.time_seconds;
2273 if dt > 1e-9 {
2274 (last.target_world - first.target_world) / dt
2275 } else {
2276 glam::DVec2::ZERO
2277 }
2278 } else {
2279 glam::DVec2::ZERO
2280 };
2281
2282 let current_wrapped = self.mercator_camera_world();
2283 let current_target_world = glam::DVec2::new(current_wrapped.0, current_wrapped.1);
2284 let look_ahead = self.camera_velocity_config.look_ahead_seconds.max(0.0);
2285 let predicted_delta = pan_velocity_world * look_ahead;
2286
2287 self.camera_motion_state = CameraMotionState {
2288 pan_velocity_world,
2289 predicted_target_world: current_target_world + predicted_delta,
2290 predicted_viewport_bounds: heavy_layers::translated_world_bounds(
2291 &self.viewport_bounds,
2292 predicted_delta,
2293 ),
2294 };
2295 }
2296
2297 fn mpp_to_zoom(mpp: f64) -> u8 {
2300 if mpp <= 0.0 || !mpp.is_finite() {
2301 return MAX_ZOOM;
2302 }
2303 let z = (WGS84_CIRCUMFERENCE / (mpp * TILE_PX)).log2();
2304 (z.round() as u8).min(MAX_ZOOM)
2305 }
2306
2307 fn compute_viewport_bounds(&self) -> WorldBounds {
2309 use rustial_math::WebMercator;
2310
2311 let w = self.camera.viewport_width() as f64;
2312 let h = self.camera.viewport_height() as f64;
2313
2314 if w <= 0.0 || h <= 0.0 {
2315 let zero = WorldCoord::new(0.0, 0.0, 0.0);
2316 return WorldBounds::new(zero, zero);
2317 }
2318
2319 let cam = &self.camera;
2320
2321 if cam.mode() == CameraMode::Orthographic {
2323 let half_w = cam.distance() * (w / h).max(1.0);
2324 let half_h = cam.distance() / (w / h).min(1.0);
2325 let target = WebMercator::project(cam.target());
2326 let overscan = 1.3;
2327 return WorldBounds::new(
2328 WorldCoord::new(
2329 target.position.x - half_w * overscan,
2330 target.position.y - half_h * overscan,
2331 0.0,
2332 ),
2333 WorldCoord::new(
2334 target.position.x + half_w * overscan,
2335 target.position.y + half_h * overscan,
2336 0.0,
2337 ),
2338 );
2339 }
2340
2341 let mut min_x = f64::MAX;
2342 let mut min_y = f64::MAX;
2343 let mut max_x = f64::MIN;
2344 let mut max_y = f64::MIN;
2345 let mut any_hit = false;
2346
2347 for (sx, sy) in viewport_sample_points(w, h) {
2348 if let Some(geo) = cam.screen_to_geo(sx, sy) {
2349 let world = WebMercator::project_clamped(&geo);
2350 min_x = min_x.min(world.position.x);
2351 min_y = min_y.min(world.position.y);
2352 max_x = max_x.max(world.position.x);
2353 max_y = max_y.max(world.position.y);
2354 any_hit = true;
2355 }
2356 }
2357
2358 if !any_hit {
2359 let target = WebMercator::project(cam.target());
2360 let mpp = cam.meters_per_pixel();
2361 let half_w = mpp * w * 0.5;
2362 let half_h = mpp * h * 0.5;
2363 return WorldBounds::new(
2364 WorldCoord::new(target.position.x - half_w, target.position.y - half_h, 0.0),
2365 WorldCoord::new(target.position.x + half_w, target.position.y + half_h, 0.0),
2366 );
2367 }
2368
2369 let overscan = perspective_viewport_overscan(cam.pitch());
2370 let cx = (min_x + max_x) * 0.5;
2371 let cy = (min_y + max_y) * 0.5;
2372 let hw = (max_x - min_x) * 0.5 * overscan;
2373 let hh = (max_y - min_y) * 0.5 * overscan;
2374 WorldBounds::new(
2375 WorldCoord::new(cx - hw, cy - hh, 0.0),
2376 WorldCoord::new(cx + hw, cy + hh, 0.0),
2377 )
2378 }
2379
2380 fn compute_scene_viewport_bounds(&self) -> WorldBounds {
2382 let w = self.camera.viewport_width() as f64;
2383 let h = self.camera.viewport_height() as f64;
2384
2385 if w <= 0.0 || h <= 0.0 {
2386 let zero = WorldCoord::new(0.0, 0.0, 0.0);
2387 return WorldBounds::new(zero, zero);
2388 }
2389
2390 let cam = &self.camera;
2391 let proj = cam.projection();
2392
2393 if cam.mode() == CameraMode::Orthographic {
2394 let half_w = cam.distance() * (w / h).max(1.0);
2395 let half_h = cam.distance() / (w / h).min(1.0);
2396 let target = proj.project(cam.target());
2397 let overscan = 1.3;
2398 return WorldBounds::new(
2399 WorldCoord::new(
2400 target.position.x - half_w * overscan,
2401 target.position.y - half_h * overscan,
2402 0.0,
2403 ),
2404 WorldCoord::new(
2405 target.position.x + half_w * overscan,
2406 target.position.y + half_h * overscan,
2407 0.0,
2408 ),
2409 );
2410 }
2411
2412 let mut min_x = f64::MAX;
2413 let mut min_y = f64::MAX;
2414 let mut max_x = f64::MIN;
2415 let mut max_y = f64::MIN;
2416 let mut any_hit = false;
2417
2418 for (sx, sy) in viewport_sample_points(w, h) {
2419 if let Some(geo) = cam.screen_to_geo(sx, sy) {
2420 let world = proj.project(&geo);
2421 min_x = min_x.min(world.position.x);
2422 min_y = min_y.min(world.position.y);
2423 max_x = max_x.max(world.position.x);
2424 max_y = max_y.max(world.position.y);
2425 any_hit = true;
2426 }
2427 }
2428
2429 if !any_hit {
2430 let target = proj.project(cam.target());
2431 let mpp = cam.meters_per_pixel();
2432 let half_w = mpp * w * 0.5;
2433 let half_h = mpp * h * 0.5;
2434 return WorldBounds::new(
2435 WorldCoord::new(target.position.x - half_w, target.position.y - half_h, 0.0),
2436 WorldCoord::new(target.position.x + half_w, target.position.y + half_h, 0.0),
2437 );
2438 }
2439
2440 let overscan = perspective_viewport_overscan(cam.pitch());
2441 let cx = (min_x + max_x) * 0.5;
2442 let cy = (min_y + max_y) * 0.5;
2443 let hw = (max_x - min_x) * 0.5 * overscan;
2444 let hh = (max_y - min_y) * 0.5 * overscan;
2445 WorldBounds::new(
2446 WorldCoord::new(cx - hw, cy - hh, 0.0),
2447 WorldCoord::new(cx + hw, cy + hh, 0.0),
2448 )
2449 }
2450
2451 fn sync_attached_style_runtime(&mut self) -> Result<(), StyleError> {
2453 let Some(style) = self.style.as_ref() else {
2454 return Ok(());
2455 };
2456 let doc = style.document();
2457 let ctx = crate::style::StyleEvalContext {
2458 zoom: self.fractional_zoom() as f32,
2459 };
2460 let now = self.style_time;
2461
2462 for style_layer in doc.layers() {
2464 let (layer_id, transition_spec, opacity, color, secondary_color, width, height, base) =
2465 extract_transition_props(style_layer, ctx, &doc.transition());
2466 let state = self
2467 .layer_transitions
2468 .entry(layer_id.to_owned())
2469 .or_insert_with(|| {
2470 crate::style::LayerTransitionState::from_initial(
2471 transition_spec,
2472 opacity,
2473 color,
2474 secondary_color,
2475 width,
2476 height,
2477 base,
2478 )
2479 });
2480 state.update(now, opacity, color, secondary_color, width, height, base);
2481 }
2482 Ok(())
2483 }
2484
2485 fn replace_or_push_named_layer(&mut self, name: &str, layer: Box<dyn crate::layer::Layer>) {
2487 if let Some(index) = self.layers.index_of(name) {
2488 let _ = self.layers.remove(index);
2489 self.layers.insert(index, layer);
2490 } else {
2491 self.layers.push(layer);
2492 }
2493 self.style = None;
2494 }
2495}
2496
2497fn extract_transition_props<'a>(
2506 layer: &'a crate::style::StyleLayer,
2507 ctx: crate::style::StyleEvalContext,
2508 global_transition: &crate::style::TransitionSpec,
2509) -> (
2510 &'a str,
2511 crate::style::TransitionSpec,
2512 f32,
2513 [f32; 4],
2514 [f32; 4],
2515 f32,
2516 f32,
2517 f32,
2518) {
2519 use crate::style::StyleLayer;
2520
2521 let meta = layer.meta();
2522 let spec = if meta.transition.is_active() {
2523 meta.transition
2524 } else {
2525 *global_transition
2526 };
2527 let opacity = meta.opacity.evaluate_with_context(ctx);
2528
2529 let transparent: [f32; 4] = [0.0, 0.0, 0.0, 0.0];
2530 let (color, secondary_color, width, height, base) = match layer {
2531 StyleLayer::Fill(f) => (
2532 f.fill_color.evaluate_with_context(ctx),
2533 f.outline_color.evaluate_with_context(ctx),
2534 f.outline_width.evaluate_with_context(ctx),
2535 0.0,
2536 0.0,
2537 ),
2538 StyleLayer::Line(l) => (
2539 l.color.evaluate_with_context(ctx),
2540 transparent,
2541 l.width.evaluate_with_context(ctx),
2542 0.0,
2543 0.0,
2544 ),
2545 StyleLayer::Circle(c) => (
2546 c.color.evaluate_with_context(ctx),
2547 c.stroke_color.evaluate_with_context(ctx),
2548 c.radius.evaluate_with_context(ctx),
2549 0.0,
2550 0.0,
2551 ),
2552 StyleLayer::FillExtrusion(e) => (
2553 e.color.evaluate_with_context(ctx),
2554 transparent,
2555 0.0,
2556 e.height.evaluate_with_context(ctx),
2557 e.base.evaluate_with_context(ctx),
2558 ),
2559 StyleLayer::Symbol(s) => (
2560 s.color.evaluate_with_context(ctx),
2561 s.halo_color.evaluate_with_context(ctx),
2562 s.size.evaluate_with_context(ctx),
2563 0.0,
2564 0.0,
2565 ),
2566 StyleLayer::Heatmap(h) => (
2567 h.color.evaluate_with_context(ctx),
2568 transparent,
2569 h.radius.evaluate_with_context(ctx),
2570 0.0,
2571 0.0,
2572 ),
2573 StyleLayer::Background(b) => (
2574 b.color.evaluate_with_context(ctx),
2575 transparent,
2576 0.0,
2577 0.0,
2578 0.0,
2579 ),
2580 _ => (transparent, transparent, 0.0, 0.0, 0.0),
2581 };
2582
2583 (
2584 meta.id.as_str(),
2585 spec,
2586 opacity,
2587 color,
2588 secondary_color,
2589 width,
2590 height,
2591 base,
2592 )
2593}