solverforge_maps/
geometry.rs

1//! Google Polyline encoding/decoding for route visualization.
2
3use serde::Serialize;
4use utoipa::ToSchema;
5
6use crate::routing::Coord;
7
8/// Encodes coordinates using Google Polyline Algorithm.
9///
10/// # Examples
11///
12/// ```
13/// use solverforge_maps::{encode_polyline, Coord};
14///
15/// let encoded = encode_polyline(&[Coord::new(38.5, -120.2)]);
16/// assert!(!encoded.is_empty());
17///
18/// let empty = encode_polyline(&[]);
19/// assert!(empty.is_empty());
20/// ```
21pub fn encode_polyline(coords: &[Coord]) -> String {
22    if coords.is_empty() {
23        return String::new();
24    }
25
26    let mut result = String::new();
27    let mut prev_lat = 0i64;
28    let mut prev_lng = 0i64;
29
30    for coord in coords {
31        let lat_e5 = (coord.lat * 1e5).round() as i64;
32        let lng_e5 = (coord.lng * 1e5).round() as i64;
33
34        encode_value(lat_e5 - prev_lat, &mut result);
35        encode_value(lng_e5 - prev_lng, &mut result);
36
37        prev_lat = lat_e5;
38        prev_lng = lng_e5;
39    }
40
41    result
42}
43
44fn encode_value(value: i64, output: &mut String) {
45    let mut encoded = if value < 0 {
46        !((value) << 1)
47    } else {
48        (value) << 1
49    };
50
51    while encoded >= 0x20 {
52        output.push(char::from_u32(((encoded & 0x1f) | 0x20) as u32 + 63).unwrap());
53        encoded >>= 5;
54    }
55    output.push(char::from_u32(encoded as u32 + 63).unwrap());
56}
57
58/// Decodes a Google Polyline string back to coordinates.
59///
60/// # Examples
61///
62/// ```
63/// use solverforge_maps::{encode_polyline, decode_polyline, Coord};
64///
65/// let original = vec![Coord::new(38.5, -120.2), Coord::new(40.7, -120.95)];
66/// let encoded = encode_polyline(&original);
67/// let decoded = decode_polyline(&encoded);
68///
69/// assert_eq!(decoded.len(), original.len());
70/// ```
71pub fn decode_polyline(encoded: &str) -> Vec<Coord> {
72    let mut coords = Vec::new();
73    let mut lat = 0i64;
74    let mut lng = 0i64;
75    let bytes = encoded.as_bytes();
76    let mut i = 0;
77
78    while i < bytes.len() {
79        let (lat_delta, consumed) = decode_value(&bytes[i..]);
80        i += consumed;
81        lat += lat_delta;
82
83        if i >= bytes.len() {
84            break;
85        }
86
87        let (lng_delta, consumed) = decode_value(&bytes[i..]);
88        i += consumed;
89        lng += lng_delta;
90
91        coords.push(Coord::new(lat as f64 / 1e5, lng as f64 / 1e5));
92    }
93
94    coords
95}
96
97fn decode_value(bytes: &[u8]) -> (i64, usize) {
98    let mut result = 0i64;
99    let mut shift = 0;
100    let mut consumed = 0;
101
102    for &b in bytes {
103        consumed += 1;
104        let chunk = (b as i64) - 63;
105        result |= (chunk & 0x1f) << shift;
106        shift += 5;
107
108        if chunk < 0x20 {
109            break;
110        }
111    }
112
113    if result & 1 != 0 {
114        result = !(result >> 1);
115    } else {
116        result >>= 1;
117    }
118
119    (result, consumed)
120}
121
122#[derive(Debug, Clone, Serialize, ToSchema)]
123pub struct EncodedSegment {
124    pub entity_idx: usize,
125    pub entity_name: String,
126    pub polyline: String,
127    pub point_count: usize,
128}
129
130impl EncodedSegment {
131    pub fn new(entity_idx: usize, entity_name: impl Into<String>, coords: &[Coord]) -> Self {
132        Self {
133            entity_idx,
134            entity_name: entity_name.into(),
135            polyline: encode_polyline(coords),
136            point_count: coords.len(),
137        }
138    }
139}