Skip to main content

everymap_providers_mapbox/domain/imaging/
mod.rs

1use crate::client::MapBoxClient;
2use async_trait::async_trait;
3use everymap_core::domains::imaging::{ImageOptions, ImageResponse, MapImageProvider};
4use everymap_core::error::EveryMapResult;
5use everymap_core::types::Coordinate;
6use std::sync::Arc;
7
8const MAP_BASE_URL: &str = "https://api.mapbox.com";
9
10/// Implementation of MapImageProvider for MapBox Static Images API.
11pub struct MapBoxMapImageProvider {
12    pub(crate) client: Arc<MapBoxClient>,
13    pub(crate) base_url: String,
14}
15
16impl MapBoxMapImageProvider {
17    pub fn new(client: Arc<MapBoxClient>) -> Self {
18        Self {
19            client,
20            base_url: MAP_BASE_URL.to_string(),
21        }
22    }
23
24    pub fn with_base_url(client: Arc<MapBoxClient>, base_url: String) -> Self {
25        Self { client, base_url }
26    }
27}
28
29#[async_trait]
30impl MapImageProvider for MapBoxMapImageProvider {
31    async fn get_image(
32        &self,
33        center: &Coordinate,
34        zoom: u32,
35        size: (u32, u32),
36        options: &ImageOptions,
37    ) -> EveryMapResult<ImageResponse> {
38        let style = options
39            .provider_extra
40            .as_ref()
41            .and_then(|e| e.get("style"))
42            .and_then(|v| v.as_str())
43            .unwrap_or("mapbox/streets-v12");
44
45        // Build the full style URL: /styles/v1/{username}/{style_id}/static/{lon},{lat},{zoom}/{width}x{height}@2x
46        let url = format!(
47            "{}/styles/v1/{}/static/{},{},{}/{}x{}@2x",
48            self.base_url, style, center.lng, center.lat, zoom, size.0, size.1
49        );
50
51        let mut params: Vec<(&str, String)> = Vec::new();
52        if let Some(lang) = &options.language {
53            params.push(("language", lang.clone()));
54        }
55        if options.format.is_some() {
56            log::warn!(
57                "MapBox Static Images API does not support a format parameter; \
58                 format will be ignored (MapBox always returns PNG)"
59            );
60        }
61
62        let builder = self
63            .client
64            .build_request(reqwest::Method::GET, &url)
65            .query(&params);
66
67        let response = self.client.request(builder).await?;
68
69        let content_type = response
70            .headers()
71            .get("content-type")
72            .and_then(|v| v.to_str().ok())
73            .map(|s| s.split(';').next().unwrap_or(s).trim().to_string());
74
75        let data = response.bytes().await?.to_vec();
76
77        Ok(ImageResponse { data, content_type })
78    }
79}