Skip to main content
Glama

Snap a GPS trace to streets

map_match
Read-onlyIdempotent

Map-match a GPS trace: snap points onto the street graph, stitch a route.

Instructions

Map-match a GPS trace: snap points onto the street graph, stitch a route.

points is an ordered list of {"lat": ..., "lon": ...} — up to 100 of
them (Mapbox's Map Matching tool takes the same cap; anything past it
returns {"error": "bad_request"} rather than truncating the trace
silently). A malformed point (missing/non-numeric lat or lon, or a
coordinate out of range) returns {"error": "bad_request"} naming the
offending index as "points[i]: ...".

Returns {"matched_length_m", "roads", "confidence", "geometry",
"unmatched_points"}: matched_length_m is the summed routed (not
straight-line) distance across every stitched leg; roads lists the
street names crossed in travel order, deduplicated only where they
repeat back to back; geometry is the stitched polyline as a GeoJSON
LineString, simplified to fit the same token budget every geometry-
returning tool here targets; unmatched_points lists the input
indices that never made it into the stitched route — points too far
from any street, and points the stitching's outlier guard rejected
(a routed detour wildly out of proportion to the straight-line gap,
e.g. a trace point that jumped to a different block). confidence
(0..1) blends how much of the trace stitched against how far the
matched points sat from their snapped street position.

A trace that matches nothing is a real answer, not an error:
matched_length_m 0, empty roads/geometry, every index in
unmatched_points, with a "note" explaining it. Do not treat that shape
as a failure — it means the trace is genuinely off the mapped street
network here, which is itself useful to know. A trace where only ONE
point matches gets the same empty-geometry shape with its own note: a
single position can't make a LineString (RFC 7946 requires two) or a
route.

Offline graph work, same as route()/isochrone(): builds one street
graph from Overture's transportation theme over the trace's own padded
bounding circle (no network beyond the existing tile/graph path) and
reuses route()'s per-mode class exclusions, so e.g. a drive trace never
snaps onto a footway. Omit mode to use the stored preferences mode,
else walk. An unrecognized mode string returns
{"error": "unsupported_mode"}. If no usable street graph exists near
the trace at all, returns {"error": "no_graph_nearby"}; a trace whose
padded bounding circle exceeds the mode's extraction cap (e.g. a walk
trace spanning well past ~10 km) returns {"error": "radius_too_large",
"max_radius_m": ...} — try the trace in segments, or a faster mode; a
scan that can't reach the upstream Overture data returns
{"error": "upstream_unavailable", "retry_advised": true}; missing
essential source columns returns {"error": "schema_degraded"}.

Attribution: street geometry and names from Overture Maps
transportation data.

Input Schema

NameRequiredDescriptionDefault
modeNoTravel mode. Default: stored preference, else walk.
pointsYes

Output Schema

NameRequiredDescriptionDefault

No arguments

Schema Changelog

Changes observed during successful MCP inspections.

  1. Addedv0.11.0

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes far beyond the annotations: discloses the 100-point cap and that overflow errors rather than truncates, the exact error shapes (bad_request with offending index, unsupported_mode, no_graph_nearby, radius_too_large with max_radius_m, upstream_unavailable with retry_advised, schema_degraded), the empty-match and single-point-match degenerate cases, and that it reuses route()'s per-mode class exclusions. This is rich, non-obvious behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the core action and then organizes return shape, edge cases, and error taxonomy in clearly separated paragraphs. Dense but nearly every sentence conveys a distinct contract; only the attribution footer is marginal.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given a 2-param tool with an output schema, the description still covers the return field semantics, degenerate business answers versus errors, mode resolution, and the full failure envelope — leaving nothing an agent needs to invoke and interpret it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is only 50% (points carries no schema description), and the description compensates fully: points is an ordered list of {"lat","lon"} capped at 100 with malformed-point error semantics and index naming; mode's null default resolves to stored preference then walk, with unsupported strings returning unsupported_mode.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a precise verb+resource+output: 'Map-match a GPS trace: snap points onto the street graph, stitch a route.' This distinguishes it from siblings like route(), isochrone(), and from_to, which compute routes rather than snapping an observed trace.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It situates itself relative to route()/isochrone() ('offline graph work, same as...') and explains mode inheritance, which implies when it applies, but it never states explicitly when to choose map_match over route() or what a caller should do instead. Usage is inferable but not spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.