High-performance Fast Map Matching (FMM) algorithm implementation in C++ with Python bindings.
pybind11-fmm provides a fast and efficient implementation of the Fast Map Matching algorithm for matching GPS trajectories to road networks. The core algorithm is implemented in C++ for maximum performance, with a convenient Python API that maintains compatibility with the topo-graph (python implementation) interface.
- High Performance: Core algorithm implemented in C++ with optimized spatial indexing
- Python Integration: Full Python bindings via pybind11
- 2D Only: Simplified implementation focusing on x/y or lon/lat coordinates (no 3D support)
- Compatible API: Maintains API compatibility with topo-graph's FMM interface
- Tested: Comprehensive test suite
# Clone the repository
git clone https://github.com/your-org/pybind11-fmm
cd pybind11-fmm
# Build and install
make build
# Or manually:
pip install -e .- Python >= 3.7
- C++17 compatible compiler
- CMake >= 3.15
- pybind11
- NumPy
import numpy as np
from pybind11_fmm import Network, FastMapMatch, FastMapMatchConfig
# Create a road network
network = Network()
# Add edges (road segments)
# Each edge is defined by a polyline of coordinates
network.add_edge(
edge_id=1,
coords=np.array([[0.0, 0.0], [10.0, 0.0], [10.0, 10.0]]),
is_wgs84=False # Set to True for lon/lat coordinates
)
network.add_edge(
edge_id=2,
coords=np.array([[10.0, 10.0], [20.0, 10.0]]),
is_wgs84=False
)
# Create GPS trajectory (with some noise)
trajectory = np.array([
[1.0, 0.1],
[5.0, -0.1],
[9.9, 0.2],
[10.1, 5.0],
[10.0, 9.8],
[15.0, 10.1]
])
# Configure matching parameters
config = FastMapMatchConfig(
k=50, # Max candidates per GPS point
radius=160.0, # Search radius in meters
gps_error=40.0, # GPS standard deviation in meters
reverse_tolerance=0.0
)
# Perform map matching
fmm = FastMapMatch(network, config)
result = fmm.match_traj(trajectory)
# Access results
if result.success:
print(f"Matched path: {result.optimal_path}")
print(f"Match score: {result.score}")
for i, matched in enumerate(result.matched_points):
print(f"Point {i}: edge={matched.edge_id}, "
f"offset={matched.offset:.2f}, "
f"prob={matched.probability:.4f}")# For lon/lat coordinates (WGS84)
network = Network()
# Add edge with lon/lat coordinates
network.add_edge(
edge_id=1,
coords=np.array([
[116.3, 39.9], # Beijing area
[116.4, 39.95]
]),
is_wgs84=True # Enable WGS84 mode
)
# GPS trajectory in lon/lat
trajectory = np.array([
[116.32, 39.91],
[116.35, 39.92],
[116.38, 39.94]
])
fmm = FastMapMatch(network)
result = fmm.match_traj(trajectory)The Fast Map Matching algorithm uses a Hidden Markov Model (HMM) approach:
- Candidate Search: For each GPS point, find nearby road segments within a search radius
- Transition Graph: Build an HMM where states are candidate road segments
- Viterbi: Find the most likely path through the transition graph
- Path Construction: Extract the final matched road sequence
Compared to pure Python implementations:
- Candidate search: 3-5x speedup (eliminates Python loops)
- HMM + Viterbi: 10-50x speedup (batched shortest path queries in C++)
- Overall: 10-30x speedup on typical trajectories
Container for road network topology and geometry.
network = Network()
network.add_edge(edge_id, coords, is_wgs84=True)
geometry = network.geometry(edge_id)
candidates = network.query_radius(point, radius)Configuration parameters for the matching algorithm.
config = FastMapMatchConfig(
k=50, # Max candidates per point
radius=160.0, # Search radius (meters)
gps_error=40.0, # GPS error std dev (meters)
reverse_tolerance=0.0 # Reverse path tolerance
)Main map matching interface.
fmm = FastMapMatch(network, config=None)
result = fmm.match_traj(trajectory)Result of map matching operation.
Attributes:
success: bool - Whether matching succeededscore: float - Log probability of the matched pathmatched_points: List[MatchedCandidate] - Per-point match detailsoptimal_path: List[int] - Sequence of matched edge IDs
# Install development dependencies
pip install scikit-build-core pyproject-metadata pathspec pybind11
# Build
make build
# Run tests
make pytest
# Clean build artifacts
make clean# Run all tests
pytest tests/
# Run with verbose output
pytest -v tests/
# Run specific test
pytest tests/test_fmm.py::test_basic_matchingThe project uses:
- clang-format for C++ code formatting
- ruff for Python linting and formatting
- pre-commit hooks for automated checks
# Run pre-commit checks manually
pre-commit run --all-filesThis package is designed to be a drop-in replacement for topo-graph.fmm:
# Old (topo-graph)
from topo_graph import Skeleton
from topo_graph.fmm import FastMapMatch, FastMapMatchConfig
# New (pybind11-fmm)
from pybind11_fmm import Network as Skeleton # Compatible API
from pybind11_fmm import FastMapMatch, FastMapMatchConfigKey differences:
- Network construction API differs (use
add_edgeinstead of loading from file) - No 3D support (2D only)
- Some advanced features from topo-graph may not be available
- Batch Processing: Process multiple trajectories in a loop to amortize network construction
- Radius Tuning: Smaller radius = faster candidate search, but may miss valid matches
- K Parameter: Larger k = more accurate but slower (typical: 20-50)
- WGS84 Mode: Slightly slower than Cartesian mode due to coordinate transformations
[Your License Here]
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
Based on the original FMM algorithm:
- Original implementation: https://github.com/cyang-kth/fmm
- Algorithm paper: https://www.tandfonline.com/doi/full/10.1080/13658816.2017.1400548
[Your Contact Information]