routing_engine 0.6.3
routing_engine: ^0.6.3 copied to clipboard
One routing API, multiple backends. Engine-agnostic interface with OSRM and Valhalla implementations. Switch between local and public servers without rewriting app logic.
Changelog #
0.6.3 #
Reconciles a tree that had drifted behind its own registry, and carries two independent fixes that had been split across that gap.
0.6.2 was published from the latlong2-widen branch and carried the widen but
not the maneuver fix. This working tree carried the maneuver fix but still read
latlong2: ^0.9.1 and version: 0.6.1 — behind the registry it publishes to.
A stranger reading the repository saw 0.6.1 while pub.flutter-io.cn served 0.6.2, and
publishing from the tree would have regressed the widen. Both halves ship here.
1. latlong2 widened to ">=0.9.1 <0.11.0", restating 0.6.2 so the
constraint survives the reconciliation rather than being silently dropped.
2. An absent maneuver distance is no longer a measured zero. Four sites, two
engines, all ?? 0:
osrm_routing_engine.dart:146-147 distance, duration
valhalla_routing_engine.dart:182-183 length, time
A missing value became 0 — the shape of "in 0.0 km, turn left", where the
driver is told to turn NOW because the server said nothing at all.
Who is affected. Anyone reading RouteManeuver.lengthKm or .timeSeconds.
No API is removed and nothing changes type: those getters still return
non-nullable doubles and still return 0 for an absent value. Added:
lengthKmOrNull, timeSecondsOrNull, hasMeasuredLength, hasMeasuredTime.
Equality now uses the nullable fields, so an absent value no longer compares
equal to a real zero.
Non-breaking by construction, and deliberately so. The correct fix is
nullable public fields. That is breaking, which forces 0.7.0 under Dart 0.x,
which ^0.5.1 will not admit — it would not reach the one consumer we know
holds this package. The additive shape was chosen for reach, not for elegance.
What this fix does NOT cover, stated rather than left to be discovered.
valhalla_routing_engine.dart:181 still reads mMap['type'] as int? ?? 0. That
one is benign and was checked, not assumed: Valhalla type 0 maps to 'none',
its own absent-type value, so it does not fabricate a turn instruction.
How it survived. route_result.dart already stated the rule on the
position field — "Absence of a measurement is not a measurement" — recorded
that both engines used to substitute LatLng(0, 0), "Null Island, a real
coordinate in the Gulf of Guinea", and then vouched by name for lengthKm and
timeSeconds as "still true". That sentence is corrected in place rather than
deleted, so a reader can see what it used to certify.
0.6.1 #
Safety defect in 0.6.0 and earlier — an absent distance was rendered as a measured zero #
Up to and including 0.6.0, both engines could hand you a route summary that
was never in the routing response. When the server omitted the distance or
duration, we substituted 0 and rendered it to the driver as
"0.0 km, 0 min" — a figure the server never sent, and the most benign
answer available, on a road she is still driving.
OsrmRoutingEngine: an absentroutes[0].distanceor.duration.ValhallaRoutingEngine: an absenttrip.summaryobject entirely, or an absentsummary.length/summary.timeinside it.
Both now throw RoutingException, exactly as an absent routes / trip /
legs already did in the same functions — the contract this package has
enforced for maneuver positions since 0.5.0 ("never Null Island"), finally
reaching the two numbers the driver actually hears.
Why this matters more than a wrong number. A silent 0.0 km sets
_route != null, so a consumer's own try/catch fallback never fires and the
narration speaks the fabricated figure. We measured this in a real consumer's
source: the zero suppressed a recovery path the developer had already
written. The throw restores it.
If you catch RoutingException you already handle this. No signature and
no type changed; calculateRoute has always thrown on network errors, non-2xx
status and empty routes.
Also in this release #
LatLngis now exported.RouteRequest.origin,.destination,RouteResult.shapeandRouteManeuver.positionare all typedLatLng, but up to 0.6.0 this library exported everything except that type — so the first example on our own pub.flutter-io.cn page did not compile for anyone who copied it.- README maneuver snippet declares its reader-supplied symbols.
reach-disposition #
reach-disposition(paila-offline-gps): BACKPORTED to 0.5.2. This consumer pins^0.5.1, which our own 0.6.0 nullable-position break walls out of 0.6.x. The fix above is pure runtime and backports without touching their compile, so it ships to them as 0.5.2 rather than leaving them on a defective 0.5.1 while we take the correct version for ourselves.reach-disposition(sngnav-app): first-party, pinned^0.5.0; the app's own dependency currency is tracked separately (12 packages behind as of 2026-08-22) and is not gated on this release.reach-disposition(example-app): first-party example, pinned^0.5.0; moves with the app.reach-disposition(jitreq-drunkenv2): GONE.Jitreq/drunkenv2/pubspec.yamlreturns HTTP 404 (verified 2026-08-22). Recorded as retired rather than left reading as a transient outage.
0.6.0 #
Safety defect in 0.5.0 and earlier — please read #
Up to and including 0.5.0, both engines could hand you a maneuver position
that was never in the routing response: they silently substituted
const LatLng(0, 0) — "Null Island", a real coordinate in the Gulf of Guinea —
whenever they could not resolve the real one.
OsrmRoutingEngine: whenever a step'smaneuver.locationwas missing or shorter than two elements.ValhallaRoutingEngine: wheneverbegin_shape_indexfell outside the decoded polyline. (A missingbegin_shape_indexwas also defaulted to index0, silently claiming the maneuver happens at the route's start.)
Nothing marked these as fabricated. RouteManeuver.position was a
non-nullable LatLng, so a consumer could not tell a parsed coordinate from a
manufactured one — and a maneuver "at 0,0" narrated or plotted for a driver in
Akita is a wrong place presented with full confidence. If you narrated,
mapped, or measured distance-to-next-maneuver from position, assume any
LatLng(0, 0) you have seen from this package was a parse failure, not a
location.
pub.flutter-io.cn versions are immutable: we cannot withdraw the affected releases. This note is the recall.
Breaking: an unknown position is now null, and will not compile away quietly #
RouteManeuver.positionis nowLatLng?.nullmeans the position is UNKNOWN — never the origin, neverLatLng(0, 0).- Added
RouteManeuver.hasPosition(position != null). - Both engines return
nullinstead ofLatLng(0, 0)for the cases above. Valhalla additionally treats a missing or negativebegin_shape_indexas unknown rather than as index0, and OSRM treats a non-numericlocationas unknown rather than throwing. RouteManeuver.toString()saysposition unknownwhen it is absent.
Every site that reads maneuver.position as a LatLng now fails to compile.
That compile error is the fix, not a side-effect of it — it lands on the
exact line where an unparseable coordinate used to become a confident one.
Migration:
| 0.5.0 | 0.6.0 |
|---|---|
narrate(m.position) |
if (m.hasPosition) narrate(m.position!) — otherwise announce the turn without a place; do not invent one |
markers.add(m.position) |
skip the marker when m.position == null; the maneuver keeps its instruction, distance and time |
distanceTo(m.position) |
no position means no distance — show the instruction, not a number derived from (0, 0) |
if (m.position == const LatLng(0, 0)) (defect workaround) |
delete it; use !m.hasPosition |
A maneuver with no position is still a valid maneuver: instruction,
lengthKm and timeSeconds remain true and usable. One unparseable
coordinate does not invalidate a route — the loom refuses the false thread,
not the whole cloth.
Tests #
Two tests in 0.5.0 certified this defect by name — missing maneuver location defaults to (0, 0) and begin_shape_index beyond decoded points defaults to (0, 0). Both are inverted, and the absent-position cases (missing,
short, non-numeric, out-of-range, negative) are now covered explicitly,
alongside tests that a well-formed response still yields a real position.
0.5.0 #
Turn-by-turn narration honors the requested language — Japanese by default.
Behavior change (the reason for the minor bump): the OSRM engine now
honors RouteRequest.language (which has always defaulted 'ja-JP').
Previously it emitted hardcoded English regardless of the request. With a
default request, instructions are now Japanese; consumers that relied on
English with default requests should pass language: 'en' explicitly.
- Japanese car-navigation register (internal
ManeuverLocalizer; NOT public API): 左折/右折, sharp turns as 鋭角に左折/右折 (preserves the tighten-up cue — deliberately not 大きく, which reads as a wide/gentle arc), merge 合流, ramps 分岐, roundabouts ロータリー. Unknown locale/type degrades to the engine's own English — never a wrong instruction. - Roundabout instructions carry the exit ordinal when OSRM supplies
maneuver.exit(「ロータリーに入り2番目の出口で…進む」). - Maneuver-type mapping fixes (visible in the emitted
RouteManeuver.type): a ramp whose side OSRM did not state maps to side-less'ramp'(previously fabricatedramp_left); an empty type+modifier maps to'proceed'(previously fabricated'straight');'exit roundabout'/'exit rotary'map to'roundabout_exit'(previously fell through unmapped). - OSRM and Valhalla responses are decoded as UTF-8 from bytes explicitly —
instruction text and street names no longer mojibake behind servers/proxies
that omit or mis-declare the content type. (With a proper
application/jsoncontent type,package:http≥1.x already defaulted to UTF-8; this closes the missing/mis-declared-type case.)
reach-disposition(jitreq-drunkenv2): restraint, verified 2026-07-04 — the one
known external adopter (pin ^0.3.0) uses the VALHALLA engine path, which is
byte-unchanged since 0.3.0 apart from formatting; every 0.5.0 change is
OSRM-narration-side and does not reach their code path; repo dormant since
2026-04-20. A 0.3.x backport would carry nothing they use. Tripwire: if the
repo wakes (any push/issue) or their usage grows to the OSRM path, the serve
fires (pin-lift offer or a 0.3.x backport, as fits).
0.4.2 #
- docs: correct stale README install pin to current version (no API change).
0.4.1 #
- Republish from the embedded-target Dart 3.10.1 SDK (Flutter 3.38.3) to correct a stale
^3.11.0SDK floor in the previously-published artifact. No source or behavior change; the source already declaredsdk: ^3.10.0. Restorespub getfor embedded/automotive Dart consumers on Dart 3.10.x.
0.4.0 — 2026-05-10 — dart format alignment #
- Apply
dart formatacrosslib/,test/, andtool/(10 files reformatted) to clear pana static-analysis formatter findings. - pubspec
descriptionalready within ≤180-character target; no trim needed. - No SDK source changes; formatter pass only.
0.3.0 #
- Harmonize package version to 0.3.0 for Sprint 80 Direction F.
- Align internal ecosystem dependency constraints to ^0.3.0 where applicable.
- No breaking API changes in this package for this release.