Skip to content
This repository has been archived by the owner on Aug 7, 2021. It is now read-only.

Bookmark individual routes at stops instead of the entire stop #457

Closed
aaronbrethorst opened this issue Nov 6, 2015 · 6 comments · Fixed by #637
Closed

Bookmark individual routes at stops instead of the entire stop #457

aaronbrethorst opened this issue Nov 6, 2015 · 6 comments · Fixed by #637
Assignees

Comments

@aaronbrethorst
Copy link
Member

There are a lot of good reasons to do this work, ranging from paving the way to building an Apple Watch app to a Today extension. I need to flesh out this issue with some more details about how the feature is going to look and work, and what—if anything—we want to do for migrating existing users' data forward.

@aaronbrethorst aaronbrethorst self-assigned this Nov 6, 2015
@aaronbrethorst aaronbrethorst added this to the 2.5: Trip Planning milestone Nov 6, 2015
@barbeau
Copy link
Member

barbeau commented Nov 10, 2015

FWIW - I recently implemented route favorites on Android - currently, it's basically a mechanism to allow the user to "pin" a route to the stop of the sliding panel, so you can monitor your favorites while browsing the map or list:

image

We'll eventually support a "starred routes" list as well (OneBusAway/onebusaway-android#354).

In the current implementation on Android, the favorite stop logic/implementation remains - users can star stops, and these show up in the "starred stops" list, accessible from the main navigation drawer:

image

So a user can have a favorite stop without necessarily starring any routes at that stop. Route favorites are implemented in parallel and tracked in a new database table.

For route favorites, I wanted to accomplish two things that are interrelated:

  1. Allow users to bookmark routes per direction - you could favorite "5 - North to UATC" but not "5 - South to Downtown"
  2. Allow users to bookmark routes per stop - you could favorite the "5 - North to UATC" at the downtown transit center, but not at the UATC, and vice versa.

The above design allows for easy bookmarking for commute trips that traverse transit centers. I may not want to see incoming trips for 5 at each transit center, and just outgoing.

So, when the user taps on the star next to the route, they see:

image

Bookmarking routes per direction and stop introduces two implementation challenges:

  1. There aren't good persistent IDs for route/direction - there is a route_id in GTFS that's intended to identify the route, but headsign (i.e., the direction name) is optional in GTFS, and treated as a user-facing value, not an internal identifier. An agency could easily do a trivial update to the headsign (e.g., fix a spelling mistake) that doesn't result in any meaningful service change to the user. tripId is available for each vehicle trip, but that only covers one instance of the trip, not the entire days worth of trips for that route/direction. direction_id in trips.txt could be used, but there is no guarantee that the 0/1 value used here is consistent for all trips going from the same origin to destination, or that it stays consistent across GTFS updates.
  2. We have a partial list of stops in the app at any given time - this makes it a little more challenging to exclude a route/direction as a favorite at a particular stop (e.g., if the user selects to favorite this route/direction at "just this stop", or if they chose to remove the favorite for "just this stop"). If we have a full set of stops stored in the app, we just toggle a boolean favorite field for each. Without a full list, we need some kind of inclusion/exclusion logic/persistence for an open set of stops. This should be designed to survive the agency adding a stop or removing stops for a route/direction.

For the first challenge, I came to the conclusion that storing a combination of the route_id and headsign as a unique identifier for that route/direction was best, which is stored per stop_id. Yes, it break easily if the agency renames/changes the headsign, but at least this is a user-facing change that the user could correlate with the app doing something different ("Where'd my favorite go? Oh, they renamed the route direction"). Other internal ID mapping (e.g., using direction_id from trips.txt) could also just as easily break when the agency publishes new GTFS data, and wouldn't have any visible user facing changes in the data itself - the user wouldn't have any indication why something stopped working ("Where'd my favorite go? *&#@^$ OneBusAway!"). Also, OBA will always populate headsign - if the agency doesn't provide it, OBA REST API returns to the apps route_short_name field from GTFS routes.txt in place of the headsign (yes, this would then be the same for both directions, but hey, that's the best we can do).

To address the 2nd challenge, I'm using a boolean exclude bit that's set to 0/1 that represents whether the route/direction combination has been excluded (removed as a favorite) for a stop. If the user selects "all stops" when starring a route/direction, a value of "ALL_STOPS" is inserted as the stop_id. Otherwise the stop_id is inserted. There then some logic to determine if the stop_id has been excluded/removed, if the entire route/direction has otherwise been favorited for ALL_STOPS.

Here's the comment in OBA Android for the route/direction/stop favorite implementation:

/**
 * Supports storing user-defined favorites for route/headsign/stop combinations.  This is
 * currently implemented without requiring knowledge of a full set of stops for a route.  This
 * allows some flexibility in terms of changes server-side without invalidating user's
 * favorites - as long as the routeId/headsign combination remains consistent (and stopId,
 * when a particular stop is referenced), then user favorites should survive changes in the
 * composition of stops for a route.
 *
 * When the user favorites a route/headsign combination in the ArrivalsListFragment/Header,
 * they are prompted if they would like to make it a favorite for the current stop, or for all
 * stops.  If they make it a favorite for the current stop, a record with
 * routeId/headsign/stopId is created, with "exclude" value of false (0).  If they make it a
 * favorite for all stops, a record with routeId/headsign/ALL_STOPS is created with exclude
 * value of false.  When arrival times are displayed for a given stopId, if a record in the
 * database with routeId/headsign/ALL_STOPS or routeId/headsign/stopId matches AND exclude is set to false, 
 * then it is shown as a favorite.  Otherwise, it is not shown as a favorite.
 * If the user unstars a stop, then routeId/headsign/stopId is inserted with an exclude value of
 * true (1).
 */

Actual Java implementation is here in ObaContract

There is also a legacy "starred route" database table that stores favorites just per route_id - this was implemented a while back but actually never surfaced in the UI. I'm now setting that to true or false depending on whether the user stars a route/direction as above. The legacy starred route table may power the "starred routes" list, as a simplified version of the route/direction/stopId tracking.

For better or worse, that's how it works on Android. I'm welcome to design improvements/ideas if anyone has any.

@aaronbrethorst
Copy link
Member Author

Awesome, thanks for all of the information and context, @barbeau! This is going to be super-helpful.

@barbeau
Copy link
Member

barbeau commented Nov 10, 2015

@aaronbrethorst np, good luck! :)

aaronbrethorst added a commit to aaronbrethorst/onebusaway-iphone that referenced this issue Jul 22, 2016
Issues Fixed:

* Fixes OneBusAway#159 - show next bus times on bookmarks tab
* Fixes OneBusAway#457 - Bookmark individual routes at stops instead of the entire stop
* Fixes OneBusAway#593 - Can no longer reorder bookmarks in v2.5.0

Static Table Views:

* Add ability to set contextual actions on a per-table row basis.

Stop View Controller:

* Extract classic departure row construction into a separate method
* Add/Delete bookmark contextual item
* Remove a now-unnecessary class method for initialization from OBAStopViewController
* Plumb in an accessor for OBAModelDAO into OBAStopViewController to allow easier testing later on.
* Implement -hasBookmarkForArrivalAndDeparture: on OBAStopViewController
* Dump the parallax code in the stops controller; it doesn't work properly and needs reimplementation
* first stab at disambiguation UI
* Never show the text "edit bookmark" at the stop level, since it no longer makes any sense.

Bookmarks Controller:

* Add a new cell: OBABookmarkedRouteCell
* Lazy accessor for OBAModelService on OBABookmarksViewController
* Show bookmarked routes on OBABookmarksViewController

Bookmarks:

* Bookmark versioning
* * Make OBABookmarkV2 conform to NSCopying
* Add ability to bookmark a single route of a stop.
* add sortOrder property to bookmarks.
* Get bookmark reordering working again.
* Persist routeShortName in bookmarks
* Create a method to determine if a bookmark is congruent to an arrival and departure object
* Clarify which bookmark properties are new in 2.6.0

General/Misc:

* Upgrade to a first-party SVProgressHUD pod, as the version is now parseable by iTC.
* Add -oba_description:-based description method to OBARouteV2
* Add San Diego GPX file
* UUID cleanup
* Start adapting Podfile and project to work on Xcode 7 and 8
* Add OCMock 3 as the mocking library for OBA
* fix -copyWithZone: bug in OBATableRow
aaronbrethorst added a commit to aaronbrethorst/onebusaway-iphone that referenced this issue Jul 22, 2016
Issues Fixed:

* Fixes OneBusAway#159 - show next bus times on bookmarks tab
* Fixes OneBusAway#457 - Bookmark individual routes at stops instead of the entire stop
* Fixes OneBusAway#593 - Can no longer reorder bookmarks in v2.5.0

Static Table Views:

* Add ability to set contextual actions on a per-table row basis.

Stop View Controller:

* Extract classic departure row construction into a separate method
* Add/Delete bookmark contextual item
* Remove a now-unnecessary class method for initialization from OBAStopViewController
* Plumb in an accessor for OBAModelDAO into OBAStopViewController to allow easier testing later on.
* Implement -hasBookmarkForArrivalAndDeparture: on OBAStopViewController
* Dump the parallax code in the stops controller; it doesn't work properly and needs reimplementation
* first stab at disambiguation UI
* Never show the text "edit bookmark" at the stop level, since it no longer makes any sense.

Bookmarks Controller:

* Add a new cell: OBABookmarkedRouteCell
* Lazy accessor for OBAModelService on OBABookmarksViewController
* Show bookmarked routes on OBABookmarksViewController

Bookmarks:

* Bookmark versioning
* * Make OBABookmarkV2 conform to NSCopying
* Add ability to bookmark a single route of a stop.
* add sortOrder property to bookmarks.
* Get bookmark reordering working again.
* Persist routeShortName in bookmarks
* Create a method to determine if a bookmark is congruent to an arrival and departure object
* Clarify which bookmark properties are new in 2.6.0

General/Misc:

* Upgrade to a first-party SVProgressHUD pod, as the version is now parseable by iTC.
* Add -oba_description:-based description method to OBARouteV2
* Add San Diego GPX file
* UUID cleanup
* Start adapting Podfile and project to work on Xcode 7 and 8
* Add OCMock 3 as the mocking library for OBA
* fix -copyWithZone: bug in OBATableRow
@barbeau
Copy link
Member

barbeau commented Sep 12, 2016

OBA will always populate headsign - if the agency doesn't provide it, OBA REST API returns to the apps route_short_name field from GTFS routes.txt in place of the headsign

@aaronbrethorst FYI - looks like my statement above doesn't always hold true. I'm not sure if something changed in the server-side code base since I looked at this, or if there are corner cases that I didn't originally see. But, at any rate, I'm seeing at least one case at RVTD where tripHeadsign is null, and from crash reports via Android Developer Console it looks like there is another one in Puget Sound. See OneBusAway/onebusaway-android#690 for details, including exact RVTD stop.

@aaronbrethorst
Copy link
Member Author

thanks @barbeau - i've logged #723 to follow up on this.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants