Skip to content

stops-for-location omits ~42% of graph stops (Unitrans, v2.7.1) #470

Description

@aaronbrethorst

Summary

For the current Unitrans GTFS feed, 118 of 278 stops (~42%) are missing from the runtime geospatial index that backs the stops-for-location API, even though those stops exist in the transit graph and are served by trips. A stops-for-location query centered on an affected stop's own coordinates returns outOfRange=false with an empty list. This silently returns incomplete "nearby stops" results to real API/app clients.

Discovered while debugging OneBusAway/docker CI (the stops-for-location smoke check flakes depending on which stop it happens to pick); worked around there by pinning a fixture feed, but the underlying behavior looks like a real defect in the geospatial indexing.

Environment

  • OBA 2.7.1 (transit-data-federation builder + webapps).
  • Feed: Unitrans — https://unitrans.ucdavis.edu/media/gtfs/Unitrans_GTFS.zip (downloaded 2026-07-06).
  • gtfstidy args OscRCSmeD → 278 stops in the built graph. (Ruled out gtfstidy: old vs. new gtfstidy releases produce byte-identical stops.txt.)

Reproduction

Build a bundle from the current Unitrans feed and start the federation + API webapps, then:

# Stop exists in the graph, valid coords:
GET /api/where/stop/unitrans_22107.json
  -> data.entry = { id: unitrans_22107, lat: 38.56334, lon: -121.75081 }   # OK

# stops-for-location centered on that stop's OWN coordinates:
GET /api/where/stops-for-location.json?lat=38.56334&lon=-121.75081          # default 500 m radius
  -> { outOfRange: false, list: [] }                                        # BUG: empty
GET /api/where/stops-for-location.json?lat=38.56334&lon=-121.75081&radius=500
  -> { outOfRange: false, list: [] }                                        # still empty

stop/{id}, stops-for-route, and agencies-with-coverage all return this stop correctly — it is only absent from the geospatial (spatial-bounds) lookup.

Contrast — a downtown stop works as expected:

GET /api/where/stops-for-location.json?lat=38.54154&lon=-121.74258&radius=500
  -> 11 stops (incl. unitrans_22002 itself)

Scope of the gap

Probing a 200–300 m box around each of the 278 stops' own coordinates:

  • 160 stops indexed, 118 not indexed (~42%).
  • Affected stops are scattered, not a single contiguous region (e.g. 22107, 22108, 22106 are absent; near-neighbors 22135, 22098 are present).
  • Not explained by the stop data itself: all 278 stops have valid, in-range, non-duplicate coordinates clustered around Davis (lat 38.537–38.588). No (0,0) / NaN / out-of-range values.
  • A very large radius does return many stops but hits the maxCount/limitExceeded truncation, and still never returns the affected stops even when the query is centered on them.

Impact

stops-for-location (used by "find stops near me" in apps and by third-party API consumers) silently omits ~40% of stops for this agency. A user standing at an affected stop gets an empty or wrong "nearby stops" result.

Where to look

The geospatial lookup is WhereGeospatialServiceImpl, which builds an STRtree from getAllStops() at startup and answers getStopsByBounds. Since the stops are present in the graph but absent from the tree/bounds results, likely suspects:

  • how/whether every StopEntry gets inserted into the STRtree (envelope construction, lat/lon axis ordering, or stops skipped by some condition);
  • interaction with the maxCount default for stops-for-location (empty-result vs. truncation paths).

I'm happy to share a reproducing bundle or run additional queries against a live instance.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions