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