Skip to content

More precise distance away from bus stop calculation for bus services #35

Description

@TechSupportz

Problem

The bus timing row currently calculates the distance between the live bus coordinate and the queried bus stop as a straight line:

This can significantly underestimate the remaining distance when the service follows winding roads, loops, or travels away from the stop before returning.

The userLatLng property in BusTimingRow is also misleadingly named: both call sites pass the bus stop coordinate, not the user's location.

Constraint

Do not add a network request to the timing-screen hot path.

LTA arrivals are fetched directly from DataMall and refreshed every 20 seconds. Calling the Transito server in US-east—or a third-party routing API—when opening or refreshing the screen would add latency and another failure point.

Relevant DataMall behaviour

The LTA Bus Routes API exposes a cumulative Distance value for every stop:

Distance travelled by bus from starting location to this bus stop (in kilometres)

Source: LTA DataMall API User Guide — Bus Routes, page 22

The app already models this as BusRouteInfo.distance.

Proposed approach

Generate a compact route-distance index from the relatively static LTA BusRoutes and BusStops datasets:

service number + direction
  → ordered stops
      → stop code
      → latitude/longitude
      → cumulative distance from route origin

Make this data available locally:

  • Bundle a baseline snapshot with the app so the first calculation requires no download.
  • Optionally refresh and version it in the background, then cache it locally.
  • Never block the timing screen on the refresh.

Runtime calculation

For each live arrival:

  1. Select the correct route using the service number and the arrival’s OriginCode and DestinationCode.

  2. Select the correct occurrence of the queried stop, accounting for VisitNumber and loop services.

  3. Consider route segments before that target occurrence.

  4. Find the closest plausible segment to the live bus coordinate.

  5. Project the bus coordinate onto that segment and interpolate between the cumulative distances of its two stops.

  6. Calculate:

    remaining distance =
    target stop cumulative distance − estimated bus cumulative distance

Only the bus’s current stop-to-stop segment is approximated. The remainder uses LTA’s cumulative route distance, which should be materially better than measuring directly to the destination stop.

Fallbacks and edge cases

Fall back to the existing straight-line calculation when:

  • Latitude or longitude is missing or zero.
  • No matching route or direction is available.
  • The bus cannot be matched to a route segment with reasonable confidence.
  • The calculated remaining distance is negative or otherwise invalid.
  • The provider is not LTA, including NUS services until equivalent route data is supported.

Special cases to test:

  • Direction 1 versus direction 2.
  • Loop routes and repeated stops.
  • VisitNumber == 2.
  • Short-working-trip and service variants.
  • Routes whose outbound and inbound sections overlap.
  • GPS jitter between 20-second refreshes.
  • A bus at or immediately past the queried stop.

If matching is ambiguous, prefer showing the existing approximate value over confidently displaying an incorrect route distance.

Cleanup

  • Rename BusTimingRow.userLatLng to busStopLocation.
  • Move the distance calculation out of the widget into a testable utility or service.

Acceptance criteria

  • Opening or refreshing bus timings makes no additional network request for route distance.
  • The direct LTA arrival request remains the only required live request for LTA services.
  • Remaining distance follows the service route when local route data is available.
  • Existing straight-line behaviour remains as a safe fallback.
  • Loop routes, both directions, invalid GPS data, and missing route data are covered by tests.
  • Distance generally decreases across successive monitored arrivals without large route-matching jumps.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions