Skip to main content

everymap_providers_google/domain/attributes/
mod.rs

1pub mod types;
2
3use crate::client::GoogleClient;
4use async_trait::async_trait;
5use everymap_core::domains::attributes::{AttributeOptions, AttributeProvider, AttributeResponse};
6use everymap_core::error::{EveryMapError, EveryMapResult};
7use std::sync::Arc;
8
9pub use types::*;
10
11const ROADS_BASE_URL: &str = "https://roads.googleapis.com/v1";
12
13/// Implementation of AttributeProvider for Google Roads API speedLimits.
14///
15/// Google's attribute coverage is limited to speed limits via the Roads API.
16/// For richer attribute data (road class, lanes, etc.), use HERE or TomTom.
17pub struct GoogleAttributeProvider {
18    pub(crate) client: Arc<GoogleClient>,
19    pub(crate) base_url: String,
20}
21
22impl GoogleAttributeProvider {
23    pub fn new(client: Arc<GoogleClient>) -> Self {
24        Self {
25            client,
26            base_url: ROADS_BASE_URL.to_string(),
27        }
28    }
29
30    pub fn with_base_url(client: Arc<GoogleClient>, base_url: String) -> Self {
31        Self { client, base_url }
32    }
33
34    /// Get speed limits by place IDs.
35    /// GET /v1/speedLimits?placeId=...&placeId=...&units=...
36    pub async fn get_speed_limits_by_ids(
37        &self,
38        place_ids: &[String],
39        units: Option<&str>,
40    ) -> EveryMapResult<GoogleSpeedLimitsResponse> {
41        let url = format!("{}/speedLimits", self.base_url);
42        let mut params: Vec<(&str, String)> = Vec::new();
43
44        for place_id in place_ids {
45            params.push(("placeId", place_id.clone()));
46        }
47
48        if let Some(u) = units {
49            params.push(("units", u.to_string()));
50        }
51
52        let builder = self
53            .client
54            .build_request(reqwest::Method::GET, &url)
55            .query(&params);
56
57        let result: GoogleSpeedLimitsResponse = self.client.request_json(builder).await?;
58        Ok(result)
59    }
60
61    /// Get speed limits along a path (snapped to roads).
62    /// GET /v1/speedLimits?path=...&units=...
63    pub async fn get_speed_limits_along_path(
64        &self,
65        path: &str,
66        units: Option<&str>,
67    ) -> EveryMapResult<GoogleSpeedLimitsResponse> {
68        let url = format!("{}/speedLimits", self.base_url);
69        let mut params: Vec<(&str, String)> = vec![("path", path.to_string())];
70
71        if let Some(u) = units {
72            params.push(("units", u.to_string()));
73        }
74
75        let builder = self
76            .client
77            .build_request(reqwest::Method::GET, &url)
78            .query(&params);
79
80        let result: GoogleSpeedLimitsResponse = self.client.request_json(builder).await?;
81        Ok(result)
82    }
83}
84
85/// Convert core `AttributeOptions` to Google-specific parameters.
86fn attribute_options_from_core(
87    options: &AttributeOptions,
88) -> EveryMapResult<GoogleAttributeOptions> {
89    let mut google_opts = GoogleAttributeOptions::default();
90
91    if options.bbox.is_some() {
92        log::warn!(
93            "Google Roads API speedLimits does not support bbox queries; \
94             use provider_extra.place_ids or provider_extra.path instead"
95        );
96    }
97    if options.language.is_some() {
98        log::warn!("Google Roads API speedLimits does not support language parameter; ignoring");
99    }
100
101    if let Some(extra) = &options.provider_extra {
102        if let Some(obj) = extra.as_object() {
103            if let Some(v) = obj.get("place_ids").and_then(|v| v.as_array()) {
104                let ids: Vec<String> = v
105                    .iter()
106                    .filter_map(|v| v.as_str().map(|s| s.to_string()))
107                    .collect();
108                google_opts.place_ids = Some(ids);
109            }
110            if let Some(v) = obj.get("path").and_then(|v| v.as_str()) {
111                google_opts.path = Some(v.to_string());
112            }
113            if let Some(v) = obj.get("units").and_then(|v| v.as_str()) {
114                google_opts.units = Some(v.to_string());
115            }
116        }
117    }
118
119    // Require either place_ids or path
120    if google_opts.place_ids.is_none() && google_opts.path.is_none() {
121        // Default to a place_id from bbox if provided (uncommon for Google)
122        return Err(EveryMapError::provider(
123            "google",
124            "MISSING_PARAMETER",
125            "Google AttributeProvider requires either 'place_ids' or 'path' in provider_extra",
126        ));
127    }
128
129    Ok(google_opts)
130}
131
132#[async_trait]
133impl AttributeProvider for GoogleAttributeProvider {
134    async fn get_attributes(
135        &self,
136        options: &AttributeOptions,
137    ) -> EveryMapResult<AttributeResponse> {
138        let google_opts = attribute_options_from_core(options)?;
139
140        let result = if let Some(place_ids) = &google_opts.place_ids {
141            self.get_speed_limits_by_ids(place_ids, google_opts.units.as_deref())
142                .await?
143        } else if let Some(path) = &google_opts.path {
144            self.get_speed_limits_along_path(path, google_opts.units.as_deref())
145                .await?
146        } else {
147            return Err(EveryMapError::provider(
148                "google",
149                "MISSING_PARAMETER",
150                "Google AttributeProvider requires either 'place_ids' or 'path' in provider_extra",
151            ));
152        };
153
154        let data = serde_json::to_value(result).unwrap_or(serde_json::json!({}));
155
156        Ok(AttributeResponse { data })
157    }
158}