maplibre/raster/
request_system.rs

1//! Requests tiles which are currently in view
2
3use std::{borrow::Cow, collections::HashSet, marker::PhantomData, rc::Rc};
4
5use crate::{
6    context::MapContext,
7    environment::{Environment, OffscreenKernel},
8    io::{
9        apc::{AsyncProcedureCall, AsyncProcedureFuture, Context, Input, ProcedureError},
10        source_type::{RasterSource, SourceType},
11    },
12    kernel::Kernel,
13    raster::{
14        process_raster::{process_raster_tile, ProcessRasterContext, RasterTileRequest},
15        transferables::{LayerRasterMissing, RasterTransferables},
16        RasterLayersDataComponent,
17    },
18    render::{tile_view_pattern::DEFAULT_TILE_SIZE, view_state::ViewStatePadding},
19    style::layer::LayerPaint,
20    tcs::system::{System, SystemResult},
21};
22
23pub struct RequestSystem<E: Environment, T: RasterTransferables> {
24    kernel: Rc<Kernel<E>>,
25    phantom_t: PhantomData<T>,
26}
27
28impl<E: Environment, T: RasterTransferables> RequestSystem<E, T> {
29    pub fn new(kernel: &Rc<Kernel<E>>) -> Self {
30        Self {
31            kernel: kernel.clone(),
32            phantom_t: Default::default(),
33        }
34    }
35}
36
37impl<E: Environment, T: RasterTransferables> System for RequestSystem<E, T> {
38    fn name(&self) -> Cow<'static, str> {
39        "raster_request".into()
40    }
41
42    fn run(
43        &mut self,
44        MapContext {
45            style,
46            view_state,
47            world,
48            ..
49        }: &mut MapContext,
50    ) -> SystemResult {
51        let view_region = view_state.create_view_region(
52            view_state.zoom().zoom_level(DEFAULT_TILE_SIZE),
53            ViewStatePadding::Loose,
54        );
55
56        if view_state.did_camera_change() || view_state.did_zoom_change() {
57            if let Some(view_region) = &view_region {
58                // TODO: We also need to request tiles from layers above if we are over the maximum zoom level
59
60                for coords in view_region.iter() {
61                    if coords.build_quad_key().is_none() {
62                        continue;
63                    }
64
65                    // TODO: Make tesselation depend on style? So maybe we need to request even if it exists
66                    if world
67                        .tiles
68                        .query::<&RasterLayersDataComponent>(coords)
69                        .is_some()
70                    {
71                        continue;
72                    }
73
74                    world
75                        .tiles
76                        .spawn_mut(coords)
77                        .expect("unable to spawn a raster tile")
78                        .insert(RasterLayersDataComponent::default());
79
80                    tracing::event!(tracing::Level::ERROR, %coords, "tile request started: {coords}");
81                    log::info!("tile request started: {coords}");
82
83                    self.kernel
84                        .apc()
85                        .call(
86                            Input::TileRequest {
87                                coords,
88                                style: style.clone(), // TODO: Avoid cloning whole style
89                            },
90                            fetch_raster_apc::<
91                                E::OffscreenKernelEnvironment,
92                                T,
93                                <E::AsyncProcedureCall as AsyncProcedureCall<
94                                    E::OffscreenKernelEnvironment,
95                                >>::Context,
96                            >,
97                        )
98                        .expect("unable to call APC"); // TODO: Remove unwrap
99                }
100            }
101        }
102
103        view_state.update_references();
104
105        Ok(())
106    }
107}
108pub fn fetch_raster_apc<K: OffscreenKernel, T: RasterTransferables, C: Context + Clone + Send>(
109    input: Input,
110    context: C,
111    kernel: K,
112) -> AsyncProcedureFuture {
113    Box::pin(async move {
114        let Input::TileRequest { coords, style } = input else {
115            return Err(ProcedureError::IncompatibleInput);
116        };
117
118        let raster_layers: HashSet<String> = style
119            .layers
120            .iter()
121            .filter_map(|layer| {
122                if matches!(layer.paint, Some(LayerPaint::Raster(_))) {
123                    layer.source_layer.clone()
124                } else {
125                    None
126                }
127            })
128            .collect();
129
130        let client = kernel.source_client();
131
132        if !raster_layers.is_empty() {
133            let context = context.clone();
134            let source = SourceType::Raster(RasterSource::default());
135
136            match client.fetch(&coords, &source).await {
137                Ok(data) => {
138                    let data = data.into_boxed_slice();
139
140                    let mut process_context = ProcessRasterContext::<T, C>::new(context);
141
142                    process_raster_tile(&data, RasterTileRequest { coords }, &mut process_context)
143                        .map_err(|e| ProcedureError::Execution(Box::new(e)))?;
144                }
145                Err(e) => {
146                    log::error!("{e:?}");
147
148                    context
149                        .send_back(<T as RasterTransferables>::LayerRasterMissing::build_from(
150                            coords,
151                        ))
152                        .map_err(ProcedureError::Send)?;
153                }
154            }
155        }
156
157        Ok(())
158    })
159}