Skip to main content

everymap_providers_google/domain/imaging/
mod.rs

1use crate::client::GoogleClient;
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 STATIC_MAPS_BASE_URL: &str = "https://maps.googleapis.com/maps/api/staticmap";
9
10/// Implementation of MapImageProvider for Google Static Maps API.
11pub struct GoogleMapImageProvider {
12    pub(crate) client: Arc<GoogleClient>,
13    pub(crate) base_url: String,
14}
15
16impl GoogleMapImageProvider {
17    pub fn new(client: Arc<GoogleClient>) -> Self {
18        Self {
19            client,
20            base_url: STATIC_MAPS_BASE_URL.to_string(),
21        }
22    }
23
24    pub fn with_base_url(client: Arc<GoogleClient>, base_url: String) -> Self {
25        Self { client, base_url }
26    }
27}
28
29#[async_trait]
30impl MapImageProvider for GoogleMapImageProvider {
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 mut params: Vec<(String, String)> = vec![
39            (
40                "center".to_string(),
41                format!("{},{}", center.lat, center.lng),
42            ),
43            ("zoom".to_string(), zoom.to_string()),
44            ("size".to_string(), format!("{}x{}", size.0, size.1)),
45        ];
46
47        // Format: png (default), jpg, gif
48        if let Some(fmt) = &options.format {
49            let format_val = match fmt.as_str() {
50                "jpg" | "jpeg" => "jpg",
51                "gif" => "gif",
52                _ => "png",
53            };
54            params.push(("format".to_string(), format_val.to_string()));
55        }
56
57        if let Some(lang) = &options.language {
58            params.push(("language".to_string(), lang.clone()));
59        }
60
61        // Extract Google-specific options from provider_extra
62        if let Some(extra) = &options.provider_extra {
63            if let Some(obj) = extra.as_object() {
64                if let Some(v) = obj.get("maptype").and_then(|v| v.as_str()) {
65                    params.push(("maptype".to_string(), v.to_string()));
66                }
67                if let Some(v) = obj.get("scale").and_then(|v| v.as_u64()) {
68                    params.push(("scale".to_string(), v.to_string()));
69                }
70                if let Some(v) = obj.get("markers").and_then(|v| v.as_str()) {
71                    params.push(("markers".to_string(), v.to_string()));
72                }
73                if let Some(v) = obj.get("path").and_then(|v| v.as_str()) {
74                    params.push(("path".to_string(), v.to_string()));
75                }
76                if let Some(v) = obj.get("visible").and_then(|v| v.as_str()) {
77                    params.push(("visible".to_string(), v.to_string()));
78                }
79                if let Some(v) = obj.get("style").and_then(|v| v.as_str()) {
80                    params.push(("style".to_string(), v.to_string()));
81                }
82            }
83        }
84
85        let url = self.base_url.clone();
86        let builder = self
87            .client
88            .build_request(reqwest::Method::GET, &url)
89            .query(&params);
90
91        let response = self.client.request(builder).await?;
92
93        let content_type = response
94            .headers()
95            .get("content-type")
96            .and_then(|v| v.to_str().ok())
97            .map(|s| s.split(';').next().unwrap_or(s).trim().to_string());
98
99        let data = response.bytes().await?.to_vec();
100
101        Ok(ImageResponse { data, content_type })
102    }
103}