Skip to content

cubao/pybind11-fmm

Repository files navigation

pybind11-fmm

High-performance Fast Map Matching (FMM) algorithm implementation in C++ with Python bindings.

Overview

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.

Key Features

  • 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

Installation

From Source

# 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 .

Requirements

  • Python >= 3.7
  • C++17 compatible compiler
  • CMake >= 3.15
  • pybind11
  • NumPy

Quick Start

Basic Usage

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}")

Working with WGS84 Coordinates

# 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)

Algorithm Overview

The Fast Map Matching algorithm uses a Hidden Markov Model (HMM) approach:

  1. Candidate Search: For each GPS point, find nearby road segments within a search radius
  2. Transition Graph: Build an HMM where states are candidate road segments
  3. Viterbi: Find the most likely path through the transition graph
  4. Path Construction: Extract the final matched road sequence

Performance

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

API Reference

Classes

Network

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)

FastMapMatchConfig

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
)

FastMapMatch

Main map matching interface.

fmm = FastMapMatch(network, config=None)
result = fmm.match_traj(trajectory)

MatchResult

Result of map matching operation.

Attributes:

  • success: bool - Whether matching succeeded
  • score: float - Log probability of the matched path
  • matched_points: List[MatchedCandidate] - Per-point match details
  • optimal_path: List[int] - Sequence of matched edge IDs

Development

Building from Source

# Install development dependencies
pip install scikit-build-core pyproject-metadata pathspec pybind11

# Build
make build

# Run tests
make pytest

# Clean build artifacts
make clean

Testing

# Run all tests
pytest tests/

# Run with verbose output
pytest -v tests/

# Run specific test
pytest tests/test_fmm.py::test_basic_matching

Code Quality

The 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-files

Migration from topo-graph

This 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, FastMapMatchConfig

Key differences:

  • Network construction API differs (use add_edge instead of loading from file)
  • No 3D support (2D only)
  • Some advanced features from topo-graph may not be available

Performance Tips

  1. Batch Processing: Process multiple trajectories in a loop to amortize network construction
  2. Radius Tuning: Smaller radius = faster candidate search, but may miss valid matches
  3. K Parameter: Larger k = more accurate but slower (typical: 20-50)
  4. WGS84 Mode: Slightly slower than Cartesian mode due to coordinate transformations

License

[Your License Here]

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

Acknowledgments

Based on the original FMM algorithm:

Contact

[Your Contact Information]