);
};
@@ -90,6 +138,8 @@ LegInfo.propTypes = {
alertSeverityLevel: PropTypes.string,
isAlternativeLeg: PropTypes.bool.isRequired,
displayTime: PropTypes.bool.isRequired,
+ changeHash: PropTypes.func,
+ tabIndex: PropTypes.number,
};
LegInfo.contextTypes = {
diff --git a/app/component/MobileItineraryWrapper.js b/app/component/MobileItineraryWrapper.js
index 5456b41cde..7bd07f9cef 100644
--- a/app/component/MobileItineraryWrapper.js
+++ b/app/component/MobileItineraryWrapper.js
@@ -39,6 +39,7 @@ const MobileItineraryWrapper = (props, context) => {
params={context.match.params}
focusToPoint={props.focusToPoint}
focusToLeg={props.focusToLeg}
+ changeHash={props.changeHash}
isMobile
/>
@@ -72,6 +73,7 @@ MobileItineraryWrapper.propTypes = {
plan: PropTypes.object,
serviceTimeRange: PropTypes.object.isRequired,
onSwipe: PropTypes.func,
+ changeHash: PropTypes.func,
};
MobileItineraryWrapper.contextTypes = {
diff --git a/app/component/RouteNumber.js b/app/component/RouteNumber.js
index e4a73ba866..cc16bcdf8f 100644
--- a/app/component/RouteNumber.js
+++ b/app/component/RouteNumber.js
@@ -153,6 +153,16 @@ function RouteNumber(props, context) {
)}
+ {props.occupancyStatus && (
+
+
+
+ )}
);
@@ -194,6 +204,7 @@ RouteNumber.propTypes = {
withBicycle: PropTypes.bool,
card: PropTypes.bool,
appendClass: PropTypes.string,
+ occupancyStatus: PropTypes.string,
};
RouteNumber.defaultProps = {
diff --git a/app/component/RouteNumberContainer.js b/app/component/RouteNumberContainer.js
index 99158cad9f..cc5cb110bb 100644
--- a/app/component/RouteNumberContainer.js
+++ b/app/component/RouteNumberContainer.js
@@ -11,6 +11,7 @@ const RouteNumberContainer = (
route,
isCallAgency,
withBicycle,
+ occupancyStatus,
...props
},
{ config },
@@ -24,6 +25,7 @@ const RouteNumberContainer = (
mode={route.mode}
text={getLegText(route, config, interliningWithRoute)}
withBicycle={withBicycle}
+ occupancyStatus={occupancyStatus}
{...props}
/>
);
@@ -37,6 +39,7 @@ RouteNumberContainer.propTypes = {
className: PropTypes.string,
fadeLong: PropTypes.bool,
withBicycle: PropTypes.bool,
+ occupancyStatus: PropTypes.string,
};
RouteNumberContainer.defaultProps = {
diff --git a/app/component/StopNearYou.js b/app/component/StopNearYou.js
index ace31ff203..3de0d74e06 100644
--- a/app/component/StopNearYou.js
+++ b/app/component/StopNearYou.js
@@ -1,19 +1,22 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, intlShape } from 'react-intl';
import { Link } from 'found';
import connectToStores from 'fluxible-addons-react/connectToStores';
+import Modal from '@hsl-fi/modal';
import { hasEntitiesOfType } from '../util/alertUtils';
import { PREFIX_STOPS, PREFIX_TERMINALS } from '../util/path';
import { AlertEntityType } from '../constants';
import StopNearYouHeader from './StopNearYouHeader';
import AlertBanner from './AlertBanner';
import StopNearYouDepartureRowContainer from './StopNearYouDepartureRowContainer';
+import CapacityModal from './CapacityModal';
const StopNearYou = (
{ stop, desc, stopId, currentTime, currentMode, relay },
{ config, intl },
) => {
+ const [capacityModalOpen, setCapacityModalOpen] = useState(false);
const stopOrStation = stop.parentStation ? stop.parentStation : stop;
const stopMode = stopOrStation.stoptimesWithoutPatterns[0]?.trip.route.mode;
useEffect(() => {
@@ -81,6 +84,7 @@ const StopNearYou = (
mode={stopMode}
stopTimes={stopOrStation.stoptimesWithoutPatterns}
isStation={isStation && stopMode !== 'SUBWAY'}
+ setCapacityModalOpen={() => setCapacityModalOpen(true)}
/>
)}
+ setCapacityModalOpen(false)}
+ >
+
+
);
};
diff --git a/app/component/StopNearYouContainer.js b/app/component/StopNearYouContainer.js
index 8c80ede416..f42b72e468 100644
--- a/app/component/StopNearYouContainer.js
+++ b/app/component/StopNearYouContainer.js
@@ -42,6 +42,9 @@ const containerComponent = createRefetchContainer(
trip {
gtfsId
tripHeadsign
+ occupancy {
+ occupancyStatus
+ }
pattern {
code
route {
@@ -91,6 +94,9 @@ const containerComponent = createRefetchContainer(
headsign
trip {
gtfsId
+ occupancy {
+ occupancyStatus
+ }
pattern {
code
route {
diff --git a/app/component/StopNearYouDepartureRowContainer.js b/app/component/StopNearYouDepartureRowContainer.js
index 05d3fe7939..3171bd8e95 100644
--- a/app/component/StopNearYouDepartureRowContainer.js
+++ b/app/component/StopNearYouDepartureRowContainer.js
@@ -4,7 +4,12 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import DepartureRow from './DepartureRow';
-const StopNearYouDepartureRowContainer = ({ stopTimes, mode, ...props }) => {
+const StopNearYouDepartureRowContainer = ({
+ stopTimes,
+ mode,
+ setCapacityModalOpen,
+ ...props
+}) => {
const sortedStopTimes = stopTimes
.slice()
.sort(
@@ -23,6 +28,7 @@ const StopNearYouDepartureRowContainer = ({ stopTimes, mode, ...props }) => {
currentTime={props.currentTime}
showPlatformCode={props.isStation}
showLink
+ onCapacityClick={() => setCapacityModalOpen(true)}
/>
);
});
@@ -61,5 +67,6 @@ StopNearYouDepartureRowContainer.propTypes = {
mode: PropTypes.string.isRequired,
isStation: PropTypes.bool.isRequired,
currentTime: PropTypes.number.isRequired,
+ setCapacityModalOpen: PropTypes.func.isRequired,
};
export default StopNearYouDepartureRowContainer;
diff --git a/app/component/SummaryPage.js b/app/component/SummaryPage.js
index 069ed86174..f4261a31a2 100644
--- a/app/component/SummaryPage.js
+++ b/app/component/SummaryPage.js
@@ -1042,6 +1042,9 @@ class SummaryPage extends React.Component {
trip {
gtfsId
directionId
+ occupancy {
+ occupancyStatus
+ }
stoptimesForDate {
scheduledDeparture
pickupType
@@ -2729,6 +2732,7 @@ class SummaryPage extends React.Component {
serviceTimeRange={this.props.serviceTimeRange}
focusToLeg={this.focusToLeg}
onSwipe={this.changeHash}
+ changeHash={this.changeHash}
>
{this.props.content &&
combinedItineraries.map((itinerary, i) =>
@@ -2972,6 +2976,9 @@ const containerComponent = createRefetchContainer(
trip {
gtfsId
directionId
+ occupancy {
+ occupancyStatus
+ }
stoptimesForDate {
scheduledDeparture
pickupType
diff --git a/app/component/SummaryPlanContainer.js b/app/component/SummaryPlanContainer.js
index 31b4c1ef5a..2643de8796 100644
--- a/app/component/SummaryPlanContainer.js
+++ b/app/component/SummaryPlanContainer.js
@@ -357,6 +357,9 @@ const connectedContainer = createFragmentContainer(
trip {
gtfsId
directionId
+ occupancy {
+ occupancyStatus
+ }
stoptimesForDate {
scheduledDeparture
pickupType
diff --git a/app/component/SummaryRow.js b/app/component/SummaryRow.js
index 7efecffa42..3fec6dff1f 100644
--- a/app/component/SummaryRow.js
+++ b/app/component/SummaryRow.js
@@ -29,6 +29,7 @@ import {
getCitybikeCapacity,
} from '../util/citybikes';
import { getRouteMode } from '../util/modeUtils';
+import { getCapacityForLeg } from '../util/occupancyUtil';
const Leg = ({
mode,
@@ -60,19 +61,31 @@ Leg.propTypes = {
renderModeIcons: PropTypes.bool,
};
-export const RouteLeg = ({
- leg,
- large,
- intl,
- legLength,
- isTransitLeg,
- interliningWithRoute,
- fitRouteNumber,
- withBicycle,
-}) => {
+export const RouteLeg = (
+ {
+ leg,
+ large,
+ intl,
+ legLength,
+ isTransitLeg,
+ interliningWithRoute,
+ fitRouteNumber,
+ withBicycle,
+ hasOneTransitLeg,
+ },
+ { config },
+) => {
const isCallAgency = isCallAgencyPickupType(leg);
let routeNumber;
const mode = getRouteMode(leg.route);
+
+ const getOccupancyStatus = () => {
+ if (hasOneTransitLeg) {
+ return getCapacityForLeg(config, leg);
+ }
+ return undefined;
+ };
+
if (isCallAgency) {
const message = intl.formatMessage({
id: 'pay-attention',
@@ -101,6 +114,7 @@ export const RouteLeg = ({
withBar
isTransitLeg={isTransitLeg}
withBicycle={withBicycle}
+ occupancyStatus={getOccupancyStatus()}
/>
);
}
@@ -124,6 +138,11 @@ RouteLeg.propTypes = {
interliningWithRoute: PropTypes.number,
isTransitLeg: PropTypes.bool,
withBicycle: PropTypes.bool.isRequired,
+ hasOneTransitLeg: PropTypes.bool,
+};
+
+RouteLeg.contextTypes = {
+ config: PropTypes.object.isRequired,
};
RouteLeg.defaultProps = {
@@ -214,6 +233,10 @@ const bikeWasParked = legs => {
return legs.length;
};
+const hasOneTransitLeg = data => {
+ return data.legs.filter(leg => leg.transitLeg).length === 1;
+};
+
const SummaryRow = (
{
data,
@@ -499,6 +522,7 @@ const SummaryRow = (
legLength={legLength}
large={breakpoint === 'large'}
withBicycle={withBicycle}
+ hasOneTransitLeg={hasOneTransitLeg(data)}
/>,
);
}
diff --git a/app/component/TransitLeg.js b/app/component/TransitLeg.js
index 20b05cdeb9..aa9eaea753 100644
--- a/app/component/TransitLeg.js
+++ b/app/component/TransitLeg.js
@@ -456,6 +456,8 @@ class TransitLeg extends React.Component {
alertSeverityLevel={alertSeverityLevel}
isAlternativeLeg={false}
displayTime={this.displayAlternativeLegs()}
+ changeHash={this.props.changeHash}
+ tabIndex={this.props.tabIndex}
/>
{this.state.showAlternativeLegs &&
@@ -695,6 +697,8 @@ TransitLeg.propTypes = {
children: PropTypes.node.isRequired,
lang: PropTypes.string.isRequired,
omitDivider: PropTypes.bool,
+ changeHash: PropTypes.func,
+ tabIndex: PropTypes.number,
};
TransitLeg.defaultProps = {
diff --git a/app/component/departure.scss b/app/component/departure.scss
index 94f4adda0f..4855d6ec7e 100644
--- a/app/component/departure.scss
+++ b/app/component/departure.scss
@@ -227,6 +227,35 @@
}
}
+.capacity-information-modal {
+ .capacity-info-row {
+ display: flex;
+ height: 20px;
+ margin-top: 8px;
+ h4 {
+ text-transform: none;
+ margin-top: 0;
+ padding-top: 0;
+ padding-left: 20px;
+ font-size: 15px;
+ }
+ }
+ .capacity-info-explanation {
+ margin: 0;
+ margin-left: 36px;
+ font-size: 13px;
+ }
+ .explanations-heading {
+ font-size: 15px;
+ }
+ .capacity-heading {
+ margin-bottom: 5px;
+ }
+ .capacity-text {
+ margin-top: 0;
+ }
+}
+
.departure-row:focus-within {
outline: 2px auto #333 !important;
}
@@ -256,6 +285,18 @@
}
}
+ .capacity-cell {
+ width: 25px;
+ .capacity-icon-container {
+ padding-left: 7px;
+ margin-top: 15px;
+ cursor: pointer;
+ svg {
+ margin-top: 4px;
+ }
+ }
+ }
+
.headsign {
color: #333;
}
diff --git a/app/component/itinerary.scss b/app/component/itinerary.scss
index 64a813f1b4..2205812ae7 100644
--- a/app/component/itinerary.scss
+++ b/app/component/itinerary.scss
@@ -1346,6 +1346,11 @@ $itinerary-tab-switch-height: 48px;
height: 1.5em;
border-radius: $border-radius;
}
+ .occupancy-icon-container {
+ color: white;
+ margin-left: auto;
+ padding-right: 8px;
+ }
.vcenter-children {
width: 100%;
display: flex;
@@ -1395,6 +1400,11 @@ $itinerary-tab-switch-height: 48px;
border: 2px solid #f4f4f5;
background-color: white;
}
+ .capacity-icon-container {
+ color: #007ac9;
+ margin-top: 2px;
+ cursor: pointer;
+ }
}
.itinerary-leg-text-gray {
@include font-book;
diff --git a/app/component/summary-row.scss b/app/component/summary-row.scss
index 4ce13cdf6f..a0f1961e2f 100644
--- a/app/component/summary-row.scss
+++ b/app/component/summary-row.scss
@@ -251,6 +251,11 @@
display: flex;
min-width: 0;
height: 1.5rem;
+ .occupancy-icon-container {
+ color: white;
+ margin-left: auto;
+ padding-right: 8px;
+ }
.vcenter-children {
.empty {
float: left;
diff --git a/app/configurations/config.default.js b/app/configurations/config.default.js
index 12bb226e92..3fcbe3d780 100644
--- a/app/configurations/config.default.js
+++ b/app/configurations/config.default.js
@@ -613,6 +613,8 @@ export default {
showTenWeeksOnRouteSchedule: true,
+ useRealtimeTravellerCapacities: false,
+
aboutThisService: {
fi: [
{
diff --git a/app/configurations/config.hsl.js b/app/configurations/config.hsl.js
index a6dcbefc91..cff397ba67 100644
--- a/app/configurations/config.hsl.js
+++ b/app/configurations/config.hsl.js
@@ -537,6 +537,8 @@ export default {
showSimilarRoutesOnRouteDropDown: true,
+ useRealtimeTravellerCapacities: true,
+
stopCard: {
header: {
virtualMonitorBaseUrl: 'https://omatnaytot.hsl.fi/',
diff --git a/app/configurations/config.kela.js b/app/configurations/config.kela.js
index 146fe10915..fc2ce0539e 100644
--- a/app/configurations/config.kela.js
+++ b/app/configurations/config.kela.js
@@ -113,6 +113,8 @@ export default configMerger(matkaConfig, {
onlyCarPlan: true,
+ useRealtimeTravellerCapacities: false,
+
aboutThisService: {
fi: [
{
diff --git a/app/configurations/config.matka.js b/app/configurations/config.matka.js
index 2f9ac1f6f3..b11e7c17bf 100644
--- a/app/configurations/config.matka.js
+++ b/app/configurations/config.matka.js
@@ -345,6 +345,8 @@ export default {
},
},
+ useRealtimeTravellerCapacities: true,
+
aboutThisService: {
fi: [
{
diff --git a/app/translations.js b/app/translations.js
index 755c980cf8..0e81bb7971 100644
--- a/app/translations.js
+++ b/app/translations.js
@@ -946,6 +946,22 @@ const translations = {
'canceled-itineraries-amount-hide':
'Hide canceled itineraries ({itineraryAmount})',
'canceled-legs': 'Canceled departures on the route',
+ 'capacity-modal.crushed-standing-room-only-body':
+ 'Only a little standing room available',
+ 'capacity-modal.crushed-standing-room-only-heading': 'Very crowded',
+ 'capacity-modal.few-seats-available-body': 'Some seats available',
+ 'capacity-modal.few-seats-available-heading': 'Not too crowded',
+ 'capacity-modal.full-capacity-body': 'No seats or standing room available',
+ 'capacity-modal.full-capacity-heading': 'Full',
+ 'capacity-modal.heading': 'Is there room in the vehicle?',
+ 'capacity-modal.legend': 'Legend for the symbols',
+ 'capacity-modal.many-seats-available-body': 'Plenty of seats available',
+ 'capacity-modal.many-seats-available-heading': 'Not crowded',
+ 'capacity-modal.standing-room-only-body':
+ 'Only a few seats and little standing room available',
+ 'capacity-modal.standing-room-only-heading': 'Nearly full',
+ 'capacity-modal.subheading':
+ 'Real-time capacity information is available for some vehicles',
car: 'Car',
'car-distance-duration': 'Drive {duration} ({distance})',
'car-distance-no-duration': 'Drive {distance}',
@@ -992,6 +1008,7 @@ const translations = {
'create-account': 'Create {contactName} account',
'create-embedded-search': 'Create a route search element',
'create-stop-monitor': 'Create a stop display',
+ 'crushed-standing-room-only': 'Crushed standing room only',
'cycle-distance-duration': 'Cycle {duration} ({distance})',
'cyclewalk-distance-duration': 'Walk your bike {duration} ({distance})',
// eslint-disable-next-line sort-keys
@@ -1052,6 +1069,7 @@ const translations = {
ferry: 'Ferry',
'ferry-with-route-number': 'Ferry {routeNumber} {headSign}',
'fetch-new-route': 'Fetch a new route',
+ 'few-seats-available': 'Few seats available',
finnish: 'Finnish',
'free-of-charge': 'Free',
'from-ferry': 'ferry',
@@ -1060,6 +1078,7 @@ const translations = {
'from-stop': 'from stop',
'from-subway': 'subway',
frontpage: 'Frontpage',
+ full: 'Full capacity',
funicular: 'Funicular',
'generic-cancelation': '{mode} {route} {headsign} at {time} is cancelled.',
'generic-error': 'There was an error',
@@ -1199,6 +1218,7 @@ const translations = {
'main-menu-label-close': 'Close the main menu',
'main-menu-label-open': 'Open the main menu',
'main-mode': "I'm traveling by",
+ 'many-seats-available': 'Many seats available',
map: 'Map',
'map-layer-citybike': 'Citybike stations',
'map-layer-park-and-ride': 'Park & ride sites',
@@ -1494,6 +1514,7 @@ const translations = {
'splash-use-positioning': 'Use location services',
'splash-welcome': 'How do you wish to start?',
'splash-you-can-also': 'or',
+ 'standing-room-only': 'Standing room only',
station: 'Station',
stop: 'Stop',
'stop-departure-time-future': 'Departure time is in {minutes} minutes',
@@ -2087,6 +2108,22 @@ const translations = {
'canceled-itineraries-amount-hide':
'Piilota perutut reittiehdotukset ({itineraryAmount})',
'canceled-legs': 'Reitillä peruttuja vuoroja',
+ 'capacity-modal.crushed-standing-room-only-body':
+ 'Vain vähän seisomapaikkoja',
+ 'capacity-modal.crushed-standing-room-only-heading': 'Kova tungos',
+ 'capacity-modal.few-seats-available-body': 'Joitain istumapaikkoja',
+ 'capacity-modal.few-seats-available-heading': 'Ei liikaa tungosta',
+ 'capacity-modal.full-capacity-body': 'Ei vapaita paikkoja',
+ 'capacity-modal.full-capacity-heading': 'Täynnä',
+ 'capacity-modal.heading': 'Onko kulkuneuvossa tilaa?',
+ 'capacity-modal.legend': 'Merkkien selitteet',
+ 'capacity-modal.many-seats-available-body': 'Paljon istumapaikkoja',
+ 'capacity-modal.many-seats-available-heading': 'Ei tungosta',
+ 'capacity-modal.standing-room-only-body':
+ 'Vain vähän istuma- ja seisomapaikkoja',
+ 'capacity-modal.standing-room-only-heading': 'Lähes täynnä',
+ 'capacity-modal.subheading':
+ 'Osasta kulkuneuvoja on saatavilla reaaliaikainen kapasiteettitieto',
car: 'Auto',
'car-distance-duration': 'Autoile {duration} ({distance})',
'car-distance-no-duration': 'Autoile {distance}',
@@ -2134,6 +2171,7 @@ const translations = {
'create-account': 'Luo {contactName} tunnus',
'create-embedded-search': 'Luo reittihakuelementti',
'create-stop-monitor': 'Luo pysäkkinäyttö',
+ 'crushed-standing-room-only': 'Kova tungos',
'cycle-distance-duration': 'Pyöräile {duration} ({distance})',
'cyclewalk-distance-duration': 'Taluta pyörää {duration} ({distance})',
// eslint-disable-next-line sort-keys
@@ -2196,6 +2234,7 @@ const translations = {
ferry: 'Lautta',
'ferry-with-route-number': 'Lautta {routeNumber} {headSign}',
'fetch-new-route': 'Hae uusi reitti',
+ 'few-seats-available': 'Joitakin istumapaikkoja vapaana',
finnish: 'Suomi',
'free-of-charge': 'Maksuton',
'from-ferry': 'lautasta',
@@ -2204,6 +2243,7 @@ const translations = {
'from-stop': 'pysäkiltä',
'from-subway': 'metrosta',
frontpage: 'Etusivu',
+ full: 'Täynnä',
funicular: 'Funikulaari',
'generic-cancelation': '{mode} {route} {headsign} kello {time} on peruttu.',
'generic-error': 'Tapahtui virhe',
@@ -2336,6 +2376,7 @@ const translations = {
'main-menu-label-close': 'Sulje päävalikko',
'main-menu-label-open': 'Avaa päävalikko',
'main-mode': 'Kulkumuoto',
+ 'many-seats-available': 'Paljon istumapaikkoja vapaana',
map: 'Kartta',
'map-layer-citybike': 'Kaupunkipyöräasemat',
'map-layer-park-and-ride': 'Liityntäpysäköintipaikat',
@@ -2629,6 +2670,7 @@ const translations = {
'splash-use-positioning': 'Käytä paikannusta',
'splash-welcome': 'Miten haluat aloittaa?',
'splash-you-can-also': 'tai',
+ 'standing-room-only': 'Lähes täynnä',
station: 'Asema',
stop: 'Pysäkki',
'stop-departure-time-future': 'Lähtöaika {minutes} min päästä',
@@ -3999,6 +4041,21 @@ const translations = {
'canceled-itineraries-amount-hide':
'Dölja inställda reseförslag ({itineraryAmount})',
'canceled-legs': 'Inställda avgångar på linjen',
+ 'capacity-modal.crushed-standing-room-only-body': 'Endast några ståplatser',
+ 'capacity-modal.crushed-standing-room-only-heading': 'Nästan fullt',
+ 'capacity-modal.few-seats-available-body': 'Några sittplatser',
+ 'capacity-modal.few-seats-available-heading': 'Ingen stor trängsel',
+ 'capacity-modal.full-capacity-body': 'Inga lediga platser',
+ 'capacity-modal.full-capacity-heading': 'Fullt',
+ 'capacity-modal.heading': 'Finns det plats ombord?',
+ 'capacity-modal.legend': 'Teckenförklaringar',
+ 'capacity-modal.many-seats-available-body': 'Många sittplatser',
+ 'capacity-modal.many-seats-available-heading': 'Ingen trängsel',
+ 'capacity-modal.standing-room-only-body':
+ 'Endast några få sitt- och ståplatser',
+ 'capacity-modal.standing-room-only-heading': 'Stor trängsel',
+ 'capacity-modal.subheading':
+ 'Information om kapaciteten i realtid finns att rå om vissa fordon',
car: 'Bil',
'car-distance-duration': 'Kör {duration} ({distance})',
'car-distance-no-duration': 'Kör {distance}',
@@ -4046,6 +4103,7 @@ const translations = {
'create-account': 'Skapa {contactName} konto',
'create-embedded-search': 'Skapa ett ruttsökningselement',
'create-stop-monitor': 'Skapa hållplatsskärm',
+ 'crushed-standing-room-only': 'Nästan fullt',
'cycle-distance-duration': 'Cykla {duration} ({distance})',
'cyclewalk-distance-duration': 'Led cykeln {duration} ({distance})',
// eslint-disable-next-line sort-keys
@@ -4107,6 +4165,7 @@ const translations = {
ferry: 'Färja',
'ferry-with-route-number': 'Färja {routeNumber} {headSign}',
'fetch-new-route': 'Sök en ny rutt',
+ 'few-seats-available': 'Några sittplatser',
finnish: 'Finska',
'free-of-charge': 'Kostnadsfri',
'from-ferry': 'färjan',
@@ -4115,6 +4174,7 @@ const translations = {
'from-stop': 'från hållplats',
'from-subway': 'metron',
frontpage: 'Framsidan',
+ full: 'Fullt',
funicular: 'Bergbanan',
'generic-cancelation': '{mode} {route} {headsign} kl. {time} ställs in.',
'generic-error': 'Det hände ett fel',
@@ -4252,6 +4312,7 @@ const translations = {
'main-menu-label-close': 'Stäng huvudmenyn',
'main-menu-label-open': 'Öppna huvudmenyn',
'main-mode': 'Jag använder',
+ 'many-seats-available': 'Många sittplatser',
map: 'Karta',
'map-layer-citybike': 'Stadscykelstation',
'map-layer-park-and-ride': 'Infartsparkering',
@@ -4550,6 +4611,7 @@ const translations = {
'Tjänsten fungerar bäst om du delar din plats.',
'splash-use-positioning': 'Använd min plats',
'splash-you-can-also': 'alternativt',
+ 'standing-room-only': 'Stor trängsel',
station: 'Station',
stop: 'Hållplats',
'stop-departure-time-future': 'Avgångstid är i {minutes} minuter',
diff --git a/app/util/occupancyUtil.js b/app/util/occupancyUtil.js
new file mode 100644
index 0000000000..8fd7cb226a
--- /dev/null
+++ b/app/util/occupancyUtil.js
@@ -0,0 +1,71 @@
+import moment from 'moment';
+
+/**
+ * Maps status to corresponding string.
+ *
+ * @param {*} status status from OTP.
+ */
+export function mapStatus(status) {
+ switch (status) {
+ case 'EMPTY':
+ return 'MANY_SEATS_AVAILABLE';
+ case 'NOT_ACCEPTING_PASSENGERS':
+ return 'FULL';
+ case 'MANY_SEATS_AVAILABLE':
+ return 'MANY_SEATS_AVAILABLE';
+ case 'FEW_SEATS_AVAILABLE':
+ return 'FEW_SEATS_AVAILABLE';
+ case 'STANDING_ROOM_ONLY':
+ return 'STANDING_ROOM_ONLY';
+ case 'CRUSHED_STANDING_ROOM_ONLY':
+ return 'CRUSHED_STANDING_ROOM_ONLY';
+ case 'FULL':
+ return 'FULL';
+ default:
+ return 'NO_DATA_AVAILABLE';
+ }
+}
+
+/**
+ * Checks that departure is within 10 minutes from now.
+ *
+ * @param {*} departureTime departure time in Unix.
+ */
+export function isDepartureWithinTenMinutes(departureTime) {
+ return (
+ moment(departureTime).diff(moment(), 'minutes') <= 10 &&
+ moment(departureTime).diff(moment(), 'minutes') > -1
+ );
+}
+
+/**
+ * Returns mapped capacity string.
+ * @param {*} config configuration object.
+ * @param {*} occupancyStatus status from OTP.
+ * @param {*} departureTime departure time in Unix.
+ */
+export function getCapacity(config, occupancyStatus, departureTime) {
+ if (
+ config.useRealtimeTravellerCapacities &&
+ occupancyStatus &&
+ occupancyStatus !== 'NO_DATA_AVAILABLE' &&
+ isDepartureWithinTenMinutes(departureTime)
+ ) {
+ return mapStatus(occupancyStatus);
+ }
+ return null;
+}
+
+/**
+ * Returns capacity string for leg.
+ *
+ * @param {*} config configuration object.
+ * @param {*} leg leg object.
+ */
+export function getCapacityForLeg(config, leg) {
+ return getCapacity(
+ config,
+ leg.trip?.occupancy?.occupancyStatus,
+ leg.startTime,
+ );
+}
diff --git a/build/schema.graphql b/build/schema.graphql
index dd6bf02526..276a8ae6f5 100644
--- a/build/schema.graphql
+++ b/build/schema.graphql
@@ -846,9 +846,12 @@ type debugOutput {
}
"""
-Departure row is a location, which lists departures of a certain pattern from a
-stop. Departure rows are identified with the pattern, so querying departure rows
-will return only departures from one stop per pattern
+Departure row is a combination of a pattern and a stop of that pattern.
+
+They are de-duplicated so for each pattern there will only be a single departure row.
+
+This is useful if you want to show a list of stop/pattern combinations but want each pattern to be
+listed only once.
"""
type DepartureRow implements Node & PlaceInterface {
"""
@@ -1236,29 +1239,59 @@ input InputUnpreferred {
}
enum RoutingErrorCode {
- """No transit connection was found between the origin and destination withing the operating day or the next day"""
+ """
+ No transit connection was found between the origin and destination within the operating day or
+ the next day, not even sub-optimal ones.
+ """
NO_TRANSIT_CONNECTION
- """Transit connection was found, but it was outside the search window, see metadata for the next search window"""
+ """
+ A transit connection was found, but it was outside the search window. See the metadata for a token
+ for retrieving the result outside the search window.
+ """
NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW
- """The date specified is outside the range of data currently loaded into the system"""
+ """
+ The date specified is outside the range of data currently loaded into the system as it is too
+ far into the future or the past.
+
+ The specific date range of the system is configurable by an administrator and also depends on
+ the input data provided.
+ """
OUTSIDE_SERVICE_PERIOD
- """The coordinates are outside the bounds of the data currently loaded into the system"""
+ """
+ The coordinates are outside the geographic bounds of the transit and street data currently loaded
+ into the system and therefore cannot return any results.
+ """
OUTSIDE_BOUNDS
- """The specified location is not close to any streets or transit stops"""
+ """
+ The specified location is not close to any streets or transit stops currently loaded into the
+ system, even though it is generally within its bounds.
+
+ This can happen when there is only transit but no street data coverage at the location in
+ question.
+ """
LOCATION_NOT_FOUND
- """No stops are reachable from the location specified. You can try searching using a different access or egress mode"""
+ """
+ No stops are reachable from the start or end locations specified.
+
+ You can try searching using a different access or egress mode, for example cycling instead of walking,
+ increase the walking/cycling/driving speed or have an administrator change the system's configuration
+ so that stops further away are considered.
+ """
NO_STOPS_IN_RANGE
- """The origin and destination are so close to each other, that walking is always better, but no direct mode was specified for the search"""
- WALKING_BETTER_THAN_TRANSIT
+ """
+ Transit connections were requested and found but because it is easier to just walk all the way
+ to the destination they were removed.
- """An unknown error happened during the search. The details have been logged to the server logs"""
- SYSTEM_ERROR
+ If you want to still show the transit results, you need to make walking less desirable by
+ increasing the walk reluctance.
+ """
+ WALKING_BETTER_THAN_TRANSIT
}
enum InputField {
@@ -1300,23 +1333,59 @@ type FareMedium {
"A container for both a fare product (a ticket) and its relationship to the itinerary."
type FareProductUse {
"""
- Identifier local to the itinerary that allows to cross-reference and deduplicate
- fare products that span more than one leg.
+ Represents the use of a single instance of a fare product throughout the itinerary. It can
+ be used to cross-reference and de-duplicate fare products that are applicable for more than one
+ leg.
If you want to uniquely identify the fare product itself (not its use) use the product's `id`.
### Example: Day pass
- When a day pass is valid for all three legs in the itinerary it will appear
- for each leg but with the same use-`id`.
-
- *It is the responsibility of the API consumers to display the day pass as a product for the
- entire itinerary rather than three day passes!*
+ The day pass is valid for both legs in the itinerary. It is listed as the applicable `product` for each leg,
+ and the same FareProductUse id is shown, indicating that only one pass was used/bought.
+
+ **Illustration**
+ ```yaml
+ itinerary:
+ leg1:
+ fareProducts:
+ id: "AAA" // id of a FareProductUse instance
+ product:
+ id: "day-pass" // product id
+ name: "Day Pass"
+ leg2:
+ fareProducts:
+ id: "AAA" // identical to leg1. the passenger needs to buy ONE pass, not two.
+ product:
+ id: "day-pass" // product id
+ name: "Day Pass"
+ ```
+
+ **It is the responsibility of the API consumers to display the day pass as a product for the
+ entire itinerary rather than two day passes!**
### Example: Several single tickets
If you have two legs and need to buy two single tickets they will appear in each leg with the
- same `product.id` but different use-`id`.
+ same `FareProduct.id` but different `FareProductUse.id`.
+
+ **Illustration**
+ ```yaml
+ itinerary:
+ leg1:
+ fareProducts:
+ id: "AAA" // id of a FareProductUse instance, not product id
+ product:
+ id: "single-ticket" // product id
+ name: "Single Ticket"
+ leg2:
+ fareProducts:
+ id: "BBB" // different to leg1. the passenger needs to buy two single tickets.
+ product:
+ id: "single-ticket" // product id
+ name: "Single Ticket"
+ ```
+
"""
id: String!
@@ -1325,7 +1394,31 @@ type FareProductUse {
}
"A fare product (a ticket) to be bought by a passenger"
-type FareProduct {
+interface FareProduct {
+ "Identifier for the fare product."
+ id: String!
+
+ """
+ Human readable name of the product, for example example "Day pass" or "Single ticket".
+ """
+ name: String!
+
+ "The category of riders this product applies to, for example students or pensioners."
+ riderCategory: RiderCategory
+
+ """
+ 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
+}
+
+"""
+The standard case of a fare product: it only has a single price to be paid by the passenger
+and no discounts are applied.
+"""
+type DefaultFareProduct implements FareProduct {
"Identifier for the fare product."
id: String!
@@ -1382,12 +1475,6 @@ type Itinerary {
"""
legs: [Leg]!
- """
- Information about the fares for this itinerary. This is primarily a GTFS Fares V1 interface
- will be removed in the future.
- """
- fares: [fare] @deprecated(reason: "Use the leg's `fareProducts`.")
-
"""
How much elevation is gained, in total, over the course of the itinerary, in meters.
"""
@@ -1420,13 +1507,24 @@ type Itinerary {
More information is available in the [feature documentation](https://docs.opentripplanner.org/en/dev-2.x/sandbox/IBIAccessibilityScore/).
"""
accessibilityScore: Float
+
+ #
+ # deprecated fields
+ #
+
+ """
+ Information about the fares for this itinerary. This is primarily a GTFS Fares V1 interface
+ will be removed in the future.
+ """
+ fares: [fare] @deprecated(reason: "Use the leg's `fareProducts`.")
}
"A currency"
type Currency {
"ISO-4217 currency code, for example `USD` or `EUR`."
code: String!
- """Fractional digits of this currency. A value of 2 would express that in this currency
+ """
+ Fractional digits of this currency. A value of 2 would express that in this currency
100 minor units make up one major unit.
Expressed more concretely: 100 Euro-cents make up one Euro.
@@ -1443,7 +1541,10 @@ type Money {
"The currency of this money amount."
currency: Currency!
"""
- Money as a fractional amount, so 3.10 USD is represented as `3.1`.
+ Money in the major currency unit, so 3.10 USD is represented as `3.1`.
+
+ If you want to get the minor currency unit (310 cents), multiply with
+ (10 to the power of `currency.digits`).
"""
amount: Float!
}
@@ -1570,8 +1671,16 @@ type Leg {
Whether the destination of this leg (field `to`) is one of the intermediate places specified in the query.
"""
intermediatePlace: Boolean
+
+ "The turn-by-turn navigation instructions."
steps: [step]
+ """
+ For transit legs, the headsign that the vehicle shows at the stop where the passenger boards.
+ For non-transit legs, null.
+ """
+ headsign: String
+
"""
This is used to indicate if boarding this leg is possible only with special arrangements.
"""
@@ -1716,7 +1825,7 @@ enum Mode {
GONDOLA
"""Only used internally. No use for API users."""
- LEG_SWITCH
+ LEG_SWITCH @deprecated
"""RAIL"""
RAIL
@@ -2049,6 +2158,22 @@ type BookingInfo {
dropOffMessage: String
}
+"The board/alight position in between two stops of the pattern of a trip with continuous pickup/drop off."
+type PositionBetweenStops {
+ "Position of the previous stop in the pattern. Positions are not required to start from 0 or be consecutive."
+ previousPosition: Int
+ "Position of the next stop in the pattern. Positions are not required to start from 0 or be consecutive."
+ nextPosition: Int
+}
+
+"Stop position at a specific stop."
+type PositionAtStop {
+ "Position of the stop in the pattern. Positions are not required to start from 0 or be consecutive."
+ position: Int
+}
+
+union StopPosition = PositionAtStop | PositionBetweenStops
+
type Place {
"""
For transit stops, the name of the stop. For points of interest, the name of the POI.
@@ -2080,8 +2205,21 @@ type Place {
"""The stop related to the place."""
stop: Stop
- """The bike rental station related to the place"""
- bikeRentalStation: BikeRentalStation @deprecated(reason: "Use vehicleRentalStation and rentalVehicle instead")
+ """
+ The position of the stop in the pattern. This is not required to start from 0 or be consecutive - any
+ increasing integer sequence along the stops is valid.
+
+ The purpose of this field is to identify the stop within the pattern so it can be cross-referenced
+ between it and the itinerary. It is safe to cross-reference when done quickly, i.e. within seconds.
+ However, it should be noted that realtime updates can change the values, so don't store it for
+ longer amounts of time.
+
+ Depending on the source data, this might not be the GTFS `stop_sequence` but another value, perhaps
+ even generated.
+
+ The position can be either at a certain stop or in between two for trips where this is possible.
+ """
+ stopPosition: StopPosition
"""The vehicle rental station related to the place"""
vehicleRentalStation: VehicleRentalStation
@@ -2089,14 +2227,17 @@ type Place {
"""The rental vehicle related to the place"""
rentalVehicle: RentalVehicle
+ """The vehicle parking related to the place"""
+ vehicleParking: VehicleParking
+
+ """The bike rental station related to the place"""
+ bikeRentalStation: BikeRentalStation @deprecated(reason: "Use vehicleRentalStation and rentalVehicle instead")
+
"""The bike parking related to the place"""
bikePark: BikePark @deprecated(reason: "bikePark is deprecated. Use vehicleParking instead.")
"""The car parking related to the place"""
carPark: CarPark @deprecated(reason: "carPark is deprecated. Use vehicleParking instead.")
-
- """The vehicle parking related to the place"""
- vehicleParking: VehicleParking
}
type placeAtDistance implements Node {
@@ -2360,7 +2501,7 @@ type QueryType {
maxResults: Int = 20
"""
- Only return places that are one of these types, e.g. `STOP` or `BICYCLE_RENT`
+ Only return places that are one of these types, e.g. `STOP` or `VEHICLE_RENT`
"""
filterByPlaceTypes: [FilterPlaceType]
@@ -2599,6 +2740,8 @@ type QueryType {
"""
vehicleRentalStation(id: String!): VehicleRentalStation
+ ## Deprecated fields
+
"""Get all bike parks"""
bikeParks: [BikePark] @deprecated(reason: "bikeParks is deprecated. Use vehicleParkings instead.")
@@ -3234,6 +3377,9 @@ enum RelativeDirection {
ELEVATOR
UTURN_LEFT
UTURN_RIGHT
+ ENTER_STATION
+ EXIT_STATION
+ FOLLOW_SIGNS
}
"""The cardinal (compass) direction taken when engaging a walking/driving step."""
@@ -3248,6 +3394,65 @@ enum AbsoluteDirection {
NORTHWEST
}
+"""Occupancy status of a vehicle."""
+enum OccupancyStatus {
+ """Default. There is no occupancy-data on this departure."""
+ NO_DATA_AVAILABLE
+
+ """
+ The vehicle is considered empty by most measures, and has few or no passengers onboard, but is
+ still accepting passengers. There isn't a big difference between this and MANY_SEATS_AVAILABLE
+ so it's possible to handle them as the same value, if one wants to limit the number of different
+ values.
+ """
+ EMPTY
+
+ """
+ The vehicle or carriage has a large number of seats available. The amount of free seats out of
+ the total seats available to be considered large enough to fall into this category is
+ determined at the discretion of the producer. There isn't a big difference between this and
+ EMPTY so it's possible to handle them as the same value, if one wants to limit the number of
+ different values.
+ """
+ MANY_SEATS_AVAILABLE
+
+ """
+ The vehicle or carriage has a small number of seats available. The amount of free seats out of
+ the total seats available to be considered small enough to fall into this category is
+ determined at the discretion of the producer.
+ """
+ FEW_SEATS_AVAILABLE
+
+ """The vehicle or carriage can currently accommodate only standing passengers."""
+ STANDING_ROOM_ONLY
+
+ """
+ The vehicle or carriage can currently accommodate only standing passengers and has limited
+ space for them. There isn't a big difference between this and FULL so it's possible to handle
+ them as the same value, if one wants to limit the number of different values.
+ """
+ CRUSHED_STANDING_ROOM_ONLY
+
+ """
+ The vehicle is considered full by most measures, but may still be allowing passengers to
+ board.
+ """
+ FULL
+
+ """
+ The vehicle or carriage is not accepting passengers. The vehicle or carriage usually accepts
+ passengers for boarding.
+ """
+ NOT_ACCEPTING_PASSENGERS
+
+ """
+ The vehicle or carriage is not boardable and never accepts passengers. Useful for special
+ vehicles or carriages (engine, maintenance carriage, etc…). It might not make sense to show
+ these types of trips to passengers at all.
+ """
+ NOT_BOARDABLE
+}
+
type step {
"""The distance in meters that this step takes."""
distance: Float
@@ -3587,6 +3792,20 @@ type Stoptime {
"""The stop where this arrival/departure happens"""
stop: Stop
+ """
+ The sequence of the stop in the pattern. This is not required to start from 0 or be consecutive - any
+ increasing integer sequence along the stops is valid.
+
+ The purpose of this field is to identify the stop within the pattern so it can be cross-referenced
+ between it and the itinerary. It is safe to cross-reference when done quickly, i.e. within seconds.
+ However, it should be noted that realtime updates can change the values, so don't store it for
+ longer amounts of time.
+
+ Depending on the source data, this might not be the GTFS `stop_sequence` but another value, perhaps
+ even generated.
+ """
+ stopPosition: Int
+
"""
Scheduled arrival time. Format: seconds since midnight of the departure date
"""
@@ -3729,9 +3948,10 @@ type Trip implements Node {
"""List of dates when this trip is in service. Format: YYYYMMDD"""
activeDates: [String]
tripShortName: String
+
"""Headsign of the vehicle when running on this trip"""
tripHeadsign(
- """If translated headsign is found from gtfs translation.txt and wanted language is not same as
+ """If a translated headsign is found from GTFS translation.txt and wanted language is not same as
feed's language then returns wanted translation. Otherwise uses name from trip_headsign.txt.
"""
language: String): String
@@ -3811,6 +4031,12 @@ type Trip implements Node {
"""
types: [TripAlertType]
): [Alert]
+
+ """
+ The latest realtime occupancy information for the latest occurance of this
+ trip.
+ """
+ occupancy: TripOccupancy
}
"""Entities, which are relevant for a trip and can contain alerts"""
@@ -3837,6 +4063,17 @@ enum TripAlertType {
STOPS_ON_TRIP
}
+"""
+Occupancy of a vehicle on a trip. This should include the most recent occupancy information
+available for a trip. Historic data might not be available.
+"""
+type TripOccupancy {
+ """
+ Occupancy information mapped to a limited set of descriptive states.
+ """
+ occupancyStatus: OccupancyStatus
+}
+
"""
A system notice is used to tag elements with system information for debugging
or other system related purpose. One use-case is to run a routing search with
diff --git a/build/schema.json b/build/schema.json
index cee36de199..e913bb5c46 100644
--- a/build/schema.json
+++ b/build/schema.json
@@ -1921,10 +1921,99 @@
"enumValues": null,
"possibleTypes": null
},
+ {
+ "kind": "OBJECT",
+ "name": "DefaultFareProduct",
+ "description": "The standard case of a fare product: it only has a single price to be paid by the passenger\nand no discounts are applied.",
+ "fields": [
+ {
+ "name": "id",
+ "description": "Identifier for the fare product.",
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Human readable name of the product, for example example \"Day pass\" or \"Single ticket\".",
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "price",
+ "description": "The price of the product",
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "Money",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "riderCategory",
+ "description": "The category of riders this product applies to, for example students or pensioners.",
+ "args": [],
+ "type": {
+ "kind": "OBJECT",
+ "name": "RiderCategory",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "medium",
+ "description": "The 'medium' that this product applies to, for example \"Oyster Card\" or \"Berlin Ticket App\".\n\nThis communicates to riders that a specific way of buying or keeping this product is required.",
+ "args": [],
+ "type": {
+ "kind": "OBJECT",
+ "name": "FareMedium",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+ {
+ "kind": "INTERFACE",
+ "name": "FareProduct",
+ "ofType": null
+ }
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
{
"kind": "OBJECT",
"name": "DepartureRow",
- "description": "Departure row is a location, which lists departures of a certain pattern from a\nstop. Departure rows are identified with the pattern, so querying departure rows\nwill return only departures from one stop per pattern",
+ "description": "Departure row is a combination of a pattern and a stop of that pattern.\n\nThey are de-duplicated so for each pattern there will only be a single departure row.\n\nThis is useful if you want to show a list of stop/pattern combinations but want each pattern to be\nlisted only once.",
"fields": [
{
"name": "id",
@@ -2124,7 +2213,7 @@
"possibleTypes": null
},
{
- "kind": "OBJECT",
+ "kind": "INTERFACE",
"name": "FareProduct",
"description": "A fare product (a ticket) to be bought by a passenger",
"fields": [
@@ -2160,22 +2249,6 @@
"isDeprecated": false,
"deprecationReason": null
},
- {
- "name": "price",
- "description": "The price of the product",
- "args": [],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": {
- "kind": "OBJECT",
- "name": "Money",
- "ofType": null
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
{
"name": "riderCategory",
"description": "The category of riders this product applies to, for example students or pensioners.",
@@ -2204,7 +2277,13 @@
"inputFields": null,
"interfaces": [],
"enumValues": null,
- "possibleTypes": null
+ "possibleTypes": [
+ {
+ "kind": "OBJECT",
+ "name": "DefaultFareProduct",
+ "ofType": null
+ }
+ ]
},
{
"kind": "OBJECT",
@@ -2213,7 +2292,7 @@
"fields": [
{
"name": "id",
- "description": "Identifier local to the itinerary that allows to cross-reference and deduplicate\nfare products that span more than one leg.\n\nIf you want to uniquely identify the fare product itself (not its use) use the product's `id`.\n\n### Example: Day pass\n\nWhen a day pass is valid for all three legs in the itinerary it will appear\nfor each leg but with the same use-`id`.\n\n*It is the responsibility of the API consumers to display the day pass as a product for the\nentire itinerary rather than three day passes!*\n\n### Example: Several single tickets\n\nIf you have two legs and need to buy two single tickets they will appear in each leg with the\nsame `product.id` but different use-`id`.",
+ "description": "Represents the use of a single instance of a fare product throughout the itinerary. It can\nbe used to cross-reference and de-duplicate fare products that are applicable for more than one\nleg.\n\nIf you want to uniquely identify the fare product itself (not its use) use the product's `id`.\n\n### Example: Day pass\n\nThe day pass is valid for both legs in the itinerary. It is listed as the applicable `product` for each leg,\nand the same FareProductUse id is shown, indicating that only one pass was used/bought.\n\n**Illustration**\n```yaml\nitinerary:\n leg1:\n fareProducts:\n id: \"AAA\" // id of a FareProductUse instance\n product:\n id: \"day-pass\" // product id\n name: \"Day Pass\"\n leg2:\n fareProducts:\n id: \"AAA\" // identical to leg1. the passenger needs to buy ONE pass, not two.\n product:\n id: \"day-pass\" // product id\n name: \"Day Pass\"\n```\n\n**It is the responsibility of the API consumers to display the day pass as a product for the\nentire itinerary rather than two day passes!**\n\n### Example: Several single tickets\n\nIf you have two legs and need to buy two single tickets they will appear in each leg with the\nsame `FareProduct.id` but different `FareProductUse.id`.\n\n**Illustration**\n```yaml\nitinerary:\n leg1:\n fareProducts:\n id: \"AAA\" // id of a FareProductUse instance, not product id\n product:\n id: \"single-ticket\" // product id\n name: \"Single Ticket\"\n leg2:\n fareProducts:\n id: \"BBB\" // different to leg1. the passenger needs to buy two single tickets.\n product:\n id: \"single-ticket\" // product id\n name: \"Single Ticket\"\n```",
"args": [],
"type": {
"kind": "NON_NULL",
@@ -2232,7 +2311,7 @@
"description": "The purchasable fare product",
"args": [],
"type": {
- "kind": "OBJECT",
+ "kind": "INTERFACE",
"name": "FareProduct",
"ofType": null
},
@@ -3090,22 +3169,6 @@
"isDeprecated": false,
"deprecationReason": null
},
- {
- "name": "fares",
- "description": "Information about the fares for this itinerary. This is primarily a GTFS Fares V1 interface\nwill be removed in the future.",
- "args": [],
- "type": {
- "kind": "LIST",
- "name": null,
- "ofType": {
- "kind": "OBJECT",
- "name": "fare",
- "ofType": null
- }
- },
- "isDeprecated": true,
- "deprecationReason": "Use the leg's `fareProducts`."
- },
{
"name": "elevationGained",
"description": "How much elevation is gained, in total, over the course of the itinerary, in meters.",
@@ -3173,6 +3236,22 @@
},
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "fares",
+ "description": "Information about the fares for this itinerary. This is primarily a GTFS Fares V1 interface\nwill be removed in the future.",
+ "args": [],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "fare",
+ "ofType": null
+ }
+ },
+ "isDeprecated": true,
+ "deprecationReason": "Use the leg's `fareProducts`."
}
],
"inputFields": null,
@@ -3479,7 +3558,7 @@
},
{
"name": "steps",
- "description": null,
+ "description": "The turn-by-turn navigation instructions.",
"args": [],
"type": {
"kind": "LIST",
@@ -3493,6 +3572,18 @@
"isDeprecated": false,
"deprecationReason": null
},
+ {
+ "name": "headsign",
+ "description": "For transit legs, the headsign that the vehicle shows at the stop where the passenger boards.\nFor non-transit legs, null.",
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
{
"name": "pickupType",
"description": "This is used to indicate if boarding this leg is possible only with special arrangements.",
@@ -3888,8 +3979,8 @@
{
"name": "LEG_SWITCH",
"description": "Only used internally. No use for API users.",
- "isDeprecated": false,
- "deprecationReason": null
+ "isDeprecated": true,
+ "deprecationReason": "No longer supported"
},
{
"name": "RAIL",
@@ -3977,7 +4068,7 @@
},
{
"name": "amount",
- "description": "Money as a fractional amount, so 3.10 USD is represented as `3.1`.",
+ "description": "Money in the major currency unit, so 3.10 USD is represented as `3.1`.\n\nIf you want to get the minor currency unit (310 cents), multiply with\n(10 to the power of `currency.digits`).",
"args": [],
"type": {
"kind": "NON_NULL",
@@ -4110,6 +4201,71 @@
}
]
},
+ {
+ "kind": "ENUM",
+ "name": "OccupancyStatus",
+ "description": "Occupancy status of a vehicle.",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": [
+ {
+ "name": "NO_DATA_AVAILABLE",
+ "description": "Default. There is no occupancy-data on this departure.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "EMPTY",
+ "description": "The vehicle is considered empty by most measures, and has few or no passengers onboard, but is\nstill accepting passengers. There isn't a big difference between this and MANY_SEATS_AVAILABLE\nso it's possible to handle them as the same value, if one wants to limit the number of different\nvalues.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "MANY_SEATS_AVAILABLE",
+ "description": "The vehicle or carriage has a large number of seats available. The amount of free seats out of\nthe total seats available to be considered large enough to fall into this category is\ndetermined at the discretion of the producer. There isn't a big difference between this and\nEMPTY so it's possible to handle them as the same value, if one wants to limit the number of\ndifferent values.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "FEW_SEATS_AVAILABLE",
+ "description": "The vehicle or carriage has a small number of seats available. The amount of free seats out of\nthe total seats available to be considered small enough to fall into this category is\ndetermined at the discretion of the producer.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "STANDING_ROOM_ONLY",
+ "description": "The vehicle or carriage can currently accommodate only standing passengers.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "CRUSHED_STANDING_ROOM_ONLY",
+ "description": "The vehicle or carriage can currently accommodate only standing passengers and has limited\nspace for them. There isn't a big difference between this and FULL so it's possible to handle\nthem as the same value, if one wants to limit the number of different values.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "FULL",
+ "description": "The vehicle is considered full by most measures, but may still be allowing passengers to\nboard.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "NOT_ACCEPTING_PASSENGERS",
+ "description": "The vehicle or carriage is not accepting passengers. The vehicle or carriage usually accepts\npassengers for boarding.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "NOT_BOARDABLE",
+ "description": "The vehicle or carriage is not boardable and never accepts passengers. Useful for special\nvehicles or carriages (engine, maintenance carriage, etc…). It might not make sense to show\nthese types of trips to passengers at all.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "possibleTypes": null
+ },
{
"kind": "OBJECT",
"name": "OpeningHours",
@@ -4821,16 +4977,16 @@
"deprecationReason": null
},
{
- "name": "bikeRentalStation",
- "description": "The bike rental station related to the place",
+ "name": "stopPosition",
+ "description": "The position of the stop in the pattern. This is not required to start from 0 or be consecutive - any\nincreasing integer sequence along the stops is valid.\n\nThe purpose of this field is to identify the stop within the pattern so it can be cross-referenced\nbetween it and the itinerary. It is safe to cross-reference when done quickly, i.e. within seconds.\nHowever, it should be noted that realtime updates can change the values, so don't store it for\nlonger amounts of time.\n\nDepending on the source data, this might not be the GTFS `stop_sequence` but another value, perhaps\neven generated.\n\nThe position can be either at a certain stop or in between two for trips where this is possible.",
"args": [],
"type": {
- "kind": "OBJECT",
- "name": "BikeRentalStation",
+ "kind": "UNION",
+ "name": "StopPosition",
"ofType": null
},
- "isDeprecated": true,
- "deprecationReason": "Use vehicleRentalStation and rentalVehicle instead"
+ "isDeprecated": false,
+ "deprecationReason": null
},
{
"name": "vehicleRentalStation",
@@ -4856,6 +5012,30 @@
"isDeprecated": false,
"deprecationReason": null
},
+ {
+ "name": "vehicleParking",
+ "description": "The vehicle parking related to the place",
+ "args": [],
+ "type": {
+ "kind": "OBJECT",
+ "name": "VehicleParking",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "bikeRentalStation",
+ "description": "The bike rental station related to the place",
+ "args": [],
+ "type": {
+ "kind": "OBJECT",
+ "name": "BikeRentalStation",
+ "ofType": null
+ },
+ "isDeprecated": true,
+ "deprecationReason": "Use vehicleRentalStation and rentalVehicle instead"
+ },
{
"name": "bikePark",
"description": "The bike parking related to the place",
@@ -4879,18 +5059,6 @@
},
"isDeprecated": true,
"deprecationReason": "carPark is deprecated. Use vehicleParking instead."
- },
- {
- "name": "vehicleParking",
- "description": "The vehicle parking related to the place",
- "args": [],
- "type": {
- "kind": "OBJECT",
- "name": "VehicleParking",
- "ofType": null
- },
- "isDeprecated": false,
- "deprecationReason": null
}
],
"inputFields": null,
@@ -5215,6 +5383,64 @@
"enumValues": null,
"possibleTypes": null
},
+ {
+ "kind": "OBJECT",
+ "name": "PositionAtStop",
+ "description": "Stop position at a specific stop.",
+ "fields": [
+ {
+ "name": "position",
+ "description": "Position of the stop in the pattern. Positions are not required to start from 0 or be consecutive.",
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "PositionBetweenStops",
+ "description": "The board/alight position in between two stops of the pattern of a trip with continuous pickup/drop off.",
+ "fields": [
+ {
+ "name": "previousPosition",
+ "description": "Position of the previous stop in the pattern. Positions are not required to start from 0 or be consecutive.",
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nextPosition",
+ "description": "Position of the next stop in the pattern. Positions are not required to start from 0 or be consecutive.",
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
{
"kind": "ENUM",
"name": "PropulsionType",
@@ -5751,7 +5977,7 @@
},
{
"name": "filterByPlaceTypes",
- "description": "Only return places that are one of these types, e.g. `STOP` or `BICYCLE_RENT`",
+ "description": "Only return places that are one of these types, e.g. `STOP` or `VEHICLE_RENT`",
"type": {
"kind": "LIST",
"name": null,
@@ -7523,6 +7749,24 @@
"description": null,
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "ENTER_STATION",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "EXIT_STATION",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "FOLLOW_SIGNS",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
}
],
"possibleTypes": null
@@ -8297,49 +8541,43 @@
"enumValues": [
{
"name": "NO_TRANSIT_CONNECTION",
- "description": "No transit connection was found between the origin and destination withing the operating day or the next day",
+ "description": "No transit connection was found between the origin and destination within the operating day or\nthe next day, not even sub-optimal ones.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW",
- "description": "Transit connection was found, but it was outside the search window, see metadata for the next search window",
+ "description": "A transit connection was found, but it was outside the search window. See the metadata for a token\nfor retrieving the result outside the search window.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "OUTSIDE_SERVICE_PERIOD",
- "description": "The date specified is outside the range of data currently loaded into the system",
+ "description": "The date specified is outside the range of data currently loaded into the system as it is too\nfar into the future or the past.\n\nThe specific date range of the system is configurable by an administrator and also depends on\nthe input data provided.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "OUTSIDE_BOUNDS",
- "description": "The coordinates are outside the bounds of the data currently loaded into the system",
+ "description": "The coordinates are outside the geographic bounds of the transit and street data currently loaded\ninto the system and therefore cannot return any results.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "LOCATION_NOT_FOUND",
- "description": "The specified location is not close to any streets or transit stops",
+ "description": "The specified location is not close to any streets or transit stops currently loaded into the\nsystem, even though it is generally within its bounds.\n\nThis can happen when there is only transit but no street data coverage at the location in\nquestion.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "NO_STOPS_IN_RANGE",
- "description": "No stops are reachable from the location specified. You can try searching using a different access or egress mode",
+ "description": "No stops are reachable from the start or end locations specified.\n\nYou can try searching using a different access or egress mode, for example cycling instead of walking,\nincrease the walking/cycling/driving speed or have an administrator change the system's configuration\nso that stops further away are considered.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "WALKING_BETTER_THAN_TRANSIT",
- "description": "The origin and destination are so close to each other, that walking is always better, but no direct mode was specified for the search",
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "SYSTEM_ERROR",
- "description": "An unknown error happened during the search. The details have been logged to the server logs",
+ "description": "Transit connections were requested and found but because it is easier to just walk all the way\nto the destination they were removed.\n\nIf you want to still show the transit results, you need to make walking less desirable by\nincreasing the walk reluctance.",
"isDeprecated": false,
"deprecationReason": null
}
@@ -9191,6 +9429,27 @@
"enumValues": null,
"possibleTypes": null
},
+ {
+ "kind": "UNION",
+ "name": "StopPosition",
+ "description": null,
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": [
+ {
+ "kind": "OBJECT",
+ "name": "PositionAtStop",
+ "ofType": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "PositionBetweenStops",
+ "ofType": null
+ }
+ ]
+ },
{
"kind": "OBJECT",
"name": "StopRelationship",
@@ -9251,6 +9510,18 @@
"isDeprecated": false,
"deprecationReason": null
},
+ {
+ "name": "stopPosition",
+ "description": "The sequence of the stop in the pattern. This is not required to start from 0 or be consecutive - any\nincreasing integer sequence along the stops is valid.\n\nThe purpose of this field is to identify the stop within the pattern so it can be cross-referenced\nbetween it and the itinerary. It is safe to cross-reference when done quickly, i.e. within seconds.\nHowever, it should be noted that realtime updates can change the values, so don't store it for\nlonger amounts of time.\n\nDepending on the source data, this might not be the GTFS `stop_sequence` but another value, perhaps\neven generated.",
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
{
"name": "scheduledArrival",
"description": "Scheduled arrival time. Format: seconds since midnight of the departure date",
@@ -9877,7 +10148,7 @@
"args": [
{
"name": "language",
- "description": "If translated headsign is found from gtfs translation.txt and wanted language is not same as\nfeed's language then returns wanted translation. Otherwise uses name from trip_headsign.txt.",
+ "description": "If a translated headsign is found from GTFS translation.txt and wanted language is not same as\nfeed's language then returns wanted translation. Otherwise uses name from trip_headsign.txt.",
"type": {
"kind": "SCALAR",
"name": "String",
@@ -10169,6 +10440,18 @@
},
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "occupancy",
+ "description": "The latest realtime occupancy information for the latest occurance of this\ntrip.",
+ "args": [],
+ "type": {
+ "kind": "OBJECT",
+ "name": "TripOccupancy",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
}
],
"inputFields": null,
@@ -10229,6 +10512,29 @@
],
"possibleTypes": null
},
+ {
+ "kind": "OBJECT",
+ "name": "TripOccupancy",
+ "description": "Occupancy of a vehicle on a trip. This should include the most recent occupancy information\navailable for a trip. Historic data might not be available.",
+ "fields": [
+ {
+ "name": "occupancyStatus",
+ "description": "Occupancy information mapped to a limited set of descriptive states.",
+ "args": [],
+ "type": {
+ "kind": "ENUM",
+ "name": "OccupancyStatus",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
{
"kind": "OBJECT",
"name": "Unknown",
diff --git a/digitransit-search-util/packages/digitransit-search-util-query-utils/package.json b/digitransit-search-util/packages/digitransit-search-util-query-utils/package.json
index ec75898105..06884daaf9 100644
--- a/digitransit-search-util/packages/digitransit-search-util-query-utils/package.json
+++ b/digitransit-search-util/packages/digitransit-search-util-query-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@digitransit-search-util/digitransit-search-util-query-utils",
- "version": "2.0.8",
+ "version": "2.0.9",
"description": "digitransit-search-util query-utils module",
"main": "lib/index.js",
"publishConfig": {
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 dd6bf02526..276a8ae6f5 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
@@ -846,9 +846,12 @@ type debugOutput {
}
"""
-Departure row is a location, which lists departures of a certain pattern from a
-stop. Departure rows are identified with the pattern, so querying departure rows
-will return only departures from one stop per pattern
+Departure row is a combination of a pattern and a stop of that pattern.
+
+They are de-duplicated so for each pattern there will only be a single departure row.
+
+This is useful if you want to show a list of stop/pattern combinations but want each pattern to be
+listed only once.
"""
type DepartureRow implements Node & PlaceInterface {
"""
@@ -1236,29 +1239,59 @@ input InputUnpreferred {
}
enum RoutingErrorCode {
- """No transit connection was found between the origin and destination withing the operating day or the next day"""
+ """
+ No transit connection was found between the origin and destination within the operating day or
+ the next day, not even sub-optimal ones.
+ """
NO_TRANSIT_CONNECTION
- """Transit connection was found, but it was outside the search window, see metadata for the next search window"""
+ """
+ A transit connection was found, but it was outside the search window. See the metadata for a token
+ for retrieving the result outside the search window.
+ """
NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW
- """The date specified is outside the range of data currently loaded into the system"""
+ """
+ The date specified is outside the range of data currently loaded into the system as it is too
+ far into the future or the past.
+
+ The specific date range of the system is configurable by an administrator and also depends on
+ the input data provided.
+ """
OUTSIDE_SERVICE_PERIOD
- """The coordinates are outside the bounds of the data currently loaded into the system"""
+ """
+ The coordinates are outside the geographic bounds of the transit and street data currently loaded
+ into the system and therefore cannot return any results.
+ """
OUTSIDE_BOUNDS
- """The specified location is not close to any streets or transit stops"""
+ """
+ The specified location is not close to any streets or transit stops currently loaded into the
+ system, even though it is generally within its bounds.
+
+ This can happen when there is only transit but no street data coverage at the location in
+ question.
+ """
LOCATION_NOT_FOUND
- """No stops are reachable from the location specified. You can try searching using a different access or egress mode"""
+ """
+ No stops are reachable from the start or end locations specified.
+
+ You can try searching using a different access or egress mode, for example cycling instead of walking,
+ increase the walking/cycling/driving speed or have an administrator change the system's configuration
+ so that stops further away are considered.
+ """
NO_STOPS_IN_RANGE
- """The origin and destination are so close to each other, that walking is always better, but no direct mode was specified for the search"""
- WALKING_BETTER_THAN_TRANSIT
+ """
+ Transit connections were requested and found but because it is easier to just walk all the way
+ to the destination they were removed.
- """An unknown error happened during the search. The details have been logged to the server logs"""
- SYSTEM_ERROR
+ If you want to still show the transit results, you need to make walking less desirable by
+ increasing the walk reluctance.
+ """
+ WALKING_BETTER_THAN_TRANSIT
}
enum InputField {
@@ -1300,23 +1333,59 @@ type FareMedium {
"A container for both a fare product (a ticket) and its relationship to the itinerary."
type FareProductUse {
"""
- Identifier local to the itinerary that allows to cross-reference and deduplicate
- fare products that span more than one leg.
+ Represents the use of a single instance of a fare product throughout the itinerary. It can
+ be used to cross-reference and de-duplicate fare products that are applicable for more than one
+ leg.
If you want to uniquely identify the fare product itself (not its use) use the product's `id`.
### Example: Day pass
- When a day pass is valid for all three legs in the itinerary it will appear
- for each leg but with the same use-`id`.
-
- *It is the responsibility of the API consumers to display the day pass as a product for the
- entire itinerary rather than three day passes!*
+ The day pass is valid for both legs in the itinerary. It is listed as the applicable `product` for each leg,
+ and the same FareProductUse id is shown, indicating that only one pass was used/bought.
+
+ **Illustration**
+ ```yaml
+ itinerary:
+ leg1:
+ fareProducts:
+ id: "AAA" // id of a FareProductUse instance
+ product:
+ id: "day-pass" // product id
+ name: "Day Pass"
+ leg2:
+ fareProducts:
+ id: "AAA" // identical to leg1. the passenger needs to buy ONE pass, not two.
+ product:
+ id: "day-pass" // product id
+ name: "Day Pass"
+ ```
+
+ **It is the responsibility of the API consumers to display the day pass as a product for the
+ entire itinerary rather than two day passes!**
### Example: Several single tickets
If you have two legs and need to buy two single tickets they will appear in each leg with the
- same `product.id` but different use-`id`.
+ same `FareProduct.id` but different `FareProductUse.id`.
+
+ **Illustration**
+ ```yaml
+ itinerary:
+ leg1:
+ fareProducts:
+ id: "AAA" // id of a FareProductUse instance, not product id
+ product:
+ id: "single-ticket" // product id
+ name: "Single Ticket"
+ leg2:
+ fareProducts:
+ id: "BBB" // different to leg1. the passenger needs to buy two single tickets.
+ product:
+ id: "single-ticket" // product id
+ name: "Single Ticket"
+ ```
+
"""
id: String!
@@ -1325,7 +1394,31 @@ type FareProductUse {
}
"A fare product (a ticket) to be bought by a passenger"
-type FareProduct {
+interface FareProduct {
+ "Identifier for the fare product."
+ id: String!
+
+ """
+ Human readable name of the product, for example example "Day pass" or "Single ticket".
+ """
+ name: String!
+
+ "The category of riders this product applies to, for example students or pensioners."
+ riderCategory: RiderCategory
+
+ """
+ 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
+}
+
+"""
+The standard case of a fare product: it only has a single price to be paid by the passenger
+and no discounts are applied.
+"""
+type DefaultFareProduct implements FareProduct {
"Identifier for the fare product."
id: String!
@@ -1382,12 +1475,6 @@ type Itinerary {
"""
legs: [Leg]!
- """
- Information about the fares for this itinerary. This is primarily a GTFS Fares V1 interface
- will be removed in the future.
- """
- fares: [fare] @deprecated(reason: "Use the leg's `fareProducts`.")
-
"""
How much elevation is gained, in total, over the course of the itinerary, in meters.
"""
@@ -1420,13 +1507,24 @@ type Itinerary {
More information is available in the [feature documentation](https://docs.opentripplanner.org/en/dev-2.x/sandbox/IBIAccessibilityScore/).
"""
accessibilityScore: Float
+
+ #
+ # deprecated fields
+ #
+
+ """
+ Information about the fares for this itinerary. This is primarily a GTFS Fares V1 interface
+ will be removed in the future.
+ """
+ fares: [fare] @deprecated(reason: "Use the leg's `fareProducts`.")
}
"A currency"
type Currency {
"ISO-4217 currency code, for example `USD` or `EUR`."
code: String!
- """Fractional digits of this currency. A value of 2 would express that in this currency
+ """
+ Fractional digits of this currency. A value of 2 would express that in this currency
100 minor units make up one major unit.
Expressed more concretely: 100 Euro-cents make up one Euro.
@@ -1443,7 +1541,10 @@ type Money {
"The currency of this money amount."
currency: Currency!
"""
- Money as a fractional amount, so 3.10 USD is represented as `3.1`.
+ Money in the major currency unit, so 3.10 USD is represented as `3.1`.
+
+ If you want to get the minor currency unit (310 cents), multiply with
+ (10 to the power of `currency.digits`).
"""
amount: Float!
}
@@ -1570,8 +1671,16 @@ type Leg {
Whether the destination of this leg (field `to`) is one of the intermediate places specified in the query.
"""
intermediatePlace: Boolean
+
+ "The turn-by-turn navigation instructions."
steps: [step]
+ """
+ For transit legs, the headsign that the vehicle shows at the stop where the passenger boards.
+ For non-transit legs, null.
+ """
+ headsign: String
+
"""
This is used to indicate if boarding this leg is possible only with special arrangements.
"""
@@ -1716,7 +1825,7 @@ enum Mode {
GONDOLA
"""Only used internally. No use for API users."""
- LEG_SWITCH
+ LEG_SWITCH @deprecated
"""RAIL"""
RAIL
@@ -2049,6 +2158,22 @@ type BookingInfo {
dropOffMessage: String
}
+"The board/alight position in between two stops of the pattern of a trip with continuous pickup/drop off."
+type PositionBetweenStops {
+ "Position of the previous stop in the pattern. Positions are not required to start from 0 or be consecutive."
+ previousPosition: Int
+ "Position of the next stop in the pattern. Positions are not required to start from 0 or be consecutive."
+ nextPosition: Int
+}
+
+"Stop position at a specific stop."
+type PositionAtStop {
+ "Position of the stop in the pattern. Positions are not required to start from 0 or be consecutive."
+ position: Int
+}
+
+union StopPosition = PositionAtStop | PositionBetweenStops
+
type Place {
"""
For transit stops, the name of the stop. For points of interest, the name of the POI.
@@ -2080,8 +2205,21 @@ type Place {
"""The stop related to the place."""
stop: Stop
- """The bike rental station related to the place"""
- bikeRentalStation: BikeRentalStation @deprecated(reason: "Use vehicleRentalStation and rentalVehicle instead")
+ """
+ The position of the stop in the pattern. This is not required to start from 0 or be consecutive - any
+ increasing integer sequence along the stops is valid.
+
+ The purpose of this field is to identify the stop within the pattern so it can be cross-referenced
+ between it and the itinerary. It is safe to cross-reference when done quickly, i.e. within seconds.
+ However, it should be noted that realtime updates can change the values, so don't store it for
+ longer amounts of time.
+
+ Depending on the source data, this might not be the GTFS `stop_sequence` but another value, perhaps
+ even generated.
+
+ The position can be either at a certain stop or in between two for trips where this is possible.
+ """
+ stopPosition: StopPosition
"""The vehicle rental station related to the place"""
vehicleRentalStation: VehicleRentalStation
@@ -2089,14 +2227,17 @@ type Place {
"""The rental vehicle related to the place"""
rentalVehicle: RentalVehicle
+ """The vehicle parking related to the place"""
+ vehicleParking: VehicleParking
+
+ """The bike rental station related to the place"""
+ bikeRentalStation: BikeRentalStation @deprecated(reason: "Use vehicleRentalStation and rentalVehicle instead")
+
"""The bike parking related to the place"""
bikePark: BikePark @deprecated(reason: "bikePark is deprecated. Use vehicleParking instead.")
"""The car parking related to the place"""
carPark: CarPark @deprecated(reason: "carPark is deprecated. Use vehicleParking instead.")
-
- """The vehicle parking related to the place"""
- vehicleParking: VehicleParking
}
type placeAtDistance implements Node {
@@ -2360,7 +2501,7 @@ type QueryType {
maxResults: Int = 20
"""
- Only return places that are one of these types, e.g. `STOP` or `BICYCLE_RENT`
+ Only return places that are one of these types, e.g. `STOP` or `VEHICLE_RENT`
"""
filterByPlaceTypes: [FilterPlaceType]
@@ -2599,6 +2740,8 @@ type QueryType {
"""
vehicleRentalStation(id: String!): VehicleRentalStation
+ ## Deprecated fields
+
"""Get all bike parks"""
bikeParks: [BikePark] @deprecated(reason: "bikeParks is deprecated. Use vehicleParkings instead.")
@@ -3234,6 +3377,9 @@ enum RelativeDirection {
ELEVATOR
UTURN_LEFT
UTURN_RIGHT
+ ENTER_STATION
+ EXIT_STATION
+ FOLLOW_SIGNS
}
"""The cardinal (compass) direction taken when engaging a walking/driving step."""
@@ -3248,6 +3394,65 @@ enum AbsoluteDirection {
NORTHWEST
}
+"""Occupancy status of a vehicle."""
+enum OccupancyStatus {
+ """Default. There is no occupancy-data on this departure."""
+ NO_DATA_AVAILABLE
+
+ """
+ The vehicle is considered empty by most measures, and has few or no passengers onboard, but is
+ still accepting passengers. There isn't a big difference between this and MANY_SEATS_AVAILABLE
+ so it's possible to handle them as the same value, if one wants to limit the number of different
+ values.
+ """
+ EMPTY
+
+ """
+ The vehicle or carriage has a large number of seats available. The amount of free seats out of
+ the total seats available to be considered large enough to fall into this category is
+ determined at the discretion of the producer. There isn't a big difference between this and
+ EMPTY so it's possible to handle them as the same value, if one wants to limit the number of
+ different values.
+ """
+ MANY_SEATS_AVAILABLE
+
+ """
+ The vehicle or carriage has a small number of seats available. The amount of free seats out of
+ the total seats available to be considered small enough to fall into this category is
+ determined at the discretion of the producer.
+ """
+ FEW_SEATS_AVAILABLE
+
+ """The vehicle or carriage can currently accommodate only standing passengers."""
+ STANDING_ROOM_ONLY
+
+ """
+ The vehicle or carriage can currently accommodate only standing passengers and has limited
+ space for them. There isn't a big difference between this and FULL so it's possible to handle
+ them as the same value, if one wants to limit the number of different values.
+ """
+ CRUSHED_STANDING_ROOM_ONLY
+
+ """
+ The vehicle is considered full by most measures, but may still be allowing passengers to
+ board.
+ """
+ FULL
+
+ """
+ The vehicle or carriage is not accepting passengers. The vehicle or carriage usually accepts
+ passengers for boarding.
+ """
+ NOT_ACCEPTING_PASSENGERS
+
+ """
+ The vehicle or carriage is not boardable and never accepts passengers. Useful for special
+ vehicles or carriages (engine, maintenance carriage, etc…). It might not make sense to show
+ these types of trips to passengers at all.
+ """
+ NOT_BOARDABLE
+}
+
type step {
"""The distance in meters that this step takes."""
distance: Float
@@ -3587,6 +3792,20 @@ type Stoptime {
"""The stop where this arrival/departure happens"""
stop: Stop
+ """
+ The sequence of the stop in the pattern. This is not required to start from 0 or be consecutive - any
+ increasing integer sequence along the stops is valid.
+
+ The purpose of this field is to identify the stop within the pattern so it can be cross-referenced
+ between it and the itinerary. It is safe to cross-reference when done quickly, i.e. within seconds.
+ However, it should be noted that realtime updates can change the values, so don't store it for
+ longer amounts of time.
+
+ Depending on the source data, this might not be the GTFS `stop_sequence` but another value, perhaps
+ even generated.
+ """
+ stopPosition: Int
+
"""
Scheduled arrival time. Format: seconds since midnight of the departure date
"""
@@ -3729,9 +3948,10 @@ type Trip implements Node {
"""List of dates when this trip is in service. Format: YYYYMMDD"""
activeDates: [String]
tripShortName: String
+
"""Headsign of the vehicle when running on this trip"""
tripHeadsign(
- """If translated headsign is found from gtfs translation.txt and wanted language is not same as
+ """If a translated headsign is found from GTFS translation.txt and wanted language is not same as
feed's language then returns wanted translation. Otherwise uses name from trip_headsign.txt.
"""
language: String): String
@@ -3811,6 +4031,12 @@ type Trip implements Node {
"""
types: [TripAlertType]
): [Alert]
+
+ """
+ The latest realtime occupancy information for the latest occurance of this
+ trip.
+ """
+ occupancy: TripOccupancy
}
"""Entities, which are relevant for a trip and can contain alerts"""
@@ -3837,6 +4063,17 @@ enum TripAlertType {
STOPS_ON_TRIP
}
+"""
+Occupancy of a vehicle on a trip. This should include the most recent occupancy information
+available for a trip. Historic data might not be available.
+"""
+type TripOccupancy {
+ """
+ Occupancy information mapped to a limited set of descriptive states.
+ """
+ occupancyStatus: OccupancyStatus
+}
+
"""
A system notice is used to tag elements with system information for debugging
or other system related purpose. One use-case is to run a routing search with
diff --git a/static/assets/svg-sprite.default.svg b/static/assets/svg-sprite.default.svg
index 2dfcb8aff8..15c77b1938 100644
--- a/static/assets/svg-sprite.default.svg
+++ b/static/assets/svg-sprite.default.svg
@@ -323,6 +323,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/assets/svg-sprite.hsl.svg b/static/assets/svg-sprite.hsl.svg
index 3e9c0859ff..6712ea0dda 100644
--- a/static/assets/svg-sprite.hsl.svg
+++ b/static/assets/svg-sprite.hsl.svg
@@ -332,6 +332,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+