Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import Occupancy Status from GTFS-RT Vehicle Positions #5372

Merged
merged 28 commits into from Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c19a0a4
Expand OccupancyStatus model to include all GTFS RT values
optionsome Sep 18, 2023
e84a514
Load occupancy status from GTFS RT into positions
optionsome Sep 21, 2023
b494353
Support fuzzy trip matching for vehicle positions
optionsome Sep 21, 2023
da163fd
Expose occupancy status for a trip through GTFS API
optionsome Sep 21, 2023
7946a9b
Rename VehiclePosition -> RealtimeVehicle apart from updater
optionsome Sep 22, 2023
0e145ea
Make vehicle position features configurable
optionsome Sep 22, 2023
d962c97
Fix npes in vehicle positions impl
optionsome Sep 22, 2023
abbc08b
Add mapping from internal enum into API
optionsome Sep 22, 2023
374f961
Rename builder setter methods
optionsome Sep 22, 2023
ea8baea
Use static imports
optionsome Sep 22, 2023
e978975
Add default for config
optionsome Sep 26, 2023
bb0e0bd
Include more types into transmodel API and less to GTFS
optionsome Sep 26, 2023
a1daee4
Merge remote-tracking branch 'upstream/dev-2.x' into gtfsrt-occupancy
optionsome Sep 28, 2023
2ed9ccf
Refactor RealtimeVehicle to return optionals
optionsome Sep 28, 2023
865947f
Include transit service in realtime vehicle service
optionsome Sep 28, 2023
4ea77e8
Fix constructor
optionsome Sep 29, 2023
00083dc
Add graphql tests
optionsome Sep 29, 2023
72baea8
Improve service testability and docs
optionsome Sep 29, 2023
91d1fb9
Update tests
optionsome Sep 29, 2023
292f250
Don't use static import when mapping from one enum to another to avoi…
optionsome Sep 29, 2023
ec3677a
Merge remote-tracking branch 'upstream/dev-2.x' into gtfsrt-occupancy
optionsome Oct 6, 2023
3aa86e5
Include SIRI nordic profile docs
optionsome Oct 6, 2023
a6311d5
Apply suggestions from code review
optionsome Oct 13, 2023
832f037
Update and refactor documentation
optionsome Oct 13, 2023
0b724bb
Update src/main/java/org/opentripplanner/standalone/config/framework/…
optionsome Oct 13, 2023
8533c21
Update src/main/java/org/opentripplanner/service/realtimevehicles/int…
optionsome Oct 16, 2023
3aa9693
Fix schema comment formatting
optionsome Oct 16, 2023
7793f69
Return immutable sets and fix default value
optionsome Oct 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/RouterConfiguration.md
Expand Up @@ -730,7 +730,11 @@ HTTP headers to add to the request. Any header key, value can be inserted.
"frequency" : "1m",
"headers" : {
"Header-Name" : "Header-Value"
}
},
"fuzzyTripMatching" : false,
"features" : [
"position"
]
},
{
"type" : "websocket-gtfs-rt-updater"
Expand Down
30 changes: 22 additions & 8 deletions docs/UpdaterConfig.md
Expand Up @@ -223,17 +223,27 @@ The information is downloaded in a single HTTP request and polled regularly.
<!-- vehicle-positions BEGIN -->
<!-- NOTE! This section is auto-generated. Do not change, change doc in code instead. -->

| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
|----------------------------|:---------------:|----------------------------------------------------------------------------|:----------:|---------------|:-----:|
| type = "vehicle-positions" | `enum` | The type of the updater. | *Required* | | 1.5 |
| feedId | `string` | Feed ID to which the update should be applied. | *Required* | | 2.2 |
| frequency | `duration` | How often the positions should be updated. | *Optional* | `"PT1M"` | 2.2 |
| url | `uri` | The URL of GTFS-RT protobuf HTTP resource to download the positions from. | *Required* | | 2.2 |
| [headers](#u__6__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.3 |
| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
|-----------------------------|:---------------:|----------------------------------------------------------------------------|:----------:|---------------|:-----:|
| type = "vehicle-positions" | `enum` | The type of the updater. | *Required* | | 1.5 |
| feedId | `string` | Feed ID to which the update should be applied. | *Required* | | 2.2 |
| frequency | `duration` | How often the positions should be updated. | *Optional* | `"PT1M"` | 2.2 |
optionsome marked this conversation as resolved.
Show resolved Hide resolved
| fuzzyTripMatching | `boolean` | Whether to match trips fuzzily. | *Optional* | `false` | 2.5 |
| url | `uri` | The URL of GTFS-RT protobuf HTTP resource to download the positions from. | *Required* | | 2.2 |
| [features](#u__6__features) | `enum set` | Which features of GTFS RT vehicle positions should be loaded into OTP. | *Optional* | | 2.5 |
optionsome marked this conversation as resolved.
Show resolved Hide resolved
| [headers](#u__6__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.3 |


##### Parameter details

<h4 id="u__6__features">features</h4>

**Since version:** `2.5`**Type:** `enum set`**Cardinality:** `Optional`
**Path:** /updaters/[6]
**Enum values:** `position` | `stop-position` | `occupancy`

Which features of GTFS RT vehicle positions should be loaded into OTP.

<h4 id="u__6__headers">headers</h4>

**Since version:** `2.3`**Type:** `map of string`**Cardinality:** `Optional`
Expand All @@ -256,7 +266,11 @@ HTTP headers to add to the request. Any header key, value can be inserted.
"frequency" : "1m",
"headers" : {
"Header-Name" : "Header-Value"
}
},
"fuzzyTripMatching" : false,
"features" : [
"position"
]
}
]
}
Expand Down
Expand Up @@ -68,7 +68,7 @@
import org.opentripplanner.routing.impl.TransitAlertServiceImpl;
import org.opentripplanner.routing.services.TransitAlertService;
import org.opentripplanner.routing.vehicle_parking.VehicleParking;
import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService;
import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService;
import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService;
import org.opentripplanner.standalone.config.framework.json.JsonSupport;
import org.opentripplanner.test.support.FilePatternSource;
Expand Down Expand Up @@ -222,7 +222,7 @@ public TransitAlertService getTransitAlertService() {
new DefaultFareService(),
graph.getVehicleParkingService(),
new DefaultVehicleRentalService(),
new DefaultVehiclePositionService(),
new DefaultRealtimeVehicleService(),
GraphFinder.getInstance(graph, transitService::findRegularStop),
new RouteRequest()
);
Expand Down
Expand Up @@ -29,7 +29,7 @@
import org.opentripplanner.routing.core.BicycleOptimizeType;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graphfinder.GraphFinder;
import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService;
import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService;
import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService;
import org.opentripplanner.test.support.VariableSource;
import org.opentripplanner.transit.service.DefaultTransitService;
Expand All @@ -51,7 +51,7 @@ class RouteRequestMapperTest implements PlanTestConstants {
new DefaultFareService(),
graph.getVehicleParkingService(),
new DefaultVehicleRentalService(),
new DefaultVehiclePositionService(),
new DefaultRealtimeVehicleService(),
GraphFinder.getInstance(graph, transitService::findRegularStop),
new RouteRequest()
);
Expand Down
Expand Up @@ -11,7 +11,6 @@
import graphql.schema.DataFetchingEnvironmentImpl;
import io.micrometer.core.instrument.Metrics;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
Expand All @@ -29,7 +28,7 @@
import org.opentripplanner.routing.api.request.preference.TimeSlopeSafetyTriangle;
import org.opentripplanner.routing.core.BicycleOptimizeType;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService;
import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService;
import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService;
import org.opentripplanner.service.worldenvelope.internal.DefaultWorldEnvelopeRepository;
import org.opentripplanner.service.worldenvelope.internal.DefaultWorldEnvelopeService;
Expand Down Expand Up @@ -74,7 +73,7 @@ public class TripRequestMapperTest implements PlanTestConstants {
Metrics.globalRegistry,
RouterConfig.DEFAULT.vectorTileLayers(),
new DefaultWorldEnvelopeService(new DefaultWorldEnvelopeRepository()),
new DefaultVehiclePositionService(),
new DefaultRealtimeVehicleService(),
new DefaultVehicleRentalService(),
RouterConfig.DEFAULT.flexConfig(),
List.of(),
Expand Down
Expand Up @@ -6,7 +6,7 @@
import org.opentripplanner.routing.fares.FareService;
import org.opentripplanner.routing.graphfinder.GraphFinder;
import org.opentripplanner.routing.vehicle_parking.VehicleParkingService;
import org.opentripplanner.service.vehiclepositions.VehiclePositionService;
import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService;
import org.opentripplanner.service.vehiclerental.VehicleRentalService;
import org.opentripplanner.standalone.api.OtpServerRequestContext;
import org.opentripplanner.transit.service.TransitService;
Expand All @@ -17,7 +17,7 @@ public record GraphQLRequestContext(
FareService fareService,
VehicleParkingService vehicleParkingService,
VehicleRentalService vehicleRentalService,
VehiclePositionService vehiclePositionService,
RealtimeVehicleService realtimeVehicleService,
GraphFinder graphFinder,
RouteRequest defaultRouteRequest
) {
Expand All @@ -28,7 +28,7 @@ public static GraphQLRequestContext ofServerContext(OtpServerRequestContext cont
context.graph().getFareService(),
context.graph().getVehicleParkingService(),
context.vehicleRentalService(),
context.vehiclePositionService(),
context.realtimeVehicleService(),
context.graphFinder(),
context.defaultRouteRequest()
);
Expand Down
Expand Up @@ -22,8 +22,8 @@
import org.opentripplanner.routing.alertpatch.EntitySelector;
import org.opentripplanner.routing.alertpatch.TransitAlert;
import org.opentripplanner.routing.services.TransitAlertService;
import org.opentripplanner.service.vehiclepositions.VehiclePositionService;
import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition;
import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService;
import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.network.TripPattern;
Expand Down Expand Up @@ -227,9 +227,9 @@ public DataFetcher<Iterable<Trip>> tripsForDate() {
}

@Override
public DataFetcher<Iterable<RealtimeVehiclePosition>> vehiclePositions() {
public DataFetcher<Iterable<RealtimeVehicle>> vehiclePositions() {
return environment ->
getVehiclePositionsService(environment).getVehiclePositions(this.getSource(environment));
getRealtimeVehiclesService(environment).getRealtimeVehicles(this.getSource(environment));
}

private Agency getAgency(DataFetchingEnvironment environment) {
Expand All @@ -252,8 +252,8 @@ private List<Trip> getTrips(DataFetchingEnvironment environment) {
return getSource(environment).scheduledTripsAsStream().collect(Collectors.toList());
}

private VehiclePositionService getVehiclePositionsService(DataFetchingEnvironment environment) {
return environment.<GraphQLRequestContext>getContext().vehiclePositionService();
private RealtimeVehicleService getRealtimeVehiclesService(DataFetchingEnvironment environment) {
return environment.<GraphQLRequestContext>getContext().realtimeVehicleService();
}

private TransitService getTransitService(DataFetchingEnvironment environment) {
Expand Down
Expand Up @@ -4,7 +4,7 @@
import graphql.schema.DataFetchingEnvironment;
import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLDataFetchers.GraphQLStopRelationship;
import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLVehicleStopStatus;
import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopRelationship;
import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopRelationship;

public class StopRelationshipImpl implements GraphQLStopRelationship {

Expand Down
Expand Up @@ -21,12 +21,14 @@
import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLDataFetchers;
import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes;
import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLWheelchairBoarding;
import org.opentripplanner.ext.gtfsgraphqlapi.model.TripOccupancy;
import org.opentripplanner.framework.time.ServiceDateUtils;
import org.opentripplanner.model.Timetable;
import org.opentripplanner.model.TripTimeOnDate;
import org.opentripplanner.routing.alertpatch.EntitySelector;
import org.opentripplanner.routing.alertpatch.TransitAlert;
import org.opentripplanner.routing.services.TransitAlertService;
import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.network.TripPattern;
Expand Down Expand Up @@ -372,6 +374,17 @@ public DataFetcher<GraphQLWheelchairBoarding> wheelchairAccessible() {
return environment -> GraphQLUtils.toGraphQL(getSource(environment).getWheelchairBoarding());
}

@Override
public DataFetcher<TripOccupancy> occupancy() {
return environment -> {
Trip trip = getSource(environment);
TripPattern pattern = getTransitService(environment).getPatternForTrip(trip);
return new TripOccupancy(
getRealtimeVehiclesService(environment).getVehicleOccupancyStatus(pattern, trip.getId())
);
optionsome marked this conversation as resolved.
Show resolved Hide resolved
};
}

private List<Object> getStops(DataFetchingEnvironment environment) {
TripPattern tripPattern = getTripPattern(environment);
if (tripPattern == null) {
Expand All @@ -396,6 +409,10 @@ private TransitService getTransitService(DataFetchingEnvironment environment) {
return environment.<GraphQLRequestContext>getContext().transitService();
}

private RealtimeVehicleService getRealtimeVehiclesService(DataFetchingEnvironment environment) {
return environment.<GraphQLRequestContext>getContext().realtimeVehicleService();
}

private Trip getSource(DataFetchingEnvironment environment) {
return environment.getSource();
}
Expand Down
Expand Up @@ -3,8 +3,8 @@
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLDataFetchers.GraphQLVehiclePosition;
import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition;
import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopRelationship;
import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle;
import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopRelationship;
import org.opentripplanner.transit.model.timetable.Trip;

public class VehiclePositionImpl implements GraphQLVehiclePosition {
Expand Down Expand Up @@ -54,7 +54,7 @@ public DataFetcher<String> vehicleId() {
return env -> getSource(env).vehicleId().toString();
}

private RealtimeVehiclePosition getSource(DataFetchingEnvironment environment) {
private RealtimeVehicle getSource(DataFetchingEnvironment environment) {
return environment.getSource();
}
}
Expand Up @@ -21,6 +21,7 @@
import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLTransitMode;
import org.opentripplanner.ext.gtfsgraphqlapi.model.RideHailingProvider;
import org.opentripplanner.ext.gtfsgraphqlapi.model.StopPosition;
import org.opentripplanner.ext.gtfsgraphqlapi.model.TripOccupancy;
import org.opentripplanner.ext.ridehailing.model.RideEstimate;
import org.opentripplanner.model.StopTimesInPattern;
import org.opentripplanner.model.SystemNotice;
Expand All @@ -43,8 +44,8 @@
import org.opentripplanner.routing.vehicle_parking.VehicleParking;
import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces;
import org.opentripplanner.routing.vehicle_parking.VehicleParkingState;
import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition;
import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopRelationship;
import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle;
import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopRelationship;
import org.opentripplanner.service.vehiclerental.model.RentalVehicleType;
import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace;
import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation;
Expand Down Expand Up @@ -309,9 +310,12 @@ public interface GraphQLDefaultFareProduct {
}

/**
* 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.
*/
public interface GraphQLDepartureRow {
public DataFetcher<graphql.relay.Relay.ResolvedGlobalId> id();
Expand Down Expand Up @@ -556,7 +560,7 @@ public interface GraphQLPattern {

public DataFetcher<Iterable<Trip>> tripsForDate();

public DataFetcher<Iterable<RealtimeVehiclePosition>> vehiclePositions();
public DataFetcher<Iterable<RealtimeVehicle>> vehiclePositions();
}

public interface GraphQLPlace {
Expand Down Expand Up @@ -1029,6 +1033,8 @@ public interface GraphQLTrip {

public DataFetcher<graphql.relay.Relay.ResolvedGlobalId> id();

public DataFetcher<TripOccupancy> occupancy();

public DataFetcher<TripPattern> pattern();

public DataFetcher<Route> route();
Expand Down Expand Up @@ -1056,6 +1062,14 @@ public interface GraphQLTrip {
public DataFetcher<org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLWheelchairBoarding> wheelchairAccessible();
}

/**
* 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.
*/
public interface GraphQLTripOccupancy {
public DataFetcher<Object> occupancyStatus();
}

/** This is used for alert entities that we don't explicitly handle or they are missing. */
public interface GraphQLUnknown {
public DataFetcher<String> description();
Expand Down
Expand Up @@ -747,6 +747,19 @@ public enum GraphQLMode {
WALK,
}

/** Occupancy status of a vehicle. */
public enum GraphQLOccupancyStatus {
CRUSHED_STANDING_ROOM_ONLY,
EMPTY,
FEW_SEATS_AVAILABLE,
FULL,
MANY_SEATS_AVAILABLE,
NOT_ACCEPTING_PASSENGERS,
NOT_BOARDABLE,
NO_DATA_AVAILABLE,
STANDING_ROOM_ONLY,
}

public static class GraphQLOpeningHoursDatesArgs {

private List<String> dates;
Expand Down
Expand Up @@ -85,14 +85,15 @@ config:
TicketType: org.opentripplanner.ext.fares.model.FareRuleSet#FareRuleSet
TranslatedString: java.util.Map#Map.Entry<String, String>
Trip: org.opentripplanner.transit.model.timetable.Trip#Trip
TripOccupancy: org.opentripplanner.ext.gtfsgraphqlapi.model.TripOccupancy#TripOccupancy
Unknown: org.opentripplanner.ext.gtfsgraphqlapi.model.UnknownModel#UnknownModel
VehicleParking: org.opentripplanner.routing.vehicle_parking.VehicleParking#VehicleParking
VehicleParkingSpaces: org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces#VehicleParkingSpaces
VehicleParkingState: org.opentripplanner.routing.vehicle_parking.VehicleParkingState#VehicleParkingState
VertexType: String
SystemNotice: org.opentripplanner.model.SystemNotice#SystemNotice
VehiclePosition: org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition#RealtimeVehiclePosition
StopRelationship: org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopRelationship#StopRelationship
VehiclePosition: org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle#RealtimeVehicle
StopRelationship: org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopRelationship#StopRelationship
WheelchairBoarding: org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLWheelchairBoarding
FormFactor: org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLFormFactor
PropulsionType: org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLPropulsionType
Expand Down
optionsome marked this conversation as resolved.
Show resolved Hide resolved
@@ -0,0 +1,8 @@
package org.opentripplanner.ext.gtfsgraphqlapi.model;

import org.opentripplanner.transit.model.timetable.OccupancyStatus;

/**
* Record for holding trip occupancy information.
*/
public record TripOccupancy(OccupancyStatus occupancyStatus) {}
Expand Up @@ -10,7 +10,7 @@ public class OccupancyMapper {

public static OccupancyStatus mapOccupancyStatus(OccupancyEnumeration occupancy) {
if (occupancy == null) {
return OccupancyStatus.NO_DATA;
return OccupancyStatus.NO_DATA_AVAILABLE;
}
return switch (occupancy) {
case SEATS_AVAILABLE -> OccupancyStatus.MANY_SEATS_AVAILABLE;
Expand Down