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        // Core width/height override the size argument when set
46        let width = options.width.unwrap_or(size.0);
47        let height = options.height.unwrap_or(size.1);
48
49        // Build the full style URL: /styles/v1/{username}/{style_id}/static/{lon},{lat},{zoom}/{width}x{height}@2x
50        let url = format!(
51            "{}/styles/v1/{}/static/{},{},{}/{}x{}@2x",
52            self.base_url, style, center.lng, center.lat, zoom, width, height
53        );
54
55        let mut params: Vec<(&str, String)> = Vec::new();
56        if let Some(lang) = &options.language {
57            params.push(("language", lang.clone()));
58        }
59        if options.format.is_some() {
60            log::warn!(
61                "MapBox Static Images API does not support a format parameter; \
62                 format will be ignored (MapBox always returns PNG)"
63            );
64        }
65
66        let builder = self
67            .client
68            .build_request(reqwest::Method::GET, &url)
69            .query(&params);
70
71        let response = self.client.request(builder).await?;
72
73        let content_type = response
74            .headers()
75            .get("content-type")
76            .and_then(|v| v.to_str().ok())
77            .map(|s| s.split(';').next().unwrap_or(s).trim().to_string());
78
79        let data = response.bytes().await?.to_vec();
80
81        Ok(ImageResponse { data, content_type })
82    }
83}