pub mod break_handler;
pub mod preprocessor;
use crate::probability::{emission_probability, transition_probability};
use crate::road_network::RoadNetwork;
use crate::types::{CandidatePoint, GpsMeasurement, SegmentId};
use crate::viterbi::{HmmLatticeBuilder, ViterbiPath};
use geo::algorithm::euclidean_distance::EuclideanDistance;
use geo::Point;
pub use break_handler::BreakHandler;
pub use preprocessor::TracePreprocessor;
#[derive(Debug, Clone)]
pub struct MapMatchingConfig {
pub max_candidate_distance: f64,
pub max_candidates_per_point: usize,
pub gps_sigma: f64,
pub break_time_threshold: f64,
pub break_distance_threshold: f64,
}
impl Default for MapMatchingConfig {
fn default() -> Self {
Self {
max_candidate_distance: 50.0,
max_candidates_per_point: 10,
gps_sigma: 5.0,
break_time_threshold: 120.0, break_distance_threshold: 200.0, }
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct MatchedPoint {
pub gps_measurement: GpsMeasurement,
pub segment_id: SegmentId,
pub projected_point: Point<f64>,
pub emission_probability: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MatchedTrace {
pub points: Vec<MatchedPoint>,
pub total_probability: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TraceBreak {
pub start_index: usize,
pub end_index: usize,
pub time_gap: f64,
pub distance_gap: f64,
}
pub struct MapMatcher {
config: MapMatchingConfig,
preprocessor: TracePreprocessor,
break_handler: BreakHandler,
}
impl MapMatcher {
pub fn new() -> Self {
let config = MapMatchingConfig::default();
Self::with_config(config)
}
pub fn with_config(config: MapMatchingConfig) -> Self {
let preprocessor = TracePreprocessor::new(&config);
let break_handler = BreakHandler::new(&config);
Self {
config,
preprocessor,
break_handler,
}
}
pub fn match_trace(
&self,
gps_trace: &[GpsMeasurement],
road_network: &RoadNetwork,
) -> Result<Vec<MatchedTrace>, MapMatchingError> {
self.match_gps_to_roads(gps_trace, road_network)
}
pub fn match_gps_to_roads(
&self,
gps_trace: &[GpsMeasurement],
road_network: &RoadNetwork,
) -> Result<Vec<MatchedTrace>, MapMatchingError> {
if gps_trace.is_empty() {
return Ok(Vec::new());
}
let preprocessed_trace = self.preprocessor.preprocess(gps_trace)?;
if preprocessed_trace.is_empty() {
return Err(MapMatchingError::InvalidInput(
"All GPS points were filtered out during preprocessing".to_string(),
));
}
let candidate_sets =
self.generate_candidates(&preprocessed_trace, road_network)?;
if candidate_sets.iter().any(|cs| cs.is_empty()) {
return self.handle_missing_candidates(
&preprocessed_trace,
road_network,
candidate_sets,
);
}
let lattice = self.build_hmm_lattice(&candidate_sets)?;
let best_path =
crate::viterbi::solver::viterbi_path(&lattice).ok_or_else(|| {
MapMatchingError::ViterbiSolverError("No valid path found".to_string())
})?;
let matched_points = self.path_to_matched_points(
&best_path,
&candidate_sets,
&preprocessed_trace,
)?;
let breaks = self.break_handler.detect_breaks(&preprocessed_trace)?;
if breaks.is_empty() {
Ok(vec![MatchedTrace {
points: matched_points,
total_probability: best_path.probability(),
}])
} else {
let trace_segments =
self.split_trace_at_breaks(&preprocessed_trace, &breaks);
let mut matched_traces = Vec::new();
for segment in trace_segments {
if !segment.is_empty() {
let segment_match = self.match_segment(&segment, road_network)?;
matched_traces.push(segment_match);
}
}
Ok(matched_traces)
}
}
fn handle_missing_candidates(
&self,
gps_trace: &[GpsMeasurement],
road_network: &RoadNetwork,
candidate_sets: Vec<Vec<CandidatePoint<'_>>>,
) -> Result<Vec<MatchedTrace>, MapMatchingError> {
let mut points_with_candidates = Vec::new();
let mut current_segment = Vec::new();
for (measurement, candidates) in gps_trace.iter().zip(candidate_sets.iter()) {
if candidates.is_empty() {
if !current_segment.is_empty() {
points_with_candidates.push(current_segment);
current_segment = Vec::new();
}
} else {
current_segment.push(measurement.clone());
}
}
if !current_segment.is_empty() {
points_with_candidates.push(current_segment);
}
let mut matched_traces = Vec::new();
for segment in points_with_candidates {
if segment.len() >= 2 {
let segment_match = self.match_segment(&segment, road_network)?;
matched_traces.push(segment_match);
}
}
if matched_traces.is_empty() {
return Err(MapMatchingError::NoValidCandidates(
"No GPS points could be matched to road network".to_string(),
));
}
Ok(matched_traces)
}
fn match_segment(
&self,
gps_trace: &[GpsMeasurement],
road_network: &RoadNetwork,
) -> Result<MatchedTrace, MapMatchingError> {
if gps_trace.is_empty() {
return Ok(MatchedTrace {
points: Vec::new(),
total_probability: 1.0,
});
}
let candidate_sets = self.generate_candidates(gps_trace, road_network)?;
let lattice = self.build_hmm_lattice(&candidate_sets)?;
let best_path =
crate::viterbi::solver::viterbi_path(&lattice).ok_or_else(|| {
MapMatchingError::ViterbiSolverError("No valid path found".to_string())
})?;
let matched_points =
self.path_to_matched_points(&best_path, &candidate_sets, gps_trace)?;
Ok(MatchedTrace {
points: matched_points,
total_probability: best_path.probability(),
})
}
fn generate_candidates<'a>(
&self,
gps_trace: &[GpsMeasurement],
road_network: &'a RoadNetwork,
) -> Result<Vec<Vec<CandidatePoint<'a>>>, MapMatchingError> {
let mut candidate_sets = Vec::new();
for gps_measurement in gps_trace {
let candidates =
self.find_nearby_segments(gps_measurement, road_network)?;
candidate_sets.push(candidates);
}
Ok(candidate_sets)
}
fn find_nearby_segments<'a>(
&self,
gps_measurement: &GpsMeasurement,
road_network: &'a RoadNetwork,
) -> Result<Vec<CandidatePoint<'a>>, MapMatchingError> {
let search_radius_degrees = self.config.max_candidate_distance / 111_000.0;
let nearby_segments = road_network
.query_segments_near(gps_measurement.point, search_radius_degrees);
let mut candidates = Vec::new();
for segment in nearby_segments {
let candidate = CandidatePoint {
segment_id: segment.id,
segment,
gps_point: gps_measurement.point,
};
if let Ok(_projected_point) = candidate.projected_point() {
let distance_meters = candidate.distance_to_gps() * 111_000.0;
if distance_meters <= self.config.max_candidate_distance {
candidates.push(candidate);
}
}
}
candidates.sort_by(|a, b| {
let dist_a = a.distance_to_gps();
let dist_b = b.distance_to_gps();
dist_a
.partial_cmp(&dist_b)
.unwrap_or(std::cmp::Ordering::Equal)
});
candidates.truncate(self.config.max_candidates_per_point);
Ok(candidates)
}
fn build_hmm_lattice<'a>(
&self,
candidate_sets: &'a [Vec<CandidatePoint<'a>>],
) -> Result<crate::viterbi::HmmLattice, MapMatchingError> {
if candidate_sets.is_empty() {
return Err(MapMatchingError::InvalidInput(
"Empty candidate sets".to_string(),
));
}
let time_steps = candidate_sets.len();
let states_per_step: Vec<usize> =
candidate_sets.iter().map(|cs| cs.len()).collect();
let mut emission_probs = Vec::with_capacity(time_steps);
for candidates in candidate_sets {
let mut time_step_probs = Vec::with_capacity(candidates.len());
for candidate in candidates {
let emission_prob = match candidate.projected_point() {
Ok(projected) => emission_probability(
&candidate.gps_point,
&projected,
self.config.gps_sigma,
),
Err(_) => 0.001, };
time_step_probs.push(emission_prob);
}
emission_probs.push(time_step_probs);
}
let mut transition_probs = Vec::with_capacity(time_steps - 1);
for time_step in 0..candidate_sets.len() - 1 {
let current_candidates = &candidate_sets[time_step];
let next_candidates = &candidate_sets[time_step + 1];
let mut time_step_transitions =
Vec::with_capacity(current_candidates.len());
for current_candidate in current_candidates {
let mut from_state_transitions =
Vec::with_capacity(next_candidates.len());
for next_candidate in next_candidates {
let great_circle_distance = self.calculate_great_circle_distance(
current_candidate,
next_candidate,
)?;
let route_distance = great_circle_distance * 1.2; let beta = 100.0;
let transition_prob = transition_probability(
great_circle_distance,
route_distance,
beta,
);
from_state_transitions.push(transition_prob);
}
time_step_transitions.push(from_state_transitions);
}
transition_probs.push(time_step_transitions);
}
let lattice = HmmLatticeBuilder::new()
.with_structure(time_steps, states_per_step)
.with_emission_probs(emission_probs)
.with_transition_probs(transition_probs)
.build()
.map_err(|e| MapMatchingError::ViterbiSolverError(e.to_string()))?;
Ok(lattice)
}
fn path_to_matched_points<'a>(
&self,
path: &ViterbiPath,
candidate_sets: &'a [Vec<CandidatePoint<'a>>],
gps_trace: &[GpsMeasurement],
) -> Result<Vec<MatchedPoint>, MapMatchingError> {
let mut matched_points = Vec::new();
for (time_step, &state_id) in path.states.iter().enumerate() {
let candidate_idx = state_id % self.config.max_candidates_per_point;
let candidate = &candidate_sets[time_step][candidate_idx];
let gps_measurement = &gps_trace[time_step];
let projected_point = candidate.projected_point()?;
let emission_prob = emission_probability(
&candidate.gps_point,
&projected_point,
self.config.gps_sigma,
);
matched_points.push(MatchedPoint {
gps_measurement: gps_measurement.clone(),
segment_id: candidate.segment_id,
projected_point,
emission_probability: emission_prob,
});
}
Ok(matched_points)
}
fn split_trace_at_breaks(
&self,
trace: &[GpsMeasurement],
breaks: &[TraceBreak],
) -> Vec<Vec<GpsMeasurement>> {
if breaks.is_empty() {
return vec![trace.to_vec()];
}
let mut segments = Vec::new();
let mut start_idx = 0;
for break_info in breaks {
if break_info.start_index > start_idx {
segments.push(trace[start_idx..break_info.start_index].to_vec());
}
start_idx = break_info.end_index;
}
if start_idx < trace.len() {
segments.push(trace[start_idx..].to_vec());
}
segments
}
fn calculate_great_circle_distance<'a>(
&self,
candidate1: &CandidatePoint<'a>,
candidate2: &CandidatePoint<'a>,
) -> Result<f64, MapMatchingError> {
let projected1 = candidate1.projected_point()?;
let projected2 = candidate2.projected_point()?;
let distance_degrees = projected1.euclidean_distance(&projected2);
let distance_meters = distance_degrees * 111_000.0;
Ok(distance_meters)
}
}
impl Default for MapMatcher {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum MapMatchingError {
InvalidInput(String),
NoValidCandidates(String),
ViterbiSolverError(String),
ProjectionError(String),
}
impl std::fmt::Display for MapMatchingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MapMatchingError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
MapMatchingError::NoValidCandidates(msg) => {
write!(f, "No valid candidates: {}", msg)
}
MapMatchingError::ViterbiSolverError(msg) => {
write!(f, "Viterbi solver error: {}", msg)
}
MapMatchingError::ProjectionError(msg) => {
write!(f, "Projection error: {}", msg)
}
}
}
}
impl std::error::Error for MapMatchingError {}
impl From<&'static str> for MapMatchingError {
fn from(msg: &'static str) -> Self {
MapMatchingError::ProjectionError(msg.to_string())
}
}