A routing service can draw a convincing line through Davao and still be wrong about the jeepney that uses it. Mapbox knows how to connect coordinates on a road network. It does not know whether a particular jeepney boards passengers on every chosen street, follows the route in that direction, or turns around at the same named place.
The current production data reflects that limitation. jeepney-routes.geojson contains 52 LineString features with name and color properties. jeepney-directions.json contains 52 route identifiers, and every current value is forward. There is no bus-style official stop sequence in the same input shape.
The production builder takes those checked-in lines, adds landmarks from the OSM place snapshot, and writes the jeepney bundle. The separate route-inference tool starts from semantic route descriptions and produces candidates. It does not silently replace the checked-in production geometry.
Candidate pipeline
The production boundary is the review state at the right of this flow. Routing services supply candidate geometry; they do not promote it into the checked-in network by themselves.
The tool's current input snapshot has 27 route specifications, 23 in the city scope and 4 regional. That number must not be confused with the 52 checked-in production features. The two files have different purposes: one is a candidate-generation notebook, and the other is the current application input.
The first step is bounded geocoding. The tool sends Nominatim a city or regional bounding box, asks for at most four choices per label, and rejects choices that are more than 300 metres from the requested point or geometry. The cache key includes the scope, bounding box, and normalized label. This prevents a future change in geographic scope from reusing a result found under a different constraint.
Choosing the nearest result for every label is also unsafe. One test constructs a case where the first geocoder result is individually attractive but creates a bad route between neighboring anchors. The dynamic-programming selector accepts a lower-ranked choice when its total chain is more continuous. That is a route inference decision, not a Mapbox feature.
One correction that changed the route description
The current correction file contains an explicit record for Mahayag – Bunawan. Its note says that the route evidence identifies Mahayag through Cabantian, reaches downtown through Buhangin and Bajada, and returns through Cabantian rather than Bunawan proper. Dacudao Avenue is retained as the routable corridor anchor for the Bajada-Dacudao flyover reference.
This is useful evidence of the kind of ambiguity the tool has to expose. A name such as “Bunawan” is not enough to choose a road sequence. The correction adds a path-shaped explanation and an anchor that the router can resolve. It is still project evidence, not an operator-issued service record, so the output remains reviewable.
The second correction, Malabog – Roxas Ave., makes the same boundary visible from another angle. Its note omits an unresolved TF Davao checkpoint because the surrounding Panabo and Lasang anchors already constrain the highway corridor. It uses DPWH Depot as the Panacan landmark and keeps Dacudao Avenue as a routable corridor anchor. The tool records what was omitted and why instead of filling the gap with an unmarked guess.
The correction records are in route-corrections.json. They are snapshots of the evidence used for this tool, not a claim that every current jeepney route has gone through the same process.
Constraints that shape the result
| Constraint | Current value | Why it exists |
|---|---|---|
| Nominatim choices per label | 4 | Preserve alternatives for continuity selection |
| Maximum geocoder snap | 300 m | Reject a place result that is too far from the requested label |
| Mapbox coordinates per request | 25 | The tool must split long anchor sequences |
| Minimum request interval | 0.21 s | Keep requests within the configured rate boundary |
| Retries | 3 | Retry only the configured transient HTTP failures |
| Minimum route length | 300 m | Reject a degenerate candidate |
| Maximum detour ratio | 6 | Reject a road route much longer than its anchor span |
| Maximum U-turn ratio | 0.08 | Flag geometry that repeatedly reverses direction |
| Auto-accept score | 0.82 with a 0.05 margin | Require a strong candidate and a meaningful alternative gap |
The scoring layer also requires evidence. One candidate cannot be auto-accepted because there is no competing path against which to measure its margin. Candidates below the review threshold remain low-confidence; candidates that clear the review threshold remain review; only the tool's auto-accept output is placed in its canonical-route-candidates.json file.
That word is local to the tool. The current generated jeepney bundle still reports reviewStatus: "unreviewed". No checked-in route-inference output report is present in this checkout, so this page does not claim that all 27 specifications passed or that any particular candidate has been promoted to production.
Long routes are stitched, never bridged
When a route has more than 25 selected anchors, the tool sends overlapping chunks. The overlap lets the next request start at the last anchor of the previous request. The stitcher requires the geometry gap and the shared-anchor gap to be no more than 2 metres. It does not draw a straight line when the router leaves a gap.
if len(coordinates) <= max_coordinates: payload = self._request(plan, tuple(range(len(coordinates))), use_alternatives) return self._parse_candidates(plan, payload, request_count=1)chunks = _chunk_indexes(len(coordinates), max_coordinates)chunk_candidates: list[CandidatePath] = []for chunk_index, indexes in enumerate(chunks, start=1): payload = self._request(plan, indexes, False) parsed = self._parse_candidates(plan, payload, request_count=1, indexes=indexes) if not parsed: raise RuntimeError(f"Mapbox returned no route for chunk {chunk_index}/{len(chunks)}") chunk_candidates.append(parsed[0])return (_stitch_chunks(plan, tuple(chunk_candidates)),)The implementation is in infer.py. Its cache stores successful Mapbox responses under a SHA-256 request key. Nominatim resolution uses the separate JSON cache described in sources.py. A failed or non-Ok Mapbox response is not written as a successful route cache entry.
For the production side, see static transit bundles. For the experiment notebook, including the review UI and regression tests, see jeepney route inference tooling.