Skip to content

Commit

Permalink
feat(stop-viewer): add nearby amenities panel
Browse files Browse the repository at this point in the history
  • Loading branch information
landonreed committed Jul 29, 2021
1 parent 8bbc90c commit 5d2cbd4
Show file tree
Hide file tree
Showing 11 changed files with 572 additions and 92 deletions.
93 changes: 84 additions & 9 deletions lib/actions/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,21 +271,43 @@ function constructRoutingQuery (state, ignoreRealtimeUpdates, injectedParams = {
export const parkAndRideError = createAction('PARK_AND_RIDE_ERROR')
export const parkAndRideResponse = createAction('PARK_AND_RIDE_RESPONSE')

export function parkAndRideQuery (params) {
export function parkAndRideQuery (
params,
responseAction = parkAndRideResponse,
errorAction = parkAndRideResponse,
options = {}
) {
let endpoint = 'park_and_ride'
if (params && Object.keys(params).length > 0) {
endpoint += '?' + qs.stringify(params)
}
return createQueryAction(endpoint, parkAndRideResponse, parkAndRideError)
return createQueryAction(
endpoint,
responseAction,
errorAction,
options
)
}

// bike rental station query

export const bikeRentalError = createAction('BIKE_RENTAL_ERROR')
export const bikeRentalResponse = createAction('BIKE_RENTAL_RESPONSE')

export function bikeRentalQuery (params) {
return createQueryAction('bike_rental', bikeRentalResponse, bikeRentalError)
export function bikeRentalQuery (
params,
responseAction = bikeRentalResponse,
errorAction = bikeRentalError,
options = {}
) {
const paramsString = qs.stringify(params)
const endpoint = `bike_rental${paramsString ? `?${paramsString}` : ''}`
return createQueryAction(
endpoint,
responseAction,
errorAction,
options
)
}

// Car rental (e.g. car2go) locations lookup query
Expand All @@ -305,8 +327,15 @@ export function carRentalQuery (params) {
export const vehicleRentalResponse = createAction('VEHICLE_RENTAL_RESPONSE')
export const vehicleRentalError = createAction('VEHICLE_RENTAL_ERROR')

export function vehicleRentalQuery (params) {
return createQueryAction('vehicle_rental', vehicleRentalResponse, vehicleRentalError)
export function vehicleRentalQuery (
params,
responseAction = vehicleRentalResponse,
errorAction = vehicleRentalError,
options = {}
) {
const paramsString = qs.stringify(params)
const endpoint = `vehicle_rental${paramsString ? `?${paramsString}` : ''}`
return createQueryAction(endpoint, responseAction, errorAction, options)
}

// Single stop lookup query
Expand Down Expand Up @@ -342,14 +371,60 @@ export function fetchStopInfo (stop) {
}
}

export const findNearbyAmenitiesResponse = createAction('FIND_NEARBY_AMENITIES_RESPONSE')
export const findNearbyAmenitiesError = createAction('FIND_NEARBY_AMENITIES_ERROR')

function findNearbyAmenities (params, stopId) {
return function (dispatch, getState) {
const {lat, lon, radius} = params
const bounds = L.latLng(lat, lon).toBounds(radius)
const {lat: low, lng: left} = bounds.getSouthWest()
const {lat: up, lng: right} = bounds.northEast()
dispatch(parkAndRideQuery({lowerLeft: `${low},${left}`, upperRight: `${up},${right}`}))
dispatch(bikeRentalQuery(params))
const {lat: up, lng: right} = bounds.getNorthEast()
dispatch(
parkAndRideQuery(
{ lowerLeft: `${low},${left}`, upperRight: `${up},${right}` },
findNearbyAmenitiesResponse,
findNearbyAmenitiesError,
{
rewritePayload: (payload) => {
return {
parkAndRideLocations: payload,
stopId
}
}
}
)
)
dispatch(
bikeRentalQuery(
{ lowerLeft: `${low},${left}`, upperRight: `${up},${right}` },
findNearbyAmenitiesResponse,
findNearbyAmenitiesError,
{
rewritePayload: (payload) => {
return {
bikeRental: payload,
stopId
}
}
}
)
)
dispatch(
vehicleRentalQuery(
{ lowerLeft: `${low},${left}`, upperRight: `${up},${right}` },
findNearbyAmenitiesResponse,
findNearbyAmenitiesError,
{
rewritePayload: (payload) => {
return {
stopId,
vehicleRental: payload
}
}
}
)
)
}
}

Expand Down
105 changes: 96 additions & 9 deletions lib/components/map/connected-stop-viewer-overlay.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,106 @@
import StopViewerOverlay from '@opentripplanner/stop-viewer-overlay'
import DefaultStopMarker from '@opentripplanner/stop-viewer-overlay/lib/default-stop-marker'
import ParkAndRideOverlay from '@opentripplanner/park-and-ride-overlay'
import VehicleRentalOverlay from '@opentripplanner/vehicle-rental-overlay'
import ZoomBasedMarkers from '@opentripplanner/zoom-based-markers'
import React from 'react'
import { FeatureGroup, MapLayer, withLeaflet } from 'react-leaflet'
import { connect } from 'react-redux'

import * as mapActions from '../../actions/map'

import EnhancedStopMarker from './enhanced-stop-marker'

/**
* An overlay to view a collection of stops.
*/
class StopViewerOverlay extends MapLayer {
componentDidMount () {}

componentWillUnmount () {}

createLeafletElement () {}

updateLeafletElement () {}

render () {
const {
configCompanies,
leaflet,
mapConfig,
setLocation,
stopData,
stops
} = this.props
if (!stopData) return null
const { bikeRental, parkAndRideLocations, vehicleRental } = stopData
// Don't render if no map or no stops are defined.
// (ZoomBasedMarkers will also not render below the minimum zoom threshold defined in the symbols prop.)
if (!leaflet || !leaflet.map) {
return <FeatureGroup />
}
const stopSymbols = [
{
minZoom: 18,
symbol: EnhancedStopMarker
}
]
const zoom = leaflet.map.getZoom()
// if (zoom < stopSymbols[0].minZoom) return <FeatureGroup />
return (
<FeatureGroup>
{stops && stops.length > 0 &&
<ZoomBasedMarkers entities={stops} symbols={stopSymbols} zoom={zoom} />
}
<ParkAndRideOverlay
parkAndRideLocations={parkAndRideLocations}
setLocation={setLocation}
visible />
{mapConfig.overlays && mapConfig.overlays.map((overlayConfig, k) => {
switch (overlayConfig.type) {
case 'bike-rental': return (
<VehicleRentalOverlay
key={k}
{...overlayConfig}
configCompanies={configCompanies}
stations={bikeRental && bikeRental.stations}
visible
/>
)
case 'micromobility-rental': return (
<VehicleRentalOverlay
key={k}
{...overlayConfig}
configCompanies={configCompanies}
stations={vehicleRental?.stations}
visible
/>
)
default: return null
}
})}
</FeatureGroup>
)
}
}

// connect to the redux store

const mapStateToProps = (state, ownProps) => {
const viewedStop = state.otp.ui.viewedStop
const stopLookup = state.otp.transitIndex.stops
const stopData = stopLookup[state.otp.ui.viewedStop?.stopId]
const childStops = stopData?.childStops?.map(stopId => stopLookup[stopId])
const stops = []
if (stopData) stops.push(stopData)
if (childStops && childStops.length > 0) stops.push(...childStops)
return {
stop: viewedStop
? state.otp.transitIndex.stops[viewedStop.stopId]
: null,
StopMarker: DefaultStopMarker
configCompanies: state.otp.config.companies,
mapConfig: state.otp.config.map,
stopData,
stops
}
}

const mapDispatchToProps = {}
const mapDispatchToProps = {
setLocation: mapActions.setLocation
}

export default connect(mapStateToProps, mapDispatchToProps)(StopViewerOverlay)
export default connect(mapStateToProps, mapDispatchToProps)(withLeaflet(StopViewerOverlay))
116 changes: 116 additions & 0 deletions lib/components/map/enhanced-stop-marker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { styled as BaseMapStyled } from '@opentripplanner/base-map'
import coreUtils from '@opentripplanner/core-utils'
import FromToLocationPicker from '@opentripplanner/from-to-location-picker'
import { divIcon } from 'leaflet'
import React, { Component } from 'react'
import ReactDOMServer from 'react-dom/server'
import { connect } from 'react-redux'
import { Marker, Popup } from 'react-leaflet'
import styled from 'styled-components'

import * as mapActions from '../../actions/map'
import * as uiActions from '../../actions/ui'
import { getStopName } from '../../util/viewer'

const { getTextWidth } = coreUtils.itinerary

const BaseStopIcon = styled.div`
background: #fff;
border: ${props => props.active ? '#000 3px' : '#333 1px'} solid;
border-radius: 17px;
color: #000;
font-size: 16px;
font-weight: bold;
height: 12px;
line-height: 0px;
padding-left: 7px;
padding-top: 12px;
width: ${props => `${props.width || 12}px`}
`

class EnhancedStopMarker extends Component {
onClickView = () => {
const { setViewedStop, stop } = this.props
setViewedStop({ stopId: stop.id })
}

onFromClick = () => {
this.setLocation('from')
}

onToClick = () => {
this.setLocation('to')
}

setLocation (locationType) {
const { setLocation, stop } = this.props
const { lat, lon, name } = stop
setLocation({ location: { lat, lon, name }, locationType })
}

render () {
const { activeStopId, languageConfig, stop } = this.props
const { code, id, lat, lon, name } = stop
const [, stopId] = id.split(':')
const stopCode = code || stopId
const routeShortNames = stop.routes?.map(r => r.shortName).join(' ')
const stopLabel = routeShortNames || ''
if (!stopLabel) return null
const width = getTextWidth(stopLabel) * 0.83 + 80 / getTextWidth(stopLabel)
const icon = divIcon({
className: '',
html: ReactDOMServer.renderToStaticMarkup(
<BaseStopIcon
active={id === activeStopId}
// Show actual stop name on hover for easy identification.
title={getStopName(stop)}
width={width}
>
{stopLabel}
</BaseStopIcon>
),
iconAnchor: [(width / 2), 10]
})
return (
<Marker icon={icon} position={[lat, lon]}>
<Popup>
<BaseMapStyled.MapOverlayPopup>
<BaseMapStyled.PopupTitle>{name}</BaseMapStyled.PopupTitle>
<BaseMapStyled.PopupRow>
<span>
<b>Stop ID:</b> {stopCode}
</span>
<button onClick={this.onClickView}>
{languageConfig.stopViewer || 'Stop Viewer'}
</button>
</BaseMapStyled.PopupRow>

{/* The 'Set as [from/to]' ButtonGroup */}
<BaseMapStyled.PopupRow>
<b>Plan a trip:</b>
<FromToLocationPicker
onFromClick={this.onFromClick}
onToClick={this.onToClick}
/>
</BaseMapStyled.PopupRow>
</BaseMapStyled.MapOverlayPopup>
</Popup>
</Marker>
)
}
}

const mapStateToProps = (state, ownProps) => {
return {
activeStopId: state.otp.ui.viewedStop.stopId,
languageConfig: state.otp.config.language,
stop: ownProps.entity
}
}

const mapDispatchToProps = {
setLocation: mapActions.setLocation,
setViewedStop: uiActions.setViewedStop
}

export default connect(mapStateToProps, mapDispatchToProps)(EnhancedStopMarker)
Loading

0 comments on commit 5d2cbd4

Please sign in to comment.