1use crate::async_data::{
65 AsyncDataPipeline, DataTaskPool, TerrainTaskInput, VectorCacheKey,
66 VectorTaskInput,
67};
68use crate::picking::{
69 HitCategory, HitProvenance, PickHit, PickOptions, PickQuery, PickResult,
70};
71use crate::geometry::{FeatureCollection, PropertyValue};
72use crate::layer::Layer;
73use crate::layer::LayerId;
74use crate::query::{
75 feature_id_for_feature, geometry_hit_distance, FeatureState, FeatureStateId, QueriedFeature,
76 QueryOptions,
77};
78use crate::camera::{Camera, CameraConstraints, CameraController, CameraMode};
79use crate::camera_projection::CameraProjection;
80use crate::camera_animator::CameraAnimator;
81use crate::geo_wrap::wrap_lon_180;
82use crate::input::InputEvent;
83use crate::layers::{FeatureProvenance, LayerStack, VectorMeshData, VectorStyle};
84use crate::models::ModelInstance;
85use crate::style::{MapStyle, StyleDocument, StyleError};
86use crate::streamed_payload::{
87 collect_affected_symbol_payloads, prune_affected_symbol_payloads,
88 resolve_streamed_vector_layer_refresh, StreamedPayloadView, StreamedSymbolPayloadKey,
89 StreamedVectorLayerRefreshSpec, SymbolDependencyPayload, SymbolQueryPayload,
90 TileQueryPayload, VisiblePlacedSymbolView, symbol_query_payloads_from_optional,
91};
92use crate::symbols::{PlacedSymbol, SymbolAssetRegistry, SymbolPlacementEngine};
93use crate::loading_placeholder::{LoadingPlaceholder, PlaceholderGenerator, PlaceholderStyle};
94use crate::terrain::{PreparedHillshadeRaster, TerrainConfig, TerrainManager, TerrainMeshData};
95use crate::tile_manager::{VisibleTile, ZoomPrefetchDirection};
96use crate::tile_cache::TileCacheStats;
97use crate::tile_lifecycle::TileLifecycleDiagnostics;
98use crate::tile_manager::{TileManagerCounters, TileSelectionStats};
99use crate::tile_source::TileSourceDiagnostics;
100use crate::tile_request_coordinator::{
101 CoordinatorConfig, CoordinatorStats, SourcePriority, TileRequestCoordinator,
102};
103use rustial_math::{
104 visible_tiles, visible_tiles_flat_view_with_config, FlatTileSelectionConfig, Frustum,
105 GeoBounds, GeoCoord, TileId, WebMercator, WorldBounds, WorldCoord, MAX_ZOOM,
106};
107use std::collections::{HashMap, HashSet, VecDeque};
108use std::sync::Arc;
109
110mod picking;
111mod tile_selection;
112mod heavy_layers;
113mod async_pipeline;
114#[cfg(test)]
115mod tests;
116
117const WGS84_CIRCUMFERENCE: f64 = 2.0 * std::f64::consts::PI * 6_378_137.0;
126
127const TILE_PX: f64 = 256.0;
130
131const SPECULATIVE_PREFETCH_BUDGET_FRACTION: f64 = 0.25;
133
134const DEFAULT_SPECULATIVE_PREFETCH_REQUEST_BUDGET: usize = 8;
136
137const ZOOM_DIRECTION_PREFETCH_THRESHOLD: f64 = 0.01;
139
140fn viewport_sample_points(width: f64, height: f64) -> Vec<(f64, f64)> {
141 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];
142 let mut samples = Vec::with_capacity(FRACTIONS.len() * FRACTIONS.len());
143 for fy in FRACTIONS {
144 for fx in FRACTIONS {
145 samples.push((width * fx, height * fy));
146 }
147 }
148 samples
149}
150
151fn perspective_viewport_overscan(pitch: f64) -> f64 {
152 let normalized_pitch = (pitch / std::f64::consts::FRAC_PI_2).clamp(0.0, 1.0);
153 1.3 + 0.5 * normalized_pitch
154}
155
156fn terrain_base_tile_budget(required_tiles: usize) -> usize {
157 required_tiles.max(80).min(256)
158}
159
160fn terrain_horizon_tile_budget(base_budget: usize, pitch: f64) -> usize {
161 if pitch <= 0.5 {
162 0
163 } else {
164 (base_budget / 3).max(24).min(96)
165 }
166}
167
168#[derive(Debug, Clone, Copy, PartialEq)]
174pub struct FitBoundsPadding {
175 pub top: f64,
177 pub bottom: f64,
179 pub left: f64,
181 pub right: f64,
183}
184
185impl Default for FitBoundsPadding {
186 fn default() -> Self {
187 Self { top: 0.0, bottom: 0.0, left: 0.0, right: 0.0 }
188 }
189}
190
191impl FitBoundsPadding {
192 pub fn uniform(px: f64) -> Self {
194 Self { top: px, bottom: px, left: px, right: px }
195 }
196}
197
198#[derive(Debug, Clone)]
202pub struct FitBoundsOptions {
203 pub padding: FitBoundsPadding,
205 pub max_zoom: Option<f64>,
207 pub animate: bool,
209 pub duration: Option<f64>,
211 pub bearing: Option<f64>,
213 pub pitch: Option<f64>,
215}
216
217impl Default for FitBoundsOptions {
218 fn default() -> Self {
219 Self {
220 padding: FitBoundsPadding::default(),
221 max_zoom: None,
222 animate: true,
223 duration: None,
224 bearing: None,
225 pitch: None,
226 }
227 }
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
240struct SyncVectorCacheKey {
241 layer_id: LayerId,
243 style_fingerprint: u64,
245 data_generation: u64,
247 projection: CameraProjection,
249}
250
251#[derive(Clone)]
253struct SyncVectorCacheEntry {
254 mesh: VectorMeshData,
255}
256
257#[derive(Debug, Default)]
276pub struct FrameOutput {
277 pub view_projection: glam::DMat4,
282
283 pub frustum: Option<Frustum>,
287
288 pub tiles: Arc<Vec<VisibleTile>>,
292
293
294 pub terrain: Arc<Vec<TerrainMeshData>>,
298
299
300 pub hillshade: Arc<Vec<PreparedHillshadeRaster>>,
302
303 pub vectors: Arc<Vec<VectorMeshData>>,
307
308 pub models: Arc<Vec<ModelInstance>>,
312
313 pub symbols: Arc<Vec<PlacedSymbol>>,
315
316 pub visualization: Arc<Vec<crate::visualization::VisualizationOverlay>>,
318
319 pub placeholders: Arc<Vec<LoadingPlaceholder>>,
325
326 pub image_overlays: Arc<Vec<crate::layers::ImageOverlayData>>,
332
333 pub zoom_level: u8,
335}
336
337#[derive(Debug, Clone, Default)]
339pub struct TilePipelineDiagnostics {
340 pub layer_name: String,
342 pub visible_tiles: usize,
344 pub visible_loaded_tiles: usize,
346 pub visible_fallback_tiles: usize,
348 pub visible_missing_tiles: usize,
350 pub visible_overzoomed_tiles: usize,
352 pub selection_stats: TileSelectionStats,
354 pub counters: TileManagerCounters,
356 pub cache_stats: TileCacheStats,
358 pub source_diagnostics: Option<TileSourceDiagnostics>,
360}
361
362#[derive(Debug, Clone, PartialEq)]
364pub struct CameraVelocityConfig {
365 pub sample_window: usize,
371 pub look_ahead_seconds: f64,
373}
374
375impl Default for CameraVelocityConfig {
376 fn default() -> Self {
377 Self {
378 sample_window: 6,
379 look_ahead_seconds: 0.5,
380 }
381 }
382}
383
384#[derive(Debug, Clone, Copy, PartialEq)]
385struct CameraMotionSample {
386 time_seconds: f64,
387 target_world: glam::DVec2,
388}
389
390#[derive(Debug, Clone, PartialEq)]
392pub struct CameraMotionState {
393 pub pan_velocity_world: glam::DVec2,
395 pub predicted_target_world: glam::DVec2,
398 pub predicted_viewport_bounds: WorldBounds,
400}
401
402impl Default for CameraMotionState {
403 fn default() -> Self {
404 let zero = WorldCoord::new(0.0, 0.0, 0.0);
405 Self {
406 pan_velocity_world: glam::DVec2::ZERO,
407 predicted_target_world: glam::DVec2::ZERO,
408 predicted_viewport_bounds: WorldBounds::new(zero, zero),
409 }
410 }
411}
412
413pub struct MapState {
449 camera: Camera,
453
454 constraints: CameraConstraints,
456
457 pub(crate) layers: LayerStack,
459
460 style: Option<MapStyle>,
462
463 pub(crate) terrain: TerrainManager,
465
466 animator: CameraAnimator,
468
469 data_update_interval: f64,
488
489 data_update_elapsed: f64,
491
492 async_pipeline: Option<AsyncDataPipeline>,
502
503 zoom_level: u8,
507
508 viewport_bounds: WorldBounds,
510
511 scene_viewport_bounds: WorldBounds,
513
514 frustum: Option<Frustum>,
516
517 terrain_meshes: Arc<Vec<TerrainMeshData>>,
519
520 pending_terrain_meshes: Option<Arc<Vec<TerrainMeshData>>>,
522
523
524 hillshade_rasters: Arc<Vec<PreparedHillshadeRaster>>,
526
527
528 visible_tiles: Arc<Vec<VisibleTile>>,
530
531
532 pub(crate) vector_meshes: Arc<Vec<VectorMeshData>>,
536
537 pending_vector_meshes: Option<Arc<Vec<VectorMeshData>>>,
539
540
541 pub(crate) model_instances: Arc<Vec<ModelInstance>>,
544
545 pending_model_instances: Option<Arc<Vec<ModelInstance>>>,
547
548 placed_symbols: Arc<Vec<PlacedSymbol>>,
550
551
552 symbol_assets: SymbolAssetRegistry,
554
555 symbol_placement: SymbolPlacementEngine,
557
558 feature_state: HashMap<FeatureStateId, FeatureState>,
560
561 streamed_vector_sources: HashMap<String, crate::layers::TileLayer>,
563
564 streamed_vector_layer_fingerprints: HashMap<String, u64>,
566
567 streamed_vector_query_payloads: HashMap<String, Vec<TileQueryPayload>>,
569
570 streamed_symbol_query_payloads: HashMap<String, Vec<SymbolQueryPayload>>,
572
573 streamed_symbol_dependency_payloads: HashMap<String, Vec<SymbolDependencyPayload>>,
575
576 dirty_streamed_symbol_layers: HashSet<String>,
578
579 dirty_streamed_symbol_tiles: HashMap<String, HashSet<TileId>>,
581
582 visualization_overlays: Arc<Vec<crate::visualization::VisualizationOverlay>>,
584
585 image_overlays: Arc<Vec<crate::layers::ImageOverlayData>>,
587
588 placeholder_style: PlaceholderStyle,
590
591 loading_placeholders: Arc<Vec<LoadingPlaceholder>>,
593
594 placeholder_time: f64,
596
597 interaction_manager: Option<crate::interaction_manager::InteractionManager>,
600
601 sync_vector_cache: HashMap<SyncVectorCacheKey, SyncVectorCacheEntry>,
607
608 request_coordinator: TileRequestCoordinator,
614
615 camera_velocity_config: CameraVelocityConfig,
617
618 camera_motion_time_seconds: f64,
620
621 camera_motion_samples: VecDeque<CameraMotionSample>,
623
624 camera_motion_state: CameraMotionState,
626
627 camera_zoom_delta: f64,
629
630 previous_fractional_zoom: Option<f64>,
632
633 prefetch_route: Option<Vec<GeoCoord>>,
639
640 event_emitter: crate::event_emitter::EventEmitter,
646
647 gesture_recognizer: crate::gesture::GestureRecognizer,
652
653 fog_config: Option<crate::style::FogConfig>,
655
656 computed_fog: crate::style::ComputedFog,
658}
659
660impl Default for MapState {
665 fn default() -> Self {
666 let zero = WorldCoord::new(0.0, 0.0, 0.0);
667 Self {
668 camera: Camera::default(),
669 constraints: CameraConstraints::default(),
670 layers: LayerStack::new(),
671 style: None,
672 terrain: TerrainManager::new(TerrainConfig::default(), 256),
673 animator: CameraAnimator::new(),
674 data_update_interval: 0.15,
675 data_update_elapsed: 0.0,
676 async_pipeline: None,
677 zoom_level: 0,
678 viewport_bounds: WorldBounds::new(zero, zero),
679 scene_viewport_bounds: WorldBounds::new(zero, zero),
680 frustum: None,
681 terrain_meshes: Arc::new(Vec::new()),
682 pending_terrain_meshes: None,
683 hillshade_rasters: Arc::new(Vec::new()),
684 visible_tiles: Arc::new(Vec::new()),
685 vector_meshes: Arc::new(Vec::new()),
686 pending_vector_meshes: None,
687 model_instances: Arc::new(Vec::new()),
688 pending_model_instances: None,
689 placed_symbols: Arc::new(Vec::new()),
690 symbol_assets: SymbolAssetRegistry::new(),
691 symbol_placement: SymbolPlacementEngine::new(),
692 feature_state: HashMap::new(),
693 streamed_vector_sources: HashMap::new(),
694 streamed_vector_layer_fingerprints: HashMap::new(),
695 streamed_vector_query_payloads: HashMap::new(),
696 streamed_symbol_query_payloads: HashMap::new(),
697 streamed_symbol_dependency_payloads: HashMap::new(),
698 dirty_streamed_symbol_layers: HashSet::new(),
699 dirty_streamed_symbol_tiles: HashMap::new(),
700 visualization_overlays: Arc::new(Vec::new()),
701 image_overlays: Arc::new(Vec::new()),
702 placeholder_style: PlaceholderStyle::default(),
703 loading_placeholders: Arc::new(Vec::new()),
704 placeholder_time: 0.0,
705 interaction_manager: None,
706 sync_vector_cache: HashMap::new(),
707 request_coordinator: TileRequestCoordinator::default(),
708 camera_velocity_config: CameraVelocityConfig::default(),
709 camera_motion_time_seconds: 0.0,
710 camera_motion_samples: VecDeque::new(),
711 camera_motion_state: CameraMotionState::default(),
712 camera_zoom_delta: 0.0,
713 previous_fractional_zoom: None,
714 prefetch_route: None,
715 event_emitter: crate::event_emitter::EventEmitter::new(),
716 gesture_recognizer: crate::gesture::GestureRecognizer::new(),
717 fog_config: None,
718 computed_fog: crate::style::ComputedFog::default(),
719 }
720 }
721}
722
723impl MapState {
728 pub fn new() -> Self {
730 Self::default()
731 }
732
733 pub fn with_terrain(terrain_config: TerrainConfig, terrain_cache_size: usize) -> Self {
742 let zero = WorldCoord::new(0.0, 0.0, 0.0);
743 Self {
744 camera: Camera::default(),
745 constraints: CameraConstraints::default(),
746 layers: LayerStack::new(),
747 style: None,
748 terrain: TerrainManager::new(terrain_config, terrain_cache_size),
749 animator: CameraAnimator::new(),
750 data_update_interval: 0.15,
751 data_update_elapsed: 0.0,
752 async_pipeline: None,
753 zoom_level: 0,
754 viewport_bounds: WorldBounds::new(zero, zero),
755 scene_viewport_bounds: WorldBounds::new(zero, zero),
756 frustum: None,
757 terrain_meshes: Arc::new(Vec::new()),
758 pending_terrain_meshes: None,
759 hillshade_rasters: Arc::new(Vec::new()),
760 visible_tiles: Arc::new(Vec::new()),
761 vector_meshes: Arc::new(Vec::new()),
762 pending_vector_meshes: None,
763 model_instances: Arc::new(Vec::new()),
764 pending_model_instances: None,
765 placed_symbols: Arc::new(Vec::new()),
766 symbol_assets: SymbolAssetRegistry::new(),
767 symbol_placement: SymbolPlacementEngine::new(),
768 feature_state: HashMap::new(),
769 streamed_vector_sources: HashMap::new(),
770 streamed_vector_layer_fingerprints: HashMap::new(),
771 streamed_vector_query_payloads: HashMap::new(),
772 streamed_symbol_query_payloads: HashMap::new(),
773 streamed_symbol_dependency_payloads: HashMap::new(),
774 dirty_streamed_symbol_layers: HashSet::new(),
775 dirty_streamed_symbol_tiles: HashMap::new(),
776 visualization_overlays: Arc::new(Vec::new()),
777 image_overlays: Arc::new(Vec::new()),
778 placeholder_style: PlaceholderStyle::default(),
779 loading_placeholders: Arc::new(Vec::new()),
780 placeholder_time: 0.0,
781 interaction_manager: None,
782 sync_vector_cache: HashMap::new(),
783 request_coordinator: TileRequestCoordinator::default(),
784 camera_velocity_config: CameraVelocityConfig::default(),
785 camera_motion_time_seconds: 0.0,
786 camera_motion_samples: VecDeque::new(),
787 camera_motion_state: CameraMotionState::default(),
788 camera_zoom_delta: 0.0,
789 previous_fractional_zoom: None,
790 prefetch_route: None,
791 event_emitter: crate::event_emitter::EventEmitter::new(),
792 gesture_recognizer: crate::gesture::GestureRecognizer::new(),
793 fog_config: None,
794 computed_fog: crate::style::ComputedFog::default(),
795 }
796 }
797
798 #[inline]
805 pub fn zoom_level(&self) -> u8 {
806 self.zoom_level
807 }
808
809 #[inline]
816 pub fn fractional_zoom(&self) -> f64 {
817 let mpp = self.camera.near_meters_per_pixel();
818 if mpp <= 0.0 || !mpp.is_finite() {
819 return MAX_ZOOM as f64;
820 }
821 (WGS84_CIRCUMFERENCE / (mpp * TILE_PX)).log2().clamp(0.0, MAX_ZOOM as f64)
822 }
823
824 #[inline]
829 pub fn viewport_bounds(&self) -> &WorldBounds {
830 &self.viewport_bounds
831 }
832
833 #[inline]
835 pub fn camera_velocity_config(&self) -> &CameraVelocityConfig {
836 &self.camera_velocity_config
837 }
838
839 pub fn set_camera_velocity_config(&mut self, config: CameraVelocityConfig) {
841 self.camera_velocity_config = config;
842 self.trim_camera_motion_samples();
843 self.recompute_camera_motion_state();
844 }
845
846 #[inline]
848 pub fn camera_motion_state(&self) -> &CameraMotionState {
849 &self.camera_motion_state
850 }
851
852 #[inline]
854 pub fn predicted_viewport_bounds(&self) -> &WorldBounds {
855 &self.camera_motion_state.predicted_viewport_bounds
856 }
857
858 #[inline]
860 pub fn scene_viewport_bounds(&self) -> &WorldBounds {
861 &self.scene_viewport_bounds
862 }
863
864 #[inline]
868 pub fn frustum(&self) -> Option<&Frustum> {
869 self.frustum.as_ref()
870 }
871
872 #[inline]
877 pub fn renderer_world_origin(&self) -> glam::DVec3 {
878 let (x, y) = self.mercator_camera_world();
879 glam::DVec3::new(x, y, 0.0)
880 }
881
882 #[inline]
890 pub fn scene_world_origin(&self) -> glam::DVec3 {
891 self.camera.target_world()
892 }
893
894 #[inline]
898 pub fn terrain_meshes(&self) -> &[TerrainMeshData] {
899 &self.terrain_meshes
900 }
901
902 #[inline]
904 pub fn hillshade_rasters(&self) -> &[PreparedHillshadeRaster] {
905 &self.hillshade_rasters
906 }
907
908 #[inline]
910 pub fn visible_tiles(&self) -> &[VisibleTile] {
911 &self.visible_tiles
912 }
913
914 #[inline]
919 pub fn loading_placeholders(&self) -> &[LoadingPlaceholder] {
920 &self.loading_placeholders
921 }
922
923 #[inline]
925 pub fn placeholder_style(&self) -> &PlaceholderStyle {
926 &self.placeholder_style
927 }
928
929 pub fn set_placeholder_style(&mut self, style: PlaceholderStyle) {
931 self.placeholder_style = style;
932 }
933
934 pub fn coordinator_stats(&self) -> &CoordinatorStats {
937 self.request_coordinator.stats()
938 }
939
940 pub fn coordinator_config(&self) -> &CoordinatorConfig {
942 self.request_coordinator.config()
943 }
944
945 pub fn set_coordinator_config(&mut self, config: CoordinatorConfig) {
950 self.request_coordinator.set_config(config);
951 }
952
953 pub fn set_prefetch_route(&mut self, route: Vec<GeoCoord>) {
964 if route.len() < 2 {
965 self.prefetch_route = None;
966 } else {
967 self.prefetch_route = Some(route);
968 }
969 }
970
971 pub fn clear_prefetch_route(&mut self) {
973 self.prefetch_route = None;
974 }
975
976 #[inline]
978 pub fn prefetch_route(&self) -> Option<&[GeoCoord]> {
979 self.prefetch_route.as_deref()
980 }
981
982 pub fn tile_pipeline_diagnostics(&self) -> Option<TilePipelineDiagnostics> {
984 use crate::layers::TileLayer;
985
986 for layer in self.layers.iter() {
987 if !layer.visible() {
988 continue;
989 }
990 let Some(tile_layer) = layer.as_any().downcast_ref::<TileLayer>() else {
991 continue;
992 };
993
994 let visible = tile_layer.visible_tiles();
995 let visible_loaded_tiles = visible.tiles.iter().filter(|tile| tile.data.is_some()).count();
996 let visible_fallback_tiles = visible.tiles.iter().filter(|tile| tile.is_fallback() && tile.data.is_some()).count();
997 let visible_missing_tiles = visible.tiles.iter().filter(|tile| tile.data.is_none()).count();
998 let visible_overzoomed_tiles = visible.tiles.iter().filter(|tile| tile.is_overzoomed()).count();
999
1000 return Some(TilePipelineDiagnostics {
1001 layer_name: tile_layer.name().to_owned(),
1002 visible_tiles: visible.len(),
1003 visible_loaded_tiles,
1004 visible_fallback_tiles,
1005 visible_missing_tiles,
1006 visible_overzoomed_tiles,
1007 selection_stats: tile_layer.last_selection_stats().clone(),
1008 counters: tile_layer.counters().clone(),
1009 cache_stats: tile_layer.cache_stats(),
1010 source_diagnostics: tile_layer.source_diagnostics(),
1011 });
1012 }
1013
1014 None
1015 }
1016
1017 pub fn tile_lifecycle_diagnostics(&self) -> Option<TileLifecycleDiagnostics> {
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 return Some(tile_layer.lifecycle_diagnostics());
1029 }
1030
1031 None
1032 }
1033
1034 pub fn background_color(&self) -> Option<[f32; 4]> {
1041 use crate::layers::BackgroundLayer;
1042
1043 let mut color = None;
1044 for layer in self.layers.iter() {
1045 if !layer.visible() {
1046 continue;
1047 }
1048 if let Some(background) = layer.as_any().downcast_ref::<BackgroundLayer>() {
1049 color = Some(background.effective_color());
1050 }
1051 }
1052 color
1053 }
1054
1055 pub fn hillshade(&self) -> Option<crate::layers::HillshadeParams> {
1058 use crate::layers::HillshadeLayer;
1059
1060 let mut params = None;
1061 for layer in self.layers.iter() {
1062 if !layer.visible() {
1063 continue;
1064 }
1065 if let Some(hillshade) = layer.as_any().downcast_ref::<HillshadeLayer>() {
1066 params = Some(hillshade.effective_params());
1067 }
1068 }
1069 params
1070 }
1071
1072 pub fn set_fog(&mut self, config: Option<crate::style::FogConfig>) {
1080 self.fog_config = config;
1081 }
1082
1083 pub fn fog(&self) -> Option<&crate::style::FogConfig> {
1085 self.fog_config.as_ref()
1086 }
1087
1088 pub fn computed_fog(&self) -> &crate::style::ComputedFog {
1095 &self.computed_fog
1096 }
1097
1098 pub fn set_visible_tiles(&mut self, tiles: Vec<VisibleTile>) {
1100 self.visible_tiles = Arc::new(tiles);
1101 }
1102
1103 pub fn set_terrain_meshes(&mut self, meshes: Vec<TerrainMeshData>) {
1105 let meshes = Arc::new(meshes);
1106 self.pending_terrain_meshes = Some(meshes.clone());
1107 self.terrain_meshes = meshes;
1108 }
1109
1110 pub fn set_hillshade_rasters(&mut self, rasters: Vec<PreparedHillshadeRaster>) {
1112 self.hillshade_rasters = Arc::new(rasters);
1113 }
1114
1115 #[inline]
1119 pub fn camera(&self) -> &Camera {
1120 &self.camera
1121 }
1122
1123 pub fn set_viewport(&mut self, width: u32, height: u32) {
1125 self.camera.set_viewport(width, height);
1126 }
1127
1128 pub fn set_camera_target(&mut self, target: GeoCoord) {
1134 if !target.lat.is_finite() || !target.lon.is_finite() {
1135 return;
1136 }
1137 let wrapped = GeoCoord::from_lat_lon(target.lat, wrap_lon_180(target.lon));
1138 self.camera.set_target(wrapped);
1139 }
1140
1141 pub fn set_camera_distance(&mut self, distance: f64) {
1143 if !distance.is_finite() || distance <= 0.0 {
1144 return;
1145 }
1146 self.camera.set_distance(distance);
1147 }
1148
1149 pub fn set_camera_pitch(&mut self, pitch: f64) {
1151 if !pitch.is_finite() {
1152 return;
1153 }
1154 self.camera.set_pitch(pitch);
1155 }
1156
1157 pub fn set_camera_yaw(&mut self, yaw: f64) {
1159 if !yaw.is_finite() {
1160 return;
1161 }
1162 self.camera.set_yaw(yaw);
1163 }
1164
1165 pub fn set_camera_mode(&mut self, mode: CameraMode) {
1167 self.camera.set_mode(mode);
1168 }
1169
1170 pub fn set_camera_projection(&mut self, projection: CameraProjection) {
1172 self.camera.set_projection(projection);
1173 }
1174
1175 pub fn set_camera_fov_y(&mut self, fov_y: f64) {
1177 if !fov_y.is_finite() || fov_y <= 0.0 {
1178 return;
1179 }
1180 self.camera.set_fov_y(fov_y);
1181 }
1182
1183 #[inline]
1187 pub fn constraints(&self) -> &CameraConstraints {
1188 &self.constraints
1189 }
1190
1191 pub fn set_max_pitch(&mut self, max_pitch: f64) {
1193 if !max_pitch.is_finite() || max_pitch <= 0.0 {
1194 return;
1195 }
1196 self.constraints.max_pitch = max_pitch;
1197 }
1198
1199 pub fn set_min_distance(&mut self, min_distance: f64) {
1201 if !min_distance.is_finite() || min_distance <= 0.0 {
1202 return;
1203 }
1204 self.constraints.min_distance = min_distance;
1205 }
1206
1207 pub fn set_max_distance(&mut self, max_distance: f64) {
1209 if !max_distance.is_finite() || max_distance <= 0.0 {
1210 return;
1211 }
1212 self.constraints.max_distance = max_distance;
1213 }
1214
1215 #[inline]
1219 pub fn layers(&self) -> &LayerStack {
1220 &self.layers
1221 }
1222
1223 pub fn push_layer(&mut self, layer: Box<dyn crate::layer::Layer>) {
1225 self.layers.push(layer);
1226 self.style = None;
1227 }
1228
1229 pub fn set_grid_scalar(
1231 &mut self,
1232 name: impl Into<String>,
1233 grid: crate::visualization::GeoGrid,
1234 field: crate::visualization::ScalarField2D,
1235 ramp: crate::visualization::ColorRamp,
1236 ) {
1237 let name = name.into();
1238 if let Some(index) = self.layers.index_of(&name) {
1239 if let Some(layer) = self.layers.get_mut(index) {
1240 if let Some(existing) = layer
1241 .as_any_mut()
1242 .downcast_mut::<crate::visualization::GridScalarLayer>()
1243 {
1244 existing.grid = grid;
1245 existing.field = field;
1246 existing.ramp = ramp;
1247 self.style = None;
1248 return;
1249 }
1250 }
1251 }
1252 let layer = Box::new(crate::visualization::GridScalarLayer::new(
1253 name.clone(),
1254 grid,
1255 field,
1256 ramp,
1257 ));
1258 self.replace_or_push_named_layer(&name, layer);
1259 }
1260
1261 pub fn set_grid_extrusion(
1263 &mut self,
1264 name: impl Into<String>,
1265 grid: crate::visualization::GeoGrid,
1266 field: crate::visualization::ScalarField2D,
1267 ramp: crate::visualization::ColorRamp,
1268 params: crate::visualization::ExtrusionParams,
1269 ) {
1270 let name = name.into();
1271 if let Some(index) = self.layers.index_of(&name) {
1272 if let Some(layer) = self.layers.get_mut(index) {
1273 if let Some(existing) = layer
1274 .as_any_mut()
1275 .downcast_mut::<crate::visualization::GridExtrusionLayer>()
1276 {
1277 existing.grid = grid;
1278 existing.field = field;
1279 existing.ramp = ramp;
1280 existing.params = params;
1281 self.style = None;
1282 return;
1283 }
1284 }
1285 }
1286 let layer = Box::new(
1287 crate::visualization::GridExtrusionLayer::new(name.clone(), grid, field, ramp)
1288 .with_params(params),
1289 );
1290 self.replace_or_push_named_layer(&name, layer);
1291 }
1292
1293 pub fn set_instanced_columns(
1295 &mut self,
1296 name: impl Into<String>,
1297 columns: crate::visualization::ColumnInstanceSet,
1298 ramp: crate::visualization::ColorRamp,
1299 ) {
1300 let name = name.into();
1301 if let Some(index) = self.layers.index_of(&name) {
1302 if let Some(layer) = self.layers.get_mut(index) {
1303 if let Some(existing) = layer
1304 .as_any_mut()
1305 .downcast_mut::<crate::visualization::InstancedColumnLayer>()
1306 {
1307 existing.columns = columns;
1308 existing.ramp = ramp;
1309 self.style = None;
1310 return;
1311 }
1312 }
1313 }
1314 let layer = Box::new(crate::visualization::InstancedColumnLayer::new(
1315 name.clone(),
1316 columns,
1317 ramp,
1318 ));
1319 self.replace_or_push_named_layer(&name, layer);
1320 }
1321
1322 pub fn set_point_cloud(
1324 &mut self,
1325 name: impl Into<String>,
1326 points: crate::visualization::PointInstanceSet,
1327 ramp: crate::visualization::ColorRamp,
1328 ) {
1329 let name = name.into();
1330 if let Some(index) = self.layers.index_of(&name) {
1331 if let Some(layer) = self.layers.get_mut(index) {
1332 if let Some(existing) = layer
1333 .as_any_mut()
1334 .downcast_mut::<crate::visualization::PointCloudLayer>()
1335 {
1336 existing.points = points;
1337 existing.ramp = ramp;
1338 self.style = None;
1339 return;
1340 }
1341 }
1342 }
1343 let layer = Box::new(crate::visualization::PointCloudLayer::new(
1344 name.clone(),
1345 points,
1346 ramp,
1347 ));
1348 self.replace_or_push_named_layer(&name, layer);
1349 }
1350
1351 #[inline]
1353 pub fn style(&self) -> Option<&MapStyle> {
1354 self.style.as_ref()
1355 }
1356
1357 #[inline]
1359 pub fn style_document(&self) -> Option<&StyleDocument> {
1360 self.style.as_ref().map(MapStyle::document)
1361 }
1362
1363 pub fn set_style(&mut self, style: MapStyle) -> Result<(), StyleError> {
1365 self.apply_style_document(style.document())?;
1366 self.style = Some(style);
1367 Ok(())
1368 }
1369
1370 pub fn set_style_document(&mut self, document: StyleDocument) -> Result<(), StyleError> {
1372 self.set_style(MapStyle::from_document(document))
1373 }
1374
1375 pub fn with_style_mut<R>(
1377 &mut self,
1378 mutate: impl FnOnce(&mut StyleDocument) -> R,
1379 ) -> Result<Option<R>, StyleError> {
1380 let mut style: MapStyle = match self.style.take() {
1381 Some(s) => s,
1382 None => return Ok(None),
1383 };
1384 let result = mutate(style.document_mut());
1385 self.apply_style_document(style.document())?;
1386 self.style = Some(style);
1387 Ok(Some(result))
1388 }
1389
1390 pub fn reapply_style(&mut self) -> Result<bool, StyleError> {
1392 let style: MapStyle = match self.style.take() {
1393 Some(s) => s,
1394 None => return Ok(false),
1395 };
1396 self.apply_style_document(style.document())?;
1397 self.style = Some(style);
1398 Ok(true)
1399 }
1400
1401 #[inline]
1405 pub fn terrain(&self) -> &TerrainManager {
1406 &self.terrain
1407 }
1408
1409 pub fn set_terrain(&mut self, terrain: TerrainManager) {
1411 self.terrain = terrain;
1412 if self.style.is_some() {
1413 self.style = None;
1414 }
1415 }
1416
1417 #[inline]
1421 pub fn animator(&self) -> &CameraAnimator {
1422 &self.animator
1423 }
1424
1425 #[inline]
1427 pub fn is_animating(&self) -> bool {
1428 self.animator.is_active()
1429 }
1430
1431 pub fn set_data_update_interval(&mut self, interval: f64) {
1436 self.data_update_interval = interval.max(0.0);
1437 }
1438
1439 #[inline]
1441 pub fn data_update_interval(&self) -> f64 {
1442 self.data_update_interval
1443 }
1444
1445 pub fn set_task_pool(&mut self, pool: Arc<dyn DataTaskPool>) {
1449 self.async_pipeline = Some(AsyncDataPipeline::new(pool));
1450 }
1451
1452 pub fn clear_task_pool(&mut self) {
1454 self.async_pipeline = None;
1455 }
1456
1457 #[inline]
1459 pub fn has_async_pipeline(&self) -> bool {
1460 self.async_pipeline.is_some()
1461 }
1462
1463 #[inline]
1467 pub fn vector_meshes(&self) -> &[VectorMeshData] {
1468 &self.vector_meshes
1469 }
1470
1471 #[inline]
1473 pub fn model_instances(&self) -> &[ModelInstance] {
1474 &self.model_instances
1475 }
1476
1477 #[inline]
1479 pub fn placed_symbols(&self) -> &[PlacedSymbol] {
1480 &self.placed_symbols
1481 }
1482
1483 #[inline]
1485 pub fn symbol_assets(&self) -> &SymbolAssetRegistry {
1486 &self.symbol_assets
1487 }
1488
1489 pub fn feature_state(&self, source_id: &str, feature_id: &str) -> Option<&FeatureState> {
1491 self.feature_state.get(&FeatureStateId::new(source_id, feature_id))
1492 }
1493
1494 pub fn set_feature_state(
1496 &mut self,
1497 source_id: impl Into<String>,
1498 feature_id: impl Into<String>,
1499 state: FeatureState,
1500 ) {
1501 self.feature_state
1502 .insert(FeatureStateId::new(source_id, feature_id), state);
1503 }
1504
1505 pub fn set_feature_state_property(
1507 &mut self,
1508 source_id: impl Into<String>,
1509 feature_id: impl Into<String>,
1510 key: impl Into<String>,
1511 value: crate::geometry::PropertyValue,
1512 ) {
1513 self.feature_state
1514 .entry(FeatureStateId::new(source_id, feature_id))
1515 .or_default()
1516 .insert(key.into(), value);
1517 }
1518
1519 pub fn remove_feature_state(&mut self, source_id: &str, feature_id: &str) -> Option<FeatureState> {
1521 self.feature_state.remove(&FeatureStateId::new(source_id, feature_id))
1522 }
1523
1524 pub fn clear_feature_state(&mut self) {
1526 self.feature_state.clear();
1527 }
1528
1529 pub fn resolve_feature_style(
1552 &self,
1553 style_layer_id: &str,
1554 source_id: &str,
1555 feature_id: &str,
1556 ) -> Option<VectorStyle> {
1557 use crate::style::StyleEvalContextFull;
1558
1559 let document = self.style_document()?;
1560 let style_layer = document.layer(style_layer_id)?;
1561 let empty_state: FeatureState = HashMap::new();
1562 let state = self
1563 .feature_state
1564 .get(&FeatureStateId::new(source_id, feature_id))
1565 .unwrap_or(&empty_state);
1566 let ctx = StyleEvalContextFull::new(self.fractional_zoom() as f32, state);
1567 style_layer.resolve_style_with_feature_state(&ctx)
1568 }
1569
1570 pub fn set_interaction_manager(
1576 &mut self,
1577 manager: crate::interaction_manager::InteractionManager,
1578 ) {
1579 self.interaction_manager = Some(manager);
1580 }
1581
1582 pub fn take_interaction_manager(
1584 &mut self,
1585 ) -> Option<crate::interaction_manager::InteractionManager> {
1586 self.interaction_manager.take()
1587 }
1588
1589 pub fn interaction_manager(&self) -> Option<&crate::interaction_manager::InteractionManager> {
1591 self.interaction_manager.as_ref()
1592 }
1593
1594 pub fn interaction_manager_mut(
1598 &mut self,
1599 ) -> Option<&mut crate::interaction_manager::InteractionManager> {
1600 self.interaction_manager.as_mut()
1601 }
1602
1603 pub fn on<F>(
1610 &mut self,
1611 kind: crate::interaction::InteractionEventKind,
1612 callback: F,
1613 ) -> crate::event_emitter::ListenerId
1614 where
1615 F: Fn(&crate::interaction::InteractionEvent) + Send + Sync + 'static,
1616 {
1617 self.event_emitter.on(kind, callback)
1618 }
1619
1620 pub fn once<F>(
1624 &mut self,
1625 kind: crate::interaction::InteractionEventKind,
1626 callback: F,
1627 ) -> crate::event_emitter::ListenerId
1628 where
1629 F: Fn(&crate::interaction::InteractionEvent) + Send + Sync + 'static,
1630 {
1631 self.event_emitter.once(kind, callback)
1632 }
1633
1634 pub fn off(&mut self, id: crate::event_emitter::ListenerId) -> bool {
1636 self.event_emitter.off(id)
1637 }
1638
1639 pub fn dispatch_events(&mut self, events: &[crate::interaction::InteractionEvent]) {
1644 self.event_emitter.dispatch(events);
1645 }
1646
1647 pub fn event_emitter_mut(&mut self) -> &mut crate::event_emitter::EventEmitter {
1649 &mut self.event_emitter
1650 }
1651
1652 pub fn invalidate_symbol_image_dependency(&mut self, image_id: &str) -> usize {
1654 self.invalidate_symbol_dependency_tiles(|deps| deps.images.contains(image_id))
1655 }
1656
1657 pub fn invalidate_symbol_glyph_dependency(&mut self, font_stack: &str, codepoint: char) -> usize {
1659 let glyph = crate::symbols::GlyphKey {
1660 font_stack: font_stack.to_owned(),
1661 codepoint,
1662 };
1663 self.invalidate_symbol_dependency_tiles(|deps| deps.glyphs.contains(&glyph))
1664 }
1665
1666
1667 pub fn set_vector_meshes(&mut self, meshes: Vec<VectorMeshData>) {
1669 let meshes = Arc::new(meshes);
1670 self.pending_vector_meshes = Some(meshes.clone());
1671 self.vector_meshes = meshes;
1672 }
1673
1674 pub fn set_model_instances(&mut self, instances: Vec<ModelInstance>) {
1676 let instances = Arc::new(instances);
1677 self.pending_model_instances = Some(instances.clone());
1678 self.model_instances = instances;
1679 }
1680
1681 pub fn set_placed_symbols(&mut self, symbols: Vec<PlacedSymbol>) {
1683 self.placed_symbols = Arc::new(symbols);
1684 self.symbol_assets.rebuild_from_symbols(&self.placed_symbols);
1685 }
1686
1687 pub fn update_with_dt(&mut self, dt: f64) {
1693 let was_flying = self.animator.is_flying() || self.animator.is_easing();
1694
1695 self.update_camera(dt);
1696
1697 let is_flying = self.animator.is_flying() || self.animator.is_easing();
1698
1699 if self.async_pipeline.is_some() {
1701 self.dispatch_data_requests();
1702 self.poll_completed_results(dt);
1703 } else {
1704 let animation_active = is_flying;
1716
1717 self.update_tile_layers();
1719
1720 if animation_active && self.data_update_interval > 0.0 {
1721 self.data_update_elapsed += dt;
1722 if self.data_update_elapsed >= self.data_update_interval {
1723 self.data_update_elapsed = 0.0;
1724 self.update_heavy_layers(dt);
1725 }
1726 } else {
1727 self.data_update_elapsed = 0.0;
1728 self.update_heavy_layers(dt);
1729 }
1730 }
1731
1732 if was_flying && !is_flying && self.async_pipeline.is_none() {
1735 self.update_tile_layers();
1736 self.update_heavy_layers(dt);
1737 }
1738
1739 self.placeholder_time += dt;
1741 self.loading_placeholders = Arc::new(PlaceholderGenerator::generate(
1742 &self.visible_tiles,
1743 &self.placeholder_style,
1744 self.placeholder_time,
1745 ));
1746
1747 if let Err(e) = self.sync_attached_style_runtime() {
1748 log::warn!("style sync error: {e:?}");
1749 }
1750
1751 self.apply_pending_frame_overrides();
1752
1753 {
1755 let bg = self.background_color().unwrap_or([1.0, 1.0, 1.0, 1.0]);
1756 let pitch = self.camera.pitch();
1757 let distance = self.camera.distance();
1758 let style_fog = self.style.as_ref()
1759 .and_then(|s| s.document().fog());
1760 let effective_fog = self.fog_config.as_ref().or(style_fog);
1761 self.computed_fog = crate::style::compute_fog(pitch, distance, bg, effective_fog);
1762 }
1763 }
1764
1765 pub fn handle_input(&mut self, event: InputEvent) {
1772 if let InputEvent::Touch(contact) = event {
1773 let derived = self.gesture_recognizer.process(contact);
1774 for e in derived {
1775 CameraController::handle_event(&mut self.camera, e, &self.constraints);
1776 }
1777 return;
1778 }
1779 CameraController::handle_event(&mut self.camera, event, &self.constraints);
1780 }
1781
1782 pub fn gesture_recognizer(&self) -> &crate::gesture::GestureRecognizer {
1784 &self.gesture_recognizer
1785 }
1786
1787 pub fn update(&mut self) {
1789 self.update_with_dt(1.0 / 60.0);
1790 }
1791
1792 pub fn fly_to(&mut self, options: crate::camera_animator::FlyToOptions) {
1794 self.animator.start_fly_to(&mut self.camera, &options);
1795 }
1796
1797 pub fn ease_to(&mut self, options: crate::camera_animator::EaseToOptions) {
1799 self.animator.start_ease_to(&mut self.camera, &options);
1800 }
1801
1802 pub fn jump_to(
1804 &mut self,
1805 target: GeoCoord,
1806 distance: f64,
1807 pitch: Option<f64>,
1808 yaw: Option<f64>,
1809 ) {
1810 self.animator.cancel();
1811 self.camera.set_target(target);
1812 self.camera.set_distance(distance);
1813 if let Some(p) = pitch {
1814 self.camera.set_pitch(p);
1815 }
1816 if let Some(y) = yaw {
1817 self.camera.set_yaw(y);
1818 }
1819 }
1820
1821 pub fn update_camera(&mut self, dt: f64) {
1823 self.animator.tick(&mut self.camera, dt);
1824
1825 {
1832 let target = *self.camera.target();
1833 let wrapped_lon = wrap_lon_180(target.lon);
1834 if (wrapped_lon - target.lon).abs() > 1e-12 {
1835 self.camera.set_target(GeoCoord::from_lat_lon(target.lat, wrapped_lon));
1836 }
1837 }
1838
1839 let mpp = self.camera.meters_per_pixel();
1840 self.zoom_level = Self::mpp_to_zoom(mpp);
1841 self.viewport_bounds = self.compute_viewport_bounds();
1842 self.scene_viewport_bounds = self.compute_scene_viewport_bounds();
1843 self.frustum = Some(Frustum::from_view_projection(
1844 &self.camera.view_projection_matrix(),
1845 ));
1846 self.update_camera_motion_state(dt);
1847
1848 let fractional_zoom = self.fractional_zoom();
1849 self.camera_zoom_delta = self
1850 .previous_fractional_zoom
1851 .map_or(0.0, |previous| fractional_zoom - previous);
1852 self.previous_fractional_zoom = Some(fractional_zoom);
1853 }
1854
1855
1856 pub fn frame_output(&self) -> FrameOutput {
1858 FrameOutput {
1859 view_projection: self.camera.view_projection_matrix(),
1860 frustum: self.frustum.clone(),
1861 tiles: Arc::clone(&self.visible_tiles),
1862 terrain: Arc::clone(&self.terrain_meshes),
1863 hillshade: Arc::clone(&self.hillshade_rasters),
1864 vectors: Arc::clone(&self.vector_meshes),
1865 models: Arc::clone(&self.model_instances),
1866 symbols: Arc::clone(&self.placed_symbols),
1867 visualization: Arc::clone(&self.visualization_overlays),
1868 placeholders: Arc::clone(&self.loading_placeholders),
1869 image_overlays: Arc::clone(&self.image_overlays),
1870 zoom_level: self.zoom_level,
1871 }
1872 }
1873
1874 pub fn elevation_at(&self, coord: &GeoCoord) -> Option<f64> {
1876 self.terrain.elevation_at(coord)
1877 }
1878
1879 pub fn screen_to_geo(&self, px: f64, py: f64) -> Option<GeoCoord> {
1881 self.camera.screen_to_geo(px, py)
1882 }
1883
1884 pub fn geo_to_screen(&self, geo: &GeoCoord) -> Option<(f64, f64)> {
1890 self.camera.geo_to_screen(geo)
1891 }
1892
1893 pub fn fit_bounds(&mut self, bounds: &GeoBounds, options: &FitBoundsOptions) {
1900 let center = bounds.center();
1901
1902 let sw_world = WebMercator::project_clamped(&bounds.sw());
1904 let ne_world = WebMercator::project_clamped(&bounds.ne());
1905
1906 let dx = (ne_world.position.x - sw_world.position.x).abs();
1907 let dy = (ne_world.position.y - sw_world.position.y).abs();
1908
1909 let vw = (self.camera.viewport_width() as f64
1911 - options.padding.left
1912 - options.padding.right)
1913 .max(1.0);
1914 let vh = (self.camera.viewport_height() as f64
1915 - options.padding.top
1916 - options.padding.bottom)
1917 .max(1.0);
1918
1919 let mpp_x = dx / vw;
1921 let mpp_y = dy / vh;
1922 let mpp = mpp_x.max(mpp_y);
1923
1924 let zoom = if mpp <= 0.0 || !mpp.is_finite() {
1926 MAX_ZOOM as f64
1927 } else {
1928 (WGS84_CIRCUMFERENCE / (mpp * TILE_PX))
1929 .log2()
1930 .clamp(0.0, MAX_ZOOM as f64)
1931 };
1932
1933 let zoom = match options.max_zoom {
1935 Some(mz) => zoom.min(mz),
1936 None => zoom,
1937 };
1938
1939 if options.animate {
1940 let fly = crate::camera_animator::FlyToOptions {
1941 center: Some(center),
1942 zoom: Some(zoom),
1943 bearing: options.bearing,
1944 pitch: options.pitch,
1945 duration: options.duration,
1946 ..Default::default()
1947 };
1948 self.fly_to(fly);
1949 } else {
1950 let is_perspective = matches!(self.camera.mode(), CameraMode::Perspective);
1952 let distance = {
1953 let mpp = WGS84_CIRCUMFERENCE / (2.0_f64.powf(zoom) * TILE_PX);
1954 let vis_h = mpp * self.camera.viewport_height().max(1) as f64;
1955 if is_perspective {
1956 vis_h / (2.0 * (self.camera.fov_y() / 2.0).tan())
1957 } else {
1958 vis_h / 2.0
1959 }
1960 };
1961 self.jump_to(center, distance, options.pitch, options.bearing);
1962 }
1963 }
1964
1965 pub fn ray_to_geo(&self, origin: glam::DVec3, direction: glam::DVec3) -> Option<GeoCoord> {
1967 if direction.z.abs() < 1e-12 {
1968 return None;
1969 }
1970 let t = -origin.z / direction.z;
1971 if t < 0.0 {
1972 return None;
1973 }
1974 let hit = origin + direction * t;
1975 let world = self.camera.target_world();
1976 let world_hit = WorldCoord::new(hit.x + world.x, hit.y + world.y, 0.0);
1977 Some(self.camera.projection().unproject(&world_hit))
1978 }
1979
1980 pub fn ray_to_geo_on_terrain(
1985 &self,
1986 origin: glam::DVec3,
1987 direction: glam::DVec3,
1988 ) -> Option<GeoCoord> {
1989 if !self.terrain.enabled() {
1990 return self.ray_to_geo(origin, direction);
1991 }
1992
1993 let world = self.camera.target_world();
1994 let steps = 64;
1995 let max_t = self.camera.distance() * 4.0;
1996 let step = max_t / steps as f64;
1997
1998 let mut prev_above = true;
1999 for i in 0..=steps {
2000 let t = step * i as f64;
2001 let p = origin + direction * t;
2002 let world_hit = WorldCoord::new(p.x + world.x, p.y + world.y, p.z);
2003 let geo = self.camera.projection().unproject(&world_hit);
2004 let elev = self.terrain.elevation_at(&geo).unwrap_or(0.0);
2005 let above = p.z >= elev;
2006 if !above && prev_above && i > 0 {
2007 let prev_t = step * (i - 1) as f64;
2009 let prev_p = origin + direction * prev_t;
2010 let prev_world = WorldCoord::new(prev_p.x + world.x, prev_p.y + world.y, prev_p.z);
2011 let prev_geo = self.camera.projection().unproject(&prev_world);
2012 let prev_elev = self.terrain.elevation_at(&prev_geo).unwrap_or(0.0);
2013 let prev_height = prev_p.z - prev_elev;
2014 let curr_height = p.z - elev;
2015 let frac = prev_height / (prev_height - curr_height);
2016 let hit_t = prev_t + (t - prev_t) * frac;
2017 let hit = origin + direction * hit_t;
2018 let hit_world = WorldCoord::new(hit.x + world.x, hit.y + world.y, hit.z);
2019 return Some(self.camera.projection().unproject(&hit_world));
2020 }
2021 prev_above = above;
2022 }
2023 self.ray_to_geo(origin, direction)
2025 }
2026
2027 pub fn ray_to_flat_geo(&self, origin: glam::DVec3, direction: glam::DVec3) -> Option<GeoCoord> {
2029 self.ray_to_geo(origin, direction)
2030 }
2031
2032 pub fn screen_to_geo_on_terrain(&self, px: f64, py: f64) -> Option<GeoCoord> {
2034 let (origin, direction) = self.camera.screen_to_ray(px, py);
2035 self.ray_to_geo_on_terrain(origin, direction)
2036 }
2037
2038 fn mercator_camera_world(&self) -> (f64, f64) {
2057 let w = WebMercator::project(self.camera.target());
2058 (w.position.x, w.position.y)
2059 }
2060
2061 fn update_camera_motion_state(&mut self, dt: f64) {
2062 self.camera_motion_time_seconds += dt.max(0.0);
2063
2064 let current_wrapped = self.mercator_camera_world();
2065 let current_target = if let Some(last) = self.camera_motion_samples.back() {
2066 glam::DVec2::new(
2067 last.target_world.x + heavy_layers::wrapped_world_delta(current_wrapped.0 - last.target_world.x),
2068 current_wrapped.1,
2069 )
2070 } else {
2071 glam::DVec2::new(current_wrapped.0, current_wrapped.1)
2072 };
2073
2074 self.camera_motion_samples.push_back(CameraMotionSample {
2075 time_seconds: self.camera_motion_time_seconds,
2076 target_world: current_target,
2077 });
2078 self.trim_camera_motion_samples();
2079 self.recompute_camera_motion_state();
2080 }
2081
2082 fn trim_camera_motion_samples(&mut self) {
2083 let max_samples = self.camera_velocity_config.sample_window.max(1) + 1;
2084 while self.camera_motion_samples.len() > max_samples {
2085 self.camera_motion_samples.pop_front();
2086 }
2087 }
2088
2089 fn recompute_camera_motion_state(&mut self) {
2090 let pan_velocity_world = if let (Some(first), Some(last)) = (
2091 self.camera_motion_samples.front(),
2092 self.camera_motion_samples.back(),
2093 ) {
2094 let dt = last.time_seconds - first.time_seconds;
2095 if dt > 1e-9 {
2096 (last.target_world - first.target_world) / dt
2097 } else {
2098 glam::DVec2::ZERO
2099 }
2100 } else {
2101 glam::DVec2::ZERO
2102 };
2103
2104 let current_wrapped = self.mercator_camera_world();
2105 let current_target_world = glam::DVec2::new(current_wrapped.0, current_wrapped.1);
2106 let look_ahead = self.camera_velocity_config.look_ahead_seconds.max(0.0);
2107 let predicted_delta = pan_velocity_world * look_ahead;
2108
2109 self.camera_motion_state = CameraMotionState {
2110 pan_velocity_world,
2111 predicted_target_world: current_target_world + predicted_delta,
2112 predicted_viewport_bounds: heavy_layers::translated_world_bounds(&self.viewport_bounds, predicted_delta),
2113 };
2114 }
2115
2116 fn mpp_to_zoom(mpp: f64) -> u8 {
2119 if mpp <= 0.0 || !mpp.is_finite() {
2120 return MAX_ZOOM;
2121 }
2122 let z = (WGS84_CIRCUMFERENCE / (mpp * TILE_PX)).log2();
2123 (z.round() as u8).min(MAX_ZOOM)
2124 }
2125
2126 fn compute_viewport_bounds(&self) -> WorldBounds {
2128 use rustial_math::WebMercator;
2129
2130 let w = self.camera.viewport_width() as f64;
2131 let h = self.camera.viewport_height() as f64;
2132
2133 if w <= 0.0 || h <= 0.0 {
2134 let zero = WorldCoord::new(0.0, 0.0, 0.0);
2135 return WorldBounds::new(zero, zero);
2136 }
2137
2138 let cam = &self.camera;
2139
2140 if cam.mode() == CameraMode::Orthographic {
2142 let half_w = cam.distance() * (w / h).max(1.0);
2143 let half_h = cam.distance() / (w / h).min(1.0);
2144 let target = WebMercator::project(cam.target());
2145 let overscan = 1.3;
2146 return WorldBounds::new(
2147 WorldCoord::new(
2148 target.position.x - half_w * overscan,
2149 target.position.y - half_h * overscan,
2150 0.0,
2151 ),
2152 WorldCoord::new(
2153 target.position.x + half_w * overscan,
2154 target.position.y + half_h * overscan,
2155 0.0,
2156 ),
2157 );
2158 }
2159
2160 let mut min_x = f64::MAX;
2161 let mut min_y = f64::MAX;
2162 let mut max_x = f64::MIN;
2163 let mut max_y = f64::MIN;
2164 let mut any_hit = false;
2165
2166 for (sx, sy) in viewport_sample_points(w, h) {
2167 if let Some(geo) = cam.screen_to_geo(sx, sy) {
2168 let world = WebMercator::project_clamped(&geo);
2169 min_x = min_x.min(world.position.x);
2170 min_y = min_y.min(world.position.y);
2171 max_x = max_x.max(world.position.x);
2172 max_y = max_y.max(world.position.y);
2173 any_hit = true;
2174 }
2175 }
2176
2177 if !any_hit {
2178 let target = WebMercator::project(cam.target());
2179 let mpp = cam.meters_per_pixel();
2180 let half_w = mpp * w * 0.5;
2181 let half_h = mpp * h * 0.5;
2182 return WorldBounds::new(
2183 WorldCoord::new(target.position.x - half_w, target.position.y - half_h, 0.0),
2184 WorldCoord::new(target.position.x + half_w, target.position.y + half_h, 0.0),
2185 );
2186 }
2187
2188 let overscan = perspective_viewport_overscan(cam.pitch());
2189 let cx = (min_x + max_x) * 0.5;
2190 let cy = (min_y + max_y) * 0.5;
2191 let hw = (max_x - min_x) * 0.5 * overscan;
2192 let hh = (max_y - min_y) * 0.5 * overscan;
2193 WorldBounds::new(
2194 WorldCoord::new(cx - hw, cy - hh, 0.0),
2195 WorldCoord::new(cx + hw, cy + hh, 0.0),
2196 )
2197 }
2198
2199 fn compute_scene_viewport_bounds(&self) -> WorldBounds {
2201 let w = self.camera.viewport_width() as f64;
2202 let h = self.camera.viewport_height() as f64;
2203
2204 if w <= 0.0 || h <= 0.0 {
2205 let zero = WorldCoord::new(0.0, 0.0, 0.0);
2206 return WorldBounds::new(zero, zero);
2207 }
2208
2209 let cam = &self.camera;
2210 let proj = cam.projection();
2211
2212 if cam.mode() == CameraMode::Orthographic {
2213 let half_w = cam.distance() * (w / h).max(1.0);
2214 let half_h = cam.distance() / (w / h).min(1.0);
2215 let target = proj.project(cam.target());
2216 let overscan = 1.3;
2217 return WorldBounds::new(
2218 WorldCoord::new(
2219 target.position.x - half_w * overscan,
2220 target.position.y - half_h * overscan,
2221 0.0,
2222 ),
2223 WorldCoord::new(
2224 target.position.x + half_w * overscan,
2225 target.position.y + half_h * overscan,
2226 0.0,
2227 ),
2228 );
2229 }
2230
2231 let mut min_x = f64::MAX;
2232 let mut min_y = f64::MAX;
2233 let mut max_x = f64::MIN;
2234 let mut max_y = f64::MIN;
2235 let mut any_hit = false;
2236
2237 for (sx, sy) in viewport_sample_points(w, h) {
2238 if let Some(geo) = cam.screen_to_geo(sx, sy) {
2239 let world = proj.project(&geo);
2240 min_x = min_x.min(world.position.x);
2241 min_y = min_y.min(world.position.y);
2242 max_x = max_x.max(world.position.x);
2243 max_y = max_y.max(world.position.y);
2244 any_hit = true;
2245 }
2246 }
2247
2248 if !any_hit {
2249 let target = proj.project(cam.target());
2250 let mpp = cam.meters_per_pixel();
2251 let half_w = mpp * w * 0.5;
2252 let half_h = mpp * h * 0.5;
2253 return WorldBounds::new(
2254 WorldCoord::new(target.position.x - half_w, target.position.y - half_h, 0.0),
2255 WorldCoord::new(target.position.x + half_w, target.position.y + half_h, 0.0),
2256 );
2257 }
2258
2259 let overscan = perspective_viewport_overscan(cam.pitch());
2260 let cx = (min_x + max_x) * 0.5;
2261 let cy = (min_y + max_y) * 0.5;
2262 let hw = (max_x - min_x) * 0.5 * overscan;
2263 let hh = (max_y - min_y) * 0.5 * overscan;
2264 WorldBounds::new(
2265 WorldCoord::new(cx - hw, cy - hh, 0.0),
2266 WorldCoord::new(cx + hw, cy + hh, 0.0),
2267 )
2268 }
2269
2270 fn sync_attached_style_runtime(&mut self) -> Result<(), StyleError> {
2272 Ok(())
2276 }
2277
2278 fn replace_or_push_named_layer(
2280 &mut self,
2281 name: &str,
2282 layer: Box<dyn crate::layer::Layer>,
2283 ) {
2284 if let Some(index) = self.layers.index_of(name) {
2285 let _ = self.layers.remove(index);
2286 self.layers.insert(index, layer);
2287 } else {
2288 self.layers.push(layer);
2289 }
2290 self.style = None;
2291 }
2292
2293
2294}
2295
2296
2297