|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
use crate::domain::{Vehicle, VehicleRoutePlan}; |
|
|
use utoipa::ToSchema; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 { |
|
|
|
|
|
let lat_e5 = (lat * 1e5).round() as i64; |
|
|
let lng_e5 = (lng * 1e5).round() as i64; |
|
|
|
|
|
|
|
|
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 |
|
|
} |
|
|
|
|
|
|
|
|
fn encode_value(value: i64, output: &mut String) { |
|
|
|
|
|
let mut encoded = if value < 0 { |
|
|
!((value) << 1) |
|
|
} else { |
|
|
(value) << 1 |
|
|
}; |
|
|
|
|
|
|
|
|
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()); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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() { |
|
|
|
|
|
let (lat_delta, consumed) = decode_value(&bytes[i..]); |
|
|
i += consumed; |
|
|
lat += lat_delta; |
|
|
|
|
|
if i >= bytes.len() { |
|
|
break; |
|
|
} |
|
|
|
|
|
|
|
|
let (lng_delta, consumed) = decode_value(&bytes[i..]); |
|
|
i += consumed; |
|
|
lng += lng_delta; |
|
|
|
|
|
coords.push((lat as f64 / 1e5, lng as f64 / 1e5)); |
|
|
} |
|
|
|
|
|
coords |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if result & 1 != 0 { |
|
|
result = !(result >> 1); |
|
|
} else { |
|
|
result >>= 1; |
|
|
} |
|
|
|
|
|
(result, consumed) |
|
|
} |
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, serde::Serialize, ToSchema)] |
|
|
pub struct EncodedSegment { |
|
|
|
|
|
pub vehicle_idx: usize, |
|
|
|
|
|
pub vehicle_name: String, |
|
|
|
|
|
pub polyline: String, |
|
|
|
|
|
pub point_count: usize, |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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() |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn get_route_coords(plan: &VehicleRoutePlan, vehicle: &Vehicle) -> Vec<(f64, f64)> { |
|
|
let mut coords = Vec::new(); |
|
|
let depot_idx = vehicle.home_location.index; |
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
|
|
let skip = if coords.is_empty() { 0 } else { 1 }; |
|
|
coords.extend(geometry.iter().skip(skip).copied()); |
|
|
} else { |
|
|
|
|
|
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() { |
|
|
|
|
|
let coords = vec![(38.5, -120.2), (40.7, -120.95), (43.252, -126.453)]; |
|
|
let encoded = encode_polyline(&coords); |
|
|
|
|
|
assert!(!encoded.is_empty()); |
|
|
|
|
|
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); |
|
|
} |
|
|
} |
|
|
|