Skip to content

Commit

Permalink
feat: Show all nearby stops (#2201)
Browse files Browse the repository at this point in the history
* WIP(MapDisplay): Show all nearby stops when zoomed in

* feat(StopCard): Don't show direction of route

* fix(MapDisplay): Don't duplicate nearby stop markers when a route shape + stops are being rendered

* feat(MapDisplay): Only show nearby stations and bus stops

* fix(MapPage): Where a stop & station are co-located, show station

* cleanup: remove unecessary surrounding fragments
  • Loading branch information
KaylaBrady authored Aug 29, 2023
1 parent 1f97169 commit 241e99b
Show file tree
Hide file tree
Showing 10 changed files with 509 additions and 144 deletions.
4 changes: 1 addition & 3 deletions assets/src/components/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { createControlComponent } from "@react-leaflet/core"
import { StateDispatchContext } from "../contexts/stateDispatchContext"
import { joinClasses } from "../helpers/dom"
import { TrainVehicle, Vehicle, VehicleId } from "../realtime.d"
import { DirectionId, Shape, Stop } from "../schedule"
import { Shape, Stop } from "../schedule"
import { equalByElements } from "../helpers/array"
import inTestGroup, { TestGroups } from "../userInTestGroup"
import {
Expand Down Expand Up @@ -72,7 +72,6 @@ export interface Props {
allowStreetView?: boolean
streetViewInitiallyEnabled?: boolean
allowFullscreen?: boolean
stopCardDirection?: DirectionId
includeStopCard?: boolean
stations?: Stop[] | null
}
Expand Down Expand Up @@ -471,7 +470,6 @@ const Map = (props: Props): ReactElement<HTMLDivElement> => {
<RouteStopMarkers
stops={stops}
zoomLevel={zoomLevel}
direction={props.stopCardDirection}
includeStopCard={
props.includeStopCard && inTestGroup(TestGroups.MapBeta)
}
Expand Down
12 changes: 4 additions & 8 deletions assets/src/components/map/markers/stopMarker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "leaflet.fullscreen"
import { MarkerProps } from "react-leaflet"

import { joinClasses } from "../../../helpers/dom"
import { DirectionId, Stop } from "../../../schedule"
import { Stop } from "../../../schedule"
import { MobileFriendlyTooltip } from "../../mapMarkers"
import StopCard from "../../stopCard"
import { ReactMarker } from "../utilities/reactMarker"
Expand Down Expand Up @@ -203,11 +203,7 @@ export const StopMarker = ({
)
}

export type StopCardProps = { direction?: DirectionId }
export const StopMarkerWithStopCard = ({
direction,
...props
}: StopMarkerProps & StopCardProps) => {
export const StopMarkerWithStopCard = ({ ...props }: StopMarkerProps) => {
const [isSelected, setIsSelected] = useState(props.selected || false)

const popupHandlers: LeafletEventHandlerFnMap = {
Expand All @@ -229,7 +225,7 @@ export const StopMarkerWithStopCard = ({
eventHandlers={{ ...props.eventHandlers, ...popupHandlers }}
selected={isSelected}
>
<StopCard.WithSafeArea stop={props.stop} direction={direction} />
<StopCard.WithSafeArea stop={props.stop} />
</StopMarker>
)
}
Expand All @@ -254,7 +250,7 @@ export type InteractiveStopMarkerProps = {
export const StopMarkerWithInfo = ({
includeStopCard = false,
...props
}: StopMarkerProps & StopCardProps & InteractiveStopMarkerProps) =>
}: StopMarkerProps & InteractiveStopMarkerProps) =>
includeStopCard ? (
<StopMarkerWithStopCard {...props} />
) : (
Expand Down
135 changes: 88 additions & 47 deletions assets/src/components/mapMarkers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { joinClasses } from "../helpers/dom"
import vehicleLabelString from "../helpers/vehicleLabel"
import { drawnStatus, statusClasses } from "../models/vehicleStatus"
import { TrainVehicle, Vehicle } from "../realtime"
import { DirectionId, Shape, Stop, StopId } from "../schedule"
import { Shape, Stop } from "../schedule"
import { UserSettings } from "../userSettings"

import garages, { Garage } from "../data/garages"
Expand Down Expand Up @@ -257,72 +257,113 @@ export const StationMarker = React.memo(
}
)

export const RouteStopMarkers = ({
/**
* @returns a list of stops at unique locations. Where a platform stop is at the exact location of a station, the list will include the station.
*/
const uniqueStopsByLocation = (stops: Stop[]) => {
const locationToStop: Record<string, Stop> = {}
stops.forEach((stop) => {
const key = `${stop.lat}_${stop.lon}`
const existingStopAtLocation = locationToStop[key]
if (
existingStopAtLocation === undefined ||
stop.locationType === LocationType.Station
) {
locationToStop[key] = stop
}
})
return Object.values(locationToStop)
}

export const StopMarkers = ({
stops,
zoomLevel,
direction,
includeStopCard,
zoomLevelConfig = {},
}: {
stops: Stop[]
zoomLevel: number
direction?: DirectionId
includeStopCard?: boolean
zoomLevelConfig?: {
minStopZoom?: number
minStationZoom?: number
}
}): JSX.Element => {
const seenStopIds = new Set<StopId>()
// Keep the first occurrence of each stop when there are duplicates
const uniqueStops: Stop[] = stops.flatMap((stop) => {
if (!seenStopIds.has(stop.id)) {
seenStopIds.add(stop.id)
return [stop]
}
return []
})
const { minStopZoom = 17, minStationZoom = 15 } = zoomLevelConfig

const uniqueStops: Stop[] = uniqueStopsByLocation(stops)
const streetViewActive = useContext(StreetViewModeEnabledContext)

return (
<>
{uniqueStops.map((stop) =>
stop.locationType === LocationType.Station ? (
<StationMarker key={stop.id} station={stop} zoomLevel={zoomLevel} />
) : (
<StopMarkerWithInfo
key={stop.id}
stop={stop}
direction={direction}
includeStopCard={includeStopCard && !streetViewActive}
zoomLevel={zoomLevel}
interactionStatesDisabled={streetViewActive}
eventHandlers={
streetViewActive
? {
click: () => {
const url = streetViewUrl({
latitude: stop.lat,
longitude: stop.lon,
})
window.FS?.event(
"User clicked map bus stop to open street view",
{
streetViewUrl_str: url,
clickedMapAt: {
latitude_real: stop.lat,
longitude_real: stop.lon,
{uniqueStops.map((stop) => {
switch (stop.locationType) {
case LocationType.Station: {
return (
zoomLevel >= minStationZoom && (
<StationMarker
key={stop.id}
station={stop}
zoomLevel={zoomLevel}
/>
)
)
}
default: {
return (
zoomLevel >= minStopZoom && (
<StopMarkerWithInfo
key={stop.id}
stop={stop}
includeStopCard={includeStopCard && !streetViewActive}
zoomLevel={zoomLevel}
interactionStatesDisabled={streetViewActive}
eventHandlers={
streetViewActive
? {
click: () => {
const url = streetViewUrl({
latitude: stop.lat,
longitude: stop.lon,
})
window.FS?.event(
"User clicked map bus stop to open street view",
{
streetViewUrl_str: url,
clickedMapAt: {
latitude_real: stop.lat,
longitude_real: stop.lon,
},
}
)
window.open(url, "_blank")
},
}
)
window.open(url, "_blank")
},
: {}
}
: {}
}
/>
)
)}
/>
)
)
}
}
})}
</>
)
}

export const RouteStopMarkers = (props: {
stops: Stop[]
zoomLevel: number
includeStopCard?: boolean
}): JSX.Element => {
return (
<StopMarkers
{...props}
zoomLevelConfig={{ minStopZoom: 0, minStationZoom: 0 }}
/>
)
}

export const RouteShape = React.memo(
({
shape,
Expand Down
Loading

0 comments on commit 241e99b

Please sign in to comment.