Skip to main content

rustial_engine/layers/
tile_layer.rs

1//! Concrete tile layer backed by a TileManager.
2
3use crate::layer::{Layer, LayerId};
4use crate::tile_manager::{
5    TileManager, TileManagerCounters, TileSelectionConfig, TileSelectionStats, VisibleTileSet,
6    ZoomPrefetchDirection,
7};
8use crate::tile_cache::TileCacheStats;
9use crate::tile_lifecycle::TileLifecycleDiagnostics;
10use crate::tile_source::TileSource;
11use crate::tile_source::TileSourceDiagnostics;
12use rustial_math::{FlatTileView, Frustum, GeoCoord, TileId, WorldBounds};
13use std::any::Any;
14use std::collections::HashSet;
15
16/// A raster tile layer that fetches slippy-map tiles from a source.
17pub struct TileLayer {
18    id: LayerId,
19    name: String,
20    visible: bool,
21    opacity: f32,
22    manager: TileManager,
23    last_visible_set: VisibleTileSet,
24}
25
26impl TileLayer {
27    /// Create a new tile layer.
28    pub fn new(
29        name: impl Into<String>,
30        source: Box<dyn TileSource>,
31        cache_capacity: usize,
32    ) -> Self {
33        Self::new_with_selection_config(
34            name,
35            source,
36            cache_capacity,
37            TileSelectionConfig::default(),
38        )
39    }
40
41    /// Create a new tile layer with an explicit tile-selection policy.
42    pub fn new_with_selection_config(
43        name: impl Into<String>,
44        source: Box<dyn TileSource>,
45        cache_capacity: usize,
46        selection_config: TileSelectionConfig,
47    ) -> Self {
48        Self {
49            id: LayerId::next(),
50            name: name.into(),
51            visible: true,
52            opacity: 1.0,
53            manager: TileManager::new_with_config(source, cache_capacity, selection_config),
54            last_visible_set: VisibleTileSet::default(),
55        }
56    }
57
58    /// Update tile fetching for the current frame.
59    pub fn update(
60        &mut self,
61        viewport_bounds: &WorldBounds,
62        zoom: u8,
63        camera_world: (f64, f64),
64        camera_distance: f64,
65    ) {
66        self.update_with_view(
67            viewport_bounds,
68            zoom,
69            camera_world,
70            camera_distance,
71            None,
72        );
73    }
74
75    /// Update tile fetching for the current frame with optional
76    /// footprint-aware flat-view selection parameters.
77    pub fn update_with_view(
78        &mut self,
79        viewport_bounds: &WorldBounds,
80        zoom: u8,
81        camera_world: (f64, f64),
82        camera_distance: f64,
83        flat_view: Option<&FlatTileView>,
84    ) {
85        if self.visible {
86            self.last_visible_set = self.manager.update_with_view(
87                viewport_bounds,
88                zoom,
89                camera_world,
90                camera_distance,
91                flat_view,
92            );
93        }
94    }
95
96    /// Update tile fetching for the current frame using frustum-based
97    /// quadtree traversal (MapLibre-equivalent `coveringTiles` path).
98    pub fn update_with_frustum(
99        &mut self,
100        frustum: &Frustum,
101        zoom: u8,
102        camera_world: (f64, f64),
103    ) {
104        if self.visible {
105            self.last_visible_set = self.manager.update_with_frustum(
106                frustum,
107                zoom,
108                camera_world,
109            );
110        }
111    }
112
113    /// Update tile fetching using MapLibre-equivalent covering-tiles
114    /// traversal with per-tile variable zoom heuristics.
115    ///
116    /// This is the preferred path for steep-pitch terrain views where
117    /// distant tiles should use lower zoom levels than near tiles.
118    pub fn update_with_covering(
119        &mut self,
120        frustum: &Frustum,
121        cam: &rustial_math::CoveringCamera,
122        opts: &rustial_math::CoveringTilesOptions,
123        camera_world: (f64, f64),
124    ) {
125        if self.visible {
126            self.last_visible_set = self.manager.update_with_covering(
127                frustum,
128                cam,
129                opts,
130                camera_world,
131            );
132        }
133    }
134
135    /// The set of tiles visible in the last update.
136    pub fn visible_tiles(&self) -> &VisibleTileSet {
137        &self.last_visible_set
138    }
139
140    /// The set of source tiles the manager last considered the desired view.
141    pub fn desired_tiles(&self) -> &HashSet<TileId> {
142        self.manager.desired_tiles()
143    }
144
145    /// Read-only access to the most recent tile-selection/update stats.
146    pub fn last_selection_stats(&self) -> &TileSelectionStats {
147        self.manager.last_selection_stats()
148    }
149
150    /// Read-only access to cumulative tile-manager counters.
151    pub fn counters(&self) -> &TileManagerCounters {
152        self.manager.counters()
153    }
154
155    /// Snapshot counts of the current tile-cache state.
156    pub fn cache_stats(&self) -> TileCacheStats {
157        self.manager.cache_stats()
158    }
159
160    /// Optional runtime diagnostics from the underlying tile source.
161    pub fn source_diagnostics(&self) -> Option<TileSourceDiagnostics> {
162        self.manager.source_diagnostics()
163    }
164
165    /// Snapshot of recent tile lifecycle diagnostics.
166    pub fn lifecycle_diagnostics(&self) -> TileLifecycleDiagnostics {
167        self.manager.lifecycle_diagnostics()
168    }
169
170    /// Read-only access to the tile-selection policy.
171    pub fn selection_config(&self) -> &TileSelectionConfig {
172        self.manager.selection_config()
173    }
174
175    /// Replace the tile-selection policy.
176    pub fn set_selection_config(&mut self, config: TileSelectionConfig) {
177        self.manager.set_selection_config(config);
178    }
179
180    /// Access the underlying tile manager.
181    pub fn manager(&self) -> &TileManager {
182        &self.manager
183    }
184
185    /// Promote externally decoded tiles into the tile manager's cache.
186    ///
187    /// See [`TileManager::promote_decoded`] for details.
188    pub fn promote_decoded(
189        &mut self,
190        decoded: Vec<(rustial_math::TileId, crate::tile_source::TileResponse)>,
191    ) {
192        self.manager.promote_decoded(decoded);
193    }
194
195    /// Speculatively prefetch tiles for a predicted viewport without changing
196    /// the current visible tile set.
197    pub fn prefetch_with_view(
198        &mut self,
199        viewport_bounds: &WorldBounds,
200        zoom: u8,
201        camera_world: (f64, f64),
202        flat_view: Option<&FlatTileView>,
203        max_requests: usize,
204    ) -> usize {
205        self.manager.prefetch_with_view(
206            viewport_bounds,
207            zoom,
208            camera_world,
209            flat_view,
210            max_requests,
211        )
212    }
213
214    /// Speculatively prefetch tiles implied by the current desired set and a
215    /// zoom direction.
216    pub fn prefetch_zoom_direction(
217        &mut self,
218        camera_world: (f64, f64),
219        direction: ZoomPrefetchDirection,
220        max_requests: usize,
221    ) -> usize {
222        self.manager
223            .prefetch_zoom_direction(camera_world, direction, max_requests)
224    }
225
226    /// Speculatively prefetch tiles along a geographic route polyline.
227    ///
228    /// See [`TileManager::prefetch_route`] for details.
229    pub fn prefetch_route(
230        &mut self,
231        route: &[GeoCoord],
232        zoom: u8,
233        camera_world: (f64, f64),
234        max_requests: usize,
235    ) -> usize {
236        self.manager
237            .prefetch_route(route, zoom, camera_world, max_requests)
238    }
239}
240
241impl Layer for TileLayer {
242    fn id(&self) -> LayerId {
243        self.id
244    }
245
246    fn kind(&self) -> crate::layer::LayerKind {
247        crate::layer::LayerKind::Tile
248    }
249
250    fn name(&self) -> &str {
251        &self.name
252    }
253
254    fn visible(&self) -> bool {
255        self.visible
256    }
257
258    fn set_visible(&mut self, visible: bool) {
259        self.visible = visible;
260    }
261
262    fn opacity(&self) -> f32 {
263        self.opacity
264    }
265
266    fn set_opacity(&mut self, opacity: f32) {
267        self.opacity = opacity.clamp(0.0, 1.0);
268    }
269
270    fn as_any(&self) -> &dyn Any {
271        self
272    }
273
274    fn as_any_mut(&mut self) -> &mut dyn Any {
275        self
276    }
277}