diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index 163b32de751..a160e197611 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -35,6 +35,7 @@ import org.opentripplanner.apis.gtfs.datafetchers.ContactInfoImpl; import org.opentripplanner.apis.gtfs.datafetchers.CoordinatesImpl; import org.opentripplanner.apis.gtfs.datafetchers.CurrencyImpl; +import org.opentripplanner.apis.gtfs.datafetchers.DatedStopTimeImpl; import org.opentripplanner.apis.gtfs.datafetchers.DatedTripImpl; import org.opentripplanner.apis.gtfs.datafetchers.DefaultFareProductImpl; import org.opentripplanner.apis.gtfs.datafetchers.DepartureRowImpl; @@ -180,6 +181,7 @@ protected static GraphQLSchema buildSchema() { .type(typeWiring.build(FareProductUseImpl.class)) .type(typeWiring.build(DefaultFareProductImpl.class)) .type(typeWiring.build(DatedTripImpl.class)) + .type(typeWiring.build(DatedStopTimeImpl.class)) .type(typeWiring.build(TripOccupancyImpl.class)) .build(); SchemaGenerator schemaGenerator = new SchemaGenerator(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/DatedStopTimeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/DatedStopTimeImpl.java new file mode 100644 index 00000000000..3ed12ac4fb6 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/DatedStopTimeImpl.java @@ -0,0 +1,117 @@ +package org.opentripplanner.apis.gtfs.datafetchers; + +import static org.opentripplanner.apis.gtfs.GraphQLUtils.stopTimeToInt; + +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import java.time.ZonedDateTime; +import org.opentripplanner.apis.gtfs.GraphQLRequestContext; +import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.apis.gtfs.mapping.PickDropMapper; +import org.opentripplanner.apis.gtfs.model.ArrivalDepartureTime; +import org.opentripplanner.framework.graphql.GraphQLUtils; +import org.opentripplanner.framework.time.ServiceDateUtils; +import org.opentripplanner.model.TripTimeOnDate; +import org.opentripplanner.transit.service.TransitService; + +public class DatedStopTimeImpl implements GraphQLDataFetchers.GraphQLDatedStopTime { + + @Override + public DataFetcher arrival() { + return environment -> { + var tripTime = getSource(environment); + var scheduledTime = getZonedDateTime(environment, tripTime.getScheduledArrival()); + if (scheduledTime == null) { + return null; + } + return tripTime.isRealtime() + ? ArrivalDepartureTime.of(scheduledTime, tripTime.getArrivalDelay()) + : ArrivalDepartureTime.ofStatic(scheduledTime); + }; + } + + @Override + public DataFetcher departure() { + return environment -> { + var tripTime = getSource(environment); + var scheduledTime = getZonedDateTime(environment, tripTime.getScheduledDeparture()); + if (scheduledTime == null) { + return null; + } + return tripTime.isRealtime() + ? ArrivalDepartureTime.of(scheduledTime, tripTime.getDepartureDelay()) + : ArrivalDepartureTime.ofStatic(scheduledTime); + }; + } + + @Override + public DataFetcher dropoffType() { + return environment -> PickDropMapper.map(getSource(environment).getDropoffType()); + } + + @Override + public DataFetcher headsign() { + return environment -> + GraphQLUtils.getTranslation(getSource(environment).getHeadsign(), environment); + } + + @Override + public DataFetcher pickupType() { + return environment -> PickDropMapper.map(getSource(environment).getPickupType()); + } + + @Override + public DataFetcher realtimeState() { + return environment -> { + var tripTime = getSource(environment); + // TODO support ADDED state + if (tripTime.isCanceledEffectively()) { + return GraphQLTypes.GraphQLStopRealTimeState.CANCELED; + } + if (tripTime.isNoDataStop()) { + return GraphQLTypes.GraphQLStopRealTimeState.NO_DATA; + } + if (tripTime.isRecordedStop()) { + return GraphQLTypes.GraphQLStopRealTimeState.RECORDED; + } + if (tripTime.isRealtime()) { + return GraphQLTypes.GraphQLStopRealTimeState.UPDATED; + } + return GraphQLTypes.GraphQLStopRealTimeState.UNUPDATED; + }; + } + + @Override + public DataFetcher stopPosition() { + return environment -> getSource(environment).getGtfsSequence(); + } + + @Override + public DataFetcher stop() { + return environment -> getSource(environment).getStop(); + } + + @Override + public DataFetcher timepoint() { + return environment -> getSource(environment).isTimepoint(); + } + + private TransitService getTransitService(DataFetchingEnvironment environment) { + return environment.getContext().transitService(); + } + + private ZonedDateTime getZonedDateTime(DataFetchingEnvironment environment, int time) { + var fixedTime = stopTimeToInt(time); + if (fixedTime == null) { + return null; + } + var serviceDate = getSource(environment).getServiceDay(); + TransitService transitService = getTransitService(environment); + return ServiceDateUtils.toZonedDateTime(serviceDate, transitService.getTimeZone(), fixedTime); + } + + private TripTimeOnDate getSource(DataFetchingEnvironment environment) { + return environment.getSource(); + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/DatedTripImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/DatedTripImpl.java index eb820fc06c5..5d567d83264 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/DatedTripImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/DatedTripImpl.java @@ -12,7 +12,7 @@ import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; -import org.opentripplanner.apis.gtfs.model.DatedTripTime; +import org.opentripplanner.apis.gtfs.model.ArrivalDepartureTime; import org.opentripplanner.ext.restapi.mapping.LocalDateMapper; import org.opentripplanner.framework.time.ServiceDateUtils; import org.opentripplanner.model.Timetable; @@ -32,7 +32,7 @@ public DataFetcher date() { } @Override - public DataFetcher end() { + public DataFetcher end() { return env -> { var tripTimes = getTripTimes(env); if (tripTimes == null) { @@ -44,8 +44,8 @@ public DataFetcher end() { return null; } return tripTimes.isRealtimeUpdated(stopIndex) - ? DatedTripTime.of(scheduledTime, tripTimes.getArrivalDelay(stopIndex)) - : DatedTripTime.ofStatic(scheduledTime); + ? ArrivalDepartureTime.of(scheduledTime, tripTimes.getArrivalDelay(stopIndex)) + : ArrivalDepartureTime.ofStatic(scheduledTime); }; } @@ -71,7 +71,7 @@ public DataFetcher route() { } @Override - public DataFetcher start() { + public DataFetcher start() { return env -> { var tripTimes = getTripTimes(env); if (tripTimes == null) { @@ -82,8 +82,8 @@ public DataFetcher start() { return null; } return tripTimes.isRealtimeUpdated(0) - ? DatedTripTime.of(scheduledTime, tripTimes.getDepartureDelay(0)) - : DatedTripTime.ofStatic(scheduledTime); + ? ArrivalDepartureTime.of(scheduledTime, tripTimes.getDepartureDelay(0)) + : ArrivalDepartureTime.ofStatic(scheduledTime); }; } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java index 706c5a4fd5e..2a433fadee0 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StoptimeImpl.java @@ -5,6 +5,7 @@ import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; +import org.opentripplanner.apis.gtfs.mapping.PickDropMapper; import org.opentripplanner.framework.graphql.GraphQLUtils; import org.opentripplanner.model.TripTimeOnDate; import org.opentripplanner.transit.model.timetable.RealTimeState; @@ -24,14 +25,7 @@ public DataFetcher departureDelay() { @Override public DataFetcher dropoffType() { - return environment -> - switch (getSource(environment).getDropoffType()) { - case SCHEDULED -> "SCHEDULED"; - case NONE -> "NONE"; - case CALL_AGENCY -> "CALL_AGENCY"; - case COORDINATE_WITH_DRIVER -> "COORDINATE_WITH_DRIVER"; - case CANCELLED -> null; - }; + return environment -> PickDropMapper.map(getSource(environment).getDropoffType()); } @Override @@ -42,14 +36,7 @@ public DataFetcher headsign() { @Override public DataFetcher pickupType() { - return environment -> - switch (getSource(environment).getPickupType()) { - case SCHEDULED -> "SCHEDULED"; - case NONE -> "NONE"; - case CALL_AGENCY -> "CALL_AGENCY"; - case COORDINATE_WITH_DRIVER -> "COORDINATE_WITH_DRIVER"; - case CANCELLED -> null; - }; + return environment -> PickDropMapper.map(getSource(environment).getPickupType()); } @Override diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 259d6db86b0..ff12746df68 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -20,8 +20,9 @@ import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLRelativeDirection; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLRoutingErrorCode; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLStopRealTimeState; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLTransitMode; -import org.opentripplanner.apis.gtfs.model.DatedTripTime; +import org.opentripplanner.apis.gtfs.model.ArrivalDepartureTime; import org.opentripplanner.apis.gtfs.model.FeedPublisher; import org.opentripplanner.apis.gtfs.model.PlanPageInfo; import org.opentripplanner.apis.gtfs.model.RideHailingProvider; @@ -141,6 +142,16 @@ public interface GraphQLAlert { /** Entity related to an alert */ public interface GraphQLAlertEntity extends TypeResolver {} + /** + * Timing of an arrival or a departure to or from a stop. May contain real-time information if + * available. + */ + public interface GraphQLArrivalDepartureTime { + public DataFetcher estimated(); + + public DataFetcher scheduledTime(); + } + /** Bike park represents a location where bicycles can be parked. */ public interface GraphQLBikePark { public DataFetcher bikeParkId(); @@ -316,11 +327,32 @@ public interface GraphQLCurrency { public DataFetcher digits(); } + /** Stoptime represents the time when a specific trip on a specific date arrives to and/or departs from a specific stop. */ + public interface GraphQLDatedStopTime { + public DataFetcher arrival(); + + public DataFetcher departure(); + + public DataFetcher dropoffType(); + + public DataFetcher headsign(); + + public DataFetcher pickupType(); + + public DataFetcher realtimeState(); + + public DataFetcher stop(); + + public DataFetcher stopPosition(); + + public DataFetcher timepoint(); + } + /** Trip on a specific date */ public interface GraphQLDatedTrip { public DataFetcher date(); - public DataFetcher end(); + public DataFetcher end(); public DataFetcher id(); @@ -328,7 +360,7 @@ public interface GraphQLDatedTrip { public DataFetcher route(); - public DataFetcher start(); + public DataFetcher start(); public DataFetcher> stops(); @@ -359,16 +391,6 @@ public interface GraphQLDatedTripEdge { public DataFetcher node(); } - /** - * Information about a dated trip's start or end times. May contain real-time information if - * available. - */ - public interface GraphQLDatedTripTime { - public DataFetcher estimated(); - - public DataFetcher scheduledTime(); - } - /** * The standard case of a fare product: it only has a single price to be paid by the passenger * and no discounts are applied. diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index acac4d61969..3d9b627154a 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -611,6 +611,25 @@ public enum GraphQLCyclingOptimizationType { SHORTEST_DURATION, } + public static class GraphQLDatedStopTimeHeadsignArgs { + + private String language; + + public GraphQLDatedStopTimeHeadsignArgs(Map args) { + if (args != null) { + this.language = (String) args.get("language"); + } + } + + public String getGraphQLLanguage() { + return this.language; + } + + public void setGraphQLLanguage(String language) { + this.language = language; + } + } + public static class GraphQLDatedTripTripHeadsignArgs { private String language; @@ -4584,6 +4603,16 @@ public enum GraphQLStopAlertType { TRIPS, } + /** Whether stop has been updated through a realtime update and if so, how. */ + public enum GraphQLStopRealTimeState { + ADDED, + CANCELED, + NO_DATA, + RECORDED, + UNUPDATED, + UPDATED, + } + public static class GraphQLStoptimeHeadsignArgs { private String language; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml index 6eb98a7f95b..1d5ebd31a4f 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml @@ -59,10 +59,11 @@ config: ContactInfo: org.opentripplanner.transit.model.organization.ContactInfo Cluster: Object Coordinates: org.locationtech.jts.geom.Coordinate#Coordinate + DatedStopTime: org.opentripplanner.model.TripTimeOnDate#TripTimeOnDate DatedTrip: org.opentripplanner.transit.model.timetable.DatedTrip#DatedTrip DatedTripConnection: graphql.relay.Connection#Connection DatedTripEdge: graphql.relay.Edge#Edge - DatedTripTime: org.opentripplanner.apis.gtfs.model.DatedTripTime#DatedTripTime + ArrivalDepartureTime: org.opentripplanner.apis.gtfs.model.ArrivalDepartureTime#ArrivalDepartureTime debugOutput: org.opentripplanner.api.resource.DebugOutput#DebugOutput DepartureRow: org.opentripplanner.routing.graphfinder.PatternAtStop#PatternAtStop elevationProfileComponent: org.opentripplanner.model.plan.ElevationProfile.Step @@ -101,6 +102,7 @@ config: stopAtDistanceEdge: graphql.relay.Edge#Edge StopOnRoute: org.opentripplanner.apis.gtfs.model.StopOnRouteModel#StopOnRouteModel StopOnTrip: org.opentripplanner.apis.gtfs.model.StopOnTripModel#StopOnTripModel + StopRealTimeState: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLStopRealTimeState#GraphQLStopRealTimeState Stoptime: org.opentripplanner.model.TripTimeOnDate#TripTimeOnDate StoptimesInPattern: org.opentripplanner.model.StopTimesInPattern#StopTimesInPattern TicketType: org.opentripplanner.ext.fares.model.FareRuleSet#FareRuleSet diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/PickDropMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/PickDropMapper.java new file mode 100644 index 00000000000..c8e4d212999 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/PickDropMapper.java @@ -0,0 +1,18 @@ +package org.opentripplanner.apis.gtfs.mapping; + +import javax.annotation.Nullable; +import org.opentripplanner.model.PickDrop; + +public final class PickDropMapper { + + @Nullable + public static String map(PickDrop pickDrop) { + return switch (pickDrop) { + case SCHEDULED -> "SCHEDULED"; + case NONE -> "NONE"; + case CALL_AGENCY -> "CALL_AGENCY"; + case COORDINATE_WITH_DRIVER -> "COORDINATE_WITH_DRIVER"; + case CANCELLED -> null; + }; + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/DatedTripTime.java b/src/main/java/org/opentripplanner/apis/gtfs/model/ArrivalDepartureTime.java similarity index 56% rename from src/main/java/org/opentripplanner/apis/gtfs/model/DatedTripTime.java rename to src/main/java/org/opentripplanner/apis/gtfs/model/ArrivalDepartureTime.java index a83eb50fe1d..529d83c7459 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/model/DatedTripTime.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/model/ArrivalDepartureTime.java @@ -6,21 +6,22 @@ import javax.annotation.Nullable; /** - * A scheduled time of a trip's start or end with an optional realtime information. + * Timing of an arrival or a departure to or from a stop. May contain real-time information + * if available. */ -public record DatedTripTime( +public record ArrivalDepartureTime( @Nonnull ZonedDateTime scheduledTime, @Nullable RealTimeEstimate estimated ) { @Nonnull - public static DatedTripTime of(ZonedDateTime realtime, int delaySecs) { + public static ArrivalDepartureTime of(ZonedDateTime realtime, int delaySecs) { var delay = Duration.ofSeconds(delaySecs); - return new DatedTripTime(realtime.minus(delay), new RealTimeEstimate(realtime, delay)); + return new ArrivalDepartureTime(realtime.minus(delay), new RealTimeEstimate(realtime, delay)); } @Nonnull - public static DatedTripTime ofStatic(ZonedDateTime staticTime) { - return new DatedTripTime(staticTime, null); + public static ArrivalDepartureTime ofStatic(ZonedDateTime staticTime) { + return new ArrivalDepartureTime(staticTime, null); } /** diff --git a/src/main/java/org/opentripplanner/model/TripTimeOnDate.java b/src/main/java/org/opentripplanner/model/TripTimeOnDate.java index 1bfb0184138..8128c36e4db 100644 --- a/src/main/java/org/opentripplanner/model/TripTimeOnDate.java +++ b/src/main/java/org/opentripplanner/model/TripTimeOnDate.java @@ -190,6 +190,14 @@ public boolean isNoDataStop() { return tripTimes.isNoDataStop(stopIndex); } + /** + * Is the real-time time a recorded time (i.e. has the vehicle already passed the stop). + * This information is currently only available from SIRI feeds. + */ + public boolean isRecordedStop() { + return tripTimes.isRecordedStop(stopIndex); + } + public RealTimeState getRealTimeState() { return tripTimes.isNoDataStop(stopIndex) ? RealTimeState.SCHEDULED diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index fb287c5c117..dcef5f67096 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -154,6 +154,17 @@ type Alert implements Node { 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.") } +""" +Timing of an arrival or a departure to or from a stop. May contain real-time information if +available. +""" +type ArrivalDepartureTime { + "The estimated time of the event. If no real-time information is available, this is null." + estimated: RealTimeEstimate + "The scheduled time of the event." + scheduledTime: OffsetDateTime +} + "Bike park represents a location where bicycles can be parked." type BikePark implements Node & PlaceInterface { "ID of the bike park" @@ -384,6 +395,55 @@ type Currency { digits: Int! } +"Stoptime represents the time when a specific trip on a specific date arrives to and/or departs from a specific stop." +type DatedStopTime { + "Scheduled arrival time to the stop and a realtime estimate, if one exists." + arrival: ArrivalDepartureTime + "Scheduled departure time from the stop and a realtime estimate, if one exists." + departure: ArrivalDepartureTime + """ + Whether the vehicle can be disembarked at this stop. This field can also be + used to indicate if disembarkation is possible only with special arrangements. + """ + dropoffType: PickupDropoffType + """ + Vehicle headsign of the trip on this stop. Trip headsigns can change during + the trip (e.g. on routes which run on loops), so this value should be used + instead of `tripHeadsign` to display the headsign relevant to the user. + """ + headsign( + """ + If 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 + """ + Whether the vehicle can be boarded at this stop. This field can also be used + to indicate if boarding is possible only with special arrangements. + """ + pickupType: PickupDropoffType + "Whether stop has been updated through a realtime update and if so, how." + realtimeState: StopRealTimeState! + "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 real-time 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 + "true, if this stop is used as a time equalization stop. false otherwise." + timepoint: Boolean +} + "Trip on a specific date" type DatedTrip implements Node { """ @@ -395,7 +455,7 @@ type DatedTrip implements Node { """ date: LocalDate! "The time when the trip ends including real-time information, if available." - end: DatedTripTime + end: ArrivalDepartureTime "Global object ID provided by Relay. This value can be used to refetch this object using **node** query." id: ID! "The pattern the trip is running on" @@ -403,11 +463,11 @@ type DatedTrip implements Node { "The route the trip is running on" route: Route! "The time when the trip starts including real-time information, if available." - start: DatedTripTime + start: ArrivalDepartureTime "List of stops this trip passes through" stops: [Stop!]! "List of times when this trip arrives to or departs from a stop" - stoptimes: [Stoptime] + stoptimes: [DatedStopTime] "Headsign of the vehicle when running on this trip" tripHeadsign( """ @@ -453,17 +513,6 @@ type DatedTripEdge { node: DatedTrip } -""" -Information about a dated trip's start or end times. May contain real-time information if -available. -""" -type DatedTripTime { - "The estimated time of the event. If no real-time information is available, this is null." - estimated: RealTimeEstimate - "The scheduled time of the event." - scheduledTime: OffsetDateTime -} - """ The standard case of a fare product: it only has a single price to be paid by the passenger and no discounts are applied. @@ -3517,6 +3566,25 @@ enum StopAlertType { TRIPS } +"Whether stop has been updated through a realtime update and if so, how." +enum StopRealTimeState { + "The stop has been added through a realtime update." + ADDED + "The stop has been cancelled through a realtime update." + CANCELED + "The realtime feed has indicated that there is no data available for this stop." + NO_DATA + """ + The vehicle has arrived to the stop or already visited it and the times are no longer estimates. + Note, not all realtime feeds indicate this information even if the vehicle has already passed the stop. + """ + RECORDED + "There have been no realtime updates." + UNUPDATED + "The trip's arrival and/or departure time has been updated." + UPDATED +} + """ Transit modes include modes that are used within organized transportation networks run by public transportation authorities, taxi companies etc.