Skip to content

Commit

Permalink
Merge branch 'dev' into trip-preview
Browse files Browse the repository at this point in the history
  • Loading branch information
binh-dam-ibigroup authored Oct 14, 2024
2 parents 83cadc9 + 17598ad commit 8a83ee5
Show file tree
Hide file tree
Showing 24 changed files with 4,173 additions and 590 deletions.
3,963 changes: 3,829 additions & 134 deletions __tests__/components/viewers/__snapshots__/nearby-view.js.snap

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ exports[`components > viewers > stop viewer should render with initial stop id a
</div>
<styled.div>
<div
className="sc-liccgK jihWTk"
className="sc-cuWdqJ jtAZHv"
>
<styled.div>
<div
Expand Down
4 changes: 0 additions & 4 deletions i18n/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,11 @@ common:
submitting: Submitting…
"yes": "Yes"
itineraryDescriptions:
calories: "{calories, number} Cal"
fareUnknown: No fare information
noItineraryToDisplay: No itinerary to display.
relativeCo2: |
{co2} {isMore, select, true {more} other {less} } CO₂ than driving alone
timeStartEnd: "{start} – {end}"
transfers: "{transfers, plural, =0 {} one {# transfer} other {# transfers}}"
linkOpensNewWindow: (Opens new window)
modes:
bicycle_rent: Bikeshare
Expand Down Expand Up @@ -154,7 +152,6 @@ common:
enterStartLocation: Enter start location or {mapAction} on map…
tap: tap
time:
departureArrivalTimes: "{startTime, time, short}—{endTime, time, short}"
duration:
aFewSeconds: a few seconds
nDays: "{days, plural, =1 {one day} other {# days}}"
Expand Down Expand Up @@ -269,7 +266,6 @@ components:
ariaLabel: Form navigation
ItinerarySummary:
itineraryDetails: Itinerary details
minMaxFare: "{minTotalFare} - {maxTotalFare}"
LocationSearch:
enterLocation: Enter location
setDestination: Set Destination
Expand Down
6 changes: 0 additions & 6 deletions i18n/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,11 @@ common:
submitting: Envoi en cours…
"yes": Oui
itineraryDescriptions:
calories: "{calories, number} kcal"
fareUnknown: Tarif inconnu
noItineraryToDisplay: Aucun trajet à afficher.
relativeCo2: |
{co2} de CO₂ en {isMore, select, true {plus} other {moins} } qu'en voiture
timeStartEnd: "{start} – {end}"
transfers: >-
{transfers, plural, =0 {} one {# correspondance} other {#
correspondances}}
linkOpensNewWindow: (Ouvre une nouvelle fenêtre)
modes:
bicycle_rent: En vélo en libre-service
Expand Down Expand Up @@ -165,7 +161,6 @@ common:
enterStartLocation: Entrez votre point de départ ou {mapAction} sur la carte…
tap: appuyez
time:
departureArrivalTimes: "{startTime, time, short}—{endTime, time, short}"
duration:
aFewSeconds: quelques secondes
nDays: "{days, plural, =1 {un jour} other {# jours}}"
Expand Down Expand Up @@ -286,7 +281,6 @@ components:
ariaLabel: Navigation du formulaire
ItinerarySummary:
itineraryDetails: Détails du trajet
minMaxFare: "{minTotalFare} - {maxTotalFare}"
LocationSearch:
enterLocation: Entrez le lieu
setDestination: Destination
Expand Down
49 changes: 44 additions & 5 deletions lib/actions/apiV2.js
Original file line number Diff line number Diff line change
Expand Up @@ -499,15 +499,13 @@ export const fetchNearby = (position, radius) => {
export const findStopTimesForStop = (params) =>
function (dispatch, getState) {
dispatch(fetchingStopTimesForStop(params))
const { date, stopId } = params
const { date, onlyRequestForOperators, stopId } = params
const timeZone = getState().otp.config.homeTimezone

// Create a service date timestamp from 3:30am local.
const serviceDay = getServiceStart(date, timeZone).getTime() / 1000

return dispatch(
createGraphQLQueryAction(
`query StopTimes(
const fullStopTimesQuery = `query StopTimes(
$serviceDay: Long!
$stopId: String!
) {
Expand Down Expand Up @@ -567,7 +565,48 @@ export const findStopTimesForStop = (params) =>
}
}
}
}`,
}`

const shorterStopTimesQueryForOperators = `query StopTimes(
$stopId: String!
) {
stop(id: $stopId) {
gtfsId
code
routes {
id: gtfsId
agency {
gtfsId
name
}
patterns {
id
headsign
}
}
stoptimesForPatterns(numberOfDepartures: 100, omitNonPickups: true, omitCanceled: false) {
pattern {
desc: name
headsign
id: code
route {
agency {
gtfsId
}
gtfsId
}
}
}
}
}`

const query = onlyRequestForOperators
? shorterStopTimesQueryForOperators
: fullStopTimesQuery

return dispatch(
createGraphQLQueryAction(
query,
{
serviceDay,
stopId
Expand Down
145 changes: 70 additions & 75 deletions lib/actions/field-trip.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
getRoutingParams,
planParamsToQueryAsync
} from '@opentripplanner/core-utils/lib/query'
import { isTransit } from '@opentripplanner/core-utils/lib/itinerary'
import { isTransitLeg } from '@opentripplanner/core-utils/lib/itinerary'
import {
OTP_API_DATE_FORMAT,
OTP_API_TIME_FORMAT
Expand Down Expand Up @@ -395,36 +395,34 @@ function makeSaveFieldTripItinerariesData(request, outbound, state) {
}

const gtfsTripsForItinerary = []
itinerary.legs
.filter((leg) => isTransit(leg.mode))
.forEach((leg) => {
let routeName = leg.routeShortName ? `(${leg.routeShortName}) ` : ''
routeName = `${routeName}${leg.routeLongName}`
const gtfsTrip = {
agencyAndId: leg.tripId,
// 'arrive' must be expressed in the agency's timezone
arrive: formatInTz(new Date(leg.endTime), FIELD_TRIP_TIME_FORMAT, {
timeZone: homeTimezone
}),
capacity: getFieldTripGroupCapacityForMode(
fieldTripModuleConfig,
leg.mode
),
// 'depart' must be expressed in the agency's timezone
depart: formatInTz(new Date(leg.startTime), FIELD_TRIP_TIME_FORMAT, {
timeZone: homeTimezone
}),
fromStopIndex: leg.from.stopIndex,
fromStopName: leg.from.name,
headsign: leg.headsign,
routeName,
toStopIndex: leg.to.stopIndex,
toStopName: leg.to.name,
tripHash: tripHashLookup[leg.tripId]
}
if (leg.tripBlockId) gtfsTrip.blockId = leg.tripBlockId
gtfsTripsForItinerary.push(gtfsTrip)
})
itinerary.legs.filter(isTransitLeg).forEach((leg) => {
let routeName = leg.routeShortName ? `(${leg.routeShortName}) ` : ''
routeName = `${routeName}${leg.routeLongName}`
const gtfsTrip = {
agencyAndId: leg.tripId,
// 'arrive' must be expressed in the agency's timezone
arrive: formatInTz(new Date(leg.endTime), FIELD_TRIP_TIME_FORMAT, {
timeZone: homeTimezone
}),
capacity: getFieldTripGroupCapacityForMode(
fieldTripModuleConfig,
leg.mode
),
// 'depart' must be expressed in the agency's timezone
depart: formatInTz(new Date(leg.startTime), FIELD_TRIP_TIME_FORMAT, {
timeZone: homeTimezone
}),
fromStopIndex: leg.from.stopIndex,
fromStopName: leg.from.name,
headsign: leg.headsign,
routeName,
toStopIndex: leg.to.stopIndex,
toStopName: leg.to.name,
tripHash: tripHashLookup[leg.tripId]
}
if (leg.tripBlockId) gtfsTrip.blockId = leg.tripBlockId
gtfsTripsForItinerary.push(gtfsTrip)
})

data.itins.push(itineraryDataToSave)
data.gtfsTrips.push(gtfsTripsForItinerary)
Expand Down Expand Up @@ -616,7 +614,6 @@ function getMissingTripHashesForActiveItineraries() {
})
)
}
return true
}

/**
Expand Down Expand Up @@ -712,52 +709,50 @@ function checkValidityAndCapacity(state, request) {

// check each individual trip to see if there aren't any trips in this
// itinerary that are already in use by another field trip
itinerary.legs
.filter((leg) => isTransit(leg.mode))
.forEach((leg) => {
const tripId = leg?.trip?.gtfsId

// this variable is used to track how many other field trips are using a
// particular trip
let capacityInUse = 0

// iterate over trips that are already being used by other field trips
// NOTE: In the use case of re-planning trips, there is currently no way
// to discern whether a tripInUse belongs to the current direction of
// the field trip being planned. Therefore, this will result in the
// re-planning of trips avoiding it's own previously planned trips
// that it currently has saved
travelDateTripsInUse.forEach((tripInUse) => {
if (!tripsOverlap(leg, tripHashLookup, tripId, tripInUse)) return

// ranges overlap! Add number of passengers on this other field trip
// to total capacity in use
capacityInUse += tripInUse.passengers
})
itinerary.legs.filter(isTransitLeg).forEach((leg) => {
const tripId = leg?.trip?.gtfsId

// this variable is used to track how many other field trips are using a
// particular trip
let capacityInUse = 0

// iterate over trips that are already being used by other field trips
// NOTE: In the use case of re-planning trips, there is currently no way
// to discern whether a tripInUse belongs to the current direction of
// the field trip being planned. Therefore, this will result in the
// re-planning of trips avoiding it's own previously planned trips
// that it currently has saved
travelDateTripsInUse.forEach((tripInUse) => {
if (!tripsOverlap(leg, tripHashLookup, tripId, tripInUse)) return

// ranges overlap! Add number of passengers on this other field trip
// to total capacity in use
capacityInUse += tripInUse.passengers
})

// check if the remaining capacity on this trip is enough to allow more
// field trip passengers on board
const legModeCapacity = getFieldTripGroupCapacityForMode(
fieldTripModuleConfig,
leg.mode
)
let remainingTripCapacity = legModeCapacity - capacityInUse
if (remainingTripCapacity < minimumAllowableRemainingCapacity) {
// This trip is already too "full" to allow any addition field trips
// on board. Ban this trip in future searches and don't use this
// itinerary in final results (set trip and itinerary capacity to 0).
remainingTripCapacity = 0
}
// check if the remaining capacity on this trip is enough to allow more
// field trip passengers on board
const legModeCapacity = getFieldTripGroupCapacityForMode(
fieldTripModuleConfig,
leg.mode
)
let remainingTripCapacity = legModeCapacity - capacityInUse
if (remainingTripCapacity < minimumAllowableRemainingCapacity) {
// This trip is already too "full" to allow any addition field trips
// on board. Ban this trip in future searches and don't use this
// itinerary in final results (set trip and itinerary capacity to 0).
remainingTripCapacity = 0
}

// always ban trips found in itineraries so that subsequent searches
// don't encounter them.
// TODO: a more advanced way of doing things might be to ban trip
// sequences to not find the same exact sequence, but also
// individual trips that are too full.
tripsToBanInSubsequentSearches.push(tripId)
// always ban trips found in itineraries so that subsequent searches
// don't encounter them.
// TODO: a more advanced way of doing things might be to ban trip
// sequences to not find the same exact sequence, but also
// individual trips that are too full.
tripsToBanInSubsequentSearches.push(tripId)

itineraryCapacity = Math.min(itineraryCapacity, remainingTripCapacity)
})
itineraryCapacity = Math.min(itineraryCapacity, remainingTripCapacity)
})

if (itineraryCapacity > 0) {
// itinerary has capacity, add to list and update remaining group size.
Expand Down
18 changes: 17 additions & 1 deletion lib/components/map/default-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// @ts-nocheck
import { connect } from 'react-redux'
import { GeolocateControl, NavigationControl } from 'react-map-gl'
import { getCurrentDate } from '@opentripplanner/core-utils/lib/time'
import { injectIntl } from 'react-intl'
import BaseMap from '@opentripplanner/base-map'
import generateOTP2TileLayers from '@opentripplanner/otp2-tile-overlay'
Expand All @@ -13,6 +14,7 @@ import {
assembleBasePath,
bikeRentalQuery,
carRentalQuery,
findStopTimesForStop,
vehicleRentalQuery
} from '../../actions/api'
import { ComponentContext } from '../../util/contexts'
Expand All @@ -22,6 +24,7 @@ import { MainPanelContent } from '../../actions/ui-constants'
import { setLocation, setMapPopupLocationAndGeocode } from '../../actions/map'
import { setViewedStop } from '../../actions/ui'
import { updateOverlayVisibility } from '../../actions/config'
import TransitOperatorIcons from '../util/connected-transit-operator-icons'

import ElevationPointMarker from './elevation-point-marker'
import EndpointsOverlay from './connected-endpoints-overlay'
Expand Down Expand Up @@ -153,6 +156,17 @@ class DefaultMap extends Component {
}
}

// Generate operator logos to pass through OTP tile layer to map-popup
getEntityPrefix = (entity) => {
const stopId = entity.gtfsId
this.props.findStopTimesForStop({
date: getCurrentDate(),
onlyRequestForOperators: true,
stopId
})
return <TransitOperatorIcons stopId={stopId} />
}

/**
* Checks whether the modes have changed between old and new queries and
* whether to update the map overlays accordingly (e.g., to show rental vehicle
Expand Down Expand Up @@ -407,7 +421,8 @@ class DefaultMap extends Component {
setLocation,
setViewedStop,
viewedRouteStops,
config.companies
config.companies,
this.getEntityPrefix
)
default:
return null
Expand Down Expand Up @@ -468,6 +483,7 @@ const mapStateToProps = (state) => {
const mapDispatchToProps = {
bikeRentalQuery,
carRentalQuery,
findStopTimesForStop,
getCurrentPosition,
setLocation,
setMapPopupLocationAndGeocode,
Expand Down
4 changes: 2 additions & 2 deletions lib/components/narrative/default/default-itinerary.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const {
isAdvanceBookingRequired,
isCoordinationRequired,
isFlex,
isTransit
isTransitLeg
} = coreUtils.itinerary

// Styled components
Expand Down Expand Up @@ -164,7 +164,7 @@ const ITINERARY_ATTRIBUTES = [
order: 3,
render: (itinerary, options) => {
const leg = clone(itinerary.legs[0])
if (isTransit(leg.mode)) {
if (isTransitLeg(leg)) {
leg.mode = 'WALK'
}

Expand Down
Loading

0 comments on commit 8a83ee5

Please sign in to comment.