diff --git a/app/action/ViaPointActions.js b/app/action/ViaPointActions.js index cf6f8d326c..b5141d795f 100644 --- a/app/action/ViaPointActions.js +++ b/app/action/ViaPointActions.js @@ -5,3 +5,7 @@ export function addViaPoint(actionContext, val) { export function setViaPoints(actionContext, points) { actionContext.dispatch('setViaPoints', points); } + +export function deleteViaPoint(actionContext, val) { + actionContext.dispatch('deleteViaPoint', val); +} diff --git a/app/component/itinerary/BicycleLeg.js b/app/component/itinerary/BicycleLeg.js index ddf1589c4d..6a3dc6be69 100644 --- a/app/component/itinerary/BicycleLeg.js +++ b/app/component/itinerary/BicycleLeg.js @@ -52,7 +52,8 @@ export default function BicycleLeg( let legDescription = {leg.from ? leg.from.name : ''}; const firstLegClassName = index === 0 ? 'start' : ''; let modeClassName = 'bicycle'; - const [address, place] = splitStringToAddressAndPlace(leg.from.name); + const [name, place] = splitStringToAddressAndPlace(leg.from.name); + const address = (leg.from.viaLocationType && leg.viaAddress) || name; const rentalVehicleNetwork = leg.from.vehicleRentalStation?.rentalNetwork.networkId || leg.from.rentalVehicle?.rentalNetwork.networkId; @@ -136,6 +137,8 @@ export default function BicycleLeg( icon="icon_scooter_rider" appendClass={!scooterSettingsOn ? 'settings' : 'scooter'} style={style} + viaType={leg.from.viaLocationType} + isStop={!!leg.from.stop} /> ); } else if (bicycleWalkLeg) { @@ -144,6 +147,8 @@ export default function BicycleLeg( index={index} modeClassName={modeClassName} boardingLeg={bicycleWalkLeg} + viaType={leg.from.viaLocationType} + isStop={!!leg.from.stop} /> ); } else if (mode === 'BICYCLE') { @@ -151,15 +156,25 @@ export default function BicycleLeg( ); } else { circleLine = ( - + ); } const fromStop = leg?.from.stop || bicycleWalkLeg?.from.stop; - const origin = bicycleWalkLeg?.from.stop ? bicycleWalkLeg.from.name : address; + const origin = + bicycleWalkLeg?.from.stop && !bicycleWalkLeg?.from.viaLocationType + ? bicycleWalkLeg.from.name + : address; const destination = bicycleWalkLeg?.to.stop ? bicycleWalkLeg?.to.name : leg.to.name; @@ -264,51 +279,56 @@ export default function BicycleLeg( }} /> - {isFirstLeg(index) || bicycleWalkLeg?.from.stop ? ( -
-
-
- {fromStop ? ( - { - e.stopPropagation(); - }} - to={stopPagePath(false, fromStop.gtfsId)} - > - {origin} - {leg.isViaPoint && ( + {isFirstLeg(index) || + bicycleWalkLeg?.from.stop || + leg.from.viaLocationType ? ( + <> + {leg.from.viaLocationType ?
: null} +
+
+
+ {fromStop ? ( + { + e.stopPropagation(); + }} + to={stopPagePath(false, fromStop.gtfsId)} + > + {origin} + {leg.isViaPoint && ( + + )} - )} - + ) : ( + address + )} +
+ {bicycleWalkLeg?.from.stop?.code && ( + <> + + - - ) : ( - address + )} +
{place}
- {bicycleWalkLeg?.from.stop?.code && ( - <> - - - - )} -
{place}
+
- -
+ ) : (
diff --git a/app/component/itinerary/BikeParkLeg.js b/app/component/itinerary/BikeParkLeg.js index 52be128428..e0af381a4a 100644 --- a/app/component/itinerary/BikeParkLeg.js +++ b/app/component/itinerary/BikeParkLeg.js @@ -45,6 +45,8 @@ const BikeParkLeg = ( bikePark index={index} modeClassName="walk" + viaType={leg.from.viaLocationType} + isStop={!!leg.from.stop} />
diff --git a/app/component/itinerary/CarLeg.js b/app/component/itinerary/CarLeg.js index 9da0ef8e9c..4739a09abb 100644 --- a/app/component/itinerary/CarLeg.js +++ b/app/component/itinerary/CarLeg.js @@ -10,6 +10,7 @@ import { durationToString } from '../../util/timeUtils'; import ItineraryCircleLineWithIcon from './ItineraryCircleLineWithIcon'; import { legTimeStr, legDestination } from '../../util/legUtils'; import ItineraryCircleLineLong from './ItineraryCircleLineLong'; +import { splitStringToAddressAndPlace } from '../../util/otpStrings'; export default function CarLeg(props, { config, intl }) { const distance = displayDistance( @@ -26,16 +27,22 @@ export default function CarLeg(props, { config, intl }) { index={props.index} modeClassName={modeClassName} boardingLeg={props.carBoardingLeg} + viaType={props.leg.from.viaLocationType} /> ) : ( ); - const [address, place] = props.leg.from.name.split(/, (.+)/); // Splits the name-string to two parts from the first occurance of ', ' + const [name, place] = splitStringToAddressAndPlace(props.leg.from.name); + const address = + props.leg.from.viaLocationType && props.leg.viaAddress + ? props.leg.viaAddress + : name; /* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */ return ( diff --git a/app/component/itinerary/CarParkLeg.js b/app/component/itinerary/CarParkLeg.js index 6d247c4657..1b0218a98a 100644 --- a/app/component/itinerary/CarParkLeg.js +++ b/app/component/itinerary/CarParkLeg.js @@ -50,12 +50,14 @@ function CarParkLeg(props, { config, intl }) { index={props.index} modeClassName="car-park-walk" carPark + viaType={props.leg.from.viaLocationType} /> ) : ( )} diff --git a/app/component/itinerary/Itinerary.js b/app/component/itinerary/Itinerary.js index 4a92a35173..b70e49127f 100644 --- a/app/component/itinerary/Itinerary.js +++ b/app/component/itinerary/Itinerary.js @@ -16,7 +16,6 @@ import RouteNumberContainer from '../RouteNumberContainer'; import { getActiveLegAlertSeverityLevel } from '../../util/alertUtils'; import { getLegMode, - splitLegsAtViaPoints, compressLegs, getLegBadgeProps, getInterliningLegs, @@ -27,6 +26,7 @@ import { legTimeStr, LegMode, getZones, + splitLegsAtViaPoints, } from '../../util/legUtils'; import { dateOrEmpty, isTomorrow, timeStr } from '../../util/timeUtils'; import withBreakpoint from '../../util/withBreakpoint'; @@ -42,6 +42,7 @@ import { getCapacityForLeg } from '../../util/occupancyUtil'; import getCo2Value from '../../util/emissions'; import { ItineraryFragment } from './queries/ItineraryFragment'; import { getTicketString } from '../../util/fareUtils'; +import { ViaLocationType } from '../../constants'; import BoardingInformation, { getBoardingInformationText, } from './BoardingInformation'; @@ -250,18 +251,6 @@ export const ViaLeg = () => (
); -const getViaPointIndex = (leg, intermediatePlaces) => { - if (!leg || !Array.isArray(intermediatePlaces)) { - return -1; - } - return intermediatePlaces.findIndex( - place => place.lat === leg.from.lat && place.lon === leg.from.lon, - ); -}; - -const connectsFromViaPoint = (currLeg, intermediatePlaces) => - getViaPointIndex(currLeg, intermediatePlaces) > -1; - const bikeWasParked = legs => { const legsLength = legs.length; for (let i = 0; i < legsLength; i++) { @@ -324,10 +313,7 @@ const Itinerary = ( nameLengthSum += getRouteText(leg.route, config).length; } nameLengthSum += 10; // every leg requires some minimum space - if ( - i > 0 && - (leg.intermediatePlace || connectsFromViaPoint(leg, intermediatePlaces)) - ) { + if (i > 0 && (leg.from.viaLocationType || leg.to.viaLocationType)) { intermediateSlack += legTime(leg.start) - legTime(compressedLegs[i - 1].end); // calculate time spent at each intermediate place } @@ -368,17 +354,12 @@ const Itinerary = ( let waitLength; const startMs = legTime(leg.start); const endMs = legTime(leg.end); - const previousLeg = i > 0 ? compressedLegs[i - 1] : null; const nextLeg = i < compressedLegs.length - 1 ? compressedLegs[i + 1] : null; let legLength = relativeLength(endMs - startMs); const longName = !leg?.route?.shortName || leg?.route?.shortName.length > 5; - if ( - nextLeg && - !nextLeg.intermediatePlace && - !connectsFromViaPoint(nextLeg, intermediatePlaces) - ) { + if (nextLeg && !leg.to.viaLocationType) { // don't show waiting in intermediate places waitTime = legTime(nextLeg.start) - endMs; waitLength = relativeLength(waitTime); @@ -427,14 +408,11 @@ const Itinerary = ( renderBar = false; addition += legLength; // carry over the length of the leg to the next } - // There are two places which inject ViaLegs in this logic, but we certainly - // don't want to add it twice in the same place with the same key, so we - // record whether we added it here at the first place. - let viaAdded = false; - if (leg.intermediatePlace) { + let viaPointAdded = false; + if (leg.from.viaLocationType === ViaLocationType.Visit) { + viaPointAdded = true; onlyIconLegs += 1; legs.push(); - viaAdded = true; } if (isLegOnFoot(leg) && renderBar) { const walkingTime = Math.floor(leg.duration / 60); @@ -596,11 +574,11 @@ const Itinerary = ( usingOwnCarWholeTrip && config.carBoardingModes[leg.route.mode] !== undefined; if ( - previousLeg && - !previousLeg.intermediatePlace && - connectsFromViaPoint(leg, intermediatePlaces) && - !viaAdded + leg.from.viaLocationType === ViaLocationType.PassThrough || + (leg.viaStopCall && !viaPointAdded) ) { + viaPointAdded = true; + onlyIconLegs += 1; legs.push(); } const renderRouteNumberForALongLeg = @@ -636,6 +614,13 @@ const Itinerary = ( ), ); stopNames.push(leg.from.name); + if ( + leg.to.viaLocationType === ViaLocationType.PassThrough && + !(nextLeg.transitLeg && nextLeg.from.viaLocationType) + ) { + onlyIconLegs += 1; + legs.push(); + } } if (waiting && !nextLeg?.interlineWithPreviousLeg) { diff --git a/app/component/itinerary/ItineraryCircleLine.js b/app/component/itinerary/ItineraryCircleLine.js index 30257e587b..0dece6081f 100644 --- a/app/component/itinerary/ItineraryCircleLine.js +++ b/app/component/itinerary/ItineraryCircleLine.js @@ -2,24 +2,27 @@ import PropTypes from 'prop-types'; import React from 'react'; import cx from 'classnames'; import Icon from '../Icon'; +import { ViaLocationType } from '../../constants'; class ItineraryCircleLine extends React.Component { static defaultProps = { - isVia: false, + viaType: null, color: null, renderBottomMarker: true, carPark: false, appendClass: undefined, + isStop: false, }; static propTypes = { index: PropTypes.number.isRequired, modeClassName: PropTypes.string.isRequired, - isVia: PropTypes.bool, + viaType: PropTypes.string, color: PropTypes.string, renderBottomMarker: PropTypes.bool, carPark: PropTypes.bool, appendClass: PropTypes.string, + isStop: PropTypes.bool, }; constructor(props) { @@ -39,7 +42,7 @@ class ItineraryCircleLine extends React.Component { } isFirstChild = () => { - return this.props.index === 0 && this.props.isVia === false; + return this.props.index === 0 && !this.props.viaType; }; getMarker = top => { @@ -80,7 +83,7 @@ class ItineraryCircleLine extends React.Component {
); } - if (this.props.isVia === true) { + if (this.props.viaType === ViaLocationType.Visit && !this.props.isStop) { return (
diff --git a/app/component/itinerary/ItineraryCircleLineLong.js b/app/component/itinerary/ItineraryCircleLineLong.js index d86f0abab1..5a0087d162 100644 --- a/app/component/itinerary/ItineraryCircleLineLong.js +++ b/app/component/itinerary/ItineraryCircleLineLong.js @@ -4,6 +4,7 @@ import cx from 'classnames'; import Icon from '../Icon'; import RouteNumber from '../RouteNumber'; import { legShape } from '../../util/shapes'; +import { ViaLocationType } from '../../constants'; const ItineraryCircleLineLong = props => { const [imgUrl, setImgUrl] = useState(''); @@ -28,6 +29,13 @@ const ItineraryCircleLineLong = props => {
); } + if (props.viaType === ViaLocationType.Visit && !props.isStop) { + return ( +
+ +
+ ); + } return null; }; @@ -156,11 +164,15 @@ ItineraryCircleLineLong.propTypes = { renderBottomMarker: PropTypes.bool, modeClassName: PropTypes.string.isRequired, boardingLeg: legShape.isRequired, + viaType: PropTypes.string, + isStop: PropTypes.bool, }; ItineraryCircleLineLong.defaultProps = { color: undefined, renderBottomMarker: false, + viaType: null, + isStop: false, }; export default ItineraryCircleLineLong; diff --git a/app/component/itinerary/ItineraryCircleLineWithIcon.js b/app/component/itinerary/ItineraryCircleLineWithIcon.js index 6921e9981e..087f1a2bb4 100644 --- a/app/component/itinerary/ItineraryCircleLineWithIcon.js +++ b/app/component/itinerary/ItineraryCircleLineWithIcon.js @@ -3,12 +3,13 @@ import React from 'react'; import cx from 'classnames'; import Icon from '../Icon'; import RouteNumber from '../RouteNumber'; +import { ViaLocationType } from '../../constants'; class ItineraryCircleLineWithIcon extends React.Component { static propTypes = { index: PropTypes.number.isRequired, modeClassName: PropTypes.string.isRequired, - isVia: PropTypes.bool, + viaType: PropTypes.string, bikePark: PropTypes.bool, carPark: PropTypes.bool, color: PropTypes.string, @@ -16,10 +17,11 @@ class ItineraryCircleLineWithIcon extends React.Component { icon: PropTypes.string, style: PropTypes.shape({}), isNotFirstLeg: PropTypes.bool, + isStop: PropTypes.bool, }; static defaultProps = { - isVia: false, + viaType: null, color: null, bikePark: false, carPark: false, @@ -27,6 +29,7 @@ class ItineraryCircleLineWithIcon extends React.Component { icon: undefined, style: {}, isNotFirstLeg: undefined, + isStop: false, }; state = { @@ -35,9 +38,7 @@ class ItineraryCircleLineWithIcon extends React.Component { isFirstChild = () => { return ( - !this.props.isNotFirstLeg && - this.props.index === 0 && - this.props.isVia === false + !this.props.isNotFirstLeg && this.props.index === 0 && !this.props.viaType ); }; @@ -50,7 +51,7 @@ class ItineraryCircleLineWithIcon extends React.Component { } getMarker = top => { - if (this.props.isVia === true) { + if (this.props.viaType === ViaLocationType.Visit && !this.props.isStop) { return (
@@ -115,7 +116,7 @@ class ItineraryCircleLineWithIcon extends React.Component { return ( - {config.showCO2InItinerarySummary && !legsWithScooter && ( - - )} + {config.showCO2InItinerarySummary && + !legsWithScooter && + !legsWithViaPoint && ( + + )} {shouldShowDisclaimer && (
); - const [address, place] = splitStringToAddressAndPlace(leg.from.name); + const [name, place] = splitStringToAddressAndPlace(props.leg.from.name); + const address = + props.leg.from.viaLocationType && props.leg.viaAddress + ? props.leg.viaAddress + : name; const { bookingUrl, infoUrl } = props.leg.pickupBookingInfo.contactInfo; return ( <> @@ -68,6 +72,7 @@ export default function TaxiLeg(props, { config, intl }) { index={index} modeClassName="walk" appendClass="taxi" + viaType={props.leg.from.viaLocationType} />
{leg.from.name} - {leg.isViaPoint && ( + {leg.from.viaLocationType && (
diff --git a/app/component/itinerary/WaitLeg.js b/app/component/itinerary/WaitLeg.js index 8ec97508d1..2d7a8937a8 100644 --- a/app/component/itinerary/WaitLeg.js +++ b/app/component/itinerary/WaitLeg.js @@ -9,6 +9,7 @@ import ItineraryMapAction from './ItineraryMapAction'; import ItineraryCircleLineWithIcon from './ItineraryCircleLineWithIcon'; import { PREFIX_STOPS } from '../../util/path'; import { legTimeStr } from '../../util/legUtils'; +import { ViaLocationType } from '../../constants'; /* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */ function WaitLeg( @@ -33,6 +34,7 @@ function WaitLeg( modeClassName={modeClassName} index={index} icon={icon} + isNotFirstLeg />
@@ -50,7 +52,7 @@ function WaitLeg( to={`/${PREFIX_STOPS}/${leg.to.stop.gtfsId}`} > {leg.to.name} - {leg.isViaPoint && ( + {leg.from.viaLocationType === ViaLocationType.PassThrough && ( i === 0; - const [address, place] = splitStringToAddressAndPlace(leg[toOrFrom].name); + const [name, place] = splitStringToAddressAndPlace(leg[toOrFrom].name); + const address = + leg[toOrFrom].viaLocationType && leg.viaAddress ? leg.viaAddress : name; const network = previousLeg?.[toOrFrom]?.vehicleRentalStation?.rentalNetwork.networkId || previousLeg?.[toOrFrom]?.rentalVehicle?.rentalNetwork.networkId; @@ -133,6 +135,8 @@ function WalkLeg( appendClass={appendClass} index={index} modeClassName={modeClassName} + viaType={leg.isViaPoint ? leg.from.viaLocationType : null} + isStop={!!leg.from.stop} />
)} - {!returnNotice && !alightNotice && leg[toOrFrom].name} + {!returnNotice && + !alightNotice && + (leg.viaAddress || leg[toOrFrom].name)} {leg[toOrFrom].stop && !alightNotice && ( - onLocationPopup(item, id, router, match, executeAction); + onLocationPopup(item, id, router, match, executeAction, config); } return ( diff --git a/app/component/map/tile-layer/TileLayerContainer.js b/app/component/map/tile-layer/TileLayerContainer.js index 4b306c27d3..65e49b98fd 100644 --- a/app/component/map/tile-layer/TileLayerContainer.js +++ b/app/component/map/tile-layer/TileLayerContainer.js @@ -44,7 +44,6 @@ class TileLayerContainer extends GridLayer { tileSize: PropTypes.number.isRequired, zoomOffset: PropTypes.number.isRequired, locationPopup: PropTypes.string, // all, none, reversegeocoding, origindestination - allowViaPoint: PropTypes.bool, // temporary, until OTP2 handles arbitrary via points onSelectLocation: PropTypes.func, mergeStops: PropTypes.bool, mapLayers: mapLayerShape.isRequired, @@ -70,7 +69,6 @@ class TileLayerContainer extends GridLayer { static defaultProps = { onSelectLocation: undefined, locationPopup: undefined, - allowViaPoint: false, objectsToHide: { vehicleRentalStations: [] }, highlightedStops: undefined, stopsToShow: undefined, @@ -352,10 +350,7 @@ class TileLayerContainer extends GridLayer { let contents; const breakpoint = getClientBreakpoint(); let showPopup = true; - const locationPopup = - this.props.allowViaPoint || this.props.locationPopup !== 'all' - ? this.props.locationPopup - : 'origindestination'; + const { locationPopup } = this.props; if (typeof this.state.selectableTargets !== 'undefined') { if (this.state.selectableTargets.length === 1) { diff --git a/app/configurations/config.default.js b/app/configurations/config.default.js index 5a4ce6130a..fe570bca4f 100644 --- a/app/configurations/config.default.js +++ b/app/configurations/config.default.js @@ -777,6 +777,7 @@ export default { }, viaPointsEnabled: true, + viaPointsMax: 1, // Toggling this off shows the alert bodytext instead of the header showAlertHeader: true, diff --git a/app/constants.js b/app/constants.js index e2c768c7be..fd0ec1a2fa 100644 --- a/app/constants.js +++ b/app/constants.js @@ -128,3 +128,11 @@ export const PlannerMessageType = Object.freeze({ NoStopsInRange: 'NO_STOPS_IN_RANGE', SystemError: 'SYSTEM_ERROR', }); + +/** + * OpenTripPlanner (v2) via point types. + */ +export const ViaLocationType = Object.freeze({ + PassThrough: 'PASS_THROUGH', + Visit: 'VISIT', +}); diff --git a/app/store/ViaPointStore.js b/app/store/ViaPointStore.js index b6e147b2fa..47541a4c4d 100644 --- a/app/store/ViaPointStore.js +++ b/app/store/ViaPointStore.js @@ -19,9 +19,17 @@ class ViaPointStore extends Store { return this.viaPoints; } + deleteViaPoint(val) { + this.viaPoints = this.viaPoints.filter( + p => p.lat !== val.lat || p.lon !== val.lon, + ); + this.emitChange(); + } + static handlers = { addViaPoint: 'addViaPoint', setViaPoints: 'setViaPoints', + deleteViaPoint: 'deleteViaPoint', }; } diff --git a/app/util/legUtils.js b/app/util/legUtils.js index 966527156e..30452b6354 100644 --- a/app/util/legUtils.js +++ b/app/util/legUtils.js @@ -209,14 +209,15 @@ function bikingEnded(leg1) { return leg1.from.vehicleRentalStation && leg1.mode === 'WALK'; } -function syntheticEndpoint(originalEndpoint, place) { - return { - ...originalEndpoint, - stop: place.stop, - lat: place.stop.lat, - lon: place.stop.lon, - name: place.stop.name, - }; +function getViaPointAddress(from, viaPoints) { + if (!from || !from.lat || !from.lon || !from.viaLocationType) { + return null; + } + return viaPoints.find( + p => + Math.round(p.lat * 1e5) === Math.round(from.lat * 1e5) && + Math.round(p.lon * 1e5) === Math.round(from.lon * 1e5), + ); } // Once a via place is matched, it is used and will not match again. @@ -238,9 +239,18 @@ function isViaPointMatch(stop, viaPoints) { ); } +function syntheticEndpoint(originalEndpoint, place) { + return { + ...originalEndpoint, + stop: place.stop, + lat: place.stop.lat, + lon: place.stop.lon, + name: place.stop.name, + }; +} + /** - * Adds intermediate: true to legs if their start point should have a via point - * marker, possibly splitting legs in case the via point belongs in the middle. + * Split legs in case the via point belongs in the middle. * Once a via point is used, it is not matched again. * * @param originalLegs Leg objects from graphql query @@ -259,7 +269,7 @@ export function splitLegsAtViaPoints(originalLegs, viaPlaces) { nextLegStartsWithIntermediate || (leg.transitLeg && isViaPointMatch(leg.from.stop, viaPoints)) ) { - leg.intermediatePlace = true; + leg.viaStopCall = true; nextLegStartsWithIntermediate = false; } if (intermediatePlaces) { @@ -273,7 +283,7 @@ export function splitLegsAtViaPoints(originalLegs, viaPlaces) { end: place.arrival, intermediatePlaces: intermediatePlaces.slice(start, i), }; - leg.intermediatePlace = true; + leg.viaStopCall = true; leg.start = place.arrival; leg.from = syntheticEndpoint(leg.from, place); splitLegs.push(leftLeg); @@ -307,11 +317,15 @@ export function splitLegsAtViaPoints(originalLegs, viaPlaces) { */ export function markViaPoints(originalLegs, viaPlaces) { const legs = []; - const viaPoints = viaPlaces.map(p => p.gtfsId); originalLegs.forEach(leg => { - const isViaPoint = isViaPointMatch(leg.from.stop, viaPoints); + const viaAddress = getViaPointAddress(leg.from, viaPlaces)?.address; + const isViaPoint = !!leg.from.viaLocationType; + if (leg.intermediatePlaces) { + // ViaLocationType pass_through + const viaPoints = viaPlaces.map(p => p.gtfsId); const intermediatePlaces = []; + leg.intermediatePlaces.forEach(place => { intermediatePlaces.push({ ...place, @@ -322,11 +336,13 @@ export function markViaPoints(originalLegs, viaPlaces) { ...leg, intermediatePlaces, isViaPoint, + viaAddress, }); } else { legs.push({ ...leg, isViaPoint, + viaAddress, }); } }); @@ -356,7 +372,7 @@ export function compressLegs(originalLegs, keepBicycleWalk = false) { compressedLeg = cloneDeep(currentLeg); return; } - if (currentLeg.intermediatePlace) { + if (currentLeg.from.viaLocationType) { compressedLegs.push(compressedLeg); compressedLeg = cloneDeep(currentLeg); return; diff --git a/app/util/planParamUtil.js b/app/util/planParamUtil.js index 05a47cd776..6c8ef4768a 100644 --- a/app/util/planParamUtil.js +++ b/app/util/planParamUtil.js @@ -313,11 +313,33 @@ export function getPlanParams( const intermediateLocations = getIntermediatePlaces({ intermediatePlaces, }); - const via = intermediateLocations.map(loc => ({ - passThrough: { - stopLocationIds: [loc.gtfsId], - }, - })); + let via = intermediateLocations + .map(loc => { + if (loc.gtfsId) { + return { + visit: { + stopLocationIds: [loc.gtfsId], + coordinate: { + latitude: loc.lat, + longitude: loc.lon, + }, + }, + }; + } + if (loc.lat && loc.lon) { + return { + visit: { + coordinate: { + latitude: loc.lat, + longitude: loc.lon, + }, + }, + }; + } + return null; + }) + .filter(Boolean); + const distance = estimateItineraryDistance( fromLocation, toLocation, @@ -399,10 +421,19 @@ export function getPlanParams( settings.bikeReluctance = null; // As of writing this comment, iterating (paging) does not support filtering of bad car transit itineraries. maxQueryIterations = 1; + // Via routing for cars is too performance intensive. + via = null; break; case PLANTYPE.PARKANDRIDE: access = ['CAR_PARKING']; transitOnly = true; + // Via routing for cars is too performance intensive. + via = null; + break; + case PLANTYPE.CAR: + direct = ['CAR']; + // Via routing for cars is too performance intensive. + via = null; break; case PLANTYPE.TRANSIT: direct = access; @@ -417,6 +448,7 @@ export function getPlanParams( egress = access; direct = directFlexOnly ? ['WALK', 'FLEX'] : null; transitOnly = false; + via = null; break; default: // direct modes direct = [planType]; diff --git a/app/util/queryUtils.js b/app/util/queryUtils.js index d4055a5789..8861d40220 100644 --- a/app/util/queryUtils.js +++ b/app/util/queryUtils.js @@ -5,7 +5,7 @@ import { getIntermediatePlaces, } from './otpStrings'; import { getPathWithEndpointObjects, PREFIX_ITINERARY_SUMMARY } from './path'; -import { addViaPoint } from '../action/ViaPointActions'; +import { addViaPoint, deleteViaPoint } from '../action/ViaPointActions'; /** * Processes query so that empty arrays will be preserved in URL @@ -99,13 +99,23 @@ export const updateItinerarySearch = ( router.replace(newLocation); }; -export const onLocationPopup = (item, id, router, match, executeAction) => { +export const onLocationPopup = ( + item, + id, + router, + match, + executeAction, + config, +) => { if (id === 'via') { - const viaPoints = getIntermediatePlaces(match.location.query) - .concat([item]) - .map(locationToOTP); + const viaPoints = getIntermediatePlaces(match.location.query) || []; + if (config.viaPointsMax && viaPoints.length >= config.viaPointsMax) { + const lastViaPoint = viaPoints.pop(); + executeAction(deleteViaPoint, lastViaPoint); + } + viaPoints.push(item); executeAction(addViaPoint, item); - setIntermediatePlaces(router, match, viaPoints); + setIntermediatePlaces(router, match, viaPoints.map(locationToOTP)); return; } let origin = otpToLocation(match.params.from); diff --git a/build/schema.graphql b/build/schema.graphql index d088f56ca7..16882b18bc 100644 --- a/build/schema.graphql +++ b/build/schema.graphql @@ -12,12 +12,23 @@ This is only worth it when the execution is long running, i.e. more than ~50 mil """ directive @async on FIELD_DEFINITION +"This directive allows results to be deferred during execution" +directive @defer( + "Deferred behaviour is controlled by this argument" + if: Boolean! = true, + "A unique label that represents the fragment being deferred" + label: String + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + "Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" - reason: String = "No longer supported" + reason: String! = "No longer supported" ) on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION +"This directive disables error propagation when a non nullable field returns null for the given operation." +directive @experimental_disableErrorPropagation on QUERY | MUTATION | SUBSCRIPTION + "Directs the executor to include this field or fragment only when the `if` argument is true" directive @include( "Included when true." @@ -39,6 +50,9 @@ directive @specifiedBy( url: String! ) on SCALAR +"Add timing data to prometheus, if Actuator API is enabled" +directive @timingData on FIELD_DEFINITION + "A fare product (a ticket) to be bought by a passenger" interface FareProduct { "Identifier for the fare product." @@ -73,9 +87,18 @@ interface PlaceInterface { "Entity related to an alert" union AlertEntity = Agency | Pattern | Route | RouteType | Stop | StopOnRoute | StopOnTrip | Trip | Unknown +"Scheduled times for a trip on a service date for a stop location." +union CallScheduledTime = ArrivalDepartureTime | TimeWindow + +"Location where a transit vehicle stops at." +union CallStopLocation = Location | LocationGroup | Stop + "Rental place union that represents either a VehicleRentalStation or a RentalVehicle" union RentalPlace = RentalVehicle | VehicleRentalStation +"A feature for a step" +union StepFeature = Entrance + union StopPosition = PositionAtStop | PositionBetweenStops "A public transport agency" @@ -116,7 +139,7 @@ type Alert implements Node { Agency affected by the disruption. Note that this value is present only if the disruption has an effect on all operations of the agency (e.g. in case of a strike). """ - agency: Agency @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected agencies.\nUse entities instead.") + agency: Agency @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected agencies.\nUse entities instead.") @timingData "Alert cause" alertCause: AlertCauseType "Long description of the alert" @@ -125,7 +148,7 @@ type Alert implements Node { language: String ): String! "Long descriptions of the alert in all different available languages" - alertDescriptionTextTranslations: [TranslatedString!]! @deprecated(reason : "Use `alertDescriptionText` instead. `accept-language` header can be used for translations or the `language` parameter on the `alertDescriptionText` field.") + alertDescriptionTextTranslations: [TranslatedString!]! @deprecated(reason : "Use `alertDescriptionText` instead. `accept-language` header can be used for translations or the `language` parameter on the `alertDescriptionText` field.") @timingData "Alert effect" alertEffect: AlertEffectType "hashcode from the original GTFS-RT alert" @@ -136,7 +159,7 @@ type Alert implements Node { language: String ): String "Header of the alert in all different available languages" - alertHeaderTextTranslations: [TranslatedString!]! @deprecated(reason : "Use `alertHeaderText` instead. `accept-language` header can be used for translations or the `language` parameter on the `alertHeaderText` field.") + alertHeaderTextTranslations: [TranslatedString!]! @deprecated(reason : "Use `alertHeaderText` instead. `accept-language` header can be used for translations or the `language` parameter on the `alertHeaderText` field.") @timingData "Alert severity level" alertSeverityLevel: AlertSeverityLevelType "Url with more information" @@ -145,7 +168,7 @@ type Alert implements Node { language: String ): String "Url with more information in all different available languages" - alertUrlTranslations: [TranslatedString!]! @deprecated(reason : "Use `alertUrl` instead. `accept-language` header can be used for translations or the `language` parameter on the `alertUrl` field.") + alertUrlTranslations: [TranslatedString!]! @deprecated(reason : "Use `alertUrl` instead. `accept-language` header can be used for translations or the `language` parameter on the `alertUrl` field.") @timingData "Time when this alert is not in effect anymore. Format: Unix timestamp in seconds" effectiveEndDate: Long "Time when this alert comes into effect. Format: Unix timestamp in seconds" @@ -157,13 +180,21 @@ type Alert implements Node { "Global object ID provided by Relay. This value can be used to refetch this object using **node** query." id: ID! "Patterns affected by the disruption" - patterns: [Pattern] @deprecated(reason : "This will always return an empty list. Use entities instead.") + patterns: [Pattern] @deprecated(reason : "This will always return an empty list. Use entities instead.") @timingData "Route affected by the disruption" - route: Route @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected routes.\nUse entities instead.") + route: Route @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected routes.\nUse entities instead.") @timingData "Stop affected by the disruption" - stop: Stop @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected stops.\nUse entities instead.") + stop: Stop @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected stops.\nUse entities instead.") @timingData "Trip affected by the disruption" - trip: Trip @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected trips.\nUse entities instead.") + trip: Trip @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected trips.\nUse entities instead.") @timingData +} + +"Arrival and departure time (not relative to midnight)." +type ArrivalDepartureTime { + "Arrival time as an ISO-8601-formatted datetime." + arrival: OffsetDateTime + "Departure time as an ISO-8601-formatted datetime." + departure: OffsetDateTime } "Bike park represents a location where bicycles can be parked." @@ -241,7 +272,7 @@ type BikeRentalStation implements Node & PlaceInterface { """ spacesAvailable: Int "A description of the current state of this bike rental station, e.g. \"Station on\"" - state: String @deprecated(reason : "Use operative instead") + state: String @deprecated(reason : "Use operative instead") @timingData "ID of the bike rental station" stationId: String } @@ -279,12 +310,16 @@ type BookingInfo { earliestBookingTime: BookingTime "When is the latest time the service can be booked" latestBookingTime: BookingTime + "Maximum duration before travel to make the request." + maximumBookingNotice: Duration "Maximum number of seconds before travel to make the request" - maximumBookingNoticeSeconds: Long + maximumBookingNoticeSeconds: Long @deprecated(reason : "Use `maximumBookingNotice`") @timingData "A general message for those booking the service" message: String + "Minimum duration before travel to make the request" + minimumBookingNotice: Duration "Minimum number of seconds before travel to make the request" - minimumBookingNoticeSeconds: Long + minimumBookingNoticeSeconds: Long @deprecated(reason : "Use `minimumBookingNotice`") @timingData "A message specific to the pick up" pickupMessage: String } @@ -297,6 +332,20 @@ type BookingTime { time: String } +"Real-time estimates for arrival and departure times for a stop location." +type CallRealTime { + "Real-time estimates for the arrival." + arrival: EstimatedTime + "Real-time estimates for the departure." + departure: EstimatedTime +} + +"What is scheduled for a trip on a service date for a stop location." +type CallSchedule { + "Scheduled time for a trip on a service date for a stop location." + time: CallScheduledTime +} + "Car park represents a location where cars can be parked." type CarPark implements Node & PlaceInterface { "ID of the car park" @@ -330,17 +379,17 @@ type CarPark implements Node & PlaceInterface { "Cluster is a list of stops grouped by name and proximity" type Cluster implements Node { "ID of the cluster" - gtfsId: String! + gtfsId: String! @deprecated(reason : "Not implemented") "Global object ID provided by Relay. This value can be used to refetch this object using **node** query." - id: ID! + id: ID! @deprecated(reason : "Not implemented") "Latitude of the center of this cluster (i.e. average latitude of stops in this cluster)" - lat: Float! + lat: Float! @deprecated(reason : "Not implemented") "Longitude of the center of this cluster (i.e. average longitude of stops in this cluster)" - lon: Float! + lon: Float! @deprecated(reason : "Not implemented") "Name of the cluster" - name: String! + name: String! @deprecated(reason : "Not implemented") "List of stops in the cluster" - stops: [Stop!] + stops: [Stop!] @deprecated(reason : "Not implemented") } "Contact information for booking an on-demand or flexible service." @@ -451,11 +500,60 @@ type DepartureRow implements Node & PlaceInterface { ): [Stoptime] } +""" +A (possibly discounted) fare product that requires another fare product to be purchased previously +in order to be valid. + +For example, when taking the train into a city, you might get a discounted "transfer fare" when +switching to the bus for the second leg. +""" +type DependentFareProduct implements FareProduct { + "The fare product is _not_ valid without purchasing at least _one_ of" + dependencies(filter: DependentFareProductFilter = ALL): [FareProduct!]! + id: String! + """ + The 'medium' that this product applies to, for example "Oyster Card" or "Berlin Ticket App". + + This communicates to riders that a specific way of buying or keeping this product is required. + """ + medium: FareMedium + "Human readable name of the product, for example example \"Day pass\" or \"Single ticket\"." + name: String! + "The price of the product" + price: Money! + "The category of riders this product applies to, for example students or pensioners." + riderCategory: RiderCategory +} + type Emissions { "CO₂ emissions in grams." co2: Grams } +"Station entrance or exit, originating from OSM or GTFS data." +type Entrance { + "ID of the entrance in the format of `FeedId:EntranceId`. If the `FeedId` is `osm`, the entrance originates from OSM data." + entranceId: String! + "Name of the entrance or exit." + name: String + "Short text or a number that identifies the entrance or exit for passengers. For example, `A` or `B`." + publicCode: String + "Whether the entrance or exit is accessible by wheelchair" + wheelchairAccessible: WheelchairBoarding +} + +"Real-time estimates for an arrival or departure at a certain place." +type EstimatedTime { + """ + The delay or "earliness" of the vehicle at a certain place. This estimate can change quite often. + + If the vehicle is early then this is a negative duration. + """ + delay: Duration! + "The estimate for a call event (such as arrival or departure) at a certain place. This estimate can change quite often." + time: OffsetDateTime! +} + "A 'medium' that a fare product applies to, for example cash, 'Oyster Card' or 'DB Navigator App'." type FareMedium { "ID of the medium" @@ -581,12 +679,12 @@ type Itinerary { "Time when the user leaves arrives at the destination." end: OffsetDateTime "Time when the user arrives to the destination. Format: Unix timestamp in milliseconds." - endTime: Long @deprecated(reason : "Use `end` instead which includes timezone information.") + endTime: Long @deprecated(reason : "Use `end` instead which includes timezone information.") @timingData """ Information about the fares for this itinerary. This is primarily a GTFS Fares V1 interface and always returns an empty list. Use the leg's `fareProducts` instead. """ - fares: [fare] @deprecated(reason : "Use the leg's `fareProducts`.") + fares: [fare] @deprecated(reason : "Use the leg's `fareProducts`.") @timingData "Generalized cost of the itinerary. Used for debugging search results." generalizedCost: Int """ @@ -607,7 +705,7 @@ type Itinerary { "Time when the user leaves from the origin." start: OffsetDateTime "Time when the user leaves from the origin. Format: Unix timestamp in milliseconds." - startTime: Long @deprecated(reason : "Use `start` instead which includes timezone information.") + startTime: Long @deprecated(reason : "Use `start` instead which includes timezone information.") @timingData """ A list of system notices. Contains debug information for itineraries. One use-case is to run a routing search with 'debugItineraryFilter: true'. @@ -642,13 +740,13 @@ type Leg { stop in this leg, i.e. scheduled time of arrival at alighting stop = `endTime - arrivalDelay` """ - arrivalDelay: Int @deprecated(reason : "Use `start.estimated.delay` instead.") + arrivalDelay: Int @deprecated(reason : "Use `start.estimated.delay` instead.") @timingData """ For transit leg, the offset from the scheduled departure time of the boarding stop in this leg, i.e. scheduled time of departure at boarding stop = `startTime - departureDelay` """ - departureDelay: Int @deprecated(reason : "Use `end.estimated.delay` instead.") + departureDelay: Int @deprecated(reason : "Use `end.estimated.delay` instead.") @timingData "The distance traveled while traversing the leg in meters." distance: Float """ @@ -663,7 +761,7 @@ type Leg { "The time when the leg ends including real-time information, if available." end: LegTime! "The date and time when this leg ends. Format: Unix timestamp in milliseconds." - endTime: Long @deprecated(reason : "Use `end.estimated.time` instead which contains timezone information.") + endTime: Long @deprecated(reason : "Use `end.estimated.time` instead which contains timezone information.") @timingData """ Fare products are purchasable tickets which may have an optional fare container or rider category that limits who can buy them or how. @@ -686,6 +784,9 @@ type Leg { Re-fetching fails when the underlying transit data no longer exists. **Note:** when both id and fare products are queried with [Relay](https://relay.dev/), id should be queried using a suitable GraphQL alias such as `legId: id`. Relay does not accept different fare product ids in otherwise identical legs. + + The identifier is valid for a maximum of 2 years, but sometimes it will fail after a few hours. + We do not recommend storing IDs for a long time. """ id: String """ @@ -695,18 +796,20 @@ type Leg { """ interlineWithPreviousLeg: Boolean "Whether the destination of this leg (field `to`) is one of the intermediate places specified in the query." - intermediatePlace: Boolean + intermediatePlace: Boolean @deprecated(reason : "Not implemented. Use `viaLocationType` from from/to fields instead.") """ For transit legs, intermediate stops between the Place where the leg originates and the Place where the leg ends. For non-transit legs, null. - Returns Place type, which has fields for e.g. departure and arrival times """ - intermediatePlaces: [Place] + intermediatePlaces: [Place] @deprecated(reason : "Use `leg.stopCalls` instead") """ For transit legs, intermediate stops between the Place where the leg originates and the Place where the leg ends. For non-transit legs, null. + + The `include` parameter allows filtering of the returned places by stop type. If not provided, the + field returns all types. An empty list is not permitted. """ - intermediateStops: [Stop] + intermediateStops(include: [StopType!]): [Stop] @deprecated(reason : "Use `leg.stopCalls` instead") "The leg's geometry." legGeometry: Geometry "The mode (e.g. `WALK`) used when traversing this leg." @@ -758,7 +861,7 @@ type Leg { realTime: Boolean "State of real-time data" realtimeState: RealtimeState - "Whether this leg is traversed with a rented bike." + "Whether this leg is traversed with a rented vehicle." rentedBike: Boolean "Estimate of a hailed ride like Uber." rideHailingEstimate: RideHailingEstimate @@ -769,9 +872,15 @@ type Leg { "The time when the leg starts including real-time information, if available." start: LegTime! "The date and time when this leg begins. Format: Unix timestamp in milliseconds." - startTime: Long @deprecated(reason : "Use `start.estimated.time` instead which contains timezone information.") + startTime: Long @deprecated(reason : "Use `start.estimated.time` instead which contains timezone information.") @timingData "The turn-by-turn navigation instructions." steps: [step] + """ + All the stop calls (stop times) of this _leg_ (but not trip) including the boarding and alighting one. + + Non-transit legs return an empty list. + """ + stopCalls: [StopCall!]! "The Place where the leg ends." to: Place! "Whether this leg is a transit leg or not." @@ -809,6 +918,34 @@ type LocalTimeSpanDate { timeSpans: [LocalTimeSpan] } +""" +A stop that isn't a fixed point but zone where passengers can board or alight anywhere. + +This is mostly used by demand-responsive services. +""" +type Location { + "The geometry representing the geographic extend of the location." + geometry: StopGeometries! + "ÌD of the location in format `FeedId:LocationId`" + gtfsId: String! + "Optional name of the location." + name: String +} + +""" +A group of fixed stops that are visited in an arbitrary order. + +This is mostly used by demand-responsive services. +""" +type LocationGroup { + "ÌD of the location group in format `FeedId:LocationGroupId`" + gtfsId: String! + "The stops that are part of the group (cannot be stations)." + members: [Stop!]! + "Optional name of the group." + name: String +} + "An amount of money." type Money { """ @@ -919,20 +1056,20 @@ type Place { """ arrival: LegTime "The time the rider will arrive at the place. Format: Unix timestamp in milliseconds." - arrivalTime: Long! @deprecated(reason : "Use `arrival` which includes timezone information.") + arrivalTime: Long! @deprecated(reason : "Use `arrival` which includes timezone information.") @timingData "The bike parking related to the place" - bikePark: BikePark @deprecated(reason : "bikePark is deprecated. Use vehicleParking instead.") + bikePark: BikePark @deprecated(reason : "bikePark is deprecated. Use vehicleParking instead.") @timingData "The bike rental station related to the place" - bikeRentalStation: BikeRentalStation @deprecated(reason : "Use vehicleRentalStation and rentalVehicle instead") + bikeRentalStation: BikeRentalStation @deprecated(reason : "Use vehicleRentalStation and rentalVehicle instead") @timingData "The car parking related to the place" - carPark: CarPark @deprecated(reason : "carPark is deprecated. Use vehicleParking instead.") + carPark: CarPark @deprecated(reason : "carPark is deprecated. Use vehicleParking instead.") @timingData """ The time the rider will depart the place. This also includes real-time information if available. """ departure: LegTime "The time the rider will depart the place. Format: Unix timestamp in milliseconds." - departureTime: Long! @deprecated(reason : "Use `departure` which includes timezone information.") + departureTime: Long! @deprecated(reason : "Use `departure` which includes timezone information.") @timingData "Latitude of the place (WGS 84)" lat: Float! "Longitude of the place (WGS 84)" @@ -966,7 +1103,12 @@ type Place { Type of vertex. (Normal, Bike sharing station, Bike P+R, Transit stop) Mostly used for better localization of bike sharing and P+R station names """ - vertexType: VertexType + vertexType: VertexType @deprecated(reason : "Unmaintained. Use `stop`, `rentalVehicle`, `vehicleParking` or `vehicleRentalStation` to tell which type it is.") @timingData + """ + This defines if the place is a requested via location, and what kind it is. If the value is + `null`, this place is not a via location. + """ + viaLocationType: ViaLocationType } type Plan { @@ -982,13 +1124,8 @@ type Plan { messageEnums: [String]! "A list of possible error messages in cleartext" messageStrings: [String]! - """ - This is the suggested search time for the "next page" or time window. Insert it together - with the searchWindowUsed in the request to get a new set of trips following in the - search-window AFTER the current search. No duplicate trips should be returned, unless a trip - is delayed and new real-time data is available. - """ - nextDateTime: Long @deprecated(reason : "Use nextPageCursor instead") + "This will not be available after Match 2026." + nextDateTime: Long @deprecated(reason : "Use nextPageCursor instead") @timingData """ Use the cursor to go to the next "page" of itineraries. Copy the cursor from the last response to the pageCursor query parameter and keep the original request as is. This will enable you to @@ -997,13 +1134,8 @@ type Plan { This is only usable when public transportation mode(s) are included in the query. """ nextPageCursor: String - """ - This is the suggested search time for the "previous page" or time window. Insert it together - with the searchWindowUsed in the request to get a new set of trips preceding in the - search-window BEFORE the current search. No duplicate trips should be returned, unless a trip - is delayed and new real-time data is available. - """ - prevDateTime: Long @deprecated(reason : "Use previousPageCursor instead") + "This will not be available after Match 2026." + prevDateTime: Long @deprecated(reason : "Use previousPageCursor instead") @timingData """ Use the cursor to go to the previous "page" of itineraries. Copy the cursor from the last response to the pageCursor query parameter and keep the original request otherwise as is. @@ -1021,7 +1153,7 @@ type Plan { The unit is seconds. """ - searchWindowUsed: Long + searchWindowUsed: Long @deprecated(reason : "This is not needed for debugging, and is misleading if the window is cropped.") "The destination" to: Place! } @@ -1116,11 +1248,11 @@ type QueryType { stop: [String!] ): [Alert] "Get a single bike park based on its ID, i.e. value of field `bikeParkId`" - bikePark(id: String!): BikePark @deprecated(reason : "bikePark is deprecated. Use vehicleParking instead.") + bikePark(id: String!): BikePark @deprecated(reason : "bikePark is deprecated. Use vehicleParking instead.") @timingData "Get all bike parks" - bikeParks: [BikePark] @deprecated(reason : "bikeParks is deprecated. Use vehicleParkings instead.") + bikeParks: [BikePark] @deprecated(reason : "bikeParks is deprecated. Use vehicleParkings instead.") @timingData "Get a single bike rental station based on its ID, i.e. value of field `stationId`" - bikeRentalStation(id: String!): BikeRentalStation @deprecated(reason : "Use rentalVehicle or vehicleRentalStation instead") + bikeRentalStation(id: String!): BikeRentalStation @deprecated(reason : "Use rentalVehicle or vehicleRentalStation instead") @timingData "Get all bike rental stations" bikeRentalStations( """ @@ -1129,32 +1261,61 @@ type QueryType { the returned list will contain `null` values. """ ids: [String] - ): [BikeRentalStation] @deprecated(reason : "Use rentalVehicles or vehicleRentalStations instead") - "Get cancelled TripTimes." + ): [BikeRentalStation] @deprecated(reason : "Use rentalVehicles or vehicleRentalStations instead") @timingData + """ + Get pages of canceled trips. Planned cancellations are not currently supported. Limiting the number of + returned trips with either `first` or `last` is highly recommended since the number of returned trips + can be really high when there is a strike affecting the transit services, for example. Follows the + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + """ + canceledTrips( + """ + This parameter is part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) + and should be used together with the `first` parameter. + """ + after: String, + """ + This parameter is part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) + and should be used together with the `last` parameter. + """ + before: String, + """ + Limits how many trips are returned. This parameter is part of the + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) and can be used together with + the `after` parameter. + """ + first: Int, + """ + This parameter is part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) + and should be used together with the `before` parameter. + """ + last: Int + ): TripOnServiceDateConnection + "Get canceled TripTimes." cancelledTripTimes( "Feed feedIds (e.g. [\"HSL\"])." feeds: [String], """ - Only cancelled trip times that have last stop arrival time at maxArrivalTime + Only canceled trip times that have last stop arrival time at maxArrivalTime or before are returned. Format: seconds since midnight of maxDate. """ maxArrivalTime: Int, - "Only cancelled trip times scheduled to run on maxDate or before are returned. Format: \"2019-12-23\" or \"20191223\"." + "Only canceled trip times scheduled to run on maxDate or before are returned. Format: \"2019-12-23\" or \"20191223\"." maxDate: String, """ - Only cancelled trip times that have first stop departure time at + Only canceled trip times that have first stop departure time at maxDepartureTime or before are returned. Format: seconds since midnight of maxDate. """ maxDepartureTime: Int, """ - Only cancelled trip times that have last stop arrival time at minArrivalTime + Only canceled trip times that have last stop arrival time at minArrivalTime or after are returned. Format: seconds since midnight of minDate. """ minArrivalTime: Int, - "Only cancelled trip times scheduled to run on minDate or after are returned. Format: \"2019-12-23\" or \"20191223\"." + "Only canceled trip times scheduled to run on minDate or after are returned. Format: \"2019-12-23\" or \"20191223\"." minDate: String, """ - Only cancelled trip times that have first stop departure time at + Only canceled trip times that have first stop departure time at minDepartureTime or after are returned. Format: seconds since midnight of minDate. """ minDepartureTime: Int, @@ -1164,9 +1325,9 @@ type QueryType { routes: [String], "Trip gtfsIds (e.g. [\"HSL:1098_20190405_Ma_2_1455\"])." trips: [String] - ): [Stoptime] + ): [Stoptime] @deprecated(reason : "`cancelledTripTimes` is not implemented. Use `canceledTrips` instead.") @timingData "Get a single car park based on its ID, i.e. value of field `carParkId`" - carPark(id: String!): CarPark @deprecated(reason : "carPark is deprecated. Use vehicleParking instead.") + carPark(id: String!): CarPark @deprecated(reason : "carPark is deprecated. Use vehicleParking instead.") @timingData "Get all car parks" carParks( """ @@ -1174,11 +1335,11 @@ type QueryType { **Note:** if an id is invalid (or the car park service is unavailable) the returned list will contain `null` values. """ ids: [String] - ): [CarPark] @deprecated(reason : "carParks is deprecated. Use vehicleParkings instead.") + ): [CarPark] @deprecated(reason : "carParks is deprecated. Use vehicleParkings instead.") @timingData "Get a single cluster based on its ID, i.e. value of field `gtfsId`" - cluster(id: String!): Cluster + cluster(id: String!): Cluster @deprecated(reason : "Not implemented") "Get all clusters" - clusters: [Cluster] + clusters: [Cluster] @deprecated(reason : "Not implemented") "Get a single departure row based on its ID (ID format is `FeedId:StopId:PatternId`)" departureRow(id: String!): DepartureRow "Get all available feeds" @@ -1244,7 +1405,7 @@ type QueryType { maxDistance: Int = 2000, "Maximum number of results. Search is stopped when this limit is reached. Default is 20." maxResults: Int = 20 - ): placeAtDistanceConnection @async + ): placeAtDistanceConnection @async @timingData "Fetches an object given its ID" node( "The ID of an object" @@ -1357,7 +1518,7 @@ type QueryType { "When true, real-time updates are ignored during this search. Default value: false" ignoreRealtimeUpdates: Boolean, "An ordered list of intermediate locations to be visited." - intermediatePlaces: [InputCoordinates] @deprecated(reason : "Not implemented in OTP2."), + intermediatePlaces: [InputCoordinates] @deprecated(reason : "Not implemented"), """ How easily bad itineraries are filtered from results. Value 0 (default) disables filtering. Itineraries are filtered if they are worse than another @@ -1424,7 +1585,7 @@ type QueryType { "Preferences for vehicle parking" parking: VehicleParkingInput, "List of routes and agencies which are given higher preference when planning the itinerary" - preferred: InputPreferred, + preferred: InputPreferred @deprecated(reason : "Not implemented"), """ **Consider this argument experimental** – setting this argument to true causes timeouts and unoptimal routes in many cases. @@ -1476,7 +1637,7 @@ type QueryType { travel time of the trip (and therefore arguments `time` and `from` must be used correctly to get meaningful itineraries). """ - startTransitTripId: String @deprecated(reason : "Not implemented in OTP2"), + startTransitTripId: String @deprecated(reason : "Not implemented"), "Time of departure or arrival in format hh:mm:ss. Default value: current time" time: String, """ @@ -1560,7 +1721,7 @@ type QueryType { walkSpeed: Float, "Whether the itinerary must be wheelchair accessible. Default value: false" wheelchair: Boolean - ): Plan @async @deprecated(reason : "Use `planConnection` instead.") + ): Plan @async @deprecated(reason : "Use `planConnection` instead.") @timingData """ Plan (itinerary) search that follows [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). @@ -1632,8 +1793,8 @@ type QueryType { preferences: PlanPreferencesInput, """ Duration of the search window. This either starts at the defined earliest departure - time or ends at the latest arrival time. If this is not provided, a reasonable - search window is automatically generated. When searching for earlier or later itineraries + time or ends at the latest arrival time. If this is not provided or the value is set as null, + a reasonable search window is automatically generated. When searching for earlier or later itineraries with paging, this search window is no longer used and the new window will be based on how many suggestions were returned in the previous search. The new search window can be shorter or longer than the original search window. Note, itineraries are returned faster @@ -1647,7 +1808,7 @@ type QueryType { searchWindow: Duration, "The list of points the itinerary is required to pass through." via: [PlanViaLocationInput!] - ): PlanConnection @async + ): PlanConnection @async @timingData "Get a single rental vehicle based on its ID, i.e. value of field `vehicleId`" rentalVehicle(id: String!): RentalVehicle "Get all rental vehicles" @@ -1683,7 +1844,7 @@ type QueryType { serviceDates: LocalDateRangeInput, "Only include routes, which use one of these modes" transportModes: [Mode] - ): [Route] + ): [Route] @timingData "Get the time range for which the API has data available" serviceTimeRange: serviceTimeRange "Get a single station based on its ID, i.e. value of field `gtfsId` (format is `FeedId:StopId`)" @@ -1801,6 +1962,10 @@ type RealTimeEstimate { type RentalVehicle implements Node & PlaceInterface { "If true, vehicle is currently available for renting." allowPickupNow: Boolean + "The vehicle should be returned before this deadline." + availableUntil: OffsetDateTime + "Fuel or battery status of the rental vehicle" + fuel: RentalVehicleFuel "Global object ID provided by Relay. This value can be used to refetch this object using **node** query." id: ID! "Latitude of the vehicle (WGS 84)" @@ -1810,7 +1975,7 @@ type RentalVehicle implements Node & PlaceInterface { "Name of the vehicle" name: String! "ID of the rental network." - network: String @deprecated(reason : "Use `networkId` from `rentalNetwork` instead.") + network: String @deprecated(reason : "Use `networkId` from `rentalNetwork` instead.") @timingData "If true, vehicle is not disabled." operative: Boolean "The vehicle rental network information. This is referred as system in the GBFS terminology." @@ -1830,6 +1995,14 @@ type RentalVehicleEntityCounts { total: Int! } +"Rental vehicle fuel represent the current status of the battery or fuel of a rental vehicle" +type RentalVehicleFuel { + "Fuel or battery power remaining in the vehicle. Expressed from 0 to 1." + percent: Ratio + "Range in meters that the vehicle can travel with the current charge or fuel." + range: Int +} + type RentalVehicleType { "The vehicle's general form factor" formFactor: FormFactor @@ -1867,6 +2040,13 @@ type RideHailingProvider { type RiderCategory { "ID of the category" id: String! + """ + If this category is considered the "default" one. In most places this means "Adult" or + "Regular". + Frontends can use this property to display this category more prominently or pre-select this + in a UI. + """ + isDefault: Boolean! "Human readable name of the category." name: String } @@ -2010,7 +2190,7 @@ type Stop implements Node & PlaceInterface { types: [StopAlertType] ): [Alert] "The cluster which this stop is part of" - cluster: Cluster + cluster: Cluster @deprecated(reason : "Not implemented") "Stop code which is visible at the stop" code: String "Description of the stop, usually a street name" @@ -2152,13 +2332,28 @@ type Stop implements Node & PlaceInterface { https://developers.google.com/transit/gtfs/reference/#routestxt and https://developers.google.com/transit/gtfs/reference/extended-route-types """ - vehicleType: Int + vehicleType: Int @deprecated(reason : "Not implemented. Use `vehicleMode`.") @timingData "Whether wheelchair boarding is possible for at least some of vehicles on this stop" wheelchairBoarding: WheelchairBoarding "ID of the zone where this stop is located" zoneId: String } +""" +Represents the time or time window when a specific trip on a specific date arrives to and/or departs +from a specific stop location. + +This may contain real-time information, if available. +""" +type StopCall { + "Real-time estimates for arrival and departure times for this stop location." + realTime: CallRealTime + "Scheduled arrival and departure times for this stop location." + schedule: CallSchedule + "The stop where this arrival/departure happens." + stopLocation: CallStopLocation! +} + type StopGeometries { "Representation of the stop geometries as GeoJSON (https://geojson.org/)" geoJson: GeoJson @@ -2314,6 +2509,12 @@ type TicketType implements Node { zones: [String!] } +"A time window when a vehicle visits a stop, area or group of stops." +type TimeWindow { + end: OffsetDateTime! + start: OffsetDateTime! +} + "Text with language" type TranslatedString { "Two-letter language code (ISO 639-1)" @@ -2336,7 +2537,11 @@ type Trip implements Node { """ types: [TripAlertType] ): [Alert] - "Arrival time to the final stop" + """ + Arrival time to the final stop. If the trip does not run on the given date, + it will return scheduled times from another date. This field is slightly + confusing and will be deprecated when a better replacement is implemented. + """ arrivalStoptime( """ Date for which the arrival time is returned. Format: YYYYMMDD. If this @@ -2347,7 +2552,11 @@ type Trip implements Node { "Whether bikes are allowed on board the vehicle running this trip" bikesAllowed: BikesAllowed blockId: String - "Departure time from the first stop" + """ + Departure time from the first stop. If the trip does not run on the given date, + it will return scheduled times from another date. This field is slightly + confusing and will be deprecated when a better replacement is implemented. + """ departureStoptime( """ Date for which the departure time is returned. Format: YYYYMMDD. If this @@ -2386,6 +2595,12 @@ type Trip implements Node { stops: [Stop!]! "List of times when this trip arrives to or departs from a stop" stoptimes: [Stoptime] + """ + List of times when this trip arrives to or departs from each stop on a given date, or + today if the date is not given. If the trip does not run on the given date, it will + return scheduled times from another date. This field is slightly confusing and + will be deprecated when a better replacement is implemented. + """ stoptimesForDate( "Date for which stoptimes are returned. Format: YYYYMMDD" serviceDate: String @@ -2414,6 +2629,60 @@ type TripOccupancy { occupancyStatus: OccupancyStatus } +"A trip on a specific service date." +type TripOnServiceDate { + "Information related to trip's scheduled arrival to the final stop location. Can contain real-time information." + end: StopCall! + """ + The service date when the trip occurs. + + **Note**: A service date is a technical term useful for transit planning purposes and might not + correspond to a how a passenger thinks of a calendar date. For example, a night bus running + on Sunday morning at 1am to 3am, might have the previous Saturday's service date. + """ + serviceDate: LocalDate! + "Information related to trip's scheduled departure from the first stop location. Can contain real-time information." + start: StopCall! + "List of times when this trip arrives to or departs from a stop location and information related to the visit to the stop location." + stopCalls: [StopCall!]! + "This trip on service date is an instance of this trip." + trip: Trip +} + +""" +A connection to a list of trips on service dates that follows +[GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). +""" +type TripOnServiceDateConnection { + """ + Edges which contain the trips. Part of the + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + """ + edges: [TripOnServiceDateEdge] + """ + Contains cursors to fetch more pages of trips. + Part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + """ + pageInfo: PageInfo! +} + +""" +An edge for TripOnServiceDate connection. Part of the +[GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). +""" +type TripOnServiceDateEdge { + """ + The cursor of the edge. Part of the + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + """ + cursor: String! + """ + Trip on a service date as a node. Part of the + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + """ + node: TripOnServiceDate +} + "This is used for alert entities that we don't explicitly handle or they are missing." type Unknown { "Entity's description" @@ -2497,8 +2766,10 @@ type VehiclePosition { heading: Float "Human-readable label of the vehicle, eg. a publicly visible number or a license plate" label: String + "When the position of the vehicle was recorded." + lastUpdate: OffsetDateTime "When the position of the vehicle was recorded in seconds since the UNIX epoch." - lastUpdated: Long + lastUpdated: Long @deprecated(reason : "Use `lastUpdate` instead.") @timingData "Latitude of the vehicle" lat: Float "Longitude of the vehicle" @@ -2558,7 +2829,7 @@ type VehicleRentalStation implements Node & PlaceInterface { "Name of the vehicle rental station" name: String! "ID of the rental network." - network: String @deprecated(reason : "Use `networkId` from `rentalNetwork` instead.") + network: String @deprecated(reason : "Use `networkId` from `rentalNetwork` instead.") @timingData "If true, station is on and in service." operative: Boolean """ @@ -2578,14 +2849,14 @@ type VehicleRentalStation implements Node & PlaceInterface { the rental station, even if the vehicle racks don't have any spaces available. See field `allowDropoffNow` to know if is currently possible to return a vehicle. """ - spacesAvailable: Int @deprecated(reason : "Use `availableSpaces` instead, which also contains the space vehicle types") + spacesAvailable: Int @deprecated(reason : "Use `availableSpaces` instead, which also contains the space vehicle types") @timingData "ID of the vehicle in the format of network:id" stationId: String """ Number of vehicles currently available on the rental station. See field `allowPickupNow` to know if is currently possible to pick up a vehicle. """ - vehiclesAvailable: Int @deprecated(reason : "Use `availableVehicles` instead, which also contains vehicle types") + vehiclesAvailable: Int @deprecated(reason : "Use `availableVehicles` instead, which also contains vehicle types") @timingData } type VehicleRentalUris { @@ -2631,12 +2902,12 @@ type fare { Fare price in cents. **Note:** this value is dependent on the currency used, as one cent is not necessarily ¹/₁₀₀ of the basic monerary unit. """ - cents: Int @deprecated(reason : "No longer supported") + cents: Int @deprecated(reason : "No longer supported") @timingData "Components which this fare is composed of" - components: [fareComponent] @deprecated(reason : "No longer supported") + components: [fareComponent] @deprecated(reason : "No longer supported") @timingData "ISO 4217 currency code" - currency: String @deprecated(reason : "No longer supported") - type: String @deprecated(reason : "No longer supported") + currency: String @deprecated(reason : "No longer supported") @timingData + type: String @deprecated(reason : "No longer supported") @timingData } """ @@ -2648,13 +2919,13 @@ type fareComponent { Fare price in cents. **Note:** this value is dependent on the currency used, as one cent is not necessarily ¹/₁₀₀ of the basic monerary unit. """ - cents: Int @deprecated(reason : "No longer supported") + cents: Int @deprecated(reason : "No longer supported") @timingData "ISO 4217 currency code" - currency: String @deprecated(reason : "No longer supported") + currency: String @deprecated(reason : "No longer supported") @timingData "ID of the ticket type. Corresponds to `fareId` in **TicketType**." - fareId: String @deprecated(reason : "No longer supported") + fareId: String @deprecated(reason : "No longer supported") @timingData "List of routes which use this fare component" - routes: [Route] @deprecated(reason : "No longer supported") + routes: [Route] @deprecated(reason : "No longer supported") @timingData } type placeAtDistance implements Node { @@ -2686,13 +2957,6 @@ type serviceTimeRange { start: Long } -type Entrance { - publicCode: String - wheelchairAccessible: Boolean -} - -union StepFeature = Entrance - type step { "The cardinal (compass) direction (e.g. north, northeast) taken when engaging this step." absoluteDirection: AbsoluteDirection @@ -2714,6 +2978,8 @@ type step { elevationProfile: [elevationProfileComponent] "When exiting a highway or traffic circle, the exit name/number." exit: String + "Information about an feature associated with a step e.g. an station entrance or exit" + feature: StepFeature "The latitude of the start of the step." lat: Float "The longitude of the start of the step." @@ -2726,8 +2992,6 @@ type step { streetName: String "Is this step walking with a bike?" walkingBike: Boolean - "Feature of a step" - feature: StepFeature } type stopAtDistance implements Node { @@ -2840,7 +3104,7 @@ enum AlertSeverityLevelType { INFO """ Severe alerts are used when a significant part of public transport services is - affected, for example: All train services are cancelled due to technical problems. + affected, for example: All train services are canceled due to technical problems. """ SEVERE "Severity of alert is unknown" @@ -2891,6 +3155,22 @@ enum CyclingOptimizationType { SHORTEST_DURATION } +""" +Dependent fare products can lead to many combinations of fares, however it is often not useful +information to the passenger. + +This enum allows filtering of the dependencies. + +Since it is recognised that this is not covered well in the specification, it is discussed here: +https://github.com/google/transit/pull/423 +""" +enum DependentFareProductFilter { + "Show all dependencies" + ALL + "Show only dependencies where the rider category and medium is the same es the main fare product." + MATCH_CATEGORY_AND_MEDIUM +} + "Entities, which are relevant for a feed and can contain alerts" enum FeedAlertType { "Alerts affecting the feed's agencies" @@ -2907,9 +3187,9 @@ enum FilterPlaceType { "Old value for VEHICLE_RENT" BICYCLE_RENT @deprecated(reason : "Use VEHICLE_RENT instead as it's clearer that it also returns rental scooters, cars...") "Parking lots (not rental stations) that contain spaces for bicycles" - BIKE_PARK + BIKE_PARK @deprecated(reason : "Not supported.") "Parking lots that contain spaces for cars" - CAR_PARK + CAR_PARK @deprecated(reason : "Not supported.") "Departure rows" DEPARTURE_ROW """ @@ -2949,6 +3229,7 @@ enum InputField { DATE_TIME FROM TO + VIA } """ @@ -3390,15 +3671,40 @@ enum RealtimeState { UPDATED } -"Actions to take relative to the current position when engaging a walking/driving step." +""" +A direction that is not absolute but rather fuzzy and context-dependent. +It provides the passenger with information what they should do in this step depending on where they +were in the previous one. +""" enum RelativeDirection { CIRCLE_CLOCKWISE CIRCLE_COUNTERCLOCKWISE + """ + Moving straight ahead in one of these cases + + - Passing through a crossing or intersection. + - Passing through a station entrance or exit when it is not know whether the passenger is + entering or exiting. If it _is_ known then `ENTER_STATION`/`EXIT_STATION` is used. + More information about the entrance is in the `step.feature` field. + """ CONTINUE DEPART ELEVATOR + """ + Entering a public transport station. If it's not known if the passenger is entering or exiting + then `CONTINUE` is used. + + More information about the entrance is in the `step.feature` field. + """ ENTER_STATION + """ + Exiting a public transport station. If it's not known if the passenger is entering or exiting + then `CONTINUE` is used. + + More information about the entrance is in the `step.feature` field. + """ EXIT_STATION + "Follow the signs indicating a specific location like \"platform 1\" or \"exit B\"." FOLLOW_SIGNS HARD_LEFT HARD_RIGHT @@ -3529,6 +3835,15 @@ enum StopAlertType { TRIPS } +enum StopType { + "An area or zone represented by a polygon. Introduced by the GTFS-Flex spec process." + LOCATION + "A group of stops. Introduced by the GTFS-Flex spec process." + LOCATION_GROUP + "A fixed stop represented by a coordinate." + STOP +} + """ Transit modes include modes that are used within organized transportation networks run by public transportation authorities, taxi companies etc. @@ -3612,6 +3927,21 @@ enum VertexType { TRANSIT } +"Categorization for via locations." +enum ViaLocationType { + """ + The via stop location must be visited as part of a transit trip as at the boarding stop, the + intermediate stop, or the alighting stop. + """ + PASS_THROUGH + """ + The location is visited physically by boarding or alighting a transit trip at a given stop, or by + traveling via requested coordinate location as part of a access, transfer, egress or direct + segment. Intermediate stops visited on-board do not count. + """ + VISIT +} + enum WheelchairBoarding { "Wheelchair boarding is not possible at this stop." NOT_POSSIBLE @@ -3733,7 +4063,10 @@ input BicyclePreferencesInput { "Preferences related to bicycle rental (station based or floating bicycle rental)." input BicycleRentalPreferencesInput { - "Rental networks which can be potentially used as part of an itinerary." + """ + Rental networks which can be potentially used as part of an itinerary. If this field has no default value, + it means that all networks are allowed unless some are banned with `bannedNetworks`. + """ allowedNetworks: [String!] "Rental networks which cannot be used as part of an itinerary." bannedNetworks: [String!] @@ -3814,6 +4147,8 @@ input CarParkingPreferencesInput { "Preferences related to traveling on a car (excluding car travel on transit services such as taxi)." input CarPreferencesInput { + "Cost of boarding a vehicle with a car." + boardCost: Cost "Car parking related preferences." parking: CarParkingPreferencesInput "A multiplier for how bad travelling on car is compared to being in transit for equal lengths of time." @@ -3824,7 +4159,10 @@ input CarPreferencesInput { "Preferences related to car rental (station based or floating car rental)." input CarRentalPreferencesInput { - "Rental networks which can be potentially used as part of an itinerary." + """ + Rental networks which can be potentially used as part of an itinerary. If this field has no default value, + it means that all networks are allowed unless some are banned with `bannedNetworks`. + """ allowedNetworks: [String!] "Rental networks which cannot be used as part of an itinerary." bannedNetworks: [String!] @@ -3878,14 +4216,14 @@ input InputBanned { banned for boarding and disembarking vehicles — it is possible to get an itinerary where a vehicle stops at one of these stops """ - stops: String @deprecated(reason : "Not implemented in OTP2.") + stops: String @deprecated(reason : "Not implemented") """ A comma-separated list of banned stop ids. Only itineraries where these stops are not travelled through are returned, e.g. if a bus route stops at one of these stops, that route will not be used in the itinerary, even if the stop is not used for boarding or disembarking the vehicle. """ - stopsHard: String @deprecated(reason : "Not implemented in OTP2.") + stopsHard: String @deprecated(reason : "Not implemented") "A comma-separated list of banned trip ids" trips: String } @@ -3948,15 +4286,11 @@ input InputModeWeight { } input InputPreferred { - "A comma-separated list of ids of the agencies preferred by the user." + "Not implemented" agencies: String - """ - Penalty added for using every route that is not preferred if user set any - route as preferred. We return number of seconds that we are willing to wait - for preferred route. - """ + "Not implemented" otherThanPreferredRoutesPenalty: Int - "A comma-separated list of ids of the routes preferred by the user." + "Not implemented" routes: String } @@ -4139,7 +4473,6 @@ input PlanModesInput { If more than one mode is selected, at least one of them must be used but not necessarily all. There are modes that automatically also use walking such as the rental modes. To force rental to be used, this should only include the rental mode and not `WALK` in addition. - The default access mode is `WALK`. """ direct: [PlanDirectMode!] "Should only the direct search without any transit be done." @@ -4147,8 +4480,7 @@ input PlanModesInput { """ Modes for different phases of an itinerary when transit is included. Also includes street mode selections related to connecting to the transit network - and transfers. By default, all transit modes are usable and `WALK` is used for - access, egress and transfers. + and transfers. By default, all transit modes are usable. """ transit: PlanTransitModesInput """ @@ -4241,7 +4573,6 @@ input PlanTransitModesInput { If more than one mode is selected, at least one of them must be used but not necessarily all. There are modes that automatically also use walking such as the rental modes. To force rental to be used, this should only include the rental mode and not `WALK` in addition. - The default access mode is `WALK`. """ access: [PlanAccessMode!] """ @@ -4249,13 +4580,9 @@ input PlanTransitModesInput { If more than one mode is selected, at least one of them must be used but not necessarily all. There are modes that automatically also use walking such as the rental modes. To force rental to be used, this should only include the rental mode and not `WALK` in addition. - The default access mode is `WALK`. """ egress: [PlanEgressMode!] - """ - Street mode that is used when searching for transfers. Selection of only one allowed for now. - The default transfer mode is `WALK`. - """ + "Street mode that is used when searching for transfers. Selection of only one allowed for now." transfer: [PlanTransferMode!] """ Transit modes and reluctances associated with them. Each defined mode can be used in @@ -4281,10 +4608,14 @@ A visit-via-location is a physical visit to one of the stop locations or coordin on-board visit does not count, the traveler must alight or board at the given stop for it to to be accepted. To visit a coordinate, the traveler must walk(bike or drive) to the closest point in the street network from a stop and back to another stop to join the transit network. - -NOTE! Coordinates are NOT supported yet. """ input PlanVisitViaLocationInput { + """ + A coordinate to route through. To visit a coordinate, the traveler must walk(bike or drive) + to the closest point in the street network from a stop and back to another stop to join the transit + network. + """ + coordinate: PlanCoordinateInput "The label/name of the location. This is pass-through information and is not used in routing." label: String """ @@ -4328,7 +4659,10 @@ input ScooterPreferencesInput { "Preferences related to scooter rental (station based or floating scooter rental)." input ScooterRentalPreferencesInput { - "Rental networks which can be potentially used as part of an itinerary." + """ + Rental networks which can be potentially used as part of an itinerary. If this field has no default value, + it means that all networks are allowed unless some are banned with `bannedNetworks`. + """ allowedNetworks: [String!] "Rental networks which cannot be used as part of an itinerary." bannedNetworks: [String!] @@ -4344,21 +4678,21 @@ input TimetablePreferencesInput { When false, real-time updates are considered during the routing. In practice, when this option is set as true, some of the suggestions might not be realistic as the transfers could be invalid due to delays, - trips can be cancelled or stops can be skipped. + trips can be canceled or stops can be skipped. """ excludeRealTimeUpdates: Boolean """ - When true, departures that have been cancelled ahead of time will be + When true, departures that have been canceled ahead of time will be included during the routing. This means that an itinerary can include - a cancelled departure while some other alternative that contains no cancellations + a canceled departure while some other alternative that contains no cancellations could be filtered out as the alternative containing a cancellation would normally be better. """ includePlannedCancellations: Boolean """ - When true, departures that have been cancelled through a real-time feed will be + When true, departures that have been canceled through a real-time feed will be included during the routing. This means that an itinerary can include - a cancelled departure while some other alternative that contains no cancellations + a canceled departure while some other alternative that contains no cancellations could be filtered out as the alternative containing a cancellation would normally be better. This option can't be set to true while `includeRealTimeUpdates` is false. """ @@ -4377,15 +4711,61 @@ input TransferPreferencesInput { "How many transfers there can be at maximum in an itinerary." maximumTransfers: Int """ - A global minimum transfer time (in seconds) that specifies the minimum amount of time - that must pass between exiting one transit vehicle and boarding another. This time is - in addition to time it might take to walk between transit stops. Setting this value - as `PT0S`, for example, can lead to passenger missing a connection when the vehicle leaves - ahead of time or the passenger arrives to the stop later than expected. + A global minimum transfer time that specifies the minimum amount of time that must pass + between exiting one transit vehicle and boarding another. This time is in addition to + time it might take to walk between transit stops. Setting this value as `PT0S`, for + example, can lead to passenger missing a connection when the vehicle leaves ahead of time + or the passenger arrives to the stop later than expected. """ slack: Duration } +""" +A collection of selectors for what routes/agencies should be included in or excluded from the search. + +The `include` is always applied to select the initial set, then `exclude` to remove elements. +If only `exclude` is present, the exclude is applied to the existing set of results. + +Therefore, if an entity is both included _and_ excluded the exclusion takes precedence. +""" +input TransitFilterInput { + """ + A list of selectors for what routes/agencies should be excluded during search. + + In order to be excluded, a route/agency has to match with at least one selector. + + An empty list or a list containing `null` is forbidden. + """ + exclude: [TransitFilterSelectInput!] + """ + A list of selectors for what routes/agencies should be allowed during the search. + + If route/agency matches with at least one selector it will be included. + + An empty list or a list containing `null` is forbidden. + """ + include: [TransitFilterSelectInput!] +} + +""" +A list of selectors for including or excluding entities from the routing results. Null +means everything is allowed to be returned and empty lists are not allowed. +""" +input TransitFilterSelectInput @oneOf { + """ + Set of ids for agencies that should be included in/excluded from the search. + + Format: `FeedId:AgencyId` + """ + agencies: [String!] + """ + Set of ids for routes that should be included in/excluded from the search. + + Format: `FeedId:AgencyId` + """ + routes: [String!] +} + "Costs related to using a transit mode." input TransitModePreferenceCostInput { "A cost multiplier of transit leg travel time." @@ -4401,6 +4781,57 @@ input TransitPreferencesInput { can be found under the street mode preferences. """ board: BoardPreferencesInput + """ + A list of filters for which trips should be included or excluded. A trip will be considered in the + result if it is included by at least one filter and isn't excluded by another one at the same time. + + An empty list of filters or no value means that all trips should be included. + + **AND/OR Logic** + + Several filters can be defined and form an OR-condition. + + The following example means "include all trips with `F:route1` _or_ `F:agency1`": + + ``` + filters: [ + { + include: { + routes: ["F:route1"] + } + }, + { + include: { + agencies: ["F:agency1"] + } + } + }] + ``` + + The following example means "include all trips of `F:agency1` _and_ exclude `F:route1`": + + ``` + filters: [ + { + include: { + agencies: ["F:agency1"] + }, + exclude: { + routes: ["F:route1"] + } + } + ] + ``` + + Be aware of the following pitfalls: + - It is easy to construct AND-conditions that can lead to zero results. + - OR-conditions that have an element which consists of only an exclude are likely to be + unwanted and may lead to unexpected results. + + **Note**: This parameter also interacts with the modes set in `modes.transit` by forming + an AND-condition. + """ + filters: [TransitFilterInput!] "Preferences related to cancellations and real-time." timetable: TimetablePreferencesInput "Preferences related to transfers between transit vehicles (typically between stops)." @@ -4497,4 +4928,4 @@ input WheelchairPreferencesInput { that the itineraries are wheelchair accessible as there can be data issues. """ enabled: Boolean -} +} \ No newline at end of file diff --git a/digitransit-component/packages/digitransit-component-autosuggest-panel/src/index.js b/digitransit-component/packages/digitransit-component-autosuggest-panel/src/index.js index b5814ffe3d..751a844339 100644 --- a/digitransit-component/packages/digitransit-component-autosuggest-panel/src/index.js +++ b/digitransit-component/packages/digitransit-component-autosuggest-panel/src/index.js @@ -559,7 +559,7 @@ class DTAutosuggestPanel extends React.Component { } lang={this.props.lang} sources={this.props.sources} - targets={['Stops', 'Stations']} + targets={this.props.targets} filterResults={this.props.filterResults} getAutoSuggestIcons={this.props.getAutoSuggestIcons} isMobile={this.props.isMobile} diff --git a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql index dec86d6317..16882b18bc 100644 --- a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql +++ b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql @@ -12,12 +12,23 @@ This is only worth it when the execution is long running, i.e. more than ~50 mil """ directive @async on FIELD_DEFINITION +"This directive allows results to be deferred during execution" +directive @defer( + "Deferred behaviour is controlled by this argument" + if: Boolean! = true, + "A unique label that represents the fragment being deferred" + label: String + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + "Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" - reason: String = "No longer supported" + reason: String! = "No longer supported" ) on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION +"This directive disables error propagation when a non nullable field returns null for the given operation." +directive @experimental_disableErrorPropagation on QUERY | MUTATION | SUBSCRIPTION + "Directs the executor to include this field or fragment only when the `if` argument is true" directive @include( "Included when true." @@ -39,6 +50,9 @@ directive @specifiedBy( url: String! ) on SCALAR +"Add timing data to prometheus, if Actuator API is enabled" +directive @timingData on FIELD_DEFINITION + "A fare product (a ticket) to be bought by a passenger" interface FareProduct { "Identifier for the fare product." @@ -73,9 +87,18 @@ interface PlaceInterface { "Entity related to an alert" union AlertEntity = Agency | Pattern | Route | RouteType | Stop | StopOnRoute | StopOnTrip | Trip | Unknown +"Scheduled times for a trip on a service date for a stop location." +union CallScheduledTime = ArrivalDepartureTime | TimeWindow + +"Location where a transit vehicle stops at." +union CallStopLocation = Location | LocationGroup | Stop + "Rental place union that represents either a VehicleRentalStation or a RentalVehicle" union RentalPlace = RentalVehicle | VehicleRentalStation +"A feature for a step" +union StepFeature = Entrance + union StopPosition = PositionAtStop | PositionBetweenStops "A public transport agency" @@ -116,7 +139,7 @@ type Alert implements Node { Agency affected by the disruption. Note that this value is present only if the disruption has an effect on all operations of the agency (e.g. in case of a strike). """ - agency: Agency @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected agencies.\nUse entities instead.") + agency: Agency @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected agencies.\nUse entities instead.") @timingData "Alert cause" alertCause: AlertCauseType "Long description of the alert" @@ -125,7 +148,7 @@ type Alert implements Node { language: String ): String! "Long descriptions of the alert in all different available languages" - alertDescriptionTextTranslations: [TranslatedString!]! @deprecated(reason : "Use `alertDescriptionText` instead. `accept-language` header can be used for translations or the `language` parameter on the `alertDescriptionText` field.") + alertDescriptionTextTranslations: [TranslatedString!]! @deprecated(reason : "Use `alertDescriptionText` instead. `accept-language` header can be used for translations or the `language` parameter on the `alertDescriptionText` field.") @timingData "Alert effect" alertEffect: AlertEffectType "hashcode from the original GTFS-RT alert" @@ -136,7 +159,7 @@ type Alert implements Node { language: String ): String "Header of the alert in all different available languages" - alertHeaderTextTranslations: [TranslatedString!]! @deprecated(reason : "Use `alertHeaderText` instead. `accept-language` header can be used for translations or the `language` parameter on the `alertHeaderText` field.") + alertHeaderTextTranslations: [TranslatedString!]! @deprecated(reason : "Use `alertHeaderText` instead. `accept-language` header can be used for translations or the `language` parameter on the `alertHeaderText` field.") @timingData "Alert severity level" alertSeverityLevel: AlertSeverityLevelType "Url with more information" @@ -145,7 +168,7 @@ type Alert implements Node { language: String ): String "Url with more information in all different available languages" - alertUrlTranslations: [TranslatedString!]! @deprecated(reason : "Use `alertUrl` instead. `accept-language` header can be used for translations or the `language` parameter on the `alertUrl` field.") + alertUrlTranslations: [TranslatedString!]! @deprecated(reason : "Use `alertUrl` instead. `accept-language` header can be used for translations or the `language` parameter on the `alertUrl` field.") @timingData "Time when this alert is not in effect anymore. Format: Unix timestamp in seconds" effectiveEndDate: Long "Time when this alert comes into effect. Format: Unix timestamp in seconds" @@ -157,13 +180,21 @@ type Alert implements Node { "Global object ID provided by Relay. This value can be used to refetch this object using **node** query." id: ID! "Patterns affected by the disruption" - patterns: [Pattern] @deprecated(reason : "This will always return an empty list. Use entities instead.") + patterns: [Pattern] @deprecated(reason : "This will always return an empty list. Use entities instead.") @timingData "Route affected by the disruption" - route: Route @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected routes.\nUse entities instead.") + route: Route @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected routes.\nUse entities instead.") @timingData "Stop affected by the disruption" - stop: Stop @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected stops.\nUse entities instead.") + stop: Stop @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected stops.\nUse entities instead.") @timingData "Trip affected by the disruption" - trip: Trip @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected trips.\nUse entities instead.") + trip: Trip @deprecated(reason : "Alert can have multiple affected entities now instead of there being duplicate alerts\nfor different entities. This will return only one of the affected trips.\nUse entities instead.") @timingData +} + +"Arrival and departure time (not relative to midnight)." +type ArrivalDepartureTime { + "Arrival time as an ISO-8601-formatted datetime." + arrival: OffsetDateTime + "Departure time as an ISO-8601-formatted datetime." + departure: OffsetDateTime } "Bike park represents a location where bicycles can be parked." @@ -241,7 +272,7 @@ type BikeRentalStation implements Node & PlaceInterface { """ spacesAvailable: Int "A description of the current state of this bike rental station, e.g. \"Station on\"" - state: String @deprecated(reason : "Use operative instead") + state: String @deprecated(reason : "Use operative instead") @timingData "ID of the bike rental station" stationId: String } @@ -279,12 +310,16 @@ type BookingInfo { earliestBookingTime: BookingTime "When is the latest time the service can be booked" latestBookingTime: BookingTime + "Maximum duration before travel to make the request." + maximumBookingNotice: Duration "Maximum number of seconds before travel to make the request" - maximumBookingNoticeSeconds: Long + maximumBookingNoticeSeconds: Long @deprecated(reason : "Use `maximumBookingNotice`") @timingData "A general message for those booking the service" message: String + "Minimum duration before travel to make the request" + minimumBookingNotice: Duration "Minimum number of seconds before travel to make the request" - minimumBookingNoticeSeconds: Long + minimumBookingNoticeSeconds: Long @deprecated(reason : "Use `minimumBookingNotice`") @timingData "A message specific to the pick up" pickupMessage: String } @@ -297,6 +332,20 @@ type BookingTime { time: String } +"Real-time estimates for arrival and departure times for a stop location." +type CallRealTime { + "Real-time estimates for the arrival." + arrival: EstimatedTime + "Real-time estimates for the departure." + departure: EstimatedTime +} + +"What is scheduled for a trip on a service date for a stop location." +type CallSchedule { + "Scheduled time for a trip on a service date for a stop location." + time: CallScheduledTime +} + "Car park represents a location where cars can be parked." type CarPark implements Node & PlaceInterface { "ID of the car park" @@ -330,17 +379,17 @@ type CarPark implements Node & PlaceInterface { "Cluster is a list of stops grouped by name and proximity" type Cluster implements Node { "ID of the cluster" - gtfsId: String! + gtfsId: String! @deprecated(reason : "Not implemented") "Global object ID provided by Relay. This value can be used to refetch this object using **node** query." - id: ID! + id: ID! @deprecated(reason : "Not implemented") "Latitude of the center of this cluster (i.e. average latitude of stops in this cluster)" - lat: Float! + lat: Float! @deprecated(reason : "Not implemented") "Longitude of the center of this cluster (i.e. average longitude of stops in this cluster)" - lon: Float! + lon: Float! @deprecated(reason : "Not implemented") "Name of the cluster" - name: String! + name: String! @deprecated(reason : "Not implemented") "List of stops in the cluster" - stops: [Stop!] + stops: [Stop!] @deprecated(reason : "Not implemented") } "Contact information for booking an on-demand or flexible service." @@ -451,11 +500,60 @@ type DepartureRow implements Node & PlaceInterface { ): [Stoptime] } +""" +A (possibly discounted) fare product that requires another fare product to be purchased previously +in order to be valid. + +For example, when taking the train into a city, you might get a discounted "transfer fare" when +switching to the bus for the second leg. +""" +type DependentFareProduct implements FareProduct { + "The fare product is _not_ valid without purchasing at least _one_ of" + dependencies(filter: DependentFareProductFilter = ALL): [FareProduct!]! + id: String! + """ + The 'medium' that this product applies to, for example "Oyster Card" or "Berlin Ticket App". + + This communicates to riders that a specific way of buying or keeping this product is required. + """ + medium: FareMedium + "Human readable name of the product, for example example \"Day pass\" or \"Single ticket\"." + name: String! + "The price of the product" + price: Money! + "The category of riders this product applies to, for example students or pensioners." + riderCategory: RiderCategory +} + type Emissions { "CO₂ emissions in grams." co2: Grams } +"Station entrance or exit, originating from OSM or GTFS data." +type Entrance { + "ID of the entrance in the format of `FeedId:EntranceId`. If the `FeedId` is `osm`, the entrance originates from OSM data." + entranceId: String! + "Name of the entrance or exit." + name: String + "Short text or a number that identifies the entrance or exit for passengers. For example, `A` or `B`." + publicCode: String + "Whether the entrance or exit is accessible by wheelchair" + wheelchairAccessible: WheelchairBoarding +} + +"Real-time estimates for an arrival or departure at a certain place." +type EstimatedTime { + """ + The delay or "earliness" of the vehicle at a certain place. This estimate can change quite often. + + If the vehicle is early then this is a negative duration. + """ + delay: Duration! + "The estimate for a call event (such as arrival or departure) at a certain place. This estimate can change quite often." + time: OffsetDateTime! +} + "A 'medium' that a fare product applies to, for example cash, 'Oyster Card' or 'DB Navigator App'." type FareMedium { "ID of the medium" @@ -581,12 +679,12 @@ type Itinerary { "Time when the user leaves arrives at the destination." end: OffsetDateTime "Time when the user arrives to the destination. Format: Unix timestamp in milliseconds." - endTime: Long @deprecated(reason : "Use `end` instead which includes timezone information.") + endTime: Long @deprecated(reason : "Use `end` instead which includes timezone information.") @timingData """ Information about the fares for this itinerary. This is primarily a GTFS Fares V1 interface and always returns an empty list. Use the leg's `fareProducts` instead. """ - fares: [fare] @deprecated(reason : "Use the leg's `fareProducts`.") + fares: [fare] @deprecated(reason : "Use the leg's `fareProducts`.") @timingData "Generalized cost of the itinerary. Used for debugging search results." generalizedCost: Int """ @@ -607,7 +705,7 @@ type Itinerary { "Time when the user leaves from the origin." start: OffsetDateTime "Time when the user leaves from the origin. Format: Unix timestamp in milliseconds." - startTime: Long @deprecated(reason : "Use `start` instead which includes timezone information.") + startTime: Long @deprecated(reason : "Use `start` instead which includes timezone information.") @timingData """ A list of system notices. Contains debug information for itineraries. One use-case is to run a routing search with 'debugItineraryFilter: true'. @@ -642,13 +740,13 @@ type Leg { stop in this leg, i.e. scheduled time of arrival at alighting stop = `endTime - arrivalDelay` """ - arrivalDelay: Int @deprecated(reason : "Use `start.estimated.delay` instead.") + arrivalDelay: Int @deprecated(reason : "Use `start.estimated.delay` instead.") @timingData """ For transit leg, the offset from the scheduled departure time of the boarding stop in this leg, i.e. scheduled time of departure at boarding stop = `startTime - departureDelay` """ - departureDelay: Int @deprecated(reason : "Use `end.estimated.delay` instead.") + departureDelay: Int @deprecated(reason : "Use `end.estimated.delay` instead.") @timingData "The distance traveled while traversing the leg in meters." distance: Float """ @@ -663,7 +761,7 @@ type Leg { "The time when the leg ends including real-time information, if available." end: LegTime! "The date and time when this leg ends. Format: Unix timestamp in milliseconds." - endTime: Long @deprecated(reason : "Use `end.estimated.time` instead which contains timezone information.") + endTime: Long @deprecated(reason : "Use `end.estimated.time` instead which contains timezone information.") @timingData """ Fare products are purchasable tickets which may have an optional fare container or rider category that limits who can buy them or how. @@ -686,6 +784,9 @@ type Leg { Re-fetching fails when the underlying transit data no longer exists. **Note:** when both id and fare products are queried with [Relay](https://relay.dev/), id should be queried using a suitable GraphQL alias such as `legId: id`. Relay does not accept different fare product ids in otherwise identical legs. + + The identifier is valid for a maximum of 2 years, but sometimes it will fail after a few hours. + We do not recommend storing IDs for a long time. """ id: String """ @@ -695,18 +796,20 @@ type Leg { """ interlineWithPreviousLeg: Boolean "Whether the destination of this leg (field `to`) is one of the intermediate places specified in the query." - intermediatePlace: Boolean + intermediatePlace: Boolean @deprecated(reason : "Not implemented. Use `viaLocationType` from from/to fields instead.") """ For transit legs, intermediate stops between the Place where the leg originates and the Place where the leg ends. For non-transit legs, null. - Returns Place type, which has fields for e.g. departure and arrival times """ - intermediatePlaces: [Place] + intermediatePlaces: [Place] @deprecated(reason : "Use `leg.stopCalls` instead") """ For transit legs, intermediate stops between the Place where the leg originates and the Place where the leg ends. For non-transit legs, null. + + The `include` parameter allows filtering of the returned places by stop type. If not provided, the + field returns all types. An empty list is not permitted. """ - intermediateStops: [Stop] + intermediateStops(include: [StopType!]): [Stop] @deprecated(reason : "Use `leg.stopCalls` instead") "The leg's geometry." legGeometry: Geometry "The mode (e.g. `WALK`) used when traversing this leg." @@ -758,7 +861,7 @@ type Leg { realTime: Boolean "State of real-time data" realtimeState: RealtimeState - "Whether this leg is traversed with a rented bike." + "Whether this leg is traversed with a rented vehicle." rentedBike: Boolean "Estimate of a hailed ride like Uber." rideHailingEstimate: RideHailingEstimate @@ -769,9 +872,15 @@ type Leg { "The time when the leg starts including real-time information, if available." start: LegTime! "The date and time when this leg begins. Format: Unix timestamp in milliseconds." - startTime: Long @deprecated(reason : "Use `start.estimated.time` instead which contains timezone information.") + startTime: Long @deprecated(reason : "Use `start.estimated.time` instead which contains timezone information.") @timingData "The turn-by-turn navigation instructions." steps: [step] + """ + All the stop calls (stop times) of this _leg_ (but not trip) including the boarding and alighting one. + + Non-transit legs return an empty list. + """ + stopCalls: [StopCall!]! "The Place where the leg ends." to: Place! "Whether this leg is a transit leg or not." @@ -809,6 +918,34 @@ type LocalTimeSpanDate { timeSpans: [LocalTimeSpan] } +""" +A stop that isn't a fixed point but zone where passengers can board or alight anywhere. + +This is mostly used by demand-responsive services. +""" +type Location { + "The geometry representing the geographic extend of the location." + geometry: StopGeometries! + "ÌD of the location in format `FeedId:LocationId`" + gtfsId: String! + "Optional name of the location." + name: String +} + +""" +A group of fixed stops that are visited in an arbitrary order. + +This is mostly used by demand-responsive services. +""" +type LocationGroup { + "ÌD of the location group in format `FeedId:LocationGroupId`" + gtfsId: String! + "The stops that are part of the group (cannot be stations)." + members: [Stop!]! + "Optional name of the group." + name: String +} + "An amount of money." type Money { """ @@ -919,20 +1056,20 @@ type Place { """ arrival: LegTime "The time the rider will arrive at the place. Format: Unix timestamp in milliseconds." - arrivalTime: Long! @deprecated(reason : "Use `arrival` which includes timezone information.") + arrivalTime: Long! @deprecated(reason : "Use `arrival` which includes timezone information.") @timingData "The bike parking related to the place" - bikePark: BikePark @deprecated(reason : "bikePark is deprecated. Use vehicleParking instead.") + bikePark: BikePark @deprecated(reason : "bikePark is deprecated. Use vehicleParking instead.") @timingData "The bike rental station related to the place" - bikeRentalStation: BikeRentalStation @deprecated(reason : "Use vehicleRentalStation and rentalVehicle instead") + bikeRentalStation: BikeRentalStation @deprecated(reason : "Use vehicleRentalStation and rentalVehicle instead") @timingData "The car parking related to the place" - carPark: CarPark @deprecated(reason : "carPark is deprecated. Use vehicleParking instead.") + carPark: CarPark @deprecated(reason : "carPark is deprecated. Use vehicleParking instead.") @timingData """ The time the rider will depart the place. This also includes real-time information if available. """ departure: LegTime "The time the rider will depart the place. Format: Unix timestamp in milliseconds." - departureTime: Long! @deprecated(reason : "Use `departure` which includes timezone information.") + departureTime: Long! @deprecated(reason : "Use `departure` which includes timezone information.") @timingData "Latitude of the place (WGS 84)" lat: Float! "Longitude of the place (WGS 84)" @@ -966,7 +1103,12 @@ type Place { Type of vertex. (Normal, Bike sharing station, Bike P+R, Transit stop) Mostly used for better localization of bike sharing and P+R station names """ - vertexType: VertexType + vertexType: VertexType @deprecated(reason : "Unmaintained. Use `stop`, `rentalVehicle`, `vehicleParking` or `vehicleRentalStation` to tell which type it is.") @timingData + """ + This defines if the place is a requested via location, and what kind it is. If the value is + `null`, this place is not a via location. + """ + viaLocationType: ViaLocationType } type Plan { @@ -982,13 +1124,8 @@ type Plan { messageEnums: [String]! "A list of possible error messages in cleartext" messageStrings: [String]! - """ - This is the suggested search time for the "next page" or time window. Insert it together - with the searchWindowUsed in the request to get a new set of trips following in the - search-window AFTER the current search. No duplicate trips should be returned, unless a trip - is delayed and new real-time data is available. - """ - nextDateTime: Long @deprecated(reason : "Use nextPageCursor instead") + "This will not be available after Match 2026." + nextDateTime: Long @deprecated(reason : "Use nextPageCursor instead") @timingData """ Use the cursor to go to the next "page" of itineraries. Copy the cursor from the last response to the pageCursor query parameter and keep the original request as is. This will enable you to @@ -997,13 +1134,8 @@ type Plan { This is only usable when public transportation mode(s) are included in the query. """ nextPageCursor: String - """ - This is the suggested search time for the "previous page" or time window. Insert it together - with the searchWindowUsed in the request to get a new set of trips preceding in the - search-window BEFORE the current search. No duplicate trips should be returned, unless a trip - is delayed and new real-time data is available. - """ - prevDateTime: Long @deprecated(reason : "Use previousPageCursor instead") + "This will not be available after Match 2026." + prevDateTime: Long @deprecated(reason : "Use previousPageCursor instead") @timingData """ Use the cursor to go to the previous "page" of itineraries. Copy the cursor from the last response to the pageCursor query parameter and keep the original request otherwise as is. @@ -1021,7 +1153,7 @@ type Plan { The unit is seconds. """ - searchWindowUsed: Long + searchWindowUsed: Long @deprecated(reason : "This is not needed for debugging, and is misleading if the window is cropped.") "The destination" to: Place! } @@ -1116,11 +1248,11 @@ type QueryType { stop: [String!] ): [Alert] "Get a single bike park based on its ID, i.e. value of field `bikeParkId`" - bikePark(id: String!): BikePark @deprecated(reason : "bikePark is deprecated. Use vehicleParking instead.") + bikePark(id: String!): BikePark @deprecated(reason : "bikePark is deprecated. Use vehicleParking instead.") @timingData "Get all bike parks" - bikeParks: [BikePark] @deprecated(reason : "bikeParks is deprecated. Use vehicleParkings instead.") + bikeParks: [BikePark] @deprecated(reason : "bikeParks is deprecated. Use vehicleParkings instead.") @timingData "Get a single bike rental station based on its ID, i.e. value of field `stationId`" - bikeRentalStation(id: String!): BikeRentalStation @deprecated(reason : "Use rentalVehicle or vehicleRentalStation instead") + bikeRentalStation(id: String!): BikeRentalStation @deprecated(reason : "Use rentalVehicle or vehicleRentalStation instead") @timingData "Get all bike rental stations" bikeRentalStations( """ @@ -1129,32 +1261,61 @@ type QueryType { the returned list will contain `null` values. """ ids: [String] - ): [BikeRentalStation] @deprecated(reason : "Use rentalVehicles or vehicleRentalStations instead") - "Get cancelled TripTimes." + ): [BikeRentalStation] @deprecated(reason : "Use rentalVehicles or vehicleRentalStations instead") @timingData + """ + Get pages of canceled trips. Planned cancellations are not currently supported. Limiting the number of + returned trips with either `first` or `last` is highly recommended since the number of returned trips + can be really high when there is a strike affecting the transit services, for example. Follows the + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + """ + canceledTrips( + """ + This parameter is part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) + and should be used together with the `first` parameter. + """ + after: String, + """ + This parameter is part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) + and should be used together with the `last` parameter. + """ + before: String, + """ + Limits how many trips are returned. This parameter is part of the + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) and can be used together with + the `after` parameter. + """ + first: Int, + """ + This parameter is part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) + and should be used together with the `before` parameter. + """ + last: Int + ): TripOnServiceDateConnection + "Get canceled TripTimes." cancelledTripTimes( "Feed feedIds (e.g. [\"HSL\"])." feeds: [String], """ - Only cancelled trip times that have last stop arrival time at maxArrivalTime + Only canceled trip times that have last stop arrival time at maxArrivalTime or before are returned. Format: seconds since midnight of maxDate. """ maxArrivalTime: Int, - "Only cancelled trip times scheduled to run on maxDate or before are returned. Format: \"2019-12-23\" or \"20191223\"." + "Only canceled trip times scheduled to run on maxDate or before are returned. Format: \"2019-12-23\" or \"20191223\"." maxDate: String, """ - Only cancelled trip times that have first stop departure time at + Only canceled trip times that have first stop departure time at maxDepartureTime or before are returned. Format: seconds since midnight of maxDate. """ maxDepartureTime: Int, """ - Only cancelled trip times that have last stop arrival time at minArrivalTime + Only canceled trip times that have last stop arrival time at minArrivalTime or after are returned. Format: seconds since midnight of minDate. """ minArrivalTime: Int, - "Only cancelled trip times scheduled to run on minDate or after are returned. Format: \"2019-12-23\" or \"20191223\"." + "Only canceled trip times scheduled to run on minDate or after are returned. Format: \"2019-12-23\" or \"20191223\"." minDate: String, """ - Only cancelled trip times that have first stop departure time at + Only canceled trip times that have first stop departure time at minDepartureTime or after are returned. Format: seconds since midnight of minDate. """ minDepartureTime: Int, @@ -1164,9 +1325,9 @@ type QueryType { routes: [String], "Trip gtfsIds (e.g. [\"HSL:1098_20190405_Ma_2_1455\"])." trips: [String] - ): [Stoptime] + ): [Stoptime] @deprecated(reason : "`cancelledTripTimes` is not implemented. Use `canceledTrips` instead.") @timingData "Get a single car park based on its ID, i.e. value of field `carParkId`" - carPark(id: String!): CarPark @deprecated(reason : "carPark is deprecated. Use vehicleParking instead.") + carPark(id: String!): CarPark @deprecated(reason : "carPark is deprecated. Use vehicleParking instead.") @timingData "Get all car parks" carParks( """ @@ -1174,11 +1335,11 @@ type QueryType { **Note:** if an id is invalid (or the car park service is unavailable) the returned list will contain `null` values. """ ids: [String] - ): [CarPark] @deprecated(reason : "carParks is deprecated. Use vehicleParkings instead.") + ): [CarPark] @deprecated(reason : "carParks is deprecated. Use vehicleParkings instead.") @timingData "Get a single cluster based on its ID, i.e. value of field `gtfsId`" - cluster(id: String!): Cluster + cluster(id: String!): Cluster @deprecated(reason : "Not implemented") "Get all clusters" - clusters: [Cluster] + clusters: [Cluster] @deprecated(reason : "Not implemented") "Get a single departure row based on its ID (ID format is `FeedId:StopId:PatternId`)" departureRow(id: String!): DepartureRow "Get all available feeds" @@ -1244,7 +1405,7 @@ type QueryType { maxDistance: Int = 2000, "Maximum number of results. Search is stopped when this limit is reached. Default is 20." maxResults: Int = 20 - ): placeAtDistanceConnection @async + ): placeAtDistanceConnection @async @timingData "Fetches an object given its ID" node( "The ID of an object" @@ -1357,7 +1518,7 @@ type QueryType { "When true, real-time updates are ignored during this search. Default value: false" ignoreRealtimeUpdates: Boolean, "An ordered list of intermediate locations to be visited." - intermediatePlaces: [InputCoordinates] @deprecated(reason : "Not implemented in OTP2."), + intermediatePlaces: [InputCoordinates] @deprecated(reason : "Not implemented"), """ How easily bad itineraries are filtered from results. Value 0 (default) disables filtering. Itineraries are filtered if they are worse than another @@ -1424,7 +1585,7 @@ type QueryType { "Preferences for vehicle parking" parking: VehicleParkingInput, "List of routes and agencies which are given higher preference when planning the itinerary" - preferred: InputPreferred, + preferred: InputPreferred @deprecated(reason : "Not implemented"), """ **Consider this argument experimental** – setting this argument to true causes timeouts and unoptimal routes in many cases. @@ -1476,7 +1637,7 @@ type QueryType { travel time of the trip (and therefore arguments `time` and `from` must be used correctly to get meaningful itineraries). """ - startTransitTripId: String @deprecated(reason : "Not implemented in OTP2"), + startTransitTripId: String @deprecated(reason : "Not implemented"), "Time of departure or arrival in format hh:mm:ss. Default value: current time" time: String, """ @@ -1560,7 +1721,7 @@ type QueryType { walkSpeed: Float, "Whether the itinerary must be wheelchair accessible. Default value: false" wheelchair: Boolean - ): Plan @async @deprecated(reason : "Use `planConnection` instead.") + ): Plan @async @deprecated(reason : "Use `planConnection` instead.") @timingData """ Plan (itinerary) search that follows [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). @@ -1632,8 +1793,8 @@ type QueryType { preferences: PlanPreferencesInput, """ Duration of the search window. This either starts at the defined earliest departure - time or ends at the latest arrival time. If this is not provided, a reasonable - search window is automatically generated. When searching for earlier or later itineraries + time or ends at the latest arrival time. If this is not provided or the value is set as null, + a reasonable search window is automatically generated. When searching for earlier or later itineraries with paging, this search window is no longer used and the new window will be based on how many suggestions were returned in the previous search. The new search window can be shorter or longer than the original search window. Note, itineraries are returned faster @@ -1647,7 +1808,7 @@ type QueryType { searchWindow: Duration, "The list of points the itinerary is required to pass through." via: [PlanViaLocationInput!] - ): PlanConnection @async + ): PlanConnection @async @timingData "Get a single rental vehicle based on its ID, i.e. value of field `vehicleId`" rentalVehicle(id: String!): RentalVehicle "Get all rental vehicles" @@ -1683,7 +1844,7 @@ type QueryType { serviceDates: LocalDateRangeInput, "Only include routes, which use one of these modes" transportModes: [Mode] - ): [Route] + ): [Route] @timingData "Get the time range for which the API has data available" serviceTimeRange: serviceTimeRange "Get a single station based on its ID, i.e. value of field `gtfsId` (format is `FeedId:StopId`)" @@ -1801,6 +1962,10 @@ type RealTimeEstimate { type RentalVehicle implements Node & PlaceInterface { "If true, vehicle is currently available for renting." allowPickupNow: Boolean + "The vehicle should be returned before this deadline." + availableUntil: OffsetDateTime + "Fuel or battery status of the rental vehicle" + fuel: RentalVehicleFuel "Global object ID provided by Relay. This value can be used to refetch this object using **node** query." id: ID! "Latitude of the vehicle (WGS 84)" @@ -1810,7 +1975,7 @@ type RentalVehicle implements Node & PlaceInterface { "Name of the vehicle" name: String! "ID of the rental network." - network: String @deprecated(reason : "Use `networkId` from `rentalNetwork` instead.") + network: String @deprecated(reason : "Use `networkId` from `rentalNetwork` instead.") @timingData "If true, vehicle is not disabled." operative: Boolean "The vehicle rental network information. This is referred as system in the GBFS terminology." @@ -1830,6 +1995,14 @@ type RentalVehicleEntityCounts { total: Int! } +"Rental vehicle fuel represent the current status of the battery or fuel of a rental vehicle" +type RentalVehicleFuel { + "Fuel or battery power remaining in the vehicle. Expressed from 0 to 1." + percent: Ratio + "Range in meters that the vehicle can travel with the current charge or fuel." + range: Int +} + type RentalVehicleType { "The vehicle's general form factor" formFactor: FormFactor @@ -1867,6 +2040,13 @@ type RideHailingProvider { type RiderCategory { "ID of the category" id: String! + """ + If this category is considered the "default" one. In most places this means "Adult" or + "Regular". + Frontends can use this property to display this category more prominently or pre-select this + in a UI. + """ + isDefault: Boolean! "Human readable name of the category." name: String } @@ -2010,7 +2190,7 @@ type Stop implements Node & PlaceInterface { types: [StopAlertType] ): [Alert] "The cluster which this stop is part of" - cluster: Cluster + cluster: Cluster @deprecated(reason : "Not implemented") "Stop code which is visible at the stop" code: String "Description of the stop, usually a street name" @@ -2152,13 +2332,28 @@ type Stop implements Node & PlaceInterface { https://developers.google.com/transit/gtfs/reference/#routestxt and https://developers.google.com/transit/gtfs/reference/extended-route-types """ - vehicleType: Int + vehicleType: Int @deprecated(reason : "Not implemented. Use `vehicleMode`.") @timingData "Whether wheelchair boarding is possible for at least some of vehicles on this stop" wheelchairBoarding: WheelchairBoarding "ID of the zone where this stop is located" zoneId: String } +""" +Represents the time or time window when a specific trip on a specific date arrives to and/or departs +from a specific stop location. + +This may contain real-time information, if available. +""" +type StopCall { + "Real-time estimates for arrival and departure times for this stop location." + realTime: CallRealTime + "Scheduled arrival and departure times for this stop location." + schedule: CallSchedule + "The stop where this arrival/departure happens." + stopLocation: CallStopLocation! +} + type StopGeometries { "Representation of the stop geometries as GeoJSON (https://geojson.org/)" geoJson: GeoJson @@ -2314,6 +2509,12 @@ type TicketType implements Node { zones: [String!] } +"A time window when a vehicle visits a stop, area or group of stops." +type TimeWindow { + end: OffsetDateTime! + start: OffsetDateTime! +} + "Text with language" type TranslatedString { "Two-letter language code (ISO 639-1)" @@ -2336,7 +2537,11 @@ type Trip implements Node { """ types: [TripAlertType] ): [Alert] - "Arrival time to the final stop" + """ + Arrival time to the final stop. If the trip does not run on the given date, + it will return scheduled times from another date. This field is slightly + confusing and will be deprecated when a better replacement is implemented. + """ arrivalStoptime( """ Date for which the arrival time is returned. Format: YYYYMMDD. If this @@ -2347,7 +2552,11 @@ type Trip implements Node { "Whether bikes are allowed on board the vehicle running this trip" bikesAllowed: BikesAllowed blockId: String - "Departure time from the first stop" + """ + Departure time from the first stop. If the trip does not run on the given date, + it will return scheduled times from another date. This field is slightly + confusing and will be deprecated when a better replacement is implemented. + """ departureStoptime( """ Date for which the departure time is returned. Format: YYYYMMDD. If this @@ -2386,6 +2595,12 @@ type Trip implements Node { stops: [Stop!]! "List of times when this trip arrives to or departs from a stop" stoptimes: [Stoptime] + """ + List of times when this trip arrives to or departs from each stop on a given date, or + today if the date is not given. If the trip does not run on the given date, it will + return scheduled times from another date. This field is slightly confusing and + will be deprecated when a better replacement is implemented. + """ stoptimesForDate( "Date for which stoptimes are returned. Format: YYYYMMDD" serviceDate: String @@ -2414,6 +2629,60 @@ type TripOccupancy { occupancyStatus: OccupancyStatus } +"A trip on a specific service date." +type TripOnServiceDate { + "Information related to trip's scheduled arrival to the final stop location. Can contain real-time information." + end: StopCall! + """ + The service date when the trip occurs. + + **Note**: A service date is a technical term useful for transit planning purposes and might not + correspond to a how a passenger thinks of a calendar date. For example, a night bus running + on Sunday morning at 1am to 3am, might have the previous Saturday's service date. + """ + serviceDate: LocalDate! + "Information related to trip's scheduled departure from the first stop location. Can contain real-time information." + start: StopCall! + "List of times when this trip arrives to or departs from a stop location and information related to the visit to the stop location." + stopCalls: [StopCall!]! + "This trip on service date is an instance of this trip." + trip: Trip +} + +""" +A connection to a list of trips on service dates that follows +[GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). +""" +type TripOnServiceDateConnection { + """ + Edges which contain the trips. Part of the + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + """ + edges: [TripOnServiceDateEdge] + """ + Contains cursors to fetch more pages of trips. + Part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + """ + pageInfo: PageInfo! +} + +""" +An edge for TripOnServiceDate connection. Part of the +[GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). +""" +type TripOnServiceDateEdge { + """ + The cursor of the edge. Part of the + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + """ + cursor: String! + """ + Trip on a service date as a node. Part of the + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + """ + node: TripOnServiceDate +} + "This is used for alert entities that we don't explicitly handle or they are missing." type Unknown { "Entity's description" @@ -2497,8 +2766,10 @@ type VehiclePosition { heading: Float "Human-readable label of the vehicle, eg. a publicly visible number or a license plate" label: String + "When the position of the vehicle was recorded." + lastUpdate: OffsetDateTime "When the position of the vehicle was recorded in seconds since the UNIX epoch." - lastUpdated: Long + lastUpdated: Long @deprecated(reason : "Use `lastUpdate` instead.") @timingData "Latitude of the vehicle" lat: Float "Longitude of the vehicle" @@ -2558,7 +2829,7 @@ type VehicleRentalStation implements Node & PlaceInterface { "Name of the vehicle rental station" name: String! "ID of the rental network." - network: String @deprecated(reason : "Use `networkId` from `rentalNetwork` instead.") + network: String @deprecated(reason : "Use `networkId` from `rentalNetwork` instead.") @timingData "If true, station is on and in service." operative: Boolean """ @@ -2578,14 +2849,14 @@ type VehicleRentalStation implements Node & PlaceInterface { the rental station, even if the vehicle racks don't have any spaces available. See field `allowDropoffNow` to know if is currently possible to return a vehicle. """ - spacesAvailable: Int @deprecated(reason : "Use `availableSpaces` instead, which also contains the space vehicle types") + spacesAvailable: Int @deprecated(reason : "Use `availableSpaces` instead, which also contains the space vehicle types") @timingData "ID of the vehicle in the format of network:id" stationId: String """ Number of vehicles currently available on the rental station. See field `allowPickupNow` to know if is currently possible to pick up a vehicle. """ - vehiclesAvailable: Int @deprecated(reason : "Use `availableVehicles` instead, which also contains vehicle types") + vehiclesAvailable: Int @deprecated(reason : "Use `availableVehicles` instead, which also contains vehicle types") @timingData } type VehicleRentalUris { @@ -2631,12 +2902,12 @@ type fare { Fare price in cents. **Note:** this value is dependent on the currency used, as one cent is not necessarily ¹/₁₀₀ of the basic monerary unit. """ - cents: Int @deprecated(reason : "No longer supported") + cents: Int @deprecated(reason : "No longer supported") @timingData "Components which this fare is composed of" - components: [fareComponent] @deprecated(reason : "No longer supported") + components: [fareComponent] @deprecated(reason : "No longer supported") @timingData "ISO 4217 currency code" - currency: String @deprecated(reason : "No longer supported") - type: String @deprecated(reason : "No longer supported") + currency: String @deprecated(reason : "No longer supported") @timingData + type: String @deprecated(reason : "No longer supported") @timingData } """ @@ -2648,13 +2919,13 @@ type fareComponent { Fare price in cents. **Note:** this value is dependent on the currency used, as one cent is not necessarily ¹/₁₀₀ of the basic monerary unit. """ - cents: Int @deprecated(reason : "No longer supported") + cents: Int @deprecated(reason : "No longer supported") @timingData "ISO 4217 currency code" - currency: String @deprecated(reason : "No longer supported") + currency: String @deprecated(reason : "No longer supported") @timingData "ID of the ticket type. Corresponds to `fareId` in **TicketType**." - fareId: String @deprecated(reason : "No longer supported") + fareId: String @deprecated(reason : "No longer supported") @timingData "List of routes which use this fare component" - routes: [Route] @deprecated(reason : "No longer supported") + routes: [Route] @deprecated(reason : "No longer supported") @timingData } type placeAtDistance implements Node { @@ -2686,13 +2957,6 @@ type serviceTimeRange { start: Long } -type Entrance { - publicCode: String - wheelchairAccessible: Boolean -} - -union StepFeature = Entrance - type step { "The cardinal (compass) direction (e.g. north, northeast) taken when engaging this step." absoluteDirection: AbsoluteDirection @@ -2714,6 +2978,8 @@ type step { elevationProfile: [elevationProfileComponent] "When exiting a highway or traffic circle, the exit name/number." exit: String + "Information about an feature associated with a step e.g. an station entrance or exit" + feature: StepFeature "The latitude of the start of the step." lat: Float "The longitude of the start of the step." @@ -2726,7 +2992,6 @@ type step { streetName: String "Is this step walking with a bike?" walkingBike: Boolean - feature: StepFeature } type stopAtDistance implements Node { @@ -2839,7 +3104,7 @@ enum AlertSeverityLevelType { INFO """ Severe alerts are used when a significant part of public transport services is - affected, for example: All train services are cancelled due to technical problems. + affected, for example: All train services are canceled due to technical problems. """ SEVERE "Severity of alert is unknown" @@ -2890,6 +3155,22 @@ enum CyclingOptimizationType { SHORTEST_DURATION } +""" +Dependent fare products can lead to many combinations of fares, however it is often not useful +information to the passenger. + +This enum allows filtering of the dependencies. + +Since it is recognised that this is not covered well in the specification, it is discussed here: +https://github.com/google/transit/pull/423 +""" +enum DependentFareProductFilter { + "Show all dependencies" + ALL + "Show only dependencies where the rider category and medium is the same es the main fare product." + MATCH_CATEGORY_AND_MEDIUM +} + "Entities, which are relevant for a feed and can contain alerts" enum FeedAlertType { "Alerts affecting the feed's agencies" @@ -2906,9 +3187,9 @@ enum FilterPlaceType { "Old value for VEHICLE_RENT" BICYCLE_RENT @deprecated(reason : "Use VEHICLE_RENT instead as it's clearer that it also returns rental scooters, cars...") "Parking lots (not rental stations) that contain spaces for bicycles" - BIKE_PARK + BIKE_PARK @deprecated(reason : "Not supported.") "Parking lots that contain spaces for cars" - CAR_PARK + CAR_PARK @deprecated(reason : "Not supported.") "Departure rows" DEPARTURE_ROW """ @@ -2948,6 +3229,7 @@ enum InputField { DATE_TIME FROM TO + VIA } """ @@ -3389,15 +3671,40 @@ enum RealtimeState { UPDATED } -"Actions to take relative to the current position when engaging a walking/driving step." +""" +A direction that is not absolute but rather fuzzy and context-dependent. +It provides the passenger with information what they should do in this step depending on where they +were in the previous one. +""" enum RelativeDirection { CIRCLE_CLOCKWISE CIRCLE_COUNTERCLOCKWISE + """ + Moving straight ahead in one of these cases + + - Passing through a crossing or intersection. + - Passing through a station entrance or exit when it is not know whether the passenger is + entering or exiting. If it _is_ known then `ENTER_STATION`/`EXIT_STATION` is used. + More information about the entrance is in the `step.feature` field. + """ CONTINUE DEPART ELEVATOR + """ + Entering a public transport station. If it's not known if the passenger is entering or exiting + then `CONTINUE` is used. + + More information about the entrance is in the `step.feature` field. + """ ENTER_STATION + """ + Exiting a public transport station. If it's not known if the passenger is entering or exiting + then `CONTINUE` is used. + + More information about the entrance is in the `step.feature` field. + """ EXIT_STATION + "Follow the signs indicating a specific location like \"platform 1\" or \"exit B\"." FOLLOW_SIGNS HARD_LEFT HARD_RIGHT @@ -3528,6 +3835,15 @@ enum StopAlertType { TRIPS } +enum StopType { + "An area or zone represented by a polygon. Introduced by the GTFS-Flex spec process." + LOCATION + "A group of stops. Introduced by the GTFS-Flex spec process." + LOCATION_GROUP + "A fixed stop represented by a coordinate." + STOP +} + """ Transit modes include modes that are used within organized transportation networks run by public transportation authorities, taxi companies etc. @@ -3611,6 +3927,21 @@ enum VertexType { TRANSIT } +"Categorization for via locations." +enum ViaLocationType { + """ + The via stop location must be visited as part of a transit trip as at the boarding stop, the + intermediate stop, or the alighting stop. + """ + PASS_THROUGH + """ + The location is visited physically by boarding or alighting a transit trip at a given stop, or by + traveling via requested coordinate location as part of a access, transfer, egress or direct + segment. Intermediate stops visited on-board do not count. + """ + VISIT +} + enum WheelchairBoarding { "Wheelchair boarding is not possible at this stop." NOT_POSSIBLE @@ -3732,7 +4063,10 @@ input BicyclePreferencesInput { "Preferences related to bicycle rental (station based or floating bicycle rental)." input BicycleRentalPreferencesInput { - "Rental networks which can be potentially used as part of an itinerary." + """ + Rental networks which can be potentially used as part of an itinerary. If this field has no default value, + it means that all networks are allowed unless some are banned with `bannedNetworks`. + """ allowedNetworks: [String!] "Rental networks which cannot be used as part of an itinerary." bannedNetworks: [String!] @@ -3813,6 +4147,8 @@ input CarParkingPreferencesInput { "Preferences related to traveling on a car (excluding car travel on transit services such as taxi)." input CarPreferencesInput { + "Cost of boarding a vehicle with a car." + boardCost: Cost "Car parking related preferences." parking: CarParkingPreferencesInput "A multiplier for how bad travelling on car is compared to being in transit for equal lengths of time." @@ -3823,7 +4159,10 @@ input CarPreferencesInput { "Preferences related to car rental (station based or floating car rental)." input CarRentalPreferencesInput { - "Rental networks which can be potentially used as part of an itinerary." + """ + Rental networks which can be potentially used as part of an itinerary. If this field has no default value, + it means that all networks are allowed unless some are banned with `bannedNetworks`. + """ allowedNetworks: [String!] "Rental networks which cannot be used as part of an itinerary." bannedNetworks: [String!] @@ -3877,14 +4216,14 @@ input InputBanned { banned for boarding and disembarking vehicles — it is possible to get an itinerary where a vehicle stops at one of these stops """ - stops: String @deprecated(reason : "Not implemented in OTP2.") + stops: String @deprecated(reason : "Not implemented") """ A comma-separated list of banned stop ids. Only itineraries where these stops are not travelled through are returned, e.g. if a bus route stops at one of these stops, that route will not be used in the itinerary, even if the stop is not used for boarding or disembarking the vehicle. """ - stopsHard: String @deprecated(reason : "Not implemented in OTP2.") + stopsHard: String @deprecated(reason : "Not implemented") "A comma-separated list of banned trip ids" trips: String } @@ -3947,15 +4286,11 @@ input InputModeWeight { } input InputPreferred { - "A comma-separated list of ids of the agencies preferred by the user." + "Not implemented" agencies: String - """ - Penalty added for using every route that is not preferred if user set any - route as preferred. We return number of seconds that we are willing to wait - for preferred route. - """ + "Not implemented" otherThanPreferredRoutesPenalty: Int - "A comma-separated list of ids of the routes preferred by the user." + "Not implemented" routes: String } @@ -4138,7 +4473,6 @@ input PlanModesInput { If more than one mode is selected, at least one of them must be used but not necessarily all. There are modes that automatically also use walking such as the rental modes. To force rental to be used, this should only include the rental mode and not `WALK` in addition. - The default access mode is `WALK`. """ direct: [PlanDirectMode!] "Should only the direct search without any transit be done." @@ -4146,8 +4480,7 @@ input PlanModesInput { """ Modes for different phases of an itinerary when transit is included. Also includes street mode selections related to connecting to the transit network - and transfers. By default, all transit modes are usable and `WALK` is used for - access, egress and transfers. + and transfers. By default, all transit modes are usable. """ transit: PlanTransitModesInput """ @@ -4240,7 +4573,6 @@ input PlanTransitModesInput { If more than one mode is selected, at least one of them must be used but not necessarily all. There are modes that automatically also use walking such as the rental modes. To force rental to be used, this should only include the rental mode and not `WALK` in addition. - The default access mode is `WALK`. """ access: [PlanAccessMode!] """ @@ -4248,13 +4580,9 @@ input PlanTransitModesInput { If more than one mode is selected, at least one of them must be used but not necessarily all. There are modes that automatically also use walking such as the rental modes. To force rental to be used, this should only include the rental mode and not `WALK` in addition. - The default access mode is `WALK`. """ egress: [PlanEgressMode!] - """ - Street mode that is used when searching for transfers. Selection of only one allowed for now. - The default transfer mode is `WALK`. - """ + "Street mode that is used when searching for transfers. Selection of only one allowed for now." transfer: [PlanTransferMode!] """ Transit modes and reluctances associated with them. Each defined mode can be used in @@ -4280,10 +4608,14 @@ A visit-via-location is a physical visit to one of the stop locations or coordin on-board visit does not count, the traveler must alight or board at the given stop for it to to be accepted. To visit a coordinate, the traveler must walk(bike or drive) to the closest point in the street network from a stop and back to another stop to join the transit network. - -NOTE! Coordinates are NOT supported yet. """ input PlanVisitViaLocationInput { + """ + A coordinate to route through. To visit a coordinate, the traveler must walk(bike or drive) + to the closest point in the street network from a stop and back to another stop to join the transit + network. + """ + coordinate: PlanCoordinateInput "The label/name of the location. This is pass-through information and is not used in routing." label: String """ @@ -4327,7 +4659,10 @@ input ScooterPreferencesInput { "Preferences related to scooter rental (station based or floating scooter rental)." input ScooterRentalPreferencesInput { - "Rental networks which can be potentially used as part of an itinerary." + """ + Rental networks which can be potentially used as part of an itinerary. If this field has no default value, + it means that all networks are allowed unless some are banned with `bannedNetworks`. + """ allowedNetworks: [String!] "Rental networks which cannot be used as part of an itinerary." bannedNetworks: [String!] @@ -4343,21 +4678,21 @@ input TimetablePreferencesInput { When false, real-time updates are considered during the routing. In practice, when this option is set as true, some of the suggestions might not be realistic as the transfers could be invalid due to delays, - trips can be cancelled or stops can be skipped. + trips can be canceled or stops can be skipped. """ excludeRealTimeUpdates: Boolean """ - When true, departures that have been cancelled ahead of time will be + When true, departures that have been canceled ahead of time will be included during the routing. This means that an itinerary can include - a cancelled departure while some other alternative that contains no cancellations + a canceled departure while some other alternative that contains no cancellations could be filtered out as the alternative containing a cancellation would normally be better. """ includePlannedCancellations: Boolean """ - When true, departures that have been cancelled through a real-time feed will be + When true, departures that have been canceled through a real-time feed will be included during the routing. This means that an itinerary can include - a cancelled departure while some other alternative that contains no cancellations + a canceled departure while some other alternative that contains no cancellations could be filtered out as the alternative containing a cancellation would normally be better. This option can't be set to true while `includeRealTimeUpdates` is false. """ @@ -4376,15 +4711,61 @@ input TransferPreferencesInput { "How many transfers there can be at maximum in an itinerary." maximumTransfers: Int """ - A global minimum transfer time (in seconds) that specifies the minimum amount of time - that must pass between exiting one transit vehicle and boarding another. This time is - in addition to time it might take to walk between transit stops. Setting this value - as `PT0S`, for example, can lead to passenger missing a connection when the vehicle leaves - ahead of time or the passenger arrives to the stop later than expected. + A global minimum transfer time that specifies the minimum amount of time that must pass + between exiting one transit vehicle and boarding another. This time is in addition to + time it might take to walk between transit stops. Setting this value as `PT0S`, for + example, can lead to passenger missing a connection when the vehicle leaves ahead of time + or the passenger arrives to the stop later than expected. """ slack: Duration } +""" +A collection of selectors for what routes/agencies should be included in or excluded from the search. + +The `include` is always applied to select the initial set, then `exclude` to remove elements. +If only `exclude` is present, the exclude is applied to the existing set of results. + +Therefore, if an entity is both included _and_ excluded the exclusion takes precedence. +""" +input TransitFilterInput { + """ + A list of selectors for what routes/agencies should be excluded during search. + + In order to be excluded, a route/agency has to match with at least one selector. + + An empty list or a list containing `null` is forbidden. + """ + exclude: [TransitFilterSelectInput!] + """ + A list of selectors for what routes/agencies should be allowed during the search. + + If route/agency matches with at least one selector it will be included. + + An empty list or a list containing `null` is forbidden. + """ + include: [TransitFilterSelectInput!] +} + +""" +A list of selectors for including or excluding entities from the routing results. Null +means everything is allowed to be returned and empty lists are not allowed. +""" +input TransitFilterSelectInput @oneOf { + """ + Set of ids for agencies that should be included in/excluded from the search. + + Format: `FeedId:AgencyId` + """ + agencies: [String!] + """ + Set of ids for routes that should be included in/excluded from the search. + + Format: `FeedId:AgencyId` + """ + routes: [String!] +} + "Costs related to using a transit mode." input TransitModePreferenceCostInput { "A cost multiplier of transit leg travel time." @@ -4400,6 +4781,57 @@ input TransitPreferencesInput { can be found under the street mode preferences. """ board: BoardPreferencesInput + """ + A list of filters for which trips should be included or excluded. A trip will be considered in the + result if it is included by at least one filter and isn't excluded by another one at the same time. + + An empty list of filters or no value means that all trips should be included. + + **AND/OR Logic** + + Several filters can be defined and form an OR-condition. + + The following example means "include all trips with `F:route1` _or_ `F:agency1`": + + ``` + filters: [ + { + include: { + routes: ["F:route1"] + } + }, + { + include: { + agencies: ["F:agency1"] + } + } + }] + ``` + + The following example means "include all trips of `F:agency1` _and_ exclude `F:route1`": + + ``` + filters: [ + { + include: { + agencies: ["F:agency1"] + }, + exclude: { + routes: ["F:route1"] + } + } + ] + ``` + + Be aware of the following pitfalls: + - It is easy to construct AND-conditions that can lead to zero results. + - OR-conditions that have an element which consists of only an exclude are likely to be + unwanted and may lead to unexpected results. + + **Note**: This parameter also interacts with the modes set in `modes.transit` by forming + an AND-condition. + """ + filters: [TransitFilterInput!] "Preferences related to cancellations and real-time." timetable: TimetablePreferencesInput "Preferences related to transfers between transit vehicles (typically between stops)." @@ -4496,4 +4928,4 @@ input WheelchairPreferencesInput { that the itineraries are wheelchair accessible as there can be data issues. """ enabled: Boolean -} +} \ No newline at end of file diff --git a/test/unit/test-data/dcw12.js b/test/unit/test-data/dcw12.js index aa5e6552d1..21fc5f1c53 100644 --- a/test/unit/test-data/dcw12.js +++ b/test/unit/test-data/dcw12.js @@ -9,6 +9,7 @@ export default { vertexType: 'NORMAL', vehicleRentalStation: null, stop: null, + viaLocationType: 'VISIT', }, to: { lat: 60.1603512, @@ -31,7 +32,6 @@ export default { end: new Date(1534234930000).toISOString(), distance: 5651.585000000007, duration: 2222, - intermediatePlace: true, route: null, trip: null, }, @@ -156,7 +156,6 @@ export default { distance: 1847.7440000000006, duration: 1722, rentedBike: false, - intermediatePlace: false, route: null, trip: null, from: { @@ -167,6 +166,7 @@ export default { }, to: { stop: null, + viaLocationType: 'VISIT', }, }, { @@ -178,7 +178,6 @@ export default { distance: 5263.962000000001, duration: 4683, rentedBike: false, - intermediatePlace: true, route: null, trip: null, from: { @@ -186,6 +185,7 @@ export default { lat: 60.156843, lon: 24.956721, stop: null, + viaLocationType: 'VISIT', }, to: { stop: null, @@ -283,7 +283,6 @@ export default { distance: 356.498, duration: 399, rentedBike: false, - intermediatePlace: true, route: null, trip: null, from: { @@ -291,6 +290,7 @@ export default { lat: 60.178882, lon: 24.960086, stop: null, + viaLocationType: 'VISIT', }, to: { stop: null }, }, @@ -386,7 +386,6 @@ export default { distance: 218.619, duration: 251, rentedBike: false, - intermediatePlace: true, route: null, trip: null, from: { @@ -394,6 +393,7 @@ export default { lat: 60.161875, lon: 24.939654, stop: null, + viaLocationType: 'VISIT', }, to: { stop: null }, }, @@ -538,7 +538,6 @@ export default { distance: 2999.7169999999983, duration: 772, rentedBike: false, - intermediatePlace: true, route: null, trip: null, from: { @@ -546,6 +545,7 @@ export default { lat: 60.178882, lon: 24.960086, stop: null, + viaLocationType: 'VISIT', }, to: { stop: null }, }, @@ -618,7 +618,6 @@ export default { distance: 3.719, duration: 4, rentedBike: false, - intermediatePlace: true, route: null, trip: null, from: { @@ -626,6 +625,7 @@ export default { lat: 60.161875, lon: 24.939654, stop: null, + viaLocationType: 'VISIT', }, to: { stop: null }, }, @@ -879,7 +879,6 @@ export default { distance: 410.70599999999996, duration: 368, rentedBike: false, - intermediatePlace: true, route: null, trip: null, from: { @@ -887,6 +886,7 @@ export default { lat: 60.217879, lon: 24.781975, stop: null, + viaLocationType: 'VISIT', }, to: { stop: { @@ -1039,7 +1039,6 @@ export default { distance: 101.31899999999999, duration: 95, rentedBike: false, - intermediatePlace: true, route: null, trip: null, from: { @@ -1049,6 +1048,7 @@ export default { stop: { gtfsId: 'HSL:1172142', }, + viaLocationType: 'VISIT', }, to: { stop: null }, }, @@ -1061,7 +1061,6 @@ export default { distance: 101.319, duration: 95, rentedBike: false, - intermediatePlace: true, route: null, trip: null, from: { @@ -1069,6 +1068,7 @@ export default { lat: 60.209802, lon: 24.919357, stop: null, + viaLocationType: 'VISIT', }, to: { stop: { @@ -1295,7 +1295,6 @@ export default { distance: 124.16699999999999, duration: 118, rentedBike: false, - intermediatePlace: true, route: null, trip: null, from: { @@ -1303,6 +1302,7 @@ export default { lat: 60.19948, lon: 24.939067, stop: null, + viaLocationType: 'VISIT', }, to: { stop: null }, }, @@ -1353,7 +1353,6 @@ export default { distance: 491.9020000000001, duration: 464, rentedBike: false, - intermediatePlace: true, route: null, trip: null, from: { @@ -1361,6 +1360,7 @@ export default { lat: 60.19948, lon: 24.939067, stop: null, + viaLocationType: 'VISIT', }, to: { stop: { @@ -1527,6 +1527,7 @@ export default { stop: { gtfsId: 'HSL:1172142', }, + viaLocationType: 'PASS_THROUGH', }, to: { stop: { @@ -1608,7 +1609,6 @@ export default { distance: 284.95300000000003, duration: 274, rentedBike: false, - intermediatePlace: true, route: null, trip: null, from: { @@ -1616,6 +1616,7 @@ export default { lat: 60.199093, lon: 24.940536, stop: null, + viaLocationType: 'VISIT', }, to: { stop: { @@ -1688,7 +1689,6 @@ export default { distance: 16.376, duration: 19, rentedBike: false, - intermediatePlace: true, route: null, trip: null, from: { @@ -1696,6 +1696,7 @@ export default { lat: 60.207595, lon: 24.921465, stop: null, + viaLocationType: 'VISIT', }, to: { stop: { @@ -1768,7 +1769,6 @@ export default { distance: 187.10399999999998, duration: 179, rentedBike: false, - intermediatePlace: true, route: null, trip: null, from: { @@ -1776,6 +1776,7 @@ export default { lat: 60.198971, lon: 24.939272, stop: null, + viaLocationType: 'VISIT', }, to: { stop: null }, }, diff --git a/test/unit/util/legUtils.test.js b/test/unit/util/legUtils.test.js index 39f47f4b2b..44ea5ebfca 100644 --- a/test/unit/util/legUtils.test.js +++ b/test/unit/util/legUtils.test.js @@ -191,7 +191,6 @@ describe('legUtils', () => { endTime: 1527760041000, distance: 536.3270000000001, duration: 124, - intermediatePlace: false, route: null, trip: null, __fragments__: { '8::client': [{}] }, @@ -214,6 +213,7 @@ describe('legUtils', () => { name: 'Jorvas, Kirkkonummi', vehicleRentalStation: null, stop: null, + viaLocationType: 'VISIT', }, legGeometry: { __dataID__: 'client:34101763480', @@ -228,7 +228,6 @@ describe('legUtils', () => { endTime: 1527760089000, distance: 32.369, duration: 48, - intermediatePlace: false, route: null, trip: null, __fragments__: { '8::client': [{}] }, @@ -243,6 +242,7 @@ describe('legUtils', () => { name: 'Jorvas, Kirkkonummi', vehicleRentalStation: null, stop: null, + viaLocationType: 'VISIT', }, to: { __dataID__: 'client:34101763485', @@ -265,7 +265,6 @@ describe('legUtils', () => { endTime: 1527760121000, distance: 23.669999999999998, duration: 32, - intermediatePlace: true, route: null, trip: null, __fragments__: { '8::client': [{}] }, @@ -288,6 +287,7 @@ describe('legUtils', () => { name: 'Elfvinginkuja 7, Kirkkonummi', vehicleRentalStation: null, stop: null, + viaLocationType: 'VISIT', }, legGeometry: { __dataID__: 'client:34101763486', @@ -303,7 +303,6 @@ describe('legUtils', () => { endTime: 1527760228000, distance: 471.849, duration: 107, - intermediatePlace: false, route: null, trip: null, __fragments__: { '8::client': [{}] }, @@ -318,6 +317,7 @@ describe('legUtils', () => { name: 'Elfvinginkuja 7, Kirkkonummi', vehicleRentalStation: null, stop: null, + viaLocationType: 'VISIT', }, to: { __dataID__: 'client:34101763491', @@ -341,7 +341,6 @@ describe('legUtils', () => { endTime: 1527760367000, distance: 617.027, duration: 139, - intermediatePlace: true, route: null, trip: null, __fragments__: { '8::client': [{}] }, @@ -367,15 +366,15 @@ describe('legUtils', () => { expect( compressedLegs.filter(leg => leg.from.name === 'Jorvas, Kirkkonummi')[0] - .intermediatePlace, + .from.viaLocationType === 'VISIT', ).to.equal(true); expect( compressedLegs.filter( leg => leg.from.name === 'Elfvinginkuja 7, Kirkkonummi', - )[0].intermediatePlace, + )[0].from.viaLocationType === 'VISIT', ).to.equal(true); expect( - compressedLegs.filter(leg => leg.intermediatePlace).length, + compressedLegs.filter(leg => !!leg.from.viaLocationType).length, ).to.equal(2); }); }); diff --git a/test/unit/views/ItineraryPage/component/Itinerary.test.js b/test/unit/views/ItineraryPage/component/Itinerary.test.js index eac5a2d45f..3e442232b0 100644 --- a/test/unit/views/ItineraryPage/component/Itinerary.test.js +++ b/test/unit/views/ItineraryPage/component/Itinerary.test.js @@ -203,6 +203,7 @@ describe('', () => { legs: [ { from: {}, + to: {}, mode: 'RAIL', route: { alerts: [ @@ -246,6 +247,7 @@ describe('', () => { legs: [ { from: {}, + to: {}, mode: 'RAIL', route: { alerts: [ @@ -291,6 +293,7 @@ describe('', () => { legs: [ { from: {}, + to: {}, mode: 'RAIL', route: { alerts: [ @@ -407,6 +410,7 @@ describe('', () => { legs: [ { from: {}, + to: {}, intermediatePlaces: [ { stop: {