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.
Summary
For the current Unitrans GTFS feed, 118 of 278 stops (~42%) are missing from the runtime geospatial index that backs the
stops-for-locationAPI, even though those stops exist in the transit graph and are served by trips. Astops-for-locationquery centered on an affected stop's own coordinates returnsoutOfRange=falsewith an empty list. This silently returns incomplete "nearby stops" results to real API/app clients.Discovered while debugging OneBusAway/docker CI (the
stops-for-locationsmoke 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
2.7.1(transit-data-federation builder + webapps).https://unitrans.ucdavis.edu/media/gtfs/Unitrans_GTFS.zip(downloaded 2026-07-06).OscRCSmeD→ 278 stops in the built graph. (Ruled out gtfstidy: old vs. new gtfstidy releases produce byte-identicalstops.txt.)Reproduction
Build a bundle from the current Unitrans feed and start the federation + API webapps, then:
stop/{id},stops-for-route, andagencies-with-coverageall return this stop correctly — it is only absent from the geospatial (spatial-bounds) lookup.Contrast — a downtown stop works as expected:
Scope of the gap
Probing a 200–300 m box around each of the 278 stops' own coordinates:
22107,22108,22106are absent; near-neighbors22135,22098are present).(0,0)/ NaN / out-of-range values.maxCount/limitExceededtruncation, 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 anSTRtreefromgetAllStops()at startup and answersgetStopsByBounds. Since the stops are present in the graph but absent from the tree/bounds results, likely suspects:StopEntrygets inserted into theSTRtree(envelope construction, lat/lon axis ordering, or stops skipped by some condition);maxCountdefault forstops-for-location(empty-result vs. truncation paths).I'm happy to share a reproducing bundle or run additional queries against a live instance.