Skip to main content

Fast Map Matching - High-performance map matching library

Project description

fastmm

fastmm is a fast (C++) map-matching library for python with no dependencies, and the ability to interpolate time on the match (not just position), and also match as much as possible of the GPS trace (not just fail if a single point is wonky).

It's based on a desire to map match a lot of vehicle trace data quickly, without the infrastructure to spin up OSRM / Valhalla. (And this is probably faster as there's no IPC ... ?)

It is based on https://github.com/cyang-kth/fmm but updated to:

  • Include Python helper classes for automatic trajectory splitting and time interpolation for the match.
  • Remove GDAL/OGR dependencies - networks are created programmatically from Python
  • Be buildable on Windows/Linux/Mac with modern tooling
  • Focus on Python packaging with distributable wheels
  • Remove STMatch - we'll focus on FMM for now
  • Automated windows, linux, and macOS wheel builds

TODO

  • currently if a point from GPS trace fails (too far etc.) it's excluded from match. Should add it to match just with start == end and appropriate error code.
  • For reverse_tolerance, why not, if the movement is < reverse tolerance, just assume they're still at the same place (and it's GPS jitter)?
  • test the time apportioning.
  • If not found in UBODT, instead of bailing, do a normal djikstra lookup.
  • max_distance_between_candidates is not a hard limit in UBODT ... I think. Test this, and if needed, add an extra check.
  • Specify versions for build libs (e.g. cibuildwheel).

Installation

pip install fastmm

Quick Start (Recommended)

The simplest way to use fastmm is with the high-level FastMapMatch class:

from fastmm import FastMapMatch, MatchErrorCode, Network, Trajectory, TransitionMode, FastMapMatchConfig

# Create and populate network
network = Network()
network.add_edge(1, source=1, target=2, geom=[(0, 0), (100, 0)])
network.add_edge(2, source=2, target=3, geom=[(100, 0), (200, 0)])
network.finalize()

# Create matcher with automatic UBODT caching (SHORTEST mode - distance-based)
matcher = FastMapMatch(
    network, TransitionMode.SHORTEST, max_distance_between_candidates=300.0, cache_dir="./cache"
)

# Match a trajectory (automatic splitting)
trajectory = Trajectory.from_xy_tuples(1, [(10, 0), (50, 0), (150, 0)])
result = matcher.match(
    trajectory,
    max_candidates=8,
    candidate_search_radius=50,
    gps_error=50
)

# Process successful sub-trajectories
for sub in result.subtrajectories:
    if sub.error_code == MatchErrorCode.SUCCESS:
        print(f"Matched points {sub.start_index} to {sub.end_index}")
        for segment in sub.segments:
            print(f"  Segment from {segment.p0} to {segment.p1}")
            for edge in segment.edges:
                print(f"    Edge {edge.edge_id} with {len(edge.points)} points")

For time-based routing you simply add a speed on all edges, and use TransitionMode.FASTEST. Otherwise it's the same.

Automatic Trajectory Splitting

For trajectories that might have gaps or failures, match() automatically filters out troublesome sections, and matches everything it can. That is:

  • It ignores points with no nearby road candidates (e.g., in tunnels, off-network) - it just returns the map matched sections either side of the erroneous point. (You can choose to merge them yourself later if you want.)
  • If there's a break in the matching due to e.g. a very long distance between two points (data issues, teleportation etc.) then again, it'll return the map matches sections either side of this gap.

Interpolating time

If your trajectory has timestamps, you often want your resulting match to include time as well i.e. show you at what speed/time your vehicle moved along the matched geometry. This library returns this. If your network doesn't have speed, it just apportions the time along the matched geometry between two GPS linearly. If you have speed, it uses this correctly e.g. if the match segment concludes a 100km/hr edge and a 50km/hr of equal length, it'll apportion less time on the faster edge than on the slower edge.

Understanding Delta Parameters

The delta parameter (called max_distance_between_candidates or max_time_between_candidates in MapMatcher) controls the maximum routing cost for precomputed paths in the UBODT table:

SHORTEST Mode (Distance-Based)

  • Units: Same as your network (typically meters)
  • Meaning: Maximum road network distance between GPS points
  • Recommendation: 2-3x your expected maximum distance between consecutive GPS points
  • Example: If GPS points are ~100m apart, use delta=300m

FASTEST Mode (Time-Based)

  • Units: Seconds
  • Meaning: Maximum travel time between GPS points
  • Recommendation: 2-3x your expected maximum travel time between GPS points
  • Example: For 200m spacing at 50km/h expected speed: 200m ÷ (50,000m/3600s) ≈ 14.4s → use delta=40s

Trade-offs:

  • Larger delta: Better matching quality (more routing options), but larger file size and slower generation
  • Smaller delta: Faster generation and smaller files, but may fail to find paths between distant GPS points

Understanding Reverse Tolerance

The reverse_tolerance parameter handles GPS measurement noise that causes slight backward movement on the same edge. This is even though we're operating on directed edges already - it's to account for a bit of GPS error etc. e.g. if the GPS goes backward. Without this, our routing would work - it'd just end up matching to the other side of the road, which would mean the route would go to the end of the road then back down to the current position (but on the other edge) ... which means if they're stationary and the GPS is jumping, it could look like the vehicle is traveling up and down the street a lot.

How It Works

Edge Traversal: The graph uses directed edges. Dijkstra routing always respects edge direction (source → target). For OSM data with bidirectional roads, you should have two edges (one per direction).

Same-Edge Positioning: When two consecutive GPS points match to the same edge with the second point having a lower offset than the first (backward movement), reverse_tolerance controls whether this is allowed:

# Example: GPS noise causes apparent backward movement
GPS Point 1  Edge 1 at offset=80m (80% along AB)
GPS Point 2  Edge 1 at offset=50m (50% along AB)

# Without reverse_tolerance (0.0):
# - Transition has infinite cost → rejected
# - Algorithm may match Point 2 to opposite-direction edge (creating fake U-turn)
# - Or Point 2 gets skipped in split mode

# With reverse_tolerance=40 (40m in these units):
# - Backward movement = 80m - 50m = 30m
# - 30m < 40m ✅ Allowed with cost=0

The Reversed Flag

When backward movement is allowed (within tolerance), the reversed flag indicates this occurred. The geometry is automatically corrected to always go forward (from lower to higher offset), so you don't need to handle backward linestrings:

for segment in result.segments:
    for edge in segment.edges:
        if edge.reversed:
            # Geometry has been auto-corrected to go forward
            # But you know GPS moved backward on this edge
            # May want to flag this for quality control
            print(f"Edge {edge.edge_id} had backward GPS movement (now corrected)")

        # All edges have forward geometry regardless of reversed flag
        # edge.points always go from lower to higher offset
        for point in edge.points:
            print(f"  Offset: {point.edge_offset}, Position: ({point.x}, {point.y})")

What the reversed flag means:

  • reversed=True: GPS moved backward (offset1 > offset2), geometry was auto-corrected to go forward
  • reversed=False: GPS moved forward normally (offset1 <= offset2)

No special handling needed - the geometry is always correct. Use the flag for:

  • Quality control (detecting erratic GPS behavior)
  • Statistics (counting backward movements)
  • Debugging (understanding matching behavior)

Recommendations

Start with reverse_tolerance=0. If you're seeing a lot of jumping around on stationary (ish) vehicles, either do some pre-filtering, or try e.g. reverse_tolerance=20m.

Routing Modes: SHORTEST vs FASTEST

FastMM supports two routing modes that affect how map matching selects the most likely path.

In both cases, the emission probability is balanced with the transition probability. The emission probability is the likelihood a candidate is on the given edge based simply on how far away it is from the edge - the closer, the higher the probability. This can be controlled with the gps_error parameter - make it larger and the emission probability will stay higher even if the point is further away.

SHORTEST Mode (Distance-based)

Uses distance as the routing metric. This is the default mode and matches trajectories based on spatial proximity. The transition probability is:

tp = min(euclidean_dist, path_dist) / max(euclidean_dist, path_dist)

This compares the straight-line distance between GPS points to the network path distance. Higher probability when the path closely follows the GPS trajectory.

If you find your routes are sticking to the nearest edge, regardless of the feasibility of the route, this is because your gps_error is likely too large (?). Likewise the converse.

FASTEST Mode (Time-based)

Uses travel time as the routing metric. Requires speed values on all edges. The transition probability is

expected_time = euclidean_dist / reference_speed
actual_time = path_time (sum of segment_length / segment_speed)
tp = min(expected_time, actual_time) / max(expected_time, actual_time)

Similarly to above, this gives a higher priority when the travel time is the same, or faster than the expected travel time (which is the euclidean distance divided by the reference speed). If you're finding your routes are sticking to the nearest edge, regardless of the feasibility of the route, either decrease gps_error as above, or decrease (?) the reference speed.

Developing

You can create stubs with

python .\generate_stubs_for_wheel.py .\python\fastmm\ .\python\fastmm\

For now, this is better than doing it in a CI/CD pipeline as Windows is painful.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

fastmm-0.2.0-cp312-cp312-win_amd64.whl (735.7 kB view details)

Uploaded CPython 3.12Windows x86-64

fastmm-0.2.0-cp312-cp312-manylinux_2_28_x86_64.whl (754.6 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.28+ x86-64

fastmm-0.2.0-cp312-cp312-macosx_11_0_arm64.whl (681.2 kB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

fastmm-0.2.0-cp311-cp311-win_amd64.whl (735.1 kB view details)

Uploaded CPython 3.11Windows x86-64

fastmm-0.2.0-cp311-cp311-manylinux_2_28_x86_64.whl (754.5 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.28+ x86-64

fastmm-0.2.0-cp311-cp311-macosx_11_0_arm64.whl (681.7 kB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

fastmm-0.2.0-cp310-cp310-win_amd64.whl (734.4 kB view details)

Uploaded CPython 3.10Windows x86-64

fastmm-0.2.0-cp310-cp310-manylinux_2_28_x86_64.whl (753.2 kB view details)

Uploaded CPython 3.10manylinux: glibc 2.28+ x86-64

fastmm-0.2.0-cp310-cp310-macosx_11_0_arm64.whl (680.4 kB view details)

Uploaded CPython 3.10macOS 11.0+ ARM64

File details

Details for the file fastmm-0.2.0-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: fastmm-0.2.0-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 735.7 kB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for fastmm-0.2.0-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 989b1d22badd7de9d2cfa2fd7ff5775630a5a1b19393d974773a3689e31f349b
MD5 d73c20d20def759cc35fa6819499a536
BLAKE2b-256 eb55c489107bbd732492b1190aee650fe7ef1a9c9158f7a0071f5eaf59b8b46a

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastmm-0.2.0-cp312-cp312-win_amd64.whl:

Publisher: build-wheels.yml on kodonnell/fastmm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastmm-0.2.0-cp312-cp312-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for fastmm-0.2.0-cp312-cp312-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 b6060c73ab2ae2544936ced411c114518d7aa9d65808b5d1ccc8b4f3944e1578
MD5 218f34c9a4bab43bc74a40be179c640b
BLAKE2b-256 dbc0aab055332a397e0d039a2c1d7b00ad0c0698204a61faac37b562f60eb9ad

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastmm-0.2.0-cp312-cp312-manylinux_2_28_x86_64.whl:

Publisher: build-wheels.yml on kodonnell/fastmm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastmm-0.2.0-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for fastmm-0.2.0-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 331809fcb39f407712f68e916eea101fe5f4bd8267d36aa8a7a7ab0e95f278ae
MD5 53b178037a57ff420e5dfdb0fd21e416
BLAKE2b-256 2f3823ffbb2b8c9ebdab29f7d789ab776c870754971f0d54cab6aa395af91c3d

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastmm-0.2.0-cp312-cp312-macosx_11_0_arm64.whl:

Publisher: build-wheels.yml on kodonnell/fastmm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastmm-0.2.0-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: fastmm-0.2.0-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 735.1 kB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for fastmm-0.2.0-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 1aa835420ecdbdd9503e0bd6a5fa078d3448dea031786fca6c3805b7ebd878f4
MD5 1a1f669fb4f1dc252e62c37ffbf41cfc
BLAKE2b-256 6e3e99f0ec6947db3fb8c35238c63bd7ce469c7acca8b3cc76ff8704cd161525

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastmm-0.2.0-cp311-cp311-win_amd64.whl:

Publisher: build-wheels.yml on kodonnell/fastmm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastmm-0.2.0-cp311-cp311-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for fastmm-0.2.0-cp311-cp311-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 d595edf75ad1464ddba2dd3a71c1ced7dfb572a255e5a7eca10a36914df3ff7d
MD5 75304e0b73b9332c5b7a85355dd8b7e5
BLAKE2b-256 865198940223f295ed62d07ac5010bde4ec9a56a479749c35d6bcf97200766a1

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastmm-0.2.0-cp311-cp311-manylinux_2_28_x86_64.whl:

Publisher: build-wheels.yml on kodonnell/fastmm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastmm-0.2.0-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for fastmm-0.2.0-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 a34d706f8a2f69a961d1bf71cec9f54fa1eaabee545c38de19793e0e82738874
MD5 06ab3c62388ceb52ade59ab5b9ded47a
BLAKE2b-256 9368cd1ca30eab561ea6817c16c7e7b78aeea89dafcc613d56618b6ec210f76d

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastmm-0.2.0-cp311-cp311-macosx_11_0_arm64.whl:

Publisher: build-wheels.yml on kodonnell/fastmm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastmm-0.2.0-cp310-cp310-win_amd64.whl.

File metadata

  • Download URL: fastmm-0.2.0-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 734.4 kB
  • Tags: CPython 3.10, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for fastmm-0.2.0-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 228c556b96890bfde38f3f0cb4ef6b41a0b5e1f00bff252054974b21c516f9d6
MD5 cafe2b27611a84493e145ea991342305
BLAKE2b-256 a75a13850698012c670c34c2a7315f2facd2bfaddec861b3b0df7007229010f0

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastmm-0.2.0-cp310-cp310-win_amd64.whl:

Publisher: build-wheels.yml on kodonnell/fastmm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastmm-0.2.0-cp310-cp310-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for fastmm-0.2.0-cp310-cp310-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 c1e4c26af3955e973fc15e0bd2b4c809bd08d2bf3ae2562b2e2a0bc5d0515bac
MD5 761c6b22f03ef4398f245a25f34a56fd
BLAKE2b-256 7e4eda264e9b92b9a6c050821ea168a5a52f07c565041d0c98d48649d5e0a4c4

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastmm-0.2.0-cp310-cp310-manylinux_2_28_x86_64.whl:

Publisher: build-wheels.yml on kodonnell/fastmm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastmm-0.2.0-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for fastmm-0.2.0-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 e347771d03d312cc12a3e401d13a4d910aa034c11eeeb9324652b95c7559279a
MD5 24e0e3d8c36057fbb8e683e93553cca0
BLAKE2b-256 664e193525a5e2fd03c0c083c9ae99556c3666c4ad8722a195e13c97e915b404

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastmm-0.2.0-cp310-cp310-macosx_11_0_arm64.whl:

Publisher: build-wheels.yml on kodonnell/fastmm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page