Skip to main content

everymap_providers_google/domain/positioning/
mod.rs

1pub mod types;
2
3use crate::client::GoogleClient;
4use async_trait::async_trait;
5use everymap_core::domains::positioning::{
6    NetworkPositioner, PositioningOptions, PositioningResponse as CorePositioningResponse,
7};
8use everymap_core::error::EveryMapResult;
9use everymap_core::types::Coordinate;
10use serde::Serialize;
11use std::sync::Arc;
12
13pub use types::*;
14
15const GEOLOCATION_BASE_URL: &str = "https://www.googleapis.com/geolocation/v1";
16
17/// Internal request body for the Geolocation API.
18#[derive(Debug, Serialize)]
19#[serde(rename_all = "camelCase")]
20struct GeolocationRequestBody {
21    #[serde(skip_serializing_if = "Option::is_none")]
22    consider_ip: Option<bool>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    wifi_access_points: Option<Vec<GoogleWifiAccessPoint>>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    cell_towers: Option<Vec<GoogleCellTower>>,
27}
28
29impl From<GooglePositioningOptions> for GeolocationRequestBody {
30    fn from(options: GooglePositioningOptions) -> Self {
31        Self {
32            consider_ip: options.consider_ip,
33            wifi_access_points: options.wifi_access_points,
34            cell_towers: options.cell_towers,
35        }
36    }
37}
38
39/// Implementation of NetworkPositioner for Google Geolocation API.
40pub struct GooglePositioner {
41    pub(crate) client: Arc<GoogleClient>,
42    pub(crate) base_url: String,
43}
44
45impl GooglePositioner {
46    pub fn new(client: Arc<GoogleClient>) -> Self {
47        Self {
48            client,
49            base_url: GEOLOCATION_BASE_URL.to_string(),
50        }
51    }
52
53    pub fn with_base_url(client: Arc<GoogleClient>, base_url: String) -> Self {
54        Self { client, base_url }
55    }
56
57    /// Get position estimate with rich response type.
58    /// POST /geolocation/v1/geolocate
59    pub async fn locate(
60        &self,
61        options: GooglePositioningOptions,
62    ) -> EveryMapResult<GoogleGeolocationResponse> {
63        let url = format!("{}/geolocate", self.base_url);
64        let body = GeolocationRequestBody::from(options);
65        let builder = self
66            .client
67            .build_request(reqwest::Method::POST, &url)
68            .json(&body);
69
70        let result: GoogleGeolocationResponse = self.client.request_json(builder).await?;
71        Ok(result)
72    }
73}
74
75/// Convert core `PositioningOptions` to Google-specific `GooglePositioningOptions`,
76/// extracting fields from `provider_extra`.
77fn positioning_options_from_core(options: &PositioningOptions) -> GooglePositioningOptions {
78    let mut google_opts = GooglePositioningOptions::default();
79
80    if let Some(extra) = &options.provider_extra {
81        if let Ok(parsed) = serde_json::from_value::<GooglePositioningOptions>(extra.clone()) {
82            google_opts = parsed;
83        }
84    }
85
86    google_opts
87}
88
89impl From<GoogleGeolocationResponse> for CorePositioningResponse {
90    fn from(response: GoogleGeolocationResponse) -> Self {
91        let coordinate = Coordinate::new(response.location.lat, response.location.lng)
92            .unwrap_or(Coordinate::ORIGIN);
93        Self {
94            coordinate,
95            accuracy: if response.accuracy > 0.0 {
96                Some(response.accuracy)
97            } else {
98                None
99            },
100            altitude: None,
101            altitude_accuracy: None,
102            raw: None,
103        }
104    }
105}
106
107#[async_trait]
108impl NetworkPositioner for GooglePositioner {
109    async fn get_position(
110        &self,
111        options: &PositioningOptions,
112    ) -> EveryMapResult<CorePositioningResponse> {
113        let google_opts = positioning_options_from_core(options);
114        let result = self.locate(google_opts).await?;
115        Ok(result.into())
116    }
117}