Skip to main content

everymap_providers_google/domain/matching/
mod.rs

1pub mod types;
2
3use crate::client::GoogleClient;
4use async_trait::async_trait;
5use everymap_core::domains::matching::{
6    MatchedPoint, MatchingOptions, RouteMatcher, TraceResponse,
7};
8use everymap_core::error::EveryMapResult;
9use everymap_core::types::Coordinate;
10use std::sync::Arc;
11
12pub use types::*;
13
14const ROADS_BASE_URL: &str = "https://roads.googleapis.com/v1";
15
16/// Implementation of RouteMatcher for Google Maps Roads API.
17///
18/// Uses the `snapToRoads` endpoint to snap GPS points to the road network.
19pub struct GoogleRouteMatcher {
20    pub(crate) client: Arc<GoogleClient>,
21    pub(crate) base_url: String,
22}
23
24impl GoogleRouteMatcher {
25    pub fn new(client: Arc<GoogleClient>) -> Self {
26        Self {
27            client,
28            base_url: ROADS_BASE_URL.to_string(),
29        }
30    }
31
32    pub fn with_base_url(client: Arc<GoogleClient>, base_url: String) -> Self {
33        Self { client, base_url }
34    }
35}
36
37/// Convert core `MatchingOptions` to Google-specific parameters.
38fn matching_options_from_core(options: &MatchingOptions) -> GoogleMatchOptions {
39    let mut google_opts = GoogleMatchOptions::default();
40
41    if options.transport_mode.is_some() {
42        log::warn!("Google Roads API snapToRoads does not support transport_mode; ignoring");
43    }
44    if options.heading.is_some() {
45        log::warn!("Google Roads API snapToRoads does not support heading; ignoring");
46    }
47    if options.departure_time.is_some() {
48        log::warn!("Google Roads API snapToRoads does not support departure_time; ignoring");
49    }
50    if !options.avoid.is_empty() {
51        log::warn!("Google Roads API snapToRoads does not support avoid restrictions; ignoring");
52    }
53
54    if let Some(extra) = &options.provider_extra {
55        if let Some(obj) = extra.as_object() {
56            if let Some(v) = obj.get("interpolate").and_then(|v| v.as_bool()) {
57                google_opts.interpolate = v;
58            }
59            if let Some(v) = obj.get("snapping").and_then(|v| v.as_str()) {
60                google_opts.snapping = Some(v.to_string());
61            }
62        }
63    }
64
65    google_opts
66}
67
68#[async_trait]
69impl RouteMatcher for GoogleRouteMatcher {
70    async fn match_route(
71        &self,
72        points: &[Coordinate],
73        options: &MatchingOptions,
74    ) -> EveryMapResult<TraceResponse> {
75        let google_opts = matching_options_from_core(options);
76
77        // Google snapToRoads requires at least 2 points, max 100
78        let path: String = points
79            .iter()
80            .map(|p| format!("{},{}", p.lat, p.lng))
81            .collect::<Vec<_>>()
82            .join("|");
83
84        let mut params: Vec<(&str, String)> = vec![("path", path)];
85
86        if google_opts.interpolate {
87            params.push(("interpolate", "true".to_string()));
88        }
89
90        let url = format!("{}/snapToRoads", self.base_url);
91        let builder = self
92            .client
93            .build_request(reqwest::Method::GET, &url)
94            .query(&params);
95
96        let response: GoogleSnapResponse = self.client.request_json(builder).await?;
97
98        let matched_points: Vec<MatchedPoint> = response
99            .snapped_points
100            .into_iter()
101            .map(|sp| {
102                MatchedPoint {
103                    coordinate: Coordinate::new(sp.location.latitude, sp.location.longitude)
104                        .unwrap_or(Coordinate::ORIGIN),
105                    confidence: None, // Google doesn't provide confidence scores
106                    road_name: sp.place_id, // Use place_id as an identifier
107                }
108            })
109            .collect();
110
111        // Google doesn't provide distance/duration in snapToRoads response
112        Ok(TraceResponse {
113            matched_points,
114            distance: 0.0,
115            duration: None,
116            raw: None,
117        })
118    }
119}