Skip to main content
Glama

map_match

Read-onlyIdempotent

Map matching snaps raw GPS breadcrumbs to the road network. Feed a recorded trace (2–100 points) and get back the road-accurate route that was actually driven: matched geometry, distance, time, road names, and a 0–1 match confidence. matched: true only means a route was returned — gate on match_status: full when every input point was matched or interpolated onto the road network and the match is one continuous section, none when no point was, otherwise partial. coverage gives the per-point counts, unmatched_ranges (0-based input indexes that did not match) and route_sections (above 1, the geometry, distance and time cover only the first section — split the trace there). match_status is null, with coverage.unavailable_reason, when the per-point check could not be completed. Sparsely-reported traces (consecutive fixes farther apart than the default breakage distance — e.g. fleet units reporting every several minutes on a highway) can split into multiple sections; increase breakage_distance_m to keep them as one. Total matched distance is limited to 200 km per call — split longer sparse traces by distance, not only by the 100-point cap. Use for fleet trace cleanup, mileage from GPS logs, and reconstructing routes from telemetry. road_names come from the route's maneuver points, so they cover the roads the route turns onto or starts on — for the name of every segment actually travelled, use trace_attributes. For planning a route between places, use directions instead.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
shapeYesRaw GPS breadcrumb trail in travel order — 2 to 100 points of { lat, lon, time? }. `time` is optional epoch seconds; timestamps improve match quality on noisy traces.
costingNoTransport mode the trace was recorded with (default: auto)
search_radiusNoSnap radius in meters around each point (1–100). Increase for noisy GPS.
vehicle_profileNoTruck preset (DRY_VAN_53, FLATBED_48, BOX_TRUCK_26, …) — same 10-profile table as directions. Implies costing=truck.
breakage_distance_mNoMaximum gap, in meters, between consecutive fixes before the trace is split into separate sections (100–20000). Increase for sparsely-reported GPS, such as fleet units reporting every several minutes on a highway. Optional — omitted uses the default.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
legsYes
metaNo
costingYes
geojsonNo
matchedYesTrue when a route was returned for the trace. It does NOT mean the whole trace matched — use `match_status`.
coverageNoPer-point coverage of the trace from the matcher's own per-point status — independent of the distance-based `confidence`. Every key is always present.
geometryNo
confidenceYes0–1: fraction of input GPS points within the snap tolerance of the matched route. A distance heuristic: it ignores point order and which parallel roadway was matched. For the matcher's own per-point result use `match_status` / `coverage`.
road_namesYes
distance_kmYes
match_statusNoWhole-trace match status from the matcher's own per-point results. `full`: every input point was matched or interpolated onto the road network AND the match is one continuous section. `none`: no input point was matched or interpolated. `partial`: anything else — some points unmatched, or the match broke into more than one section. null when the per-point check could not be completed; `coverage.unavailable_reason` says why. Gate on this field, not on `matched`.
costing_optionsNo
vehicle_profileNo
duration_secondsYes

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed4 schema fields changed
    • addedInput schema / properties / breakage_distance_m
      Added value: +{
      +  "description": "Maximum gap, in meters, between consecutive fixes before the trace is split into separate sections (100–20000). Increase for sparsely-reported GPS, such as fleet units reporting every several minutes on a highway. Optional — omitted uses the default.",
      +  "maximum": 20000,
      +  "minimum": 100,
      +  "type": "integer"
      +}
    • changedOutput schema / properties / meta / additionalProperties
      Previous value: -{}New value: +true
    • addedOutput schema / properties / meta / properties
      Added value: +{
      +  "breakage_distance_m": {
      +    "description": "Echoes the breakage_distance_m the caller requested, for confirmation — present only when the caller set it. This is the value SENT to the matcher, not a claim about what it did with it.",
      +    "type": "integer"
      +  },
      +  "point_count": {
      +    "type": "integer"
      +  },
      +  "upstream_ms": {
      +    "type": "number"
      +  }
      +}
    • addedOutput schema / properties / meta / required
      Added value: +[
      +  "upstream_ms",
      +  "point_count"
      +]
  2. Changed4 schema fields changed
    • changedOutput schema / properties / confidence / description
      Previous value: -"0–1: fraction of input GPS points within the snap tolerance of the matched route"New value: +"0–1: fraction of input GPS points within the snap tolerance of the matched route. A distance heuristic: it ignores point order and which parallel roadway was matched. For the matcher's own per-point result use `match_status` / `coverage`."
    • addedOutput schema / properties / coverage
      Added value: +{
      +  "additionalProperties": true,
      +  "description": "Per-point coverage of the trace from the matcher's own per-point status — independent of the distance-based `confidence`. Every key is always present.",
      +  "properties": {
      +    "interpolated_count": {
      +      "anyOf": [
      +        {
      +          "type": "integer"
      +        },
      +        {
      +          "type": "null"
      +        }
      +      ],
      +      "description": "Input points the matcher placed on the road network by interpolating between matched neighbours; counted as covered. null when unavailable."
      +    },
      +    "matched_count": {
      +      "anyOf": [
      +        {
      +          "type": "integer"
      +        },
      +        {
      +          "type": "null"
      +        }
      +      ],
      +      "description": "Input points the matcher placed on the road network directly. null when unavailable."
      +    },
      +    "point_count": {
      +      "description": "Number of input GPS points",
      +      "type": "integer"
      +    },
      +    "ratio": {
      +      "description": "0–1: (matched_count + interpolated_count) / point_count, rounded down to 3 decimals so a partial trace never reads 1. null when unavailable.",
      +      "type": [
      +        "number",
      +        "null"
      +      ]
      +    },
      +    "route_sections": {
      +      "description": "Number of disjoint sections the match broke into (1 = continuous). When greater than 1, the returned geometry, distance and duration cover only the FIRST section — split the trace at the break and match the parts separately.",
      +      "type": "integer"
      +    },
      +    "unavailable_reason": {
      +      "anyOf": [
      +        {
      +          "enum": [
      +            "upstream_error",
      +            "timeout",
      +            "no_point_status",
      +            "point_count_mismatch",
      +            "unknown_point_type"
      +          ],
      +          "type": "string"
      +        },
      +        {
      +          "type": "null"
      +        }
      +      ],
      +      "description": "Why the per-point counts and `match_status` are null: `upstream_error` (the per-point check failed), `timeout` (it did not answer in time), `no_point_status` (it answered without per-point results), `point_count_mismatch` (its results did not line up one-to-one with the input points), `unknown_point_type` (it reported a status this API does not recognise). null when coverage is available."
      +    },
      +    "unmatched_count": {
      +      "anyOf": [
      +        {
      +          "type": "integer"
      +        },
      +        {
      +          "type": "null"
      +        }
      +      ],
      +      "description": "Input points the matcher could not place on the road network. null when unavailable."
      +    },
      +    "unmatched_ranges": {
      +      "anyOf": [
      +        {
      +          "items": {
      +            "additionalProperties": true,
      +            "properties": {
      +              "end_index": {
      +                "description": "0-based index of the last unmatched input point in this run (inclusive)",
      +                "type": "integer"
      +              },
      +              "point_count": {
      +                "description": "Number of input points in this run",
      +                "type": "integer"
      +              },
      +              "start_index": {
      +                "description": "0-based index of the first unmatched input point in this run",
      +                "type": "integer"
      +              }
      +            },
      +            "required": [
      +              "start_index",
      +              "end_index",
      +              "point_count"
      +            ],
      +            "type": "object"
      +          },
      +          "type": "array"
      +        },
      +        {
      +          "type": "null"
      +        }
      +      ],
      +      "description": "Contiguous runs of unmatched input points, as 0-based inclusive index ranges into `shape`, in trace order. Empty when every point was covered; null when unavailable."
      +    }
      +  },
      +  "required": [
      +    "point_count",
      +    "matched_count",
      +    "interpolated_count",
      +    "unmatched_count",
      +    "ratio",
      +    "unmatched_ranges",
      +    "route_sections",
      +    "unavailable_reason"
      +  ],
      +  "type": "object"
      +}
    • addedOutput schema / properties / match_status
      Added value: +{
      +  "anyOf": [
      +    {
      +      "enum": [
      +        "full",
      +        "partial",
      +        "none"
      +      ],
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Whole-trace match status from the matcher's own per-point results. `full`: every input point was matched or interpolated onto the road network AND the match is one continuous section. `none`: no input point was matched or interpolated. `partial`: anything else — some points unmatched, or the match broke into more than one section. null when the per-point check could not be completed; `coverage.unavailable_reason` says why. Gate on this field, not on `matched`."
      +}
    • addedOutput schema / properties / matched / description
      Added value: +"True when a route was returned for the trace. It does NOT mean the whole trace matched — use `match_status`."
  3. Added

TDQS

A4.8/5.0
Behavior5/5

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

Annotations declare readOnlyHint=true and idempotentHint=true, and the description does not contradict them — map matching is a pure read operation. Beyond the annotations, the description discloses substantial behavior: the matched:true vs match_status distinction (full/none/partial), coverage counts, unmatched_ranges, route_sections semantics (above 1, only first section's geometry/distance/time returned), the 200 km matched-distance cap, sparse-trace splitting behavior, and the caveat that road_names derive only from maneuver points. This is rich context well beyond the structured fields.

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?

The description is long and information-dense, but every sentence carries real behavioral or routing content — return semantics, edge cases, limits, alternatives. It is front-loaded with the core purpose and output before diving into nuance. Given the subtlety of match_status and route_sections, the length is earned; it's thorough rather than bloated.

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?

For a high-complexity tool with subtle return semantics, the description covers purpose, all critical edge cases (sparse traces, 200 km limit, per-point matching failure via unavailable_reason), return-value interpretation, and sibling routing. An output schema exists for structured returns, and the description still clarifies the ambiguous parts (what matched:true really means, what route_sections>1 implies). Nothing an agent needs to call it correctly is missing.

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

Parameters4/5

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

Schema coverage is 100%, so baseline is 3. The description adds value on top: it explains the behavioral consequence of breakage_distance_m (sparse traces split into sections; increase to keep them one), ties the 200 km limit to distance-based splitting of long traces, and clarifies costing/vehicle_profile interplay via the truck preset note. It doesn't deeply re-explain every param since the schema already does, but the added behavioral context pushes it above baseline.

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?

The description states a specific verb and resource ('snaps raw GPS breadcrumbs to the road network') and enumerates the output (matched geometry, distance, time, road names, match confidence). It explicitly distinguishes itself from siblings — 'For planning a route between places, use directions instead' and 'use trace_attributes' for per-segment names — so an agent can select it correctly without opening other schemas.

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

Usage Guidelines5/5

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

Provides explicit use cases ('Use for fleet trace cleanup, mileage from GPS logs, and reconstructing routes from telemetry') plus clear exclusions with named alternatives ('For planning a route between places, use directions instead'; 'for the name of every segment actually travelled, use trace_attributes'). This is exactly the when/when-not guidance the rubric rewards.

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

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources