From 43c09c8e6350a1797feff15113462bf79368abe4 Mon Sep 17 00:00:00 2001 From: kodonnell Date: Mon, 2 Feb 2026 17:31:26 +1300 Subject: [PATCH] fix for division by 0 --- src/mm/fmm/fmm_algorithm.cpp | 18 +++++++++-- test_fastmm.py | 62 ++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/mm/fmm/fmm_algorithm.cpp b/src/mm/fmm/fmm_algorithm.cpp index f026dca..e79c795 100644 --- a/src/mm/fmm/fmm_algorithm.cpp +++ b/src/mm/fmm/fmm_algorithm.cpp @@ -764,7 +764,14 @@ std::vector FastMapMatch::build_py_segments(const MatchedCandida else { cumulative_expected_time += point.d / point.speed; - point.t = t0 + cumulative_expected_time / total_expected_time * (t1 - t0); + if (total_expected_time > 0) + { + point.t = t0 + (cumulative_expected_time / total_expected_time) * (t1 - t0); + } + else + { + point.t = t0; + } } } } @@ -797,7 +804,14 @@ std::vector FastMapMatch::build_py_segments(const MatchedCandida else { cumulative_segment_distance += point.d; - point.t = t0 + (cumulative_segment_distance / total_segment_distance) * (t1 - t0); + if (total_segment_distance > 0) + { + point.t = t0 + (cumulative_segment_distance / total_segment_distance) * (t1 - t0); + } + else + { + point.t = t0; + } } } } diff --git a/test_fastmm.py b/test_fastmm.py index c3cb6cd..1846f75 100644 --- a/test_fastmm.py +++ b/test_fastmm.py @@ -585,6 +585,68 @@ def test_reversed_geometry_consistency(self): print(f" Offset range: {edge.points[0].edge_offset:.1f} to {edge.points[-1].edge_offset:.1f}") +class TestTimeInterpolationEdgeCases: + """Test time interpolation with potential divide-by-zero edge cases.""" + + def test_stationary_trajectory_time_interpolation(self): + """Test time interpolation when GPS points are at the exact same location.""" + network = Network() + network.add_edge(1, source=1, target=2, geom=[(0, 0), (100, 0)], speed=50) + network.finalize() + + matcher = FastMapMatch( + network, + TransitionMode.SHORTEST, + max_distance_between_candidates=1000, + cache_dir=".cache/test_stationary_time", + ) + # Two identical GPS points with different timestamps + t = Trajectory.from_xyt_tuples([(50, 0, 100), (50, 0, 200)]) + result = matcher.match(t, max_candidates=4, candidate_search_radius=50, gps_error=50) + + assert len(result.subtrajectories) == 1 + sub = result.subtrajectories[0] + assert sub.error_code == MatchErrorCode.SUCCESS + + # Check that timestamps are NOT nan + import math + + for segment in sub.segments: + for edge in segment.edges: + for point in edge.points: + assert not math.isnan(point.t), f"Point at ({point.x}, {point.y}) has NaN timestamp" + assert point.t >= 100.0 and point.t <= 200.0 + + def test_zero_speed_segment_time_interpolation(self): + """Test time interpolation when edge speed is very low or distance is zero.""" + network = Network() + # Edge with very low distance + network.add_edge(1, source=1, target=2, geom=[(0, 0), (0.000001, 0)], speed=50) + network.finalize() + + matcher = FastMapMatch( + network, + TransitionMode.FASTEST, + max_time_between_candidates=1000, + cache_dir=".cache/test_zero_dist_time", + ) + # GPS points far outside the tiny edge, but will snap to it + t = Trajectory.from_xyt_tuples([(10, 0, 10), (20, 0, 20)]) + result = matcher.match(t, max_candidates=4, candidate_search_radius=50, gps_error=50, reference_speed=50) + + assert len(result.subtrajectories) == 1 + sub = result.subtrajectories[0] + assert sub.error_code == MatchErrorCode.SUCCESS + + # Check that timestamps are NOT nan + import math + + for segment in sub.segments: + for edge in segment.edges: + for point in edge.points: + assert not math.isnan(point.t), f"Point at ({point.x}, {point.y}) has NaN timestamp" + + if __name__ == "__main__": # Allow running without pytest print("Running tests...")