Appearance
F-032: Apartments Near Metro Stations
Status: Done · Priority: P1 · Updated: Mar 10, 2026
Summary
Full metro/transit station browsing experience. Users can explore all Montreal transit stations (STM Metro, REM, Exo commuter trains), view nearby listings with interactive maps, and filter the main listings page by station proximity.
What Was Built
Pages
/metro— Index page listing all stations grouped by transit network (STM Metro lines, REM, Exo trains), with listing counts per station/metro/[slug]— Station detail page with Google Map (station marker + listing markers), nearby listings with configurable radius (500m/1km/2km), per-bedroom price stats, line navigation (prev/next station)
API Endpoints
GET /metro-stations— List all transit stations with nearby listing countsGET /metro-stations/:slug— Station detail with paginated nearby listings, stats, and line info
Listing Filter
- Near Station filter in browse page — searchable station picker grouped by line (color-coded), radius selector (500m/1km/2km)
nearStation+radiusquery params onGET /listings— filters by proximity using Haversine distance
Data Pipeline
- GTFS parsers for STM Metro, Exo commuter trains, REM
- Station name title-casing with override table for tricky names (Berri-UQAM, McGill, etc.)
seed-transit.tsfor seeding station data from GTFS feeds- Download script updated to fetch Exo + REM GTFS data
Shared Components
ListingMarkerLayer— Unified map marker component (price pills, clustering, click popups) used across search map, neighborhood detail, and station detailtransit-lines.ts— Static line definitions with 120+ stations, coordinates, colors, bilingual names
Bilingual Support
- Full FR/EN translations for metro index, station detail, and filter UI
Implementation Notes
- Station data stored in existing
points_of_interesttable with categorymetro/rem/train - Proximity filter uses bounding box pre-filter + Haversine distance for accuracy
- Map markers use Supercluster for clustering at high zoom levels
- Station slugs generated from station names via
slugifyStation()helper