Skip to content

Add mapbox map-matching - #45

Open
mattpodwysocki wants to merge 1 commit into
feat/isochrone-apifrom
feat/map-matching-api
Open

mattpodwysocki wants to merge 1 commit into
feat/isochrone-apifrom
feat/map-matching-api

Conversation

@mattpodwysocki

@mattpodwysocki mattpodwysocki commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Temporarily based on #44, not main

Same stacking situation as #44 on #43. This reuses the flattening mechanism
(spec::FLATTENED_SERVICES), ARG_NAME_OVERRIDES, and the
path_segment_for/UNESCAPED_PATH_PARAMS fix #43 introduces, since every
one of these Navigation APIs' profile path parameter hits the same
global-flag collision and needs the same free-form profile. Once #43 and
#44 merge in order, this should be retargeted to main (gh pr edit --base main). Everything below is scoped to what this PR actually adds on top of
#44.

What

mapbox map-matching, the third Navigation-category API with no prior
CLI coverage. Same shape as #43/#44: hand-authored into custom-openapi/
since openapi-specs publishes no spec for it either.

No subcommand: like mapbox directions and mapbox isochrone, this API
has one operation, so there's nothing a second word (the old match)
would disambiguate. Same shape mapbox usage already has.

Snaps a noisy GPS trace to the road network and returns the route it most
likely followed, for driving (with or without live traffic), walking, or
cycling. One or more matched routes (more than one where the trace is
ambiguous enough to split), each with a confidence, plus one tracepoint
per input coordinate (null for one too far from any candidate to match at
all).

Excluded on purpose: POST, for the same reason directions excludes
it. This spec format has no way to express "GET or POST, caller's choice"
for one operationId. The API's own POST exists specifically for a trace
too long for a URL (~8100 bytes), a real gap for a very long trace rather
than a design choice.

Routing profile is free-form here too

Same fix as #43 and #44: an earlier version of this command validated
profile against the four documented values client-side, via a clap enum.
Some OEM accounts have additional profiles that aren't published, so that
validation would have broken this command for exactly the accounts that
most need it. profile is now sent exactly as typed, and reaches the URL
unescaped through UNESCAPED_PATH_PARAMS (("map-matching", "profile")).

Verification

Smoke-tested against production: a three-point San Francisco trace
returned a real match with legs/steps/geometry, including a null
tracepoint for a point deliberately placed too far from the road network to
match, the documented shape for that case, confirmed rather than assumed.
The bare mapbox map-matching command shape itself is also verified (no
subcommand accepted, --schema reflects the new name).

489 tests, cargo fmt --check and cargo clippy --all-targets -- -D warnings both clean.

🤖 Generated with Claude Code

Third of the Navigation-category APIs with no prior CLI coverage. Same
shape as directions/isochrone (#43, #44): hand-authored
into custom-openapi/ since openapi-specs has no spec for this API either,
reusing ARG_NAME_OVERRIDES for the same profile-vs-global-flag collision
(third row, not a third mechanism).

Excludes POST, for the same documented reason directions route does: this
spec format can't express "GET or POST, caller's choice" for one
operationId, and the API's own POST exists specifically for a trace too
long for a URL (~8100 bytes) — a real gap, not a design choice.

Smoke-tested against production: a three-point San Francisco trace
returned a real match with legs/steps/geometry, including a null
tracepoint for a point too far from the road network to match — the
documented shape for that case, not a bug.

486 tests, fmt and clippy clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@zmofei zmofei left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command name does not match the design doc, and a few parameter descriptions differ from the Map Matching docs. I checked each one against the real API. Details inline.

Comment thread src/spec.rs
/// [`FLATTENED_SERVICES`] service correctly for free, because they all go
/// through `command()` rather than reconstructing the string themselves.
pub const FLATTENED_SERVICES: &[&str] = &["directions", "isochrone"];
pub const FLATTENED_SERVICES: &[&str] = &["directions", "isochrone", "map-matching"];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The design doc names this command mapbox map-match, not mapbox map-matching. Please rename it everywhere: the custom-openapi entry, the three tables in spec.rs, remedy.rs, docs, README, CHANGELOG and the command surface fixture. #48 will need the same change.

index. Requires `--steps`.
schema:
type: string
example: "0,2"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API wants waypoints separated by ;, not ,. With --waypoints 0,2 (the example here) it returns 422: "Waypoints must be a list of at least two indexes separated by ';'". Please change the example to 0;2 and say "semicolon-separated" in the description and in docs/commands.md. Also, --waypoints does not require --steps. The docs only say it is "most useful" with steps, and the API accepts it without them.

# `PossibleValuesParser`, which accepts one value and would refuse
# `distance,duration`. Same reasoning as `directions.yaml`'s
# `annotations`.
- name: "annotations"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs limit some values to certain profiles, but the descriptions here do not say so: congestion/congestion_numeric work only with mapbox/driving-traffic, maxspeed only with driving and driving-traffic, and --linear-references (line 223) only with driving and driving-traffic. I checked: congestion on mapbox/driving returns all "unknown", and --linear-references on mapbox/walking returns a 422 that does not mention the profile. Please add these limits to the descriptions and docs/commands.md.

schema:
type: string
enum: ["tomtom"]
- name: "depart_at"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the docs do not limit depart_at to mapbox/driving-traffic, and the API takes only three formats, not any ISO 8601. For example, 2030-01-01T08:00:00 returns 422. Maybe say "Departure time from the first coordinate" and list YYYY-MM-DDThh:mm, YYYY-MM-DDThh:mm:ssZ and YYYY-MM-DDThh:mm:ss±hh:mm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants