Skip to content

refactor: create RoutesForLocation handler#848

Merged
fletcherw merged 1 commit intoOneBusAway:mainfrom
fletcherw:split-stops
Apr 11, 2026
Merged

refactor: create RoutesForLocation handler#848
fletcherw merged 1 commit intoOneBusAway:mainfrom
fletcherw:split-stops

Conversation

@fletcherw
Copy link
Copy Markdown
Collaborator

Create a new RoutesForLocation database query that encapsulates logic for routes-for-location handler. Splitting out this logic allows simplifying the StopsForLocation function and moving Go logic from the RPC handlers into SQL.

Add a SQLite build flag to support math functions used for calculating haversine distance for latlng.

@fletcherw fletcherw requested a review from Ahmedhossamdev April 9, 2026 20:19
return items, nil
}

const getActiveRoutesWithinBounds = `
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

some open questions about this still:

  1. I need to check the query plan to make sure it's not terrible
  2. does the ORDER BY do the right thing in combination with the DISTINCT? For example if I have two stops for a route, will it order by the closest stop?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

my suspicion was correct, this was picking a random stop to use for the distance ordering. I've fixed this in the next commit.

query plan looks reasonable:

--SCAN r VIRTUAL TABLE INDEX 2:D0B1D2B3
|--SEARCH stops USING INTEGER PRIMARY KEY (rowid=?)
|--SEARCH stop_times USING COVERING INDEX idx_stop_times_stop_id_trip_id (stop_id=?)
|--SEARCH trips USING INDEX sqlite_autoindex_trips_1 (id=?)
|--SEARCH routes USING INDEX sqlite_autoindex_routes_1 (id=?)
|--USE TEMP B-TREE FOR GROUP BY
`--USE TEMP B-TREE FOR ORDER BY

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.

hey @fletcherw
Great!, I'm still testing it, and I'll add my review soon.

`

type GetActiveRoutesWithinBoundsParams struct {
MinLat float64
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

could simplify the param count by calculating Lat/Lon as the median of Min/Max.

cos(radians(stops.lon) - radians(?7)) +
sin(radians(?6)) *
sin(radians(stops.lat))
) ASC
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

since I'm calculating the distance here, I could also pass through radius to filter out routes that are in the lat/lon bounding box but outside the radius.

api.clientCanceledResponse(w, r, ctx.Err())
var agencies []models.AgencyReference
for agencyID := range agencyIDs {
agency, err := api.GtfsManager.FindAgency(ctx, agencyID)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

this could be improved with a batch agency query.

Copy link
Copy Markdown
Member

@Ahmedhossamdev Ahmedhossamdev left a comment

Choose a reason for hiding this comment

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

LGTM, just we should need to handle the lowercase route short name in the query.

WHERE
r.min_lat >= ?3 AND r.max_lat <= ?4
AND r.min_lon >= ?5 AND r.max_lon <= ?6
AND (?7 == "" OR routes.short_name == ?7)
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.

One small thing the handler lowercases the query query = strings.ToLower(sanitizedQuery) before passing it to the SQL, but the SQL compares it against routes.short_name as is, so it'll never match routes with mixed-case short names.
Fix would be LOWER(routes.short_name) == ?7.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This was a good catch, I didn't think about this. Fortunately it turns out sqlite is case-insensitive by default if you use LIKE.

sqlite> select * from routes where short_name like 'ShuTTle' limit 10;
1-SHUTTLE|40|Shuttle|Link Replacement Shuttle Bus||3||FFB819|000000|1|1
2-SHUTTLE|40|Shuttle|2 Line Shuttle Bus||3||FFB819|000000|1|1
TLINE_S|40|Shuttle|T Line Replacement Shuttle Bus||3||DA7C00|FFFFFF|1|1

Added a test for this case as well to make sure this doesn't break in the future.

Create a new RoutesForLocation database query that encapsulates logic
for routes-for-location handler. Splitting out this logic allows
simplifying the StopsForLocation function and moving Go logic from the
RPC handlers into SQL.

Add a SQLite build flag to support math functions used for calculating
haversine distance for latlng.
@fletcherw fletcherw merged commit 34e106f into OneBusAway:main Apr 11, 2026
6 checks passed
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