Skip to content

fix(nearby): fan out co-located map pins so each stop is visible and tappable - #174

Closed
ai-tiro wants to merge 1 commit into
masterfrom
fix/172-colocated-marker-offset
Closed

fix(nearby): fan out co-located map pins so each stop is visible and tappable#174
ai-tiro wants to merge 1 commit into
masterfrom
fix/172-colocated-marker-offset

Conversation

@ai-tiro

@ai-tiro ai-tiro commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

What

Co-located map pins are now fanned apart so each stop is visible and individually tappable. Different modes serving one physical station share identical PTV coordinates — the Richmond train stop (route_type 0) and the V/Line "Richmond Railway Station" (route_type 3) are both stop_id 1162 at -37.82407, 144.99016. Rendered as-is, their circles stacked on a single pixel: only the top one was visible, and the tap hit-test (nearest stop by true coordinate) could never resolve to the other.

How

New pure helper spreadColocatedStops() (in ColocationSpread.kt):

  • Buckets stops by coordinate (~1 m grid, so exact + near-exact duplicates group).
  • Fans any shared-point group evenly around a small ~20 m circle, slot order sorted by route type then id → each stop's nudged position is deterministic and doesn't jitter between renders.
  • Lone stops keep their exact coordinate; grouping runs on the already-filtered set so only genuinely overlapping pins are nudged.

MapLibreOpenPtvMap uses it as the single source of display positions for both rendering and the tap hit-test, so a fanned-out dot is selectable exactly where it's drawn (this was the subtle part — without updating the hit-test, the dots would look separated but both taps would still resolve to the same stop, since they share a true coordinate).

Why pixel-offset, not clustering

You picked the offset approach. Re-enabling MapLibre clustering would reintroduce the #124 bug it was deliberately removed to fix (supercluster dropped stops at zoom extremes — metro vanished zoomed out, empty tiles zoomed in). The offset is a few metres, well under one stop's spacing, and keeps every stop visible at every zoom.

Testing

  • Unit tests (ColocationSpreadTest, 4 cases): lone stop unchanged; two co-located stops fanned to distinct points each ~20 m from the shared point and ~40 m apart; fan-out deterministic and independent of input order; genuinely distinct nearby stops not merged.
  • Manual QA on the AOSP emulator at Richmond (geo-fixed), Train + V/Line filters on: the V/Line pin renders as a distinct dot offset from the shared station point (the train pin sits at the true coordinate under the user-location dot), and tapping the offset V/Line pin opens the V/Line stop sheet — confirming it's independently selectable. Screenshots attached.
  • Full :feature:nearby unit suite green; :app:assembleDebug green.

Discovered / notes

  • The map pin list is not deduped (the ViewModel emits both stops), so the fan-out engages as intended.
  • Unrelated pre-existing quirk noticed while testing: the Nearby tap-sheet for this stop lists the train serving routes (Alamein/Belgrave/…) alongside V/Line departures. Not touched here — flagging in case it's worth its own issue.
  • The ~20 m separation is intentionally subtle; it only becomes visually obvious at street-level zoom. The emulator's scriptable zoom is coarse, so the attached "after" shot is at high zoom.

Fixes #172

…tappable

Different modes at one station share identical PTV coordinates — the Richmond
train stop and the V/Line "Richmond Railway Station" are both stop_id 1162 at
the same lat/lng. Rendered as-is their circles stacked on one pixel, so only
the top stop was visible and the tap hit-test (nearest by true coordinate)
could never resolve to the other.

spreadColocatedStops() buckets stops by coordinate and fans any shared-point
group evenly around a small (~20 m) circle, ordered by route type then id so a
stop's nudged position is deterministic. It's the single source of display
positions for both rendering and the tap hit-test, so a fanned-out dot is
selectable exactly where it's drawn. Lone stops keep their exact coordinate.

Chose the pixel-offset approach over re-enabling MapLibre clustering, which
would reintroduce the #124 zoom-extreme bug it was removed to fix.

Fixes #172

Co-Authored-By: ai-tiro <ai-tiro@jfx.ac>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ai-tiro

ai-tiro commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator Author

After — at Richmond (Train + V/Line on), the V/Line pin (purple) is fanned ~20 m off the shared station point so it no longer hides under the train pin. The train pin sits at the true coordinate under the blue user-location dot.

Tapping the offset V/Line pin resolves correctly to the V/Line 'Richmond Railway Station' sheet (V/Line departures: Melbourne, Bairnsdale) — proving the fanned-out dot is independently selectable.

Uploaded via pr-attach.

@github-actions

Copy link
Copy Markdown

Debug APK: app-debug-88e4d74cc42e9ed446336779f62f7556e63f0b6b.apk (built from 88e4d74 at 2026-06-20 06:07 UTC)

Requires GitHub login. Artifact expires after 3 days.

@ai-tiro

ai-tiro commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #179, which combined these commits and has merged to master. Closing as superseded.

@ai-tiro ai-tiro closed this Jun 21, 2026
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.

Overlapping map markers when stops share coordinates (Train + V/Line at Richmond)

2 participants