File size: 9,571 Bytes
44dceed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
//! Geometry utilities for route visualization.
//!
//! Implements Google Polyline encoding for efficient route transmission.
//! See: <https://developers.google.com/maps/documentation/utilities/polylinealgorithm>

use crate::domain::{Vehicle, VehicleRoutePlan};
use utoipa::ToSchema;

/// Encodes a sequence of coordinates using Google Polyline Algorithm.
///
/// The algorithm encodes latitude/longitude pairs as an ASCII string for
/// efficient transmission. Each coordinate is encoded as the difference
/// from the previous point, with 5 decimal places of precision.
///
/// # Examples
///
/// ```
/// use vehicle_routing::geometry::encode_polyline;
///
/// // Single point encodes to non-empty string
/// let encoded = encode_polyline(&[(38.5, -120.2)]);
/// assert!(!encoded.is_empty());
///
/// // Empty input gives empty output
/// let empty = encode_polyline(&[]);
/// assert!(empty.is_empty());
///
/// // Two points create a line
/// let line = encode_polyline(&[(38.5, -120.2), (40.7, -120.95)]);
/// assert!(!line.is_empty());
/// ```
pub fn encode_polyline(coords: &[(f64, f64)]) -> String {
    if coords.is_empty() {
        return String::new();
    }

    let mut result = String::new();
    let mut prev_lat = 0i64;
    let mut prev_lng = 0i64;

    for &(lat, lng) in coords {
        // Convert to fixed-point with 5 decimal places
        let lat_e5 = (lat * 1e5).round() as i64;
        let lng_e5 = (lng * 1e5).round() as i64;

        // Encode deltas
        encode_value(lat_e5 - prev_lat, &mut result);
        encode_value(lng_e5 - prev_lng, &mut result);

        prev_lat = lat_e5;
        prev_lng = lng_e5;
    }

    result
}

/// Encodes a single signed value using the polyline algorithm.
fn encode_value(value: i64, output: &mut String) {
    // Left-shift and invert if negative
    let mut encoded = if value < 0 {
        !((value) << 1)
    } else {
        (value) << 1
    };

    // Break into 5-bit chunks, OR with 0x20 if more chunks follow
    while encoded >= 0x20 {
        output.push(char::from_u32(((encoded & 0x1f) | 0x20) as u32 + 63).unwrap());
        encoded >>= 5;
    }
    output.push(char::from_u32(encoded as u32 + 63).unwrap());
}

/// Decodes a Google Polyline string back to coordinates.
///
/// # Examples
///
/// ```
/// use vehicle_routing::geometry::{encode_polyline, decode_polyline};
///
/// let original = vec![(38.5, -120.2), (40.7, -120.95), (43.252, -126.453)];
/// let encoded = encode_polyline(&original);
/// let decoded = decode_polyline(&encoded);
///
/// // Check round-trip (within 5 decimal places precision)
/// assert_eq!(decoded.len(), original.len());
/// for (orig, dec) in original.iter().zip(decoded.iter()) {
///     assert!((orig.0 - dec.0).abs() < 0.00001);
///     assert!((orig.1 - dec.1).abs() < 0.00001);
/// }
/// ```
pub fn decode_polyline(encoded: &str) -> Vec<(f64, f64)> {
    let mut coords = Vec::new();
    let mut lat = 0i64;
    let mut lng = 0i64;
    let bytes = encoded.as_bytes();
    let mut i = 0;

    while i < bytes.len() {
        // Decode latitude delta
        let (lat_delta, consumed) = decode_value(&bytes[i..]);
        i += consumed;
        lat += lat_delta;

        if i >= bytes.len() {
            break;
        }

        // Decode longitude delta
        let (lng_delta, consumed) = decode_value(&bytes[i..]);
        i += consumed;
        lng += lng_delta;

        coords.push((lat as f64 / 1e5, lng as f64 / 1e5));
    }

    coords
}

/// Decodes a single value, returning (value, bytes_consumed).
fn decode_value(bytes: &[u8]) -> (i64, usize) {
    let mut result = 0i64;
    let mut shift = 0;
    let mut consumed = 0;

    for &b in bytes {
        consumed += 1;
        let chunk = (b as i64) - 63;
        result |= (chunk & 0x1f) << shift;
        shift += 5;

        if chunk < 0x20 {
            break;
        }
    }

    // Invert if negative (check LSB)
    if result & 1 != 0 {
        result = !(result >> 1);
    } else {
        result >>= 1;
    }

    (result, consumed)
}

/// Encoded route segment for a vehicle's route.
#[derive(Debug, Clone, serde::Serialize, ToSchema)]
pub struct EncodedSegment {
    /// Vehicle index.
    pub vehicle_idx: usize,
    /// Vehicle name.
    pub vehicle_name: String,
    /// Encoded polyline string (Google format).
    pub polyline: String,
    /// Number of points in the route.
    pub point_count: usize,
}

/// Generates encoded polylines for all vehicle routes.
///
/// Returns segments for each vehicle with non-empty routes.
///
/// # Examples
///
/// ```
/// use vehicle_routing::domain::{Location, Visit, Vehicle, VehicleRoutePlan};
/// use vehicle_routing::geometry::encode_routes;
///
/// let depot = Location::new(0, 39.95, -75.16);
/// let loc_a = Location::new(1, 39.96, -75.17);
/// let loc_b = Location::new(2, 39.94, -75.15);
///
/// let locations = vec![depot.clone(), loc_a.clone(), loc_b.clone()];
/// let visits = vec![
///     Visit::new(0, "A", loc_a),
///     Visit::new(1, "B", loc_b),
/// ];
/// let mut vehicle = Vehicle::new(0, "Alpha", 100, depot);
/// vehicle.visits = vec![0, 1];  // A -> B
///
/// let mut plan = VehicleRoutePlan::new("test", locations, visits, vec![vehicle]);
///
/// // Set up route geometries (normally done by init_routing)
/// // Route: depot(0) -> A(1) -> B(2) -> depot(0)
/// plan.route_geometries.insert((0, 1), vec![(39.95, -75.16), (39.96, -75.17)]);
/// plan.route_geometries.insert((1, 2), vec![(39.96, -75.17), (39.94, -75.15)]);
/// plan.route_geometries.insert((2, 0), vec![(39.94, -75.15), (39.95, -75.16)]);
///
/// let segments = encode_routes(&plan);
/// assert_eq!(segments.len(), 1);  // One vehicle with visits
/// assert_eq!(segments[0].vehicle_name, "Alpha");
/// assert_eq!(segments[0].point_count, 4);  // depot -> A -> B -> depot
/// ```
pub fn encode_routes(plan: &VehicleRoutePlan) -> Vec<EncodedSegment> {
    plan.vehicles
        .iter()
        .filter(|v| !v.visits.is_empty())
        .map(|vehicle| {
            let coords = get_route_coords(plan, vehicle);
            let polyline = encode_polyline(&coords);
            EncodedSegment {
                vehicle_idx: vehicle.id,
                vehicle_name: vehicle.name.clone(),
                polyline,
                point_count: coords.len(),
            }
        })
        .collect()
}

/// Gets coordinates for a vehicle's complete route (depot -> visits -> depot).
///
/// Uses stored route geometries from road network routing.
/// Returns empty if route geometries are not initialized.
fn get_route_coords(plan: &VehicleRoutePlan, vehicle: &Vehicle) -> Vec<(f64, f64)> {
    let mut coords = Vec::new();
    let depot_idx = vehicle.home_location.index;

    // Build the sequence of location indices: depot -> visits -> depot
    let visit_loc_indices: Vec<usize> = vehicle
        .visits
        .iter()
        .filter_map(|&v| plan.get_visit(v).map(|visit| visit.location.index))
        .collect();

    let route: Vec<usize> = std::iter::once(depot_idx)
        .chain(visit_loc_indices)
        .chain(std::iter::once(depot_idx))
        .collect();

    // Process each leg
    for i in 0..route.len().saturating_sub(1) {
        let from_idx = route[i];
        let to_idx = route[i + 1];

        if let Some(geometry) = plan.route_geometry(from_idx, to_idx) {
            // Use stored road geometry
            // Skip first point of subsequent segments to avoid duplicates
            let skip = if coords.is_empty() { 0 } else { 1 };
            coords.extend(geometry.iter().skip(skip).copied());
        } else {
            // Fallback: use direct lat/lng when road geometry unavailable
            if coords.is_empty() {
                if let Some(from_loc) = plan.get_location(from_idx) {
                    coords.push((from_loc.latitude, from_loc.longitude));
                }
            }
            if let Some(to_loc) = plan.get_location(to_idx) {
                coords.push((to_loc.latitude, to_loc.longitude));
            }
        }
    }

    coords
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_encode_decode_roundtrip() {
        let coords = vec![
            (38.5, -120.2),
            (40.7, -120.95),
            (43.252, -126.453),
        ];
        let encoded = encode_polyline(&coords);
        let decoded = decode_polyline(&encoded);

        assert_eq!(decoded.len(), coords.len());
        for (orig, dec) in coords.iter().zip(decoded.iter()) {
            assert!((orig.0 - dec.0).abs() < 0.00001);
            assert!((orig.1 - dec.1).abs() < 0.00001);
        }
    }

    #[test]
    fn test_known_encoding() {
        // Known encoding from Google's examples
        let coords = vec![(38.5, -120.2), (40.7, -120.95), (43.252, -126.453)];
        let encoded = encode_polyline(&coords);
        // The encoding should be deterministic
        assert!(!encoded.is_empty());
        // Verify we can decode it back
        let decoded = decode_polyline(&encoded);
        assert_eq!(decoded.len(), 3);
    }

    #[test]
    fn test_empty_coords() {
        let encoded = encode_polyline(&[]);
        assert!(encoded.is_empty());
        let decoded = decode_polyline("");
        assert!(decoded.is_empty());
    }

    #[test]
    fn test_single_point() {
        let coords = vec![(0.0, 0.0)];
        let encoded = encode_polyline(&coords);
        let decoded = decode_polyline(&encoded);
        assert_eq!(decoded.len(), 1);
        assert!((decoded[0].0).abs() < 0.00001);
        assert!((decoded[0].1).abs() < 0.00001);
    }
}