From c19a0a424bb3ed646f998863f4f7f753b2df7e44 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 18 Sep 2023 22:49:04 +0300 Subject: [PATCH 01/26] Expand OccupancyStatus model to include all GTFS RT values --- .../ext/siri/mapper/OccupancyMapper.java | 2 +- .../mapping/OccupancyStatusMapper.java | 25 +++++++++++ .../ext/transmodelapi/model/EnumTypes.java | 4 +- .../model/siri/et/EstimatedCallType.java | 5 ++- .../model/timetable/OccupancyStatus.java | 43 ++++++++++++++----- .../transit/model/timetable/TripTimes.java | 4 +- 6 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java diff --git a/src/ext/java/org/opentripplanner/ext/siri/mapper/OccupancyMapper.java b/src/ext/java/org/opentripplanner/ext/siri/mapper/OccupancyMapper.java index d745ea3d473..61b7a15798a 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/mapper/OccupancyMapper.java +++ b/src/ext/java/org/opentripplanner/ext/siri/mapper/OccupancyMapper.java @@ -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; diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java new file mode 100644 index 00000000000..8d1b74daa9e --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java @@ -0,0 +1,25 @@ +package org.opentripplanner.ext.transmodelapi.mapping; + +import org.opentripplanner.transit.model.timetable.OccupancyStatus; + +/** + * Transmodel API supports a subset of {@link OccupancyStatus} and this mapper can be used to map + * any value to a value supported by the transmodel API. + */ +public class OccupancyStatusMapper { + + /** + * @return {@link OccupancyStatus} supported by the Transmodel API that is the closes match to the + * original. + */ + public static OccupancyStatus mapStatus(OccupancyStatus occupancyStatus) { + return switch (occupancyStatus) { + case NO_DATA_AVAILABLE -> OccupancyStatus.NO_DATA_AVAILABLE; + case MANY_SEATS_AVAILABLE, EMPTY -> OccupancyStatus.MANY_SEATS_AVAILABLE; + case FEW_SEATS_AVAILABLE -> OccupancyStatus.FEW_SEATS_AVAILABLE; + case STANDING_ROOM_ONLY, CRUSHED_STANDING_ROOM_ONLY -> OccupancyStatus.STANDING_ROOM_ONLY; + case FULL -> OccupancyStatus.FULL; + case NOT_ACCEPTING_PASSENGERS, NOT_BOARDABLE -> OccupancyStatus.NOT_ACCEPTING_PASSENGERS; + }; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java index ff7a8002504..ac10b59f398 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java @@ -195,7 +195,7 @@ public class EnumTypes { .name("OccupancyStatus") .value( "noData", - OccupancyStatus.NO_DATA, + OccupancyStatus.NO_DATA_AVAILABLE, "The vehicle or carriage doesn't have any occupancy data available." ) .value( @@ -205,7 +205,7 @@ public class EnumTypes { ) .value( "fewSeatsAvailable", - OccupancyStatus.SEATS_AVAILABLE, + OccupancyStatus.FEW_SEATS_AVAILABLE, "The vehicle or carriage has a few seats available." ) .value( diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java index 3b7156854d9..7a39aee6cca 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/siri/et/EstimatedCallType.java @@ -15,6 +15,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; +import org.opentripplanner.ext.transmodelapi.mapping.OccupancyStatusMapper; import org.opentripplanner.ext.transmodelapi.model.EnumTypes; import org.opentripplanner.ext.transmodelapi.support.GqlUtil; import org.opentripplanner.model.TripTimeOnDate; @@ -202,7 +203,9 @@ public static GraphQLObjectType create( .name("occupancyStatus") .type(new GraphQLNonNull(EnumTypes.OCCUPANCY_STATUS)) .dataFetcher(environment -> - ((TripTimeOnDate) environment.getSource()).getOccupancyStatus() + OccupancyStatusMapper.mapStatus( + ((TripTimeOnDate) environment.getSource()).getOccupancyStatus() + ) ) .build() ) diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java index 8be2f468b46..f47eb01baa2 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java @@ -2,34 +2,55 @@ /** * OccupancyStatus to be exposed in the API. The values are based on GTFS-RT - * (transit_realtime.VehiclePosition.OccupancyStatus), but is currently a subset that easily can - * be mapped to the Nordic SIRI-profile (SIRI 2.1) - * - * Descriptions are also based on the SIRI-profile + * (transit_realtime.VehiclePosition.OccupancyStatus) that can be easily be mapped to the Nordic + * SIRI-profile (SIRI 2.1) + *

+ * Descriptions are copied from the GTFS-RT specification. */ public enum OccupancyStatus { /** * Default. There is no occupancy-data on this departure */ - NO_DATA, + NO_DATA_AVAILABLE, /** - * More than ~50% of seats available + * The vehicle is considered empty by most measures, and has few or no passengers onboard, but is + * still accepting passengers. + */ + 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. */ MANY_SEATS_AVAILABLE, /** - * Less than ~50% of 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. */ - SEATS_AVAILABLE, + FEW_SEATS_AVAILABLE, /** - * Less than ~10% of seats available + * The vehicle or carriage can currently accommodate only standing passengers. */ STANDING_ROOM_ONLY, /** - * Close to or at full capacity + * The vehicle or carriage can currently accommodate only standing passengers and has limited + * space for them. + */ + CRUSHED_STANDING_ROOM_ONLY, + /** + * The vehicle is considered full by most measures, but may still be allowing passengers to + * board. */ FULL, /** - * If vehicle/carriage is not in use / unavailable, or passengers are only allowed to alight due to e.g. crowding + * 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…). + */ + NOT_BOARDABLE, } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index 8b86a3b37b3..c6b4ea51b0f 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -324,7 +324,7 @@ public void setOccupancyStatus(int stop, OccupancyStatus occupancyStatus) { public OccupancyStatus getOccupancyStatus(int stop) { if (this.occupancyStatus == null) { - return OccupancyStatus.NO_DATA; + return OccupancyStatus.NO_DATA_AVAILABLE; } return this.occupancyStatus[stop]; } @@ -680,7 +680,7 @@ private void prepareForRealTimeUpdates() { arrivalTimes[i] += timeShift; departureTimes[i] += timeShift; stopRealTimeStates[i] = StopRealTimeState.DEFAULT; - occupancyStatus[i] = OccupancyStatus.NO_DATA; + occupancyStatus[i] = OccupancyStatus.NO_DATA_AVAILABLE; } // Update the real-time state From e84a514b15bef36ac81fb315d9436690c31229e5 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 21 Sep 2023 12:02:30 +0300 Subject: [PATCH 02/26] Load occupancy status from GTFS RT into positions --- .../model/RealtimeVehiclePosition.java | 8 +++++- .../model/RealtimeVehiclePositionBuilder.java | 10 +++++++- .../VehiclePositionPatternMatcher.java | 25 +++++++++++++++++-- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java b/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java index 56c99c445c1..ffd76771f5d 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java +++ b/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java @@ -4,6 +4,7 @@ import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.transit.model.timetable.OccupancyStatus; import org.opentripplanner.transit.model.timetable.Trip; /** @@ -32,7 +33,12 @@ public record RealtimeVehiclePosition( * Status of the vehicle, ie. if approaching the next stop or if it is there already. */ StopRelationship stop, - Trip trip + Trip trip, + + /** + * How full the vehicle is and is it still accepting passengers. + */ + OccupancyStatus occupancyStatus ) { public static RealtimeVehiclePositionBuilder builder() { return new RealtimeVehiclePositionBuilder(); diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java b/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java index 00f2c3f7b51..809cfe5ab79 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java +++ b/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java @@ -7,6 +7,7 @@ import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopStatus; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.transit.model.timetable.OccupancyStatus; import org.opentripplanner.transit.model.timetable.Trip; public class RealtimeVehiclePositionBuilder { @@ -20,6 +21,7 @@ public class RealtimeVehiclePositionBuilder { private StopStatus stopStatus = StopStatus.IN_TRANSIT_TO; private StopLocation stop; private Trip trip; + private OccupancyStatus occupancyStatus; public RealtimeVehiclePositionBuilder setVehicleId(FeedScopedId vehicleId) { this.vehicleId = vehicleId; @@ -66,6 +68,11 @@ public RealtimeVehiclePositionBuilder setTrip(Trip trip) { return this; } + public RealtimeVehiclePositionBuilder setOccupancyStatus(OccupancyStatus occupancyStatus) { + this.occupancyStatus = occupancyStatus; + return this; + } + public RealtimeVehiclePosition build() { var stop = Optional .ofNullable(this.stop) @@ -79,7 +86,8 @@ public RealtimeVehiclePosition build() { heading, time, stop, - trip + trip, + occupancyStatus ); } } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java index 1fb05c9dcec..c181bebb6f8 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java @@ -37,6 +37,7 @@ import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.transit.model.timetable.OccupancyStatus; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.updater.spi.ResultLogger; @@ -226,7 +227,7 @@ private RealtimeVehiclePosition mapVehiclePosition( } if (vehiclePosition.hasCurrentStatus()) { - newPosition.setStopStatus(toModel(vehiclePosition.getCurrentStatus())); + newPosition.setStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); } // we prefer the to get the current stop from the stop_id @@ -259,6 +260,10 @@ else if (vehiclePosition.hasCurrentStopSequence()) { newPosition.setTrip(trip); + if (vehiclePosition.hasOccupancyStatus()) { + newPosition.setOccupancyStatus(occupancyStatusToModel(vehiclePosition.getOccupancyStatus())); + } + return newPosition.build(); } @@ -271,7 +276,7 @@ private static boolean validStopIndex(int stopIndex, List stopsOnV private record TemporalDistance(LocalDate date, long distance) {} - private static StopStatus toModel(VehicleStopStatus currentStatus) { + private static StopStatus stopStatusToModel(VehicleStopStatus currentStatus) { return switch (currentStatus) { case IN_TRANSIT_TO -> StopStatus.IN_TRANSIT_TO; case INCOMING_AT -> StopStatus.INCOMING_AT; @@ -279,6 +284,22 @@ private static StopStatus toModel(VehicleStopStatus currentStatus) { }; } + private static OccupancyStatus occupancyStatusToModel( + VehiclePosition.OccupancyStatus occupancyStatus + ) { + return switch (occupancyStatus) { + case NO_DATA_AVAILABLE -> OccupancyStatus.NO_DATA_AVAILABLE; + case EMPTY -> OccupancyStatus.EMPTY; + case MANY_SEATS_AVAILABLE -> OccupancyStatus.MANY_SEATS_AVAILABLE; + case FEW_SEATS_AVAILABLE -> OccupancyStatus.FEW_SEATS_AVAILABLE; + case STANDING_ROOM_ONLY -> OccupancyStatus.STANDING_ROOM_ONLY; + case CRUSHED_STANDING_ROOM_ONLY -> OccupancyStatus.CRUSHED_STANDING_ROOM_ONLY; + case FULL -> OccupancyStatus.FULL; + case NOT_ACCEPTING_PASSENGERS -> OccupancyStatus.NOT_ACCEPTING_PASSENGERS; + case NOT_BOARDABLE -> OccupancyStatus.NOT_BOARDABLE; + }; + } + private static String toString(VehiclePosition vehiclePosition) { try { return JsonFormat.printer().omittingInsignificantWhitespace().print(vehiclePosition); From b494353a4ce348b2fb1b2db9fbc79016f78a494e Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 21 Sep 2023 23:45:43 +0300 Subject: [PATCH 03/26] Support fuzzy trip matching for vehicle positions --- docs/UpdaterConfig.md | 1 + .../config/framework/json/OtpVersion.java | 3 ++- .../VehiclePositionsUpdaterConfig.java | 15 ++++++++++++- .../PollingVehiclePositionUpdater.java | 8 ++++++- .../VehiclePositionPatternMatcher.java | 21 +++++++++++++++---- .../VehiclePositionsUpdaterParameters.java | 3 ++- .../VehiclePositionsMatcherTest.java | 12 +++++++---- 7 files changed, 51 insertions(+), 12 deletions(-) diff --git a/docs/UpdaterConfig.md b/docs/UpdaterConfig.md index cd3ee3f7035..12c6c919bde 100644 --- a/docs/UpdaterConfig.md +++ b/docs/UpdaterConfig.md @@ -228,6 +228,7 @@ The information is downloaded in a single HTTP request and polled regularly. | 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 | +| 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 | | [headers](#u__6__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.3 | diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java index 9cfb7237c66..6caf9082cff 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java @@ -9,7 +9,8 @@ public enum OtpVersion { V2_1("2.1"), V2_2("2.2"), V2_3("2.3"), - V2_4("2.4"); + V2_4("2.4"), + V2_5("2.5"); private final String text; diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java index 4090da7991a..da3b99d002d 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java @@ -2,6 +2,7 @@ import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_5; import java.time.Duration; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; @@ -25,7 +26,19 @@ public static VehiclePositionsUpdaterParameters create(String updaterRef, NodeAd .since(V2_2) .summary("The URL of GTFS-RT protobuf HTTP resource to download the positions from.") .asUri(); + var fuzzyTripMatching = c + .of("fuzzyTripMatching") + .since(V2_5) + .summary("Whether to match trips fuzzily.") + .asBoolean(false); var headers = HttpHeadersConfig.headers(c, V2_3); - return new VehiclePositionsUpdaterParameters(updaterRef, feedId, url, frequency, headers); + return new VehiclePositionsUpdaterParameters( + updaterRef, + feedId, + url, + frequency, + headers, + fuzzyTripMatching + ); } } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java index 31d94caeb00..ad525fe5104 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java @@ -8,7 +8,9 @@ import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.Trip; +import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.updater.GtfsRealtimeFuzzyTripMatcher; import org.opentripplanner.updater.spi.PollingGraphUpdater; import org.opentripplanner.updater.spi.WriteToGraphCallback; import org.slf4j.Logger; @@ -42,6 +44,9 @@ public PollingVehiclePositionUpdater( this.vehiclePositionSource = new GtfsRealtimeHttpVehiclePositionSource(params.url(), params.headers()); var index = transitModel.getTransitModelIndex(); + var fuzzyTripMatcher = params.fuzzyTripMatching() + ? new GtfsRealtimeFuzzyTripMatcher(new DefaultTransitService(transitModel)) + : null; this.vehiclePositionPatternMatcher = new VehiclePositionPatternMatcher( params.feedId(), @@ -49,7 +54,8 @@ public PollingVehiclePositionUpdater( trip -> index.getPatternForTrip().get(trip), (trip, date) -> getPatternIncludingRealtime(transitModel, trip, date), vehiclePositionService, - transitModel.getTimeZone() + transitModel.getTimeZone(), + fuzzyTripMatcher ); LOG.info( diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java index c181bebb6f8..5e7fb0c1dab 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java @@ -40,6 +40,7 @@ import org.opentripplanner.transit.model.timetable.OccupancyStatus; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.updater.GtfsRealtimeFuzzyTripMatcher; import org.opentripplanner.updater.spi.ResultLogger; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateResult; @@ -62,6 +63,7 @@ public class VehiclePositionPatternMatcher { private final Function getTripForId; private final Function getStaticPattern; private final BiFunction getRealtimePattern; + private final GtfsRealtimeFuzzyTripMatcher fuzzyTripMatcher; private Set patternsInPreviousUpdate = Set.of(); @@ -71,7 +73,8 @@ public VehiclePositionPatternMatcher( Function getStaticPattern, BiFunction getRealtimePattern, VehiclePositionRepository repository, - ZoneId timeZoneId + ZoneId timeZoneId, + GtfsRealtimeFuzzyTripMatcher fuzzyTripMatcher ) { this.feedId = feedId; this.getTripForId = getTripForId; @@ -79,6 +82,7 @@ public VehiclePositionPatternMatcher( this.getRealtimePattern = getRealtimePattern; this.repository = repository; this.timeZoneId = timeZoneId; + this.fuzzyTripMatcher = fuzzyTripMatcher; } /** @@ -308,6 +312,11 @@ private static String toString(VehiclePosition vehiclePosition) { } } + private VehiclePosition fuzzilySetTrip(VehiclePosition vehiclePosition) { + var trip = fuzzyTripMatcher.match(feedId, vehiclePosition.getTrip()); + return vehiclePosition.toBuilder().setTrip(trip).build(); + } + private Result toRealtimeVehiclePosition( String feedId, VehiclePosition vehiclePosition @@ -320,7 +329,11 @@ private Result toRealtimeVehiclePosition return Result.failure(UpdateError.noTripId(INVALID_INPUT_STRUCTURE)); } - var tripId = vehiclePosition.getTrip().getTripId(); + var vehiclePositionWithTripId = fuzzyTripMatcher == null + ? vehiclePosition + : fuzzilySetTrip(vehiclePosition); + + var tripId = vehiclePositionWithTripId.getTrip().getTripId(); if (StringUtils.hasNoValue(tripId)) { return Result.failure(UpdateError.noTripId(UpdateError.UpdateErrorType.NO_TRIP_ID)); @@ -338,7 +351,7 @@ private Result toRealtimeVehiclePosition } var serviceDate = Optional - .of(vehiclePosition.getTrip().getStartDate()) + .of(vehiclePositionWithTripId.getTrip().getStartDate()) .map(Strings::emptyToNull) .flatMap(ServiceDateUtils::parseStringToOptional) .orElseGet(() -> inferServiceDate(trip)); @@ -359,7 +372,7 @@ private Result toRealtimeVehiclePosition // Add position to pattern var newPosition = mapVehiclePosition( - vehiclePosition, + vehiclePositionWithTripId, pattern.getStops(), trip, staticTripTimes::stopIndexOfGtfsSequence diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java index 7d08158903a..16db929c317 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java @@ -11,7 +11,8 @@ public record VehiclePositionsUpdaterParameters( String feedId, URI url, Duration frequency, - HttpHeaders headers + HttpHeaders headers, + boolean fuzzyTripMatching ) implements PollingGraphUpdaterParameters { public VehiclePositionsUpdaterParameters { diff --git a/src/test/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsMatcherTest.java b/src/test/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsMatcherTest.java index 4a1a5d287bf..8cac79fc008 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsMatcherTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsMatcherTest.java @@ -81,7 +81,8 @@ public void tripNotFoundInPattern() { ignored -> pattern, (id, time) -> pattern, service, - zoneId + zoneId, + null ); var positions = List.of(vehiclePosition(secondTripId)); @@ -112,7 +113,8 @@ public void sequenceId() { patternForTrip::get, (id, time) -> patternForTrip.get(id), service, - zoneId + zoneId, + null ); var pos = VehiclePosition @@ -165,7 +167,8 @@ private void testVehiclePositions(VehiclePosition pos) { patternForTrip::get, (id, time) -> patternForTrip.get(id), service, - zoneId + zoneId, + null ); var positions = List.of(pos); @@ -220,7 +223,8 @@ public void clearOldTrips() { patternForTrip::get, (id, time) -> patternForTrip.get(id), service, - zoneId + zoneId, + null ); var pos1 = vehiclePosition(tripId1); From da163fd5f16c19483a4eb2949e9200670c6474c3 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 21 Sep 2023 23:52:17 +0300 Subject: [PATCH 04/26] Expose occupancy status for a trip through GTFS API --- .../gtfsgraphqlapi/datafetchers/TripImpl.java | 17 +++++ .../generated/GraphQLDataFetchers.java | 20 ++++- .../generated/GraphQLTypes.java | 13 ++++ .../generated/graphql-codegen.yml | 1 + .../gtfsgraphqlapi/model/TripOccupancy.java | 8 ++ .../resources/gtfsgraphqlapi/schema.graphqls | 76 +++++++++++++++++++ .../VehiclePositionService.java | 7 ++ .../DefaultVehiclePositionService.java | 16 ++++ 8 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/model/TripOccupancy.java diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java index a12cedff82c..437b96c5ec6 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java @@ -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.vehiclepositions.VehiclePositionService; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.network.TripPattern; @@ -372,6 +374,17 @@ public DataFetcher wheelchairAccessible() { return environment -> GraphQLUtils.toGraphQL(getSource(environment).getWheelchairBoarding()); } + @Override + public DataFetcher occupancy() { + return environment -> { + Trip trip = getSource(environment); + TripPattern pattern = getTransitService(environment).getPatternForTrip(trip); + return new TripOccupancy( + getVehiclePositionsService(environment).getVehicleOccupancyStatus(pattern, trip.getId()) + ); + }; + } + private List getStops(DataFetchingEnvironment environment) { TripPattern tripPattern = getTripPattern(environment); if (tripPattern == null) { @@ -396,6 +409,10 @@ private TransitService getTransitService(DataFetchingEnvironment environment) { return environment.getContext().transitService(); } + private VehiclePositionService getVehiclePositionsService(DataFetchingEnvironment environment) { + return environment.getContext().vehiclePositionService(); + } + private Trip getSource(DataFetchingEnvironment environment) { return environment.getSource(); } diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java index 318c4fc2a11..adad8efc6d3 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java @@ -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; @@ -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 id(); @@ -1029,6 +1033,8 @@ public interface GraphQLTrip { public DataFetcher id(); + public DataFetcher occupancy(); + public DataFetcher pattern(); public DataFetcher route(); @@ -1056,6 +1062,14 @@ public interface GraphQLTrip { public DataFetcher 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 occupancyStatus(); + } + /** This is used for alert entities that we don't explicitly handle or they are missing. */ public interface GraphQLUnknown { public DataFetcher description(); diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java index 3861f0e49ef..5fca60671b9 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java @@ -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 dates; diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml index 7408e72cbca..aa9fac20d27 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml @@ -85,6 +85,7 @@ config: TicketType: org.opentripplanner.ext.fares.model.FareRuleSet#FareRuleSet TranslatedString: java.util.Map#Map.Entry 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 diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/model/TripOccupancy.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/model/TripOccupancy.java new file mode 100644 index 00000000000..3c48e727050 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/model/TripOccupancy.java @@ -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) {} diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls index aaf312244d1..276a8ae6f53 100644 --- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls +++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls @@ -3394,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 @@ -3972,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""" @@ -3998,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/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java b/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java index b4cbf1176ea..bc36668f294 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java +++ b/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java @@ -2,11 +2,18 @@ import java.util.List; import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; +import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.OccupancyStatus; public interface VehiclePositionService { /** * Get the vehicle positions for a certain trip. */ List getVehiclePositions(TripPattern pattern); + + /** + * Get the latest occupancy status for a certain trip. + */ + OccupancyStatus getVehicleOccupancyStatus(TripPattern pattern, FeedScopedId tripId); } diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java b/src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java index a9027f044d2..5c762a89009 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java +++ b/src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java @@ -2,13 +2,17 @@ import jakarta.inject.Inject; import jakarta.inject.Singleton; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nonnull; import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; import org.opentripplanner.service.vehiclepositions.VehiclePositionService; import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; +import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.OccupancyStatus; @Singleton public class DefaultVehiclePositionService @@ -34,4 +38,16 @@ public List getVehiclePositions(TripPattern pattern) { // the list is made immutable during insertion, so we can safely return them return positions.getOrDefault(pattern, List.of()); } + + @Nonnull + @Override + public OccupancyStatus getVehicleOccupancyStatus(TripPattern pattern, FeedScopedId tripId) { + return positions + .getOrDefault(pattern, List.of()) + .stream() + .filter(vehicle -> tripId.equals(vehicle.trip().getId())) + .max(Comparator.comparing(vehicle -> vehicle.time())) + .map(vehicle -> vehicle.occupancyStatus()) + .orElse(OccupancyStatus.NO_DATA_AVAILABLE); + } } From 7946a9b2e058a604f192c9e2034597dc38ae18f8 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 22 Sep 2023 11:29:27 +0300 Subject: [PATCH 05/26] Rename VehiclePosition -> RealtimeVehicle apart from updater --- .../GraphQLIntegrationTest.java | 4 +- .../mapping/RouteRequestMapperTest.java | 4 +- .../mapping/TripRequestMapperTest.java | 5 +- .../gtfsgraphqlapi/GraphQLRequestContext.java | 6 +- .../datafetchers/PatternImpl.java | 12 +-- .../datafetchers/StopRelationshipImpl.java | 2 +- .../gtfsgraphqlapi/datafetchers/TripImpl.java | 8 +- .../datafetchers/VehiclePositionImpl.java | 6 +- .../generated/GraphQLDataFetchers.java | 6 +- .../generated/graphql-codegen.yml | 4 +- .../RealtimeVehicleRepository.java | 29 +++++++ .../RealtimeVehicleService.java} | 10 +-- .../RealtimeVehicleRepositoryModule.java | 16 ++++ .../RealtimeVehicleServiceModule.java | 16 ++++ .../DefaultRealtimeVehicleService.java} | 30 +++---- .../model/RealtimeVehicle.java} | 12 +-- .../model/RealtimeVehicleBuilder.java} | 32 ++++---- .../VehiclePositionRepository.java | 29 ------- .../VehiclePositionsRepositoryModule.java | 16 ---- .../VehiclePositionsServiceModule.java | 16 ---- .../api/OtpServerRequestContext.java | 4 +- .../configure/ConstructApplication.java | 8 +- .../ConstructApplicationFactory.java | 16 ++-- .../configure/ConstructApplicationModule.java | 6 +- .../server/DefaultServerRequestContext.java | 16 ++-- .../configure/UpdaterConfigurator.java | 18 ++-- .../PollingVehiclePositionUpdater.java | 18 ++-- ...ava => RealtimeVehiclePatternMatcher.java} | 82 +++++++++---------- .../VehiclePositionUpdaterRunnable.java | 4 +- .../opentripplanner/TestServerContext.java | 10 +-- .../transit/speed_test/SpeedTest.java | 6 +- ...t.java => RealtimeVehicleMatcherTest.java} | 70 ++++++++-------- 32 files changed, 260 insertions(+), 261 deletions(-) create mode 100644 src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleRepository.java rename src/main/java/org/opentripplanner/service/{vehiclepositions/VehiclePositionService.java => realtimevehicles/RealtimeVehicleService.java} (56%) create mode 100644 src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleRepositoryModule.java create mode 100644 src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleServiceModule.java rename src/main/java/org/opentripplanner/service/{vehiclepositions/internal/DefaultVehiclePositionService.java => realtimevehicles/internal/DefaultRealtimeVehicleService.java} (50%) rename src/main/java/org/opentripplanner/service/{vehiclepositions/model/RealtimeVehiclePosition.java => realtimevehicles/model/RealtimeVehicle.java} (79%) rename src/main/java/org/opentripplanner/service/{vehiclepositions/model/RealtimeVehiclePositionBuilder.java => realtimevehicles/model/RealtimeVehicleBuilder.java} (57%) delete mode 100644 src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionRepository.java delete mode 100644 src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsRepositoryModule.java delete mode 100644 src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsServiceModule.java rename src/main/java/org/opentripplanner/updater/vehicle_position/{VehiclePositionPatternMatcher.java => RealtimeVehiclePatternMatcher.java} (82%) rename src/test/java/org/opentripplanner/updater/vehicle_position/{VehiclePositionsMatcherTest.java => RealtimeVehicleMatcherTest.java} (81%) diff --git a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java index 56466db1e03..7f8226413c6 100644 --- a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLIntegrationTest.java @@ -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; @@ -222,7 +222,7 @@ public TransitAlertService getTransitAlertService() { new DefaultFareService(), graph.getVehicleParkingService(), new DefaultVehicleRentalService(), - new DefaultVehiclePositionService(), + new DefaultRealtimeVehicleService(), GraphFinder.getInstance(graph, transitService::findRegularStop), new RouteRequest() ); diff --git a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/RouteRequestMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/RouteRequestMapperTest.java index c9ea4666b06..6b53f1acb56 100644 --- a/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/RouteRequestMapperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/gtfsgraphqlapi/mapping/RouteRequestMapperTest.java @@ -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; @@ -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() ); diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java index 2d8a03c5ce4..7a2d9a64465 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java @@ -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; @@ -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; @@ -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(), diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLRequestContext.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLRequestContext.java index 166aa93fd35..414de1b43bd 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLRequestContext.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GraphQLRequestContext.java @@ -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; @@ -17,7 +17,7 @@ public record GraphQLRequestContext( FareService fareService, VehicleParkingService vehicleParkingService, VehicleRentalService vehicleRentalService, - VehiclePositionService vehiclePositionService, + RealtimeVehicleService realtimeVehicleService, GraphFinder graphFinder, RouteRequest defaultRouteRequest ) { @@ -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() ); diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/PatternImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/PatternImpl.java index f38a024b721..e165b7f7490 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/PatternImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/PatternImpl.java @@ -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; @@ -227,9 +227,9 @@ public DataFetcher> tripsForDate() { } @Override - public DataFetcher> vehiclePositions() { + public DataFetcher> vehiclePositions() { return environment -> - getVehiclePositionsService(environment).getVehiclePositions(this.getSource(environment)); + getRealtimeVehiclesService(environment).getRealtimeVehicles(this.getSource(environment)); } private Agency getAgency(DataFetchingEnvironment environment) { @@ -252,8 +252,8 @@ private List getTrips(DataFetchingEnvironment environment) { return getSource(environment).scheduledTripsAsStream().collect(Collectors.toList()); } - private VehiclePositionService getVehiclePositionsService(DataFetchingEnvironment environment) { - return environment.getContext().vehiclePositionService(); + private RealtimeVehicleService getRealtimeVehiclesService(DataFetchingEnvironment environment) { + return environment.getContext().realtimeVehicleService(); } private TransitService getTransitService(DataFetchingEnvironment environment) { diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/StopRelationshipImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/StopRelationshipImpl.java index 1fccf47e828..bdfdf993910 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/StopRelationshipImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/StopRelationshipImpl.java @@ -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 { diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java index 437b96c5ec6..0c92ac28e8c 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripImpl.java @@ -28,7 +28,7 @@ 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.realtimevehicles.RealtimeVehicleService; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.network.TripPattern; @@ -380,7 +380,7 @@ public DataFetcher occupancy() { Trip trip = getSource(environment); TripPattern pattern = getTransitService(environment).getPatternForTrip(trip); return new TripOccupancy( - getVehiclePositionsService(environment).getVehicleOccupancyStatus(pattern, trip.getId()) + getRealtimeVehiclesService(environment).getVehicleOccupancyStatus(pattern, trip.getId()) ); }; } @@ -409,8 +409,8 @@ private TransitService getTransitService(DataFetchingEnvironment environment) { return environment.getContext().transitService(); } - private VehiclePositionService getVehiclePositionsService(DataFetchingEnvironment environment) { - return environment.getContext().vehiclePositionService(); + private RealtimeVehicleService getRealtimeVehiclesService(DataFetchingEnvironment environment) { + return environment.getContext().realtimeVehicleService(); } private Trip getSource(DataFetchingEnvironment environment) { diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java index 861c4b61310..c64e15ce407 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java @@ -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 { @@ -54,7 +54,7 @@ public DataFetcher vehicleId() { return env -> getSource(env).vehicleId().toString(); } - private RealtimeVehiclePosition getSource(DataFetchingEnvironment environment) { + private RealtimeVehicle getSource(DataFetchingEnvironment environment) { return environment.getSource(); } } diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java index adad8efc6d3..95fe58cd894 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java @@ -44,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; @@ -560,7 +560,7 @@ public interface GraphQLPattern { public DataFetcher> tripsForDate(); - public DataFetcher> vehiclePositions(); + public DataFetcher> vehiclePositions(); } public interface GraphQLPlace { diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml index aa9fac20d27..c71c0e8dfa2 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml @@ -92,8 +92,8 @@ config: 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 diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleRepository.java b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleRepository.java new file mode 100644 index 00000000000..acf558cb2d1 --- /dev/null +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleRepository.java @@ -0,0 +1,29 @@ +package org.opentripplanner.service.realtimevehicles; + +import java.util.List; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; +import org.opentripplanner.transit.model.network.TripPattern; + +public interface RealtimeVehicleRepository { + /** + * For the given pattern set all realtime vehicles. + *

+ * The list is expected to be exhaustive: all existing vehicles will be overridden. + *

+ * This means that if there are two updaters providing vehicles for the same pattern they + * overwrite each other. + */ + void setRealtimeVehicles(TripPattern pattern, List updates); + + /** + * Remove all vehicles for a given pattern. + *

+ * This is useful to clear old vehicles for which there are no more updates and we assume that + * they have stopped their trip. + */ + void clearRealtimeVehicles(TripPattern pattern); + /** + * Get the vehicles for a certain trip. + */ + List getRealtimeVehicles(TripPattern pattern); +} diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java similarity index 56% rename from src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java rename to src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java index bc36668f294..65d69db57cc 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java @@ -1,16 +1,16 @@ -package org.opentripplanner.service.vehiclepositions; +package org.opentripplanner.service.realtimevehicles; import java.util.List; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.OccupancyStatus; -public interface VehiclePositionService { +public interface RealtimeVehicleService { /** - * Get the vehicle positions for a certain trip. + * Get the realtime vehicles for a certain trip pattern. */ - List getVehiclePositions(TripPattern pattern); + List getRealtimeVehicles(TripPattern pattern); /** * Get the latest occupancy status for a certain trip. diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleRepositoryModule.java b/src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleRepositoryModule.java new file mode 100644 index 00000000000..c247420c301 --- /dev/null +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleRepositoryModule.java @@ -0,0 +1,16 @@ +package org.opentripplanner.service.realtimevehicles.configure; + +import dagger.Binds; +import dagger.Module; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; +import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; + +/** + * The repository is used during application loading phase, so we need to provide + * a module for the repository as well as the service. + */ +@Module +public interface RealtimeVehicleRepositoryModule { + @Binds + RealtimeVehicleRepository bindRepository(DefaultRealtimeVehicleService repository); +} diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleServiceModule.java b/src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleServiceModule.java new file mode 100644 index 00000000000..625d3ee9510 --- /dev/null +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/configure/RealtimeVehicleServiceModule.java @@ -0,0 +1,16 @@ +package org.opentripplanner.service.realtimevehicles.configure; + +import dagger.Binds; +import dagger.Module; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; +import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; + +/** + * The service is used during application serve phase, not loading, so we need to provide + * a module for the service without the repository, which is injected from the loading phase. + */ +@Module +public interface RealtimeVehicleServiceModule { + @Binds + RealtimeVehicleService bindService(DefaultRealtimeVehicleService service); +} diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java similarity index 50% rename from src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java rename to src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java index 5c762a89009..5e59d931ebd 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java @@ -1,4 +1,4 @@ -package org.opentripplanner.service.vehiclepositions.internal; +package org.opentripplanner.service.realtimevehicles.internal; import jakarta.inject.Inject; import jakarta.inject.Singleton; @@ -7,42 +7,42 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nonnull; -import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; +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.TripPattern; import org.opentripplanner.transit.model.timetable.OccupancyStatus; @Singleton -public class DefaultVehiclePositionService - implements VehiclePositionService, VehiclePositionRepository { +public class DefaultRealtimeVehicleService + implements RealtimeVehicleService, RealtimeVehicleRepository { - private final Map> positions = new ConcurrentHashMap<>(); + private final Map> vehicles = new ConcurrentHashMap<>(); @Inject - public DefaultVehiclePositionService() {} + public DefaultRealtimeVehicleService() {} @Override - public void setVehiclePositions(TripPattern pattern, List updates) { - positions.put(pattern, List.copyOf(updates)); + public void setRealtimeVehicles(TripPattern pattern, List updates) { + vehicles.put(pattern, List.copyOf(updates)); } @Override - public void clearVehiclePositions(TripPattern pattern) { - positions.remove(pattern); + public void clearRealtimeVehicles(TripPattern pattern) { + vehicles.remove(pattern); } @Override - public List getVehiclePositions(TripPattern pattern) { + public List getRealtimeVehicles(TripPattern pattern) { // the list is made immutable during insertion, so we can safely return them - return positions.getOrDefault(pattern, List.of()); + return vehicles.getOrDefault(pattern, List.of()); } @Nonnull @Override public OccupancyStatus getVehicleOccupancyStatus(TripPattern pattern, FeedScopedId tripId) { - return positions + return vehicles .getOrDefault(pattern, List.of()) .stream() .filter(vehicle -> tripId.equals(vehicle.trip().getId())) diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java similarity index 79% rename from src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java rename to src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java index ffd76771f5d..6cdbd7e1415 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java @@ -1,4 +1,4 @@ -package org.opentripplanner.service.vehiclepositions.model; +package org.opentripplanner.service.realtimevehicles.model; import java.time.Instant; import org.opentripplanner.framework.geometry.WgsCoordinate; @@ -8,9 +8,9 @@ import org.opentripplanner.transit.model.timetable.Trip; /** - * Internal model of a realtime vehicle position. + * Internal model of a realtime vehicle. */ -public record RealtimeVehiclePosition( +public record RealtimeVehicle( FeedScopedId vehicleId, String label, WgsCoordinate coordinates, @@ -25,7 +25,7 @@ public record RealtimeVehiclePosition( Double heading, /** - * When the realtime position was recorded + * When the realtime vehicle was recorded */ Instant time, @@ -40,8 +40,8 @@ public record RealtimeVehiclePosition( */ OccupancyStatus occupancyStatus ) { - public static RealtimeVehiclePositionBuilder builder() { - return new RealtimeVehiclePositionBuilder(); + public static RealtimeVehicleBuilder builder() { + return new RealtimeVehicleBuilder(); } public enum StopStatus { diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java similarity index 57% rename from src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java rename to src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java index 809cfe5ab79..3679ba43311 100644 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java @@ -1,16 +1,16 @@ -package org.opentripplanner.service.vehiclepositions.model; +package org.opentripplanner.service.realtimevehicles.model; import java.time.Instant; import java.util.Optional; import org.opentripplanner.framework.geometry.WgsCoordinate; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopRelationship; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopStatus; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopRelationship; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopStatus; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.OccupancyStatus; import org.opentripplanner.transit.model.timetable.Trip; -public class RealtimeVehiclePositionBuilder { +public class RealtimeVehicleBuilder { private FeedScopedId vehicleId; private String label; @@ -23,62 +23,62 @@ public class RealtimeVehiclePositionBuilder { private Trip trip; private OccupancyStatus occupancyStatus; - public RealtimeVehiclePositionBuilder setVehicleId(FeedScopedId vehicleId) { + public RealtimeVehicleBuilder setVehicleId(FeedScopedId vehicleId) { this.vehicleId = vehicleId; return this; } - public RealtimeVehiclePositionBuilder setLabel(String label) { + public RealtimeVehicleBuilder setLabel(String label) { this.label = label; return this; } - public RealtimeVehiclePositionBuilder setCoordinates(WgsCoordinate c) { + public RealtimeVehicleBuilder setCoordinates(WgsCoordinate c) { this.coordinates = c; return this; } - public RealtimeVehiclePositionBuilder setSpeed(double speed) { + public RealtimeVehicleBuilder setSpeed(double speed) { this.speed = speed; return this; } - public RealtimeVehiclePositionBuilder setHeading(double heading) { + public RealtimeVehicleBuilder setHeading(double heading) { this.heading = heading; return this; } - public RealtimeVehiclePositionBuilder setTime(Instant time) { + public RealtimeVehicleBuilder setTime(Instant time) { this.time = time; return this; } - public RealtimeVehiclePositionBuilder setStopStatus(StopStatus stopStatus) { + public RealtimeVehicleBuilder setStopStatus(StopStatus stopStatus) { this.stopStatus = stopStatus; return this; } - public RealtimeVehiclePositionBuilder setStop(StopLocation stop) { + public RealtimeVehicleBuilder setStop(StopLocation stop) { this.stop = stop; return this; } - public RealtimeVehiclePositionBuilder setTrip(Trip trip) { + public RealtimeVehicleBuilder setTrip(Trip trip) { this.trip = trip; return this; } - public RealtimeVehiclePositionBuilder setOccupancyStatus(OccupancyStatus occupancyStatus) { + public RealtimeVehicleBuilder setOccupancyStatus(OccupancyStatus occupancyStatus) { this.occupancyStatus = occupancyStatus; return this; } - public RealtimeVehiclePosition build() { + public RealtimeVehicle build() { var stop = Optional .ofNullable(this.stop) .map(s -> new StopRelationship(s, stopStatus)) .orElse(null); - return new RealtimeVehiclePosition( + return new RealtimeVehicle( vehicleId, label, coordinates, diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionRepository.java b/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionRepository.java deleted file mode 100644 index a28e316ac37..00000000000 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionRepository.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.opentripplanner.service.vehiclepositions; - -import java.util.List; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; -import org.opentripplanner.transit.model.network.TripPattern; - -public interface VehiclePositionRepository { - /** - * For the given pattern set all realtime vehicle positions. - *

- * The list is expected to be exhaustive: all existing positions will be overridden. - *

- * This means that if there are two updaters providing positions for the same pattern they - * overwrite each other. - */ - void setVehiclePositions(TripPattern pattern, List updates); - - /** - * Remove all vehicle positions for a given pattern. - *

- * This is useful to clear old vehicles for which there are no more updates and we assume that - * they have stopped their trip. - */ - void clearVehiclePositions(TripPattern pattern); - /** - * Get the vehicle positions for a certain trip. - */ - List getVehiclePositions(TripPattern pattern); -} diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsRepositoryModule.java b/src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsRepositoryModule.java deleted file mode 100644 index d4820e1fe31..00000000000 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsRepositoryModule.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.opentripplanner.service.vehiclepositions.configure; - -import dagger.Binds; -import dagger.Module; -import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; -import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService; - -/** - * The repository is used during application loading phase, so we need to provide - * a module for the repository as well as the service. - */ -@Module -public interface VehiclePositionsRepositoryModule { - @Binds - VehiclePositionRepository bindRepository(DefaultVehiclePositionService repository); -} diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsServiceModule.java b/src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsServiceModule.java deleted file mode 100644 index a8cfcbfbe4c..00000000000 --- a/src/main/java/org/opentripplanner/service/vehiclepositions/configure/VehiclePositionsServiceModule.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.opentripplanner.service.vehiclepositions.configure; - -import dagger.Binds; -import dagger.Module; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; -import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService; - -/** - * The service is used during application serve phase, not loading, so we need to provide - * a module for the service without the repository, which is injected from the loading phase. - */ -@Module -public interface VehiclePositionsServiceModule { - @Binds - VehiclePositionService bindService(DefaultVehiclePositionService service); -} diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index a7effd16704..5c4b6ad0c1e 100644 --- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -17,7 +17,7 @@ import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graphfinder.GraphFinder; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; import org.opentripplanner.service.vehiclerental.VehicleRentalService; import org.opentripplanner.service.worldenvelope.WorldEnvelopeService; import org.opentripplanner.standalone.config.sandbox.FlexConfig; @@ -82,7 +82,7 @@ public interface OtpServerRequestContext { */ WorldEnvelopeService worldEnvelopeService(); - VehiclePositionService vehiclePositionService(); + RealtimeVehicleService realtimeVehicleService(); VehicleRentalService vehicleRentalService(); diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index ee71aa3f542..e60fd159332 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -18,7 +18,7 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.TransitLayerMapper; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.TransitLayerUpdater; import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; import org.opentripplanner.service.vehiclerental.VehicleRentalRepository; import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository; import org.opentripplanner.standalone.api.OtpServerRequestContext; @@ -149,7 +149,7 @@ private void setupTransitRoutingServer() { /* Create updater modules from JSON config. */ UpdaterConfigurator.configure( graph(), - vehiclePositionRepository(), + realtimeVehicleRepository(), vehicleRentalRepository(), transitModel(), routerConfig().updaterConfig() @@ -239,8 +239,8 @@ public DataImportIssueSummary dataImportIssueSummary() { return factory.dataImportIssueSummary(); } - public VehiclePositionRepository vehiclePositionRepository() { - return factory.vehiclePositionRepository(); + public RealtimeVehicleRepository realtimeVehicleRepository() { + return factory.realtimeVehicleRepository(); } public VehicleRentalRepository vehicleRentalRepository() { diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index f882bad3ffd..b776df1ae5d 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -9,10 +9,10 @@ import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; -import org.opentripplanner.service.vehiclepositions.configure.VehiclePositionsRepositoryModule; -import org.opentripplanner.service.vehiclepositions.configure.VehiclePositionsServiceModule; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; +import org.opentripplanner.service.realtimevehicles.configure.RealtimeVehicleRepositoryModule; +import org.opentripplanner.service.realtimevehicles.configure.RealtimeVehicleServiceModule; import org.opentripplanner.service.vehiclerental.VehicleRentalRepository; import org.opentripplanner.service.vehiclerental.VehicleRentalService; import org.opentripplanner.service.vehiclerental.configure.VehicleRentalRepositoryModule; @@ -38,8 +38,8 @@ ConfigModule.class, TransitModule.class, WorldEnvelopeServiceModule.class, - VehiclePositionsServiceModule.class, - VehiclePositionsRepositoryModule.class, + RealtimeVehicleServiceModule.class, + RealtimeVehicleRepositoryModule.class, VehicleRentalServiceModule.class, VehicleRentalRepositoryModule.class, ConstructApplicationModule.class, @@ -53,8 +53,8 @@ public interface ConstructApplicationFactory { TransitModel transitModel(); WorldEnvelopeRepository worldEnvelopeRepository(); WorldEnvelopeService worldEnvelopeService(); - VehiclePositionRepository vehiclePositionRepository(); - VehiclePositionService vehiclePositionService(); + RealtimeVehicleRepository realtimeVehicleRepository(); + RealtimeVehicleService realtimeVehicleService(); VehicleRentalRepository vehicleRentalRepository(); VehicleRentalService vehicleRentalService(); DataImportIssueSummary dataImportIssueSummary(); diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java index bc677028296..3b43644e24a 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java @@ -10,7 +10,7 @@ import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; import org.opentripplanner.service.vehiclerental.VehicleRentalService; import org.opentripplanner.service.worldenvelope.WorldEnvelopeService; import org.opentripplanner.standalone.api.OtpServerRequestContext; @@ -29,7 +29,7 @@ OtpServerRequestContext providesServerContext( Graph graph, TransitService transitService, WorldEnvelopeService worldEnvelopeService, - VehiclePositionService vehiclePositionService, + RealtimeVehicleService realtimeVehicleService, VehicleRentalService vehicleRentalService, List rideHailingServices, @Nullable TraverseVisitor traverseVisitor @@ -43,7 +43,7 @@ OtpServerRequestContext providesServerContext( Metrics.globalRegistry, routerConfig.vectorTileLayers(), worldEnvelopeService, - vehiclePositionService, + realtimeVehicleService, vehicleRentalService, routerConfig.flexConfig(), rideHailingServices, diff --git a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java index dad75d1f6f6..62b848fd5e9 100644 --- a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java @@ -16,7 +16,7 @@ import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.service.DefaultRoutingService; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; import org.opentripplanner.service.vehiclerental.VehicleRentalService; import org.opentripplanner.service.worldenvelope.WorldEnvelopeService; import org.opentripplanner.standalone.api.HttpRequestScoped; @@ -41,7 +41,7 @@ public class DefaultServerRequestContext implements OtpServerRequestContext { private final FlexConfig flexConfig; private final TraverseVisitor traverseVisitor; private final WorldEnvelopeService worldEnvelopeService; - private final VehiclePositionService vehiclePositionService; + private final RealtimeVehicleService realtimeVehicleService; private final VehicleRentalService vehicleRentalService; /** @@ -57,7 +57,7 @@ private DefaultServerRequestContext( TileRendererManager tileRendererManager, VectorTilesResource.LayersParameters vectorTileLayers, WorldEnvelopeService worldEnvelopeService, - VehiclePositionService vehiclePositionService, + RealtimeVehicleService realtimeVehicleService, VehicleRentalService vehicleRentalService, List rideHailingServices, TraverseVisitor traverseVisitor, @@ -75,7 +75,7 @@ private DefaultServerRequestContext( this.traverseVisitor = traverseVisitor; this.routeRequestDefaults = routeRequestDefaults; this.worldEnvelopeService = worldEnvelopeService; - this.vehiclePositionService = vehiclePositionService; + this.realtimeVehicleService = realtimeVehicleService; this.rideHailingServices = rideHailingServices; } @@ -91,7 +91,7 @@ public static DefaultServerRequestContext create( MeterRegistry meterRegistry, VectorTilesResource.LayersParameters vectorTileLayers, WorldEnvelopeService worldEnvelopeService, - VehiclePositionService vehiclePositionService, + RealtimeVehicleService realtimeVehicleService, VehicleRentalService vehicleRentalService, FlexConfig flexConfig, List rideHailingServices, @@ -107,7 +107,7 @@ public static DefaultServerRequestContext create( new TileRendererManager(graph, routeRequestDefaults.preferences()), vectorTileLayers, worldEnvelopeService, - vehiclePositionService, + realtimeVehicleService, vehicleRentalService, rideHailingServices, traverseVisitor, @@ -158,8 +158,8 @@ public WorldEnvelopeService worldEnvelopeService() { } @Override - public VehiclePositionService vehiclePositionService() { - return vehiclePositionService; + public RealtimeVehicleService realtimeVehicleService() { + return realtimeVehicleService; } @Override diff --git a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java index ea63561ccb3..037bd080f47 100644 --- a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java +++ b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java @@ -13,7 +13,7 @@ import org.opentripplanner.framework.io.OtpHttpClient; import org.opentripplanner.model.calendar.openinghours.OpeningHoursCalendarService; import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; import org.opentripplanner.service.vehiclerental.VehicleRentalRepository; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.updater.GraphUpdaterManager; @@ -46,20 +46,20 @@ public class UpdaterConfigurator { private final Graph graph; private final TransitModel transitModel; private final UpdatersParameters updatersParameters; - private final VehiclePositionRepository vehiclePositionRepository; + private final RealtimeVehicleRepository realtimeVehicleRepository; private final VehicleRentalRepository vehicleRentalRepository; private SiriTimetableSnapshotSource siriTimetableSnapshotSource = null; private TimetableSnapshotSource gtfsTimetableSnapshotSource = null; private UpdaterConfigurator( Graph graph, - VehiclePositionRepository vehiclePositionRepository, + RealtimeVehicleRepository realtimeVehicleRepository, VehicleRentalRepository vehicleRentalRepository, TransitModel transitModel, UpdatersParameters updatersParameters ) { this.graph = graph; - this.vehiclePositionRepository = vehiclePositionRepository; + this.realtimeVehicleRepository = realtimeVehicleRepository; this.vehicleRentalRepository = vehicleRentalRepository; this.transitModel = transitModel; this.updatersParameters = updatersParameters; @@ -67,15 +67,15 @@ private UpdaterConfigurator( public static void configure( Graph graph, - VehiclePositionRepository vehiclePositionService, - VehicleRentalRepository vehicleRentalService, + RealtimeVehicleRepository realtimeVehicleRepository, + VehicleRentalRepository vehicleRentalRepository, TransitModel transitModel, UpdatersParameters updatersParameters ) { new UpdaterConfigurator( graph, - vehiclePositionService, - vehicleRentalService, + realtimeVehicleRepository, + vehicleRentalRepository, transitModel, updatersParameters ) @@ -163,7 +163,7 @@ private List createUpdatersFromConfig() { } for (var configItem : updatersParameters.getVehiclePositionsUpdaterParameters()) { updaters.add( - new PollingVehiclePositionUpdater(configItem, vehiclePositionRepository, transitModel) + new PollingVehiclePositionUpdater(configItem, realtimeVehicleRepository, transitModel) ); } for (var configItem : updatersParameters.getSiriETUpdaterParameters()) { diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java index ad525fe5104..8489836d218 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.Optional; import org.opentripplanner.framework.tostring.ToStringBuilder; -import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.service.DefaultTransitService; @@ -17,7 +17,9 @@ import org.slf4j.LoggerFactory; /** - * Add vehicle positions to OTP patterns via a GTFS-RT source. + * Map vehicle positions to + * {@link org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle} and add them to OTP + * patterns via a GTFS-RT source. */ public class PollingVehiclePositionUpdater extends PollingGraphUpdater { @@ -28,7 +30,7 @@ public class PollingVehiclePositionUpdater extends PollingGraphUpdater { */ private final GtfsRealtimeHttpVehiclePositionSource vehiclePositionSource; - private final VehiclePositionPatternMatcher vehiclePositionPatternMatcher; + private final RealtimeVehiclePatternMatcher realtimeVehiclePatternMatcher; /** * Parent update manager. Is used to execute graph writer runnables. @@ -37,7 +39,7 @@ public class PollingVehiclePositionUpdater extends PollingGraphUpdater { public PollingVehiclePositionUpdater( VehiclePositionsUpdaterParameters params, - VehiclePositionRepository vehiclePositionService, + RealtimeVehicleRepository realtimeVehicleRepository, TransitModel transitModel ) { super(params); @@ -47,13 +49,13 @@ public PollingVehiclePositionUpdater( var fuzzyTripMatcher = params.fuzzyTripMatching() ? new GtfsRealtimeFuzzyTripMatcher(new DefaultTransitService(transitModel)) : null; - this.vehiclePositionPatternMatcher = - new VehiclePositionPatternMatcher( + this.realtimeVehiclePatternMatcher = + new RealtimeVehiclePatternMatcher( params.feedId(), tripId -> index.getTripForId().get(tripId), trip -> index.getPatternForTrip().get(trip), (trip, date) -> getPatternIncludingRealtime(transitModel, trip, date), - vehiclePositionService, + realtimeVehicleRepository, transitModel.getTimeZone(), fuzzyTripMatcher ); @@ -81,7 +83,7 @@ public void runPolling() { if (updates != null) { // Handle updating trip positions via graph writer runnable - var runnable = new VehiclePositionUpdaterRunnable(updates, vehiclePositionPatternMatcher); + var runnable = new VehiclePositionUpdaterRunnable(updates, realtimeVehiclePatternMatcher); saveResultOnGraph.execute(runnable); } } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java similarity index 82% rename from src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java rename to src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java index 5e7fb0c1dab..844fc41b075 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java @@ -30,9 +30,9 @@ import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.lang.StringUtils; import org.opentripplanner.framework.time.ServiceDateUtils; -import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition; -import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopStatus; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopStatus; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.TripPattern; @@ -52,12 +52,12 @@ * Responsible for converting vehicle positions in memory to exportable ones, and associating each * position with a pattern. */ -public class VehiclePositionPatternMatcher { +public class RealtimeVehiclePatternMatcher { - private static final Logger LOG = LoggerFactory.getLogger(VehiclePositionPatternMatcher.class); + private static final Logger LOG = LoggerFactory.getLogger(RealtimeVehiclePatternMatcher.class); private final String feedId; - private final VehiclePositionRepository repository; + private final RealtimeVehicleRepository repository; private final ZoneId timeZoneId; private final Function getTripForId; @@ -67,12 +67,12 @@ public class VehiclePositionPatternMatcher { private Set patternsInPreviousUpdate = Set.of(); - public VehiclePositionPatternMatcher( + public RealtimeVehiclePatternMatcher( String feedId, Function getTripForId, Function getStaticPattern, BiFunction getRealtimePattern, - VehiclePositionRepository repository, + RealtimeVehicleRepository repository, ZoneId timeZoneId, GtfsRealtimeFuzzyTripMatcher fuzzyTripMatcher ) { @@ -86,25 +86,25 @@ public VehiclePositionPatternMatcher( } /** - * Attempts to match each vehicle position to a pattern, then adds each to a pattern + * Attempts to match each vehicle to a pattern, then adds each to a pattern * * @param vehiclePositions List of vehicle positions to match to patterns */ - public UpdateResult applyVehiclePositionUpdates(List vehiclePositions) { + public UpdateResult applyRealtimeVehicleUpdates(List vehiclePositions) { var matchResults = vehiclePositions .stream() - .map(vehiclePosition -> toRealtimeVehiclePosition(feedId, vehiclePosition)) + .map(vehiclePosition -> toRealtimeVehicle(feedId, vehiclePosition)) .toList(); - // we take the list of positions and out of them create a Map> - // that map makes it very easy to update the positions in the service - // it also enables the bookkeeping about which pattern previously had positions but no longer do + // we take the list of vehicles and out of them create a Map> + // that map makes it very easy to update the vehicles in the service + // it also enables the bookkeeping about which pattern previously had vehicles but no longer do // these need to be removed from the service as we assume that the vehicle has stopped - var positions = matchResults + var vehicles = matchResults .stream() .filter(Result::isSuccess) .map(Result::successValue) - .collect(Collectors.groupingBy(PatternAndVehiclePosition::pattern)) + .collect(Collectors.groupingBy(PatternAndRealtimeVehicle::pattern)) .entrySet() .stream() .collect( @@ -114,18 +114,18 @@ public UpdateResult applyVehiclePositionUpdates(List vehiclePos e .getValue() .stream() - .map(PatternAndVehiclePosition::position) + .map(PatternAndRealtimeVehicle::vehicle) .collect(Collectors.toList()) ) ); - positions.forEach(repository::setVehiclePositions); - Set patternsInCurrentUpdate = positions.keySet(); + vehicles.forEach(repository::setRealtimeVehicles); + Set patternsInCurrentUpdate = vehicles.keySet(); - // if there was a position in the previous update but not in the current one, we assume - // that the pattern has no more vehicle positions. + // if there was a vehicle in the previous update but not in the current one, we assume + // that the pattern has no more vehicles. var toDelete = Sets.difference(patternsInPreviousUpdate, patternsInCurrentUpdate); - toDelete.forEach(repository::clearVehiclePositions); + toDelete.forEach(repository::clearRealtimeVehicles); patternsInPreviousUpdate = patternsInCurrentUpdate; if (!vehiclePositions.isEmpty() && patternsInCurrentUpdate.isEmpty()) { @@ -190,48 +190,46 @@ protected static LocalDate inferServiceDate( } /** - * Converts GtfsRealtime vehicle position to the OTP RealtimeVehiclePosition which can be used by + * Converts GtfsRealtime vehicle position to the OTP RealtimeVehicle which can be used by * the API. * * @param stopIndexOfGtfsSequence A function that takes a GTFS stop_sequence and returns the index * of the stop in the trip. */ - private RealtimeVehiclePosition mapVehiclePosition( + private RealtimeVehicle mapRealtimeVehicle( VehiclePosition vehiclePosition, List stopsOnVehicleTrip, @Nonnull Trip trip, @Nonnull Function stopIndexOfGtfsSequence ) { - var newPosition = RealtimeVehiclePosition.builder(); + var newVehicle = RealtimeVehicle.builder(); if (vehiclePosition.hasPosition()) { var position = vehiclePosition.getPosition(); - newPosition.setCoordinates( - new WgsCoordinate(position.getLatitude(), position.getLongitude()) - ); + newVehicle.setCoordinates(new WgsCoordinate(position.getLatitude(), position.getLongitude())); if (position.hasSpeed()) { - newPosition.setSpeed(position.getSpeed()); + newVehicle.setSpeed(position.getSpeed()); } if (position.hasBearing()) { - newPosition.setHeading(position.getBearing()); + newVehicle.setHeading(position.getBearing()); } } if (vehiclePosition.hasVehicle()) { var vehicle = vehiclePosition.getVehicle(); var id = new FeedScopedId(feedId, vehicle.getId()); - newPosition + newVehicle .setVehicleId(id) .setLabel(Optional.ofNullable(vehicle.getLabel()).orElse(vehicle.getLicensePlate())); } if (vehiclePosition.hasTimestamp()) { - newPosition.setTime(Instant.ofEpochSecond(vehiclePosition.getTimestamp())); + newVehicle.setTime(Instant.ofEpochSecond(vehiclePosition.getTimestamp())); } if (vehiclePosition.hasCurrentStatus()) { - newPosition.setStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); + newVehicle.setStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); } // we prefer the to get the current stop from the stop_id @@ -241,7 +239,7 @@ private RealtimeVehiclePosition mapVehiclePosition( .filter(stop -> stop.getId().getId().equals(vehiclePosition.getStopId())) .toList(); if (matchedStops.size() == 1) { - newPosition.setStop(matchedStops.get(0)); + newVehicle.setStop(matchedStops.get(0)); } else { LOG.warn( "Stop ID {} is not in trip {}. Not setting stopRelationship.", @@ -257,18 +255,18 @@ else if (vehiclePosition.hasCurrentStopSequence()) { .ifPresent(stopIndex -> { if (validStopIndex(stopIndex, stopsOnVehicleTrip)) { var stop = stopsOnVehicleTrip.get(stopIndex); - newPosition.setStop(stop); + newVehicle.setStop(stop); } }); } - newPosition.setTrip(trip); + newVehicle.setTrip(trip); if (vehiclePosition.hasOccupancyStatus()) { - newPosition.setOccupancyStatus(occupancyStatusToModel(vehiclePosition.getOccupancyStatus())); + newVehicle.setOccupancyStatus(occupancyStatusToModel(vehiclePosition.getOccupancyStatus())); } - return newPosition.build(); + return newVehicle.build(); } /** @@ -317,7 +315,7 @@ private VehiclePosition fuzzilySetTrip(VehiclePosition vehiclePosition) { return vehiclePosition.toBuilder().setTrip(trip).build(); } - private Result toRealtimeVehiclePosition( + private Result toRealtimeVehicle( String feedId, VehiclePosition vehiclePosition ) { @@ -371,15 +369,15 @@ private Result toRealtimeVehiclePosition } // Add position to pattern - var newPosition = mapVehiclePosition( + var newVehicle = mapRealtimeVehicle( vehiclePositionWithTripId, pattern.getStops(), trip, staticTripTimes::stopIndexOfGtfsSequence ); - return Result.success(new PatternAndVehiclePosition(pattern, newPosition)); + return Result.success(new PatternAndRealtimeVehicle(pattern, newVehicle)); } - record PatternAndVehiclePosition(TripPattern pattern, RealtimeVehiclePosition position) {} + record PatternAndRealtimeVehicle(TripPattern pattern, RealtimeVehicle vehicle) {} } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionUpdaterRunnable.java b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionUpdaterRunnable.java index 3bb5f349fb2..d1f923696b5 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionUpdaterRunnable.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionUpdaterRunnable.java @@ -9,7 +9,7 @@ public record VehiclePositionUpdaterRunnable( List updates, - VehiclePositionPatternMatcher matcher + RealtimeVehiclePatternMatcher matcher ) implements GraphWriterRunnable { public VehiclePositionUpdaterRunnable { @@ -20,6 +20,6 @@ public record VehiclePositionUpdaterRunnable( @Override public void run(Graph graph, TransitModel transitModel) { // Apply new vehicle positions - matcher.applyVehiclePositionUpdates(updates); + matcher.applyRealtimeVehicleUpdates(updates); } } diff --git a/src/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java index 969769f93fa..e6609a2b780 100644 --- a/src/test/java/org/opentripplanner/TestServerContext.java +++ b/src/test/java/org/opentripplanner/TestServerContext.java @@ -6,8 +6,8 @@ import java.util.List; import org.opentripplanner.raptor.configure.RaptorConfig; import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.service.vehiclepositions.VehiclePositionService; -import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService; +import org.opentripplanner.service.realtimevehicles.RealtimeVehicleService; +import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.service.vehiclerental.VehicleRentalService; import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; import org.opentripplanner.service.worldenvelope.WorldEnvelopeService; @@ -39,7 +39,7 @@ public static OtpServerRequestContext createServerContext( Metrics.globalRegistry, routerConfig.vectorTileLayers(), createWorldEnvelopeService(), - createVehiclePositionService(), + createRealtimeVehicleService(), createVehicleRentalService(), routerConfig.flexConfig(), List.of(), @@ -54,8 +54,8 @@ public static WorldEnvelopeService createWorldEnvelopeService() { return new DefaultWorldEnvelopeService(new DefaultWorldEnvelopeRepository()); } - public static VehiclePositionService createVehiclePositionService() { - return new DefaultVehiclePositionService(); + public static RealtimeVehicleService createRealtimeVehicleService() { + return new DefaultRealtimeVehicleService(); } public static VehicleRentalService createVehicleRentalService() { diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java index 5faffc6a606..9fa217903c1 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java @@ -22,7 +22,7 @@ import org.opentripplanner.routing.framework.DebugTimingAggregator; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graph.SerializedGraphObject; -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.OtpStartupInfo; import org.opentripplanner.standalone.api.OtpServerRequestContext; @@ -92,7 +92,7 @@ public SpeedTest( UpdaterConfigurator.configure( graph, - new DefaultVehiclePositionService(), + new DefaultRealtimeVehicleService(), new DefaultVehicleRentalService(), transitModel, config.updatersConfig @@ -111,7 +111,7 @@ public SpeedTest( timer.getRegistry(), List::of, TestServerContext.createWorldEnvelopeService(), - TestServerContext.createVehiclePositionService(), + TestServerContext.createRealtimeVehicleService(), TestServerContext.createVehicleRentalService(), config.flexConfig, List.of(), diff --git a/src/test/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsMatcherTest.java b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java similarity index 81% rename from src/test/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsMatcherTest.java rename to src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java index 8cac79fc008..b60d99da951 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsMatcherTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java @@ -24,7 +24,7 @@ import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.model.StopTime; -import org.opentripplanner.service.vehiclepositions.internal.DefaultVehiclePositionService; +import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.Deduplicator; @@ -35,7 +35,7 @@ import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripTimes; -public class VehiclePositionsMatcherTest { +public class RealtimeVehicleMatcherTest { public static final Route ROUTE = TransitModelForTest.route("1").build(); ZoneId zoneId = ZoneIds.BERLIN; @@ -43,7 +43,7 @@ public class VehiclePositionsMatcherTest { FeedScopedId scopedTripId = TransitModelForTest.id(tripId); @Test - public void matchRealtimePositionsToTrip() { + public void matchRealtimeVehiclesToTrip() { var pos = vehiclePosition(tripId); testVehiclePositions(pos); } @@ -64,7 +64,7 @@ public void inferStartDate() { @Test public void tripNotFoundInPattern() { - var service = new DefaultVehiclePositionService(); + var service = new DefaultRealtimeVehicleService(); final String secondTripId = "trip2"; @@ -75,7 +75,7 @@ public void tripNotFoundInPattern() { var pattern = tripPattern(trip1, stopTimes); // Map positions to trips in feed - VehiclePositionPatternMatcher matcher = new VehiclePositionPatternMatcher( + RealtimeVehiclePatternMatcher matcher = new RealtimeVehiclePatternMatcher( TransitModelForTest.FEED_ID, ignored -> trip2, ignored -> pattern, @@ -86,7 +86,7 @@ public void tripNotFoundInPattern() { ); var positions = List.of(vehiclePosition(secondTripId)); - var result = matcher.applyVehiclePositionUpdates(positions); + var result = matcher.applyRealtimeVehicleUpdates(positions); assertEquals(1, result.failed()); assertEquals(Set.of(TRIP_NOT_FOUND_IN_PATTERN), result.failures().keySet()); @@ -94,7 +94,7 @@ public void tripNotFoundInPattern() { @Test public void sequenceId() { - var service = new DefaultVehiclePositionService(); + var service = new DefaultRealtimeVehicleService(); var tripId = "trip1"; var scopedTripId = TransitModelForTest.id(tripId); @@ -107,7 +107,7 @@ public void sequenceId() { var patternForTrip = Map.of(trip1, pattern1); // Map positions to trips in feed - VehiclePositionPatternMatcher matcher = new VehiclePositionPatternMatcher( + RealtimeVehiclePatternMatcher matcher = new RealtimeVehiclePatternMatcher( TransitModelForTest.FEED_ID, tripForId::get, patternForTrip::get, @@ -126,11 +126,11 @@ public void sequenceId() { var positions = List.of(pos); // Execute the same match-to-pattern step as the runner - matcher.applyVehiclePositionUpdates(positions); + matcher.applyRealtimeVehicleUpdates(positions); // ensure that gtfs-rt was matched to an OTP pattern correctly - assertEquals(1, service.getVehiclePositions(pattern1).size()); - var nextStop = service.getVehiclePositions(pattern1).get(0).stop().stop(); + assertEquals(1, service.getRealtimeVehicles(pattern1).size()); + var nextStop = service.getRealtimeVehicles(pattern1).get(0).stop().stop(); assertEquals("F:stop-20", nextStop.getId().toString()); } @@ -148,7 +148,7 @@ void invalidStopSequence() { } private void testVehiclePositions(VehiclePosition pos) { - var service = new DefaultVehiclePositionService(); + var service = new DefaultRealtimeVehicleService(); var trip = TransitModelForTest.trip(tripId).build(); var stopTimes = List.of(stopTime(trip, 0), stopTime(trip, 1), stopTime(trip, 2)); @@ -158,10 +158,10 @@ private void testVehiclePositions(VehiclePosition pos) { var patternForTrip = Map.of(trip, pattern); // an untouched pattern has no vehicle positions - assertEquals(0, service.getVehiclePositions(pattern).size()); + assertEquals(0, service.getRealtimeVehicles(pattern).size()); // Map positions to trips in feed - VehiclePositionPatternMatcher matcher = new VehiclePositionPatternMatcher( + RealtimeVehiclePatternMatcher matcher = new RealtimeVehiclePatternMatcher( TransitModelForTest.FEED_ID, tripForId::get, patternForTrip::get, @@ -174,25 +174,25 @@ private void testVehiclePositions(VehiclePosition pos) { var positions = List.of(pos); // Execute the same match-to-pattern step as the runner - matcher.applyVehiclePositionUpdates(positions); + matcher.applyRealtimeVehicleUpdates(positions); // ensure that gtfs-rt was matched to an OTP pattern correctly - var vehiclePositions = service.getVehiclePositions(pattern); - assertEquals(1, vehiclePositions.size()); + var realtimeVehicles = service.getRealtimeVehicles(pattern); + assertEquals(1, realtimeVehicles.size()); - var parsedPos = vehiclePositions.get(0); - assertEquals(tripId, parsedPos.trip().getId().getId()); - assertEquals(new WgsCoordinate(1, 1), parsedPos.coordinates()); - assertEquals(30, parsedPos.heading()); + var parsedVehicle = realtimeVehicles.get(0); + assertEquals(tripId, parsedVehicle.trip().getId().getId()); + assertEquals(new WgsCoordinate(1, 1), parsedVehicle.coordinates()); + assertEquals(30, parsedVehicle.heading()); // if we have an empty list of updates then clear the positions from the previous update - matcher.applyVehiclePositionUpdates(List.of()); - assertEquals(0, service.getVehiclePositions(pattern).size()); + matcher.applyRealtimeVehicleUpdates(List.of()); + assertEquals(0, service.getRealtimeVehicles(pattern).size()); } @Test public void clearOldTrips() { - var service = new DefaultVehiclePositionService(); + var service = new DefaultRealtimeVehicleService(); var tripId1 = "trip1"; var tripId2 = "trip2"; @@ -213,11 +213,11 @@ public void clearOldTrips() { var patternForTrip = Map.of(trip1, pattern1, trip2, pattern2); - // an untouched pattern has no vehicle positions - assertEquals(0, service.getVehiclePositions(pattern1).size()); + // an untouched pattern has no vehicles + assertEquals(0, service.getRealtimeVehicles(pattern1).size()); // Map positions to trips in feed - VehiclePositionPatternMatcher matcher = new VehiclePositionPatternMatcher( + RealtimeVehiclePatternMatcher matcher = new RealtimeVehiclePatternMatcher( TransitModelForTest.FEED_ID, tripForId::get, patternForTrip::get, @@ -234,16 +234,16 @@ public void clearOldTrips() { var positions = List.of(pos1, pos2); // Execute the same match-to-pattern step as the runner - matcher.applyVehiclePositionUpdates(positions); + matcher.applyRealtimeVehicleUpdates(positions); // ensure that gtfs-rt was matched to an OTP pattern correctly - assertEquals(1, service.getVehiclePositions(pattern1).size()); - assertEquals(1, service.getVehiclePositions(pattern2).size()); + assertEquals(1, service.getRealtimeVehicles(pattern1).size()); + assertEquals(1, service.getRealtimeVehicles(pattern2).size()); - matcher.applyVehiclePositionUpdates(List.of(pos1)); - assertEquals(1, service.getVehiclePositions(pattern1).size()); + matcher.applyRealtimeVehicleUpdates(List.of(pos1)); + assertEquals(1, service.getRealtimeVehicles(pattern1).size()); // because there are no more updates for pattern2 we remove all positions - assertEquals(0, service.getVehiclePositions(pattern2).size()); + assertEquals(0, service.getRealtimeVehicles(pattern2).size()); } static Stream inferenceTestCases = Stream.of( @@ -265,7 +265,7 @@ void inferServiceDayOfTripAt6(String time, String expectedDate) { var tripTimes = new TripTimes(trip, stopTimes, new Deduplicator()); var instant = OffsetDateTime.parse(time).toInstant(); - var inferredDate = VehiclePositionPatternMatcher.inferServiceDate(tripTimes, zoneId, instant); + var inferredDate = RealtimeVehiclePatternMatcher.inferServiceDate(tripTimes, zoneId, instant); assertEquals(LocalDate.parse(expectedDate), inferredDate); } @@ -284,7 +284,7 @@ void inferServiceDateCloseToMidnight() { // because the trip "crosses" midnight and we are already on the next day, we infer the service date to be // yesterday - var inferredDate = VehiclePositionPatternMatcher.inferServiceDate(tripTimes, zoneId, time); + var inferredDate = RealtimeVehiclePatternMatcher.inferServiceDate(tripTimes, zoneId, time); assertEquals(LocalDate.parse("2022-04-04"), inferredDate); } From 0e145ea6d2b240f3f3982ad54e4ae0b1c2e5944d Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 22 Sep 2023 13:28:02 +0300 Subject: [PATCH 06/26] Make vehicle position features configurable --- docs/RouterConfiguration.md | 6 +- docs/UpdaterConfig.md | 31 +++++-- .../VehiclePositionsUpdaterConfig.java | 14 +++- .../PollingVehiclePositionUpdater.java | 3 +- .../RealtimeVehiclePatternMatcher.java | 82 ++++++++++++------- .../VehiclePositionsUpdaterParameters.java | 5 +- .../RealtimeVehicleMatcherTest.java | 25 +++++- .../standalone/config/router-config.json | 4 +- 8 files changed, 121 insertions(+), 49 deletions(-) diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index 1e77d3513a2..42f6237ee34 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -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" diff --git a/docs/UpdaterConfig.md b/docs/UpdaterConfig.md index 12c6c919bde..be9f9579c17 100644 --- a/docs/UpdaterConfig.md +++ b/docs/UpdaterConfig.md @@ -223,18 +223,27 @@ The information is downloaded in a single HTTP request and polled regularly. -| 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 | -| 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 | -| [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 | +| 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 | +| [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 +

features

+ +**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. +

headers

**Since version:** `2.3` ∙ **Type:** `map of string` ∙ **Cardinality:** `Optional` @@ -257,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" + ] } ] } diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java index da3b99d002d..e41a14de79d 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java @@ -31,6 +31,11 @@ public static VehiclePositionsUpdaterParameters create(String updaterRef, NodeAd .since(V2_5) .summary("Whether to match trips fuzzily.") .asBoolean(false); + var features = c + .of("features") + .since(V2_5) + .summary("Which features of GTFS RT vehicle positions should be loaded into OTP.") + .asEnumSet(VehiclePositionFeature.class); var headers = HttpHeadersConfig.headers(c, V2_3); return new VehiclePositionsUpdaterParameters( updaterRef, @@ -38,7 +43,14 @@ public static VehiclePositionsUpdaterParameters create(String updaterRef, NodeAd url, frequency, headers, - fuzzyTripMatching + fuzzyTripMatching, + features ); } + + public enum VehiclePositionFeature { + POSITION, + STOP_POSITION, + OCCUPANCY, + } } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java index 8489836d218..bb2e27c3ce8 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java @@ -57,7 +57,8 @@ public PollingVehiclePositionUpdater( (trip, date) -> getPatternIncludingRealtime(transitModel, trip, date), realtimeVehicleRepository, transitModel.getTimeZone(), - fuzzyTripMatcher + fuzzyTripMatcher, + params.vehiclePositionFeatures() ); LOG.info( diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java index 844fc41b075..f24bbbe5365 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java @@ -33,6 +33,7 @@ import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopStatus; +import org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.framework.Result; import org.opentripplanner.transit.model.network.TripPattern; @@ -64,6 +65,7 @@ public class RealtimeVehiclePatternMatcher { private final Function getStaticPattern; private final BiFunction getRealtimePattern; private final GtfsRealtimeFuzzyTripMatcher fuzzyTripMatcher; + private final Set vehiclePositionFeatures; private Set patternsInPreviousUpdate = Set.of(); @@ -74,7 +76,8 @@ public RealtimeVehiclePatternMatcher( BiFunction getRealtimePattern, RealtimeVehicleRepository repository, ZoneId timeZoneId, - GtfsRealtimeFuzzyTripMatcher fuzzyTripMatcher + GtfsRealtimeFuzzyTripMatcher fuzzyTripMatcher, + Set vehiclePositionFeatures ) { this.feedId = feedId; this.getTripForId = getTripForId; @@ -83,6 +86,7 @@ public RealtimeVehiclePatternMatcher( this.repository = repository; this.timeZoneId = timeZoneId; this.fuzzyTripMatcher = fuzzyTripMatcher; + this.vehiclePositionFeatures = vehiclePositionFeatures; } /** @@ -204,7 +208,12 @@ private RealtimeVehicle mapRealtimeVehicle( ) { var newVehicle = RealtimeVehicle.builder(); - if (vehiclePosition.hasPosition()) { + if ( + vehiclePositionFeatures.contains( + VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION + ) && + vehiclePosition.hasPosition() + ) { var position = vehiclePosition.getPosition(); newVehicle.setCoordinates(new WgsCoordinate(position.getLatitude(), position.getLongitude())); @@ -228,41 +237,52 @@ private RealtimeVehicle mapRealtimeVehicle( newVehicle.setTime(Instant.ofEpochSecond(vehiclePosition.getTimestamp())); } - if (vehiclePosition.hasCurrentStatus()) { - newVehicle.setStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); - } + if ( + vehiclePositionFeatures.contains( + VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION + ) + ) { + if (vehiclePosition.hasCurrentStatus()) { + newVehicle.setStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); + } - // we prefer the to get the current stop from the stop_id - if (vehiclePosition.hasStopId()) { - var matchedStops = stopsOnVehicleTrip - .stream() - .filter(stop -> stop.getId().getId().equals(vehiclePosition.getStopId())) - .toList(); - if (matchedStops.size() == 1) { - newVehicle.setStop(matchedStops.get(0)); - } else { - LOG.warn( - "Stop ID {} is not in trip {}. Not setting stopRelationship.", - vehiclePosition.getStopId(), - trip.getId() - ); + // we prefer the to get the current stop from the stop_id + if (vehiclePosition.hasStopId()) { + var matchedStops = stopsOnVehicleTrip + .stream() + .filter(stop -> stop.getId().getId().equals(vehiclePosition.getStopId())) + .toList(); + if (matchedStops.size() == 1) { + newVehicle.setStop(matchedStops.get(0)); + } else { + LOG.warn( + "Stop ID {} is not in trip {}. Not setting stopRelationship.", + vehiclePosition.getStopId(), + trip.getId() + ); + } + } + // but if stop_id isn't there we try current_stop_sequence + else if (vehiclePosition.hasCurrentStopSequence()) { + stopIndexOfGtfsSequence + .apply(vehiclePosition.getCurrentStopSequence()) + .ifPresent(stopIndex -> { + if (validStopIndex(stopIndex, stopsOnVehicleTrip)) { + var stop = stopsOnVehicleTrip.get(stopIndex); + newVehicle.setStop(stop); + } + }); } - } - // but if stop_id isn't there we try current_stop_sequence - else if (vehiclePosition.hasCurrentStopSequence()) { - stopIndexOfGtfsSequence - .apply(vehiclePosition.getCurrentStopSequence()) - .ifPresent(stopIndex -> { - if (validStopIndex(stopIndex, stopsOnVehicleTrip)) { - var stop = stopsOnVehicleTrip.get(stopIndex); - newVehicle.setStop(stop); - } - }); } newVehicle.setTrip(trip); - if (vehiclePosition.hasOccupancyStatus()) { + if ( + vehiclePositionFeatures.contains( + VehiclePositionsUpdaterConfig.VehiclePositionFeature.OCCUPANCY + ) && + vehiclePosition.hasOccupancyStatus() + ) { newVehicle.setOccupancyStatus(occupancyStatusToModel(vehiclePosition.getOccupancyStatus())); } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java index 16db929c317..14b3d3a04de 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java @@ -3,6 +3,8 @@ import java.net.URI; import java.time.Duration; import java.util.Objects; +import java.util.Set; +import org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig; import org.opentripplanner.updater.spi.HttpHeaders; import org.opentripplanner.updater.spi.PollingGraphUpdaterParameters; @@ -12,7 +14,8 @@ public record VehiclePositionsUpdaterParameters( URI url, Duration frequency, HttpHeaders headers, - boolean fuzzyTripMatching + boolean fuzzyTripMatching, + Set vehiclePositionFeatures ) implements PollingGraphUpdaterParameters { public VehiclePositionsUpdaterParameters { diff --git a/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java index b60d99da951..cbd94f49322 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java @@ -25,6 +25,7 @@ import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.model.StopTime; import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; +import org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig; import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.Deduplicator; @@ -82,7 +83,11 @@ public void tripNotFoundInPattern() { (id, time) -> pattern, service, zoneId, - null + null, + Set.of( + VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION, + VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION + ) ); var positions = List.of(vehiclePosition(secondTripId)); @@ -114,7 +119,11 @@ public void sequenceId() { (id, time) -> patternForTrip.get(id), service, zoneId, - null + null, + Set.of( + VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION, + VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION + ) ); var pos = VehiclePosition @@ -168,7 +177,11 @@ private void testVehiclePositions(VehiclePosition pos) { (id, time) -> patternForTrip.get(id), service, zoneId, - null + null, + Set.of( + VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION, + VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION + ) ); var positions = List.of(pos); @@ -224,7 +237,11 @@ public void clearOldTrips() { (id, time) -> patternForTrip.get(id), service, zoneId, - null + null, + Set.of( + VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION, + VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION + ) ); var pos1 = vehiclePosition(tripId1); diff --git a/src/test/resources/standalone/config/router-config.json b/src/test/resources/standalone/config/router-config.json index ddfafff3eea..9cf87635680 100644 --- a/src/test/resources/standalone/config/router-config.json +++ b/src/test/resources/standalone/config/router-config.json @@ -291,7 +291,9 @@ "frequency": "1m", "headers": { "Header-Name": "Header-Value" - } + }, + "fuzzyTripMatching": false, + "features": ["position"] }, // Streaming differential GTFS-RT TripUpdates over websockets { From d962c97f868e220c48730333ae967b51d600c94c Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 22 Sep 2023 15:53:55 +0300 Subject: [PATCH 07/26] Fix npes in vehicle positions impl --- .../datafetchers/VehiclePositionImpl.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java index c64e15ce407..4c2da6cb0ca 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/VehiclePositionImpl.java @@ -21,17 +21,19 @@ public DataFetcher label() { @Override public DataFetcher lastUpdated() { - return env -> getSource(env).time().getEpochSecond(); + return env -> getSource(env).time() != null ? getSource(env).time().getEpochSecond() : null; } @Override public DataFetcher lat() { - return env -> getSource(env).coordinates().latitude(); + return env -> + getSource(env).coordinates() != null ? getSource(env).coordinates().latitude() : null; } @Override public DataFetcher lon() { - return env -> getSource(env).coordinates().longitude(); + return env -> + getSource(env).coordinates() != null ? getSource(env).coordinates().longitude() : null; } @Override @@ -51,7 +53,7 @@ public DataFetcher trip() { @Override public DataFetcher vehicleId() { - return env -> getSource(env).vehicleId().toString(); + return env -> getSource(env).vehicleId() != null ? getSource(env).vehicleId().toString() : null; } private RealtimeVehicle getSource(DataFetchingEnvironment environment) { From abbc08b0e1dc0bd6c0c21a5b57a0662d48e12276 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 22 Sep 2023 16:22:11 +0300 Subject: [PATCH 08/26] Add mapping from internal enum into API --- .../ext/gtfsgraphqlapi/GtfsGraphQLIndex.java | 2 ++ .../datafetchers/TripOccupancyImpl.java | 31 +++++++++++++++++++ .../generated/GraphQLDataFetchers.java | 2 +- .../generated/graphql-codegen.yml | 1 + 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GtfsGraphQLIndex.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GtfsGraphQLIndex.java index 9a8651a2389..50d6a9e45bf 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GtfsGraphQLIndex.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/GtfsGraphQLIndex.java @@ -73,6 +73,7 @@ import org.opentripplanner.ext.gtfsgraphqlapi.datafetchers.TicketTypeImpl; import org.opentripplanner.ext.gtfsgraphqlapi.datafetchers.TranslatedStringImpl; import org.opentripplanner.ext.gtfsgraphqlapi.datafetchers.TripImpl; +import org.opentripplanner.ext.gtfsgraphqlapi.datafetchers.TripOccupancyImpl; import org.opentripplanner.ext.gtfsgraphqlapi.datafetchers.UnknownImpl; import org.opentripplanner.ext.gtfsgraphqlapi.datafetchers.VehicleParkingImpl; import org.opentripplanner.ext.gtfsgraphqlapi.datafetchers.VehiclePositionImpl; @@ -172,6 +173,7 @@ protected static GraphQLSchema buildSchema() { .type(typeWiring.build(CurrencyImpl.class)) .type(typeWiring.build(FareProductUseImpl.class)) .type(typeWiring.build(DefaultFareProductImpl.class)) + .type(typeWiring.build(TripOccupancyImpl.class)) .build(); SchemaGenerator schemaGenerator = new SchemaGenerator(); return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring); diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java new file mode 100644 index 00000000000..75543936918 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java @@ -0,0 +1,31 @@ +package org.opentripplanner.ext.gtfsgraphqlapi.datafetchers; + +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLDataFetchers; +import org.opentripplanner.ext.gtfsgraphqlapi.model.TripOccupancy; + +public class TripOccupancyImpl implements GraphQLDataFetchers.GraphQLTripOccupancy { + + @Override + public DataFetcher occupancyStatus() { + return env -> { + var occupancyStatus = getSource(env).occupancyStatus(); + return switch (occupancyStatus) { + case NO_DATA_AVAILABLE -> "NO_DATA_AVAILABLE"; + case EMPTY -> "EMPTY"; + case MANY_SEATS_AVAILABLE -> "MANY_SEATS_AVAILABLE"; + case FEW_SEATS_AVAILABLE -> "FEW_SEATS_AVAILABLE"; + case STANDING_ROOM_ONLY -> "STANDING_ROOM_ONLY"; + case CRUSHED_STANDING_ROOM_ONLY -> "CRUSHED_STANDING_ROOM_ONLY"; + case FULL -> "FULL"; + case NOT_ACCEPTING_PASSENGERS -> "NOT_ACCEPTING_PASSENGERS"; + case NOT_BOARDABLE -> "NOT_BOARDABLE"; + }; + }; + } + + private TripOccupancy getSource(DataFetchingEnvironment environment) { + return environment.getSource(); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java index 95fe58cd894..279bae6cc53 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java @@ -1067,7 +1067,7 @@ public interface GraphQLTrip { * available for a trip. Historic data might not be available. */ public interface GraphQLTripOccupancy { - public DataFetcher occupancyStatus(); + public DataFetcher occupancyStatus(); } /** This is used for alert entities that we don't explicitly handle or they are missing. */ diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml index c71c0e8dfa2..fcb1e840431 100644 --- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml +++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml @@ -59,6 +59,7 @@ config: Leg: org.opentripplanner.model.plan.Leg#Leg Mode: String TransitMode: org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLTransitMode#GraphQLTransitMode + OccupancyStatus: String PageInfo: Object Pattern: org.opentripplanner.transit.model.network.TripPattern#TripPattern PickupDropoffType: String From 374f9616985271a1389185b1e0373d5b1ed2b3ae Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 22 Sep 2023 16:27:30 +0300 Subject: [PATCH 09/26] Rename builder setter methods --- .../model/RealtimeVehicleBuilder.java | 20 ++++++++-------- .../RealtimeVehiclePatternMatcher.java | 24 ++++++++++--------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java index 3679ba43311..75dd64666de 100644 --- a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java @@ -23,52 +23,52 @@ public class RealtimeVehicleBuilder { private Trip trip; private OccupancyStatus occupancyStatus; - public RealtimeVehicleBuilder setVehicleId(FeedScopedId vehicleId) { + public RealtimeVehicleBuilder withVehicleId(FeedScopedId vehicleId) { this.vehicleId = vehicleId; return this; } - public RealtimeVehicleBuilder setLabel(String label) { + public RealtimeVehicleBuilder withLabel(String label) { this.label = label; return this; } - public RealtimeVehicleBuilder setCoordinates(WgsCoordinate c) { + public RealtimeVehicleBuilder withCoordinates(WgsCoordinate c) { this.coordinates = c; return this; } - public RealtimeVehicleBuilder setSpeed(double speed) { + public RealtimeVehicleBuilder withSpeed(double speed) { this.speed = speed; return this; } - public RealtimeVehicleBuilder setHeading(double heading) { + public RealtimeVehicleBuilder withHeading(double heading) { this.heading = heading; return this; } - public RealtimeVehicleBuilder setTime(Instant time) { + public RealtimeVehicleBuilder withTime(Instant time) { this.time = time; return this; } - public RealtimeVehicleBuilder setStopStatus(StopStatus stopStatus) { + public RealtimeVehicleBuilder withStopStatus(StopStatus stopStatus) { this.stopStatus = stopStatus; return this; } - public RealtimeVehicleBuilder setStop(StopLocation stop) { + public RealtimeVehicleBuilder withStop(StopLocation stop) { this.stop = stop; return this; } - public RealtimeVehicleBuilder setTrip(Trip trip) { + public RealtimeVehicleBuilder withTrip(Trip trip) { this.trip = trip; return this; } - public RealtimeVehicleBuilder setOccupancyStatus(OccupancyStatus occupancyStatus) { + public RealtimeVehicleBuilder withOccupancyStatus(OccupancyStatus occupancyStatus) { this.occupancyStatus = occupancyStatus; return this; } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java index f24bbbe5365..0a461df1326 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java @@ -215,13 +215,15 @@ private RealtimeVehicle mapRealtimeVehicle( vehiclePosition.hasPosition() ) { var position = vehiclePosition.getPosition(); - newVehicle.setCoordinates(new WgsCoordinate(position.getLatitude(), position.getLongitude())); + newVehicle.withCoordinates( + new WgsCoordinate(position.getLatitude(), position.getLongitude()) + ); if (position.hasSpeed()) { - newVehicle.setSpeed(position.getSpeed()); + newVehicle.withSpeed(position.getSpeed()); } if (position.hasBearing()) { - newVehicle.setHeading(position.getBearing()); + newVehicle.withHeading(position.getBearing()); } } @@ -229,12 +231,12 @@ private RealtimeVehicle mapRealtimeVehicle( var vehicle = vehiclePosition.getVehicle(); var id = new FeedScopedId(feedId, vehicle.getId()); newVehicle - .setVehicleId(id) - .setLabel(Optional.ofNullable(vehicle.getLabel()).orElse(vehicle.getLicensePlate())); + .withVehicleId(id) + .withLabel(Optional.ofNullable(vehicle.getLabel()).orElse(vehicle.getLicensePlate())); } if (vehiclePosition.hasTimestamp()) { - newVehicle.setTime(Instant.ofEpochSecond(vehiclePosition.getTimestamp())); + newVehicle.withTime(Instant.ofEpochSecond(vehiclePosition.getTimestamp())); } if ( @@ -243,7 +245,7 @@ private RealtimeVehicle mapRealtimeVehicle( ) ) { if (vehiclePosition.hasCurrentStatus()) { - newVehicle.setStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); + newVehicle.withStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); } // we prefer the to get the current stop from the stop_id @@ -253,7 +255,7 @@ private RealtimeVehicle mapRealtimeVehicle( .filter(stop -> stop.getId().getId().equals(vehiclePosition.getStopId())) .toList(); if (matchedStops.size() == 1) { - newVehicle.setStop(matchedStops.get(0)); + newVehicle.withStop(matchedStops.get(0)); } else { LOG.warn( "Stop ID {} is not in trip {}. Not setting stopRelationship.", @@ -269,13 +271,13 @@ else if (vehiclePosition.hasCurrentStopSequence()) { .ifPresent(stopIndex -> { if (validStopIndex(stopIndex, stopsOnVehicleTrip)) { var stop = stopsOnVehicleTrip.get(stopIndex); - newVehicle.setStop(stop); + newVehicle.withStop(stop); } }); } } - newVehicle.setTrip(trip); + newVehicle.withTrip(trip); if ( vehiclePositionFeatures.contains( @@ -283,7 +285,7 @@ else if (vehiclePosition.hasCurrentStopSequence()) { ) && vehiclePosition.hasOccupancyStatus() ) { - newVehicle.setOccupancyStatus(occupancyStatusToModel(vehiclePosition.getOccupancyStatus())); + newVehicle.withOccupancyStatus(occupancyStatusToModel(vehiclePosition.getOccupancyStatus())); } return newVehicle.build(); From ea8baea22eefd76aa866131d10376efb32433505 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 22 Sep 2023 21:14:54 +0300 Subject: [PATCH 10/26] Use static imports --- .../RealtimeVehiclePatternMatcher.java | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java index 0a461df1326..8b5833ad4e9 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java @@ -1,5 +1,8 @@ package org.opentripplanner.updater.vehicle_position; +import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.OCCUPANCY; +import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION; +import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.INVALID_INPUT_STRUCTURE; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NO_SERVICE_ON_DATE; import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.TRIP_NOT_FOUND; @@ -208,12 +211,7 @@ private RealtimeVehicle mapRealtimeVehicle( ) { var newVehicle = RealtimeVehicle.builder(); - if ( - vehiclePositionFeatures.contains( - VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION - ) && - vehiclePosition.hasPosition() - ) { + if (vehiclePositionFeatures.contains(POSITION) && vehiclePosition.hasPosition()) { var position = vehiclePosition.getPosition(); newVehicle.withCoordinates( new WgsCoordinate(position.getLatitude(), position.getLongitude()) @@ -239,11 +237,7 @@ private RealtimeVehicle mapRealtimeVehicle( newVehicle.withTime(Instant.ofEpochSecond(vehiclePosition.getTimestamp())); } - if ( - vehiclePositionFeatures.contains( - VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION - ) - ) { + if (vehiclePositionFeatures.contains(STOP_POSITION)) { if (vehiclePosition.hasCurrentStatus()) { newVehicle.withStopStatus(stopStatusToModel(vehiclePosition.getCurrentStatus())); } @@ -279,12 +273,7 @@ else if (vehiclePosition.hasCurrentStopSequence()) { newVehicle.withTrip(trip); - if ( - vehiclePositionFeatures.contains( - VehiclePositionsUpdaterConfig.VehiclePositionFeature.OCCUPANCY - ) && - vehiclePosition.hasOccupancyStatus() - ) { + if (vehiclePositionFeatures.contains(OCCUPANCY) && vehiclePosition.hasOccupancyStatus()) { newVehicle.withOccupancyStatus(occupancyStatusToModel(vehiclePosition.getOccupancyStatus())); } From e9789751294c8bef3f197a37f2583d299212c1d7 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 26 Sep 2023 13:16:04 +0300 Subject: [PATCH 11/26] Add default for config --- .../config/framework/json/ParameterBuilder.java | 14 ++++++++++++++ .../updaters/VehiclePositionsUpdaterConfig.java | 6 +++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java index 3bc66ce6ea5..11672f99005 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java @@ -4,6 +4,7 @@ import static org.opentripplanner.standalone.config.framework.json.ConfigType.COST_LINEAR_FUNCTION; import static org.opentripplanner.standalone.config.framework.json.ConfigType.DOUBLE; import static org.opentripplanner.standalone.config.framework.json.ConfigType.DURATION; +import static org.opentripplanner.standalone.config.framework.json.ConfigType.ENUM; import static org.opentripplanner.standalone.config.framework.json.ConfigType.FEED_SCOPED_ID; import static org.opentripplanner.standalone.config.framework.json.ConfigType.INTEGER; import static org.opentripplanner.standalone.config.framework.json.ConfigType.LOCALE; @@ -236,6 +237,19 @@ public > Set asEnumSet(Class enumClass) { return result.isEmpty() ? EnumSet.noneOf(enumClass) : EnumSet.copyOf(result); } + public > Set asEnumSet(Class enumClass, Collection defaultValues) { + List dft = (defaultValues instanceof List) + ? (List) defaultValues + : List.copyOf(defaultValues); + info.withOptional(dft.toString()).withEnumSet(enumClass); + List> optionalList = buildAndListSimpleArrayElements( + List.of(), + it -> parseOptionalEnum(it.asText(), enumClass) + ); + List result = optionalList.stream().filter(Optional::isPresent).map(Optional::get).toList(); + return result.isEmpty() ? Set.copyOf(dft) : EnumSet.copyOf(result); + } + /** * Get a map of enum values listed in the config like this: (This example has Boolean values) *
diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java
index e41a14de79d..502f0ea456e 100644
--- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java
+++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehiclePositionsUpdaterConfig.java
@@ -3,8 +3,12 @@
 import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2;
 import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3;
 import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_5;
+import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.OCCUPANCY;
+import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION;
+import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION;
 
 import java.time.Duration;
+import java.util.List;
 import org.opentripplanner.standalone.config.framework.json.NodeAdapter;
 import org.opentripplanner.updater.vehicle_position.VehiclePositionsUpdaterParameters;
 
@@ -35,7 +39,7 @@ public static VehiclePositionsUpdaterParameters create(String updaterRef, NodeAd
       .of("features")
       .since(V2_5)
       .summary("Which features of GTFS RT vehicle positions should be loaded into OTP.")
-      .asEnumSet(VehiclePositionFeature.class);
+      .asEnumSet(VehiclePositionFeature.class, List.of(POSITION, STOP_POSITION, OCCUPANCY));
     var headers = HttpHeadersConfig.headers(c, V2_3);
     return new VehiclePositionsUpdaterParameters(
       updaterRef,

From bb0e0bddb2540eda77da3ccaf8b490c54b487a05 Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Tue, 26 Sep 2023 18:05:19 +0300
Subject: [PATCH 12/26] Include more types into transmodel API and less to GTFS

---
 src/ext/graphql/transmodelapi/schema.graphql  | 13 +++++++++
 .../datafetchers/TripOccupancyImpl.java       | 29 ++++++++++++-------
 .../generated/GraphQLDataFetchers.java        |  3 +-
 .../generated/GraphQLTypes.java               |  1 -
 .../generated/graphql-codegen.yml             |  2 +-
 .../mapping/OccupancyStatusMapper.java        |  8 +++--
 .../ext/transmodelapi/model/EnumTypes.java    | 19 ++++++++++++
 .../resources/gtfsgraphqlapi/schema.graphqls  | 10 +------
 .../model/timetable/OccupancyStatus.java      |  8 +----
 .../RealtimeVehiclePatternMatcher.java        |  2 +-
 10 files changed, 62 insertions(+), 33 deletions(-)

diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql
index f568c46f7fc..652e01920c1 100644
--- a/src/ext/graphql/transmodelapi/schema.graphql
+++ b/src/ext/graphql/transmodelapi/schema.graphql
@@ -1523,6 +1523,19 @@ enum MultiModalMode {
 }
 
 enum OccupancyStatus {
+  """
+  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.
+  """
+  crushedStandingRoomOnly
+  """
+  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 `manySeatsAvailable`
+  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 few seats available."
   fewSeatsAvailable
   "The vehicle or carriage is considered full by most measures, but may still be allowing passengers to board."
diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java
index 75543936918..6818d3e4420 100644
--- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java
+++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/datafetchers/TripOccupancyImpl.java
@@ -1,26 +1,35 @@
 package org.opentripplanner.ext.gtfsgraphqlapi.datafetchers;
 
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.CRUSHED_STANDING_ROOM_ONLY;
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.EMPTY;
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.FEW_SEATS_AVAILABLE;
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.FULL;
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.MANY_SEATS_AVAILABLE;
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.NOT_ACCEPTING_PASSENGERS;
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.NO_DATA_AVAILABLE;
+import static org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus.STANDING_ROOM_ONLY;
+
 import graphql.schema.DataFetcher;
 import graphql.schema.DataFetchingEnvironment;
 import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLDataFetchers;
+import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes;
 import org.opentripplanner.ext.gtfsgraphqlapi.model.TripOccupancy;
 
 public class TripOccupancyImpl implements GraphQLDataFetchers.GraphQLTripOccupancy {
 
   @Override
-  public DataFetcher occupancyStatus() {
+  public DataFetcher occupancyStatus() {
     return env -> {
       var occupancyStatus = getSource(env).occupancyStatus();
       return switch (occupancyStatus) {
-        case NO_DATA_AVAILABLE -> "NO_DATA_AVAILABLE";
-        case EMPTY -> "EMPTY";
-        case MANY_SEATS_AVAILABLE -> "MANY_SEATS_AVAILABLE";
-        case FEW_SEATS_AVAILABLE -> "FEW_SEATS_AVAILABLE";
-        case STANDING_ROOM_ONLY -> "STANDING_ROOM_ONLY";
-        case CRUSHED_STANDING_ROOM_ONLY -> "CRUSHED_STANDING_ROOM_ONLY";
-        case FULL -> "FULL";
-        case NOT_ACCEPTING_PASSENGERS -> "NOT_ACCEPTING_PASSENGERS";
-        case NOT_BOARDABLE -> "NOT_BOARDABLE";
+        case NO_DATA_AVAILABLE -> NO_DATA_AVAILABLE;
+        case EMPTY -> EMPTY;
+        case MANY_SEATS_AVAILABLE -> MANY_SEATS_AVAILABLE;
+        case FEW_SEATS_AVAILABLE -> FEW_SEATS_AVAILABLE;
+        case STANDING_ROOM_ONLY -> STANDING_ROOM_ONLY;
+        case CRUSHED_STANDING_ROOM_ONLY -> CRUSHED_STANDING_ROOM_ONLY;
+        case FULL -> FULL;
+        case NOT_ACCEPTING_PASSENGERS -> NOT_ACCEPTING_PASSENGERS;
       };
     };
   }
diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java
index 279bae6cc53..83c823adde2 100644
--- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java
+++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLDataFetchers.java
@@ -16,6 +16,7 @@
 import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLAlertEffectType;
 import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLAlertSeverityLevelType;
 import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLInputField;
+import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus;
 import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLRelativeDirection;
 import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLRoutingErrorCode;
 import org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLTransitMode;
@@ -1067,7 +1068,7 @@ public interface GraphQLTrip {
    * available for a trip. Historic data might not be available.
    */
   public interface GraphQLTripOccupancy {
-    public DataFetcher occupancyStatus();
+    public DataFetcher occupancyStatus();
   }
 
   /** This is used for alert entities that we don't explicitly handle or they are missing. */
diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java
index 5fca60671b9..b27fee0cfef 100644
--- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java
+++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/GraphQLTypes.java
@@ -755,7 +755,6 @@ public enum GraphQLOccupancyStatus {
     FULL,
     MANY_SEATS_AVAILABLE,
     NOT_ACCEPTING_PASSENGERS,
-    NOT_BOARDABLE,
     NO_DATA_AVAILABLE,
     STANDING_ROOM_ONLY,
   }
diff --git a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml
index fcb1e840431..ff51e2df9b7 100644
--- a/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml
+++ b/src/ext/java/org/opentripplanner/ext/gtfsgraphqlapi/generated/graphql-codegen.yml
@@ -59,7 +59,7 @@ config:
     Leg: org.opentripplanner.model.plan.Leg#Leg
     Mode: String
     TransitMode: org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLTransitMode#GraphQLTransitMode
-    OccupancyStatus: String
+    OccupancyStatus: org.opentripplanner.ext.gtfsgraphqlapi.generated.GraphQLTypes.GraphQLOccupancyStatus#GraphQLOccupancyStatus
     PageInfo: Object
     Pattern: org.opentripplanner.transit.model.network.TripPattern#TripPattern
     PickupDropoffType: String
diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java
index 8d1b74daa9e..978f8d46c2c 100644
--- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java
+++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/OccupancyStatusMapper.java
@@ -15,11 +15,13 @@ public class OccupancyStatusMapper {
   public static OccupancyStatus mapStatus(OccupancyStatus occupancyStatus) {
     return switch (occupancyStatus) {
       case NO_DATA_AVAILABLE -> OccupancyStatus.NO_DATA_AVAILABLE;
-      case MANY_SEATS_AVAILABLE, EMPTY -> OccupancyStatus.MANY_SEATS_AVAILABLE;
+      case EMPTY -> OccupancyStatus.EMPTY;
+      case MANY_SEATS_AVAILABLE -> OccupancyStatus.MANY_SEATS_AVAILABLE;
       case FEW_SEATS_AVAILABLE -> OccupancyStatus.FEW_SEATS_AVAILABLE;
-      case STANDING_ROOM_ONLY, CRUSHED_STANDING_ROOM_ONLY -> OccupancyStatus.STANDING_ROOM_ONLY;
+      case STANDING_ROOM_ONLY -> OccupancyStatus.STANDING_ROOM_ONLY;
+      case CRUSHED_STANDING_ROOM_ONLY -> occupancyStatus.CRUSHED_STANDING_ROOM_ONLY;
       case FULL -> OccupancyStatus.FULL;
-      case NOT_ACCEPTING_PASSENGERS, NOT_BOARDABLE -> OccupancyStatus.NOT_ACCEPTING_PASSENGERS;
+      case NOT_ACCEPTING_PASSENGERS -> OccupancyStatus.NOT_ACCEPTING_PASSENGERS;
     };
   }
 }
diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java
index ac10b59f398..3fa3ed67cc2 100644
--- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java
+++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java
@@ -198,6 +198,16 @@ public class EnumTypes {
       OccupancyStatus.NO_DATA_AVAILABLE,
       "The vehicle or carriage doesn't have any occupancy data available."
     )
+    .value(
+      "empty",
+      OccupancyStatus.EMPTY,
+      """
+      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 `manySeatsAvailable`
+      so it's possible to handle them as the same value, if one wants to limit the number of different
+      values.
+      """
+    )
     .value(
       "manySeatsAvailable",
       OccupancyStatus.MANY_SEATS_AVAILABLE,
@@ -213,6 +223,15 @@ public class EnumTypes {
       OccupancyStatus.STANDING_ROOM_ONLY,
       "The vehicle or carriage only has standing room available."
     )
+    .value(
+      "crushedStandingRoomOnly",
+      OccupancyStatus.CRUSHED_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.
+        """
+    )
     .value(
       "full",
       OccupancyStatus.FULL,
diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls
index 276a8ae6f53..7117acd8902 100644
--- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls
+++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls
@@ -3440,17 +3440,9 @@ enum OccupancyStatus {
   FULL
 
   """
-  The vehicle or carriage is not accepting passengers. The vehicle or carriage usually accepts
-  passengers for boarding.
+  The vehicle or carriage is not accepting passengers.
   """
   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 {
diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java
index f47eb01baa2..53ef887e347 100644
--- a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java
+++ b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java
@@ -44,13 +44,7 @@ public enum OccupancyStatus {
    */
   FULL,
   /**
-   * The vehicle or carriage is not accepting passengers. The vehicle or carriage usually accepts
-   * passengers for boarding.
+   * The vehicle or carriage is not accepting passengers.
    */
   NOT_ACCEPTING_PASSENGERS,
-  /**
-   * The vehicle or carriage is not boardable and never accepts passengers. Useful for special
-   * vehicles or carriages (engine, maintenance carriage, etc…).
-   */
-  NOT_BOARDABLE,
 }
diff --git a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java
index 8b5833ad4e9..fe29ef35b97 100644
--- a/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java
+++ b/src/main/java/org/opentripplanner/updater/vehicle_position/RealtimeVehiclePatternMatcher.java
@@ -309,7 +309,7 @@ private static OccupancyStatus occupancyStatusToModel(
       case CRUSHED_STANDING_ROOM_ONLY -> OccupancyStatus.CRUSHED_STANDING_ROOM_ONLY;
       case FULL -> OccupancyStatus.FULL;
       case NOT_ACCEPTING_PASSENGERS -> OccupancyStatus.NOT_ACCEPTING_PASSENGERS;
-      case NOT_BOARDABLE -> OccupancyStatus.NOT_BOARDABLE;
+      case NOT_BOARDABLE -> OccupancyStatus.NOT_ACCEPTING_PASSENGERS;
     };
   }
 

From 2ed9ccf3413b28497daac450000c2c4ce8fbcc5c Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Thu, 28 Sep 2023 15:21:20 +0300
Subject: [PATCH 13/26] Refactor RealtimeVehicle to return optionals

---
 .../datafetchers/VehiclePositionImpl.java     | 16 ++--
 .../DefaultRealtimeVehicleService.java        |  9 +-
 .../model/RealtimeVehicle.java                | 82 ++++++++++++++++---
 .../model/RealtimeVehicleBuilder.java         | 58 +++++++++----
 4 files changed, 127 insertions(+), 38 deletions(-)

diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/VehiclePositionImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/VehiclePositionImpl.java
index a4c9f6c36fe..7c2c0a7172a 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/VehiclePositionImpl.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/VehiclePositionImpl.java
@@ -11,39 +11,39 @@ public class VehiclePositionImpl implements GraphQLDataFetchers.GraphQLVehiclePo
 
   @Override
   public DataFetcher heading() {
-    return env -> getSource(env).heading();
+    return env -> getSource(env).heading().orElse(null);
   }
 
   @Override
   public DataFetcher label() {
-    return env -> getSource(env).label();
+    return env -> getSource(env).label().orElse(null);
   }
 
   @Override
   public DataFetcher lastUpdated() {
-    return env -> getSource(env).time() != null ? getSource(env).time().getEpochSecond() : null;
+    return env -> getSource(env).time().map(time -> time.getEpochSecond()).orElse(null);
   }
 
   @Override
   public DataFetcher lat() {
     return env ->
-      getSource(env).coordinates() != null ? getSource(env).coordinates().latitude() : null;
+      getSource(env).coordinates().map(coordinates -> coordinates.latitude()).orElse(null);
   }
 
   @Override
   public DataFetcher lon() {
     return env ->
-      getSource(env).coordinates() != null ? getSource(env).coordinates().longitude() : null;
+      getSource(env).coordinates().map(coordinates -> coordinates.longitude()).orElse(null);
   }
 
   @Override
   public DataFetcher speed() {
-    return env -> getSource(env).speed();
+    return env -> getSource(env).speed().orElse(null);
   }
 
   @Override
   public DataFetcher stopRelationship() {
-    return env -> getSource(env).stop();
+    return env -> getSource(env).stop().orElse(null);
   }
 
   @Override
@@ -53,7 +53,7 @@ public DataFetcher trip() {
 
   @Override
   public DataFetcher vehicleId() {
-    return env -> getSource(env).vehicleId() != null ? getSource(env).vehicleId().toString() : null;
+    return env -> getSource(env).vehicleId().map(vehicleId -> vehicleId.toString()).orElse(null);
   }
 
   private RealtimeVehicle getSource(DataFetchingEnvironment environment) {
diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
index 5e59d931ebd..40485cd70ae 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
@@ -1,10 +1,14 @@
 package org.opentripplanner.service.realtimevehicles.internal;
 
+import static org.opentripplanner.transit.model.timetable.OccupancyStatus.NO_DATA_AVAILABLE;
+
 import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
+import java.time.Instant;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import javax.annotation.Nonnull;
 import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository;
@@ -46,8 +50,9 @@ public OccupancyStatus getVehicleOccupancyStatus(TripPattern pattern, FeedScoped
       .getOrDefault(pattern, List.of())
       .stream()
       .filter(vehicle -> tripId.equals(vehicle.trip().getId()))
-      .max(Comparator.comparing(vehicle -> vehicle.time()))
+      .max(Comparator.comparing(vehicle -> vehicle.time().orElse(Instant.MIN)))
       .map(vehicle -> vehicle.occupancyStatus())
-      .orElse(OccupancyStatus.NO_DATA_AVAILABLE);
+      .orElse(Optional.empty())
+      .orElse(NO_DATA_AVAILABLE);
   }
 }
diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java
index 6cdbd7e1415..98cfc6300f7 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java
@@ -1,6 +1,8 @@
 package org.opentripplanner.service.realtimevehicles.model;
 
 import java.time.Instant;
+import java.util.Optional;
+import javax.annotation.Nonnull;
 import org.opentripplanner.framework.geometry.WgsCoordinate;
 import org.opentripplanner.transit.model.framework.FeedScopedId;
 import org.opentripplanner.transit.model.site.StopLocation;
@@ -10,36 +12,94 @@
 /**
  * Internal model of a realtime vehicle.
  */
-public record RealtimeVehicle(
-  FeedScopedId vehicleId,
-  String label,
-  WgsCoordinate coordinates,
+public class RealtimeVehicle {
+
+  private final FeedScopedId vehicleId;
+
+  private final String label;
+
+  private final WgsCoordinate coordinates;
+
   /**
    * Speed in meters per second
    */
-  Double speed,
+  private final Double speed;
   /**
    * Bearing, in degrees, clockwise from North, i.e., 0 is North and 90 is East. This can be the
    * compass bearing, or the direction towards the next stop or intermediate location.
    */
-  Double heading,
+  private final Double heading;
 
   /**
    * When the realtime vehicle was recorded
    */
-  Instant time,
+  private final Instant time;
 
   /**
    * Status of the vehicle, ie. if approaching the next stop or if it is there already.
    */
-  StopRelationship stop,
-  Trip trip,
+  private final StopRelationship stop;
+
+  private final Trip trip;
 
   /**
    * How full the vehicle is and is it still accepting passengers.
    */
-  OccupancyStatus occupancyStatus
-) {
+  private final OccupancyStatus occupancyStatus;
+
+  RealtimeVehicle(RealtimeVehicleBuilder builder) {
+    var stopRelationship = Optional
+      .ofNullable(builder.stop())
+      .map(s -> new StopRelationship(s, builder.stopStatus()))
+      .orElse(null);
+    vehicleId = builder.vehicleId();
+    label = builder().label();
+    coordinates = builder.coordinates();
+    speed = builder().speed();
+    heading = builder().heading();
+    time = builder.time();
+    stop = stopRelationship;
+    trip = builder.trip();
+    occupancyStatus = builder.occupancyStatus();
+  }
+
+  public Optional vehicleId() {
+    return Optional.ofNullable(vehicleId);
+  }
+
+  public Optional label() {
+    return Optional.ofNullable(label);
+  }
+
+  public Optional coordinates() {
+    return Optional.ofNullable(coordinates);
+  }
+
+  public Optional speed() {
+    return Optional.ofNullable(speed);
+  }
+
+  public Optional heading() {
+    return Optional.ofNullable(heading);
+  }
+
+  public Optional time() {
+    return Optional.ofNullable(time);
+  }
+
+  public Optional stop() {
+    return Optional.ofNullable(stop);
+  }
+
+  @Nonnull
+  public Trip trip() {
+    return trip;
+  }
+
+  public Optional occupancyStatus() {
+    return Optional.ofNullable(occupancyStatus);
+  }
+
   public static RealtimeVehicleBuilder builder() {
     return new RealtimeVehicleBuilder();
   }
diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java
index 75dd64666de..cc68919f41d 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java
@@ -1,9 +1,7 @@
 package org.opentripplanner.service.realtimevehicles.model;
 
 import java.time.Instant;
-import java.util.Optional;
 import org.opentripplanner.framework.geometry.WgsCoordinate;
-import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopRelationship;
 import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopStatus;
 import org.opentripplanner.transit.model.framework.FeedScopedId;
 import org.opentripplanner.transit.model.site.StopLocation;
@@ -23,71 +21,97 @@ public class RealtimeVehicleBuilder {
   private Trip trip;
   private OccupancyStatus occupancyStatus;
 
+  public FeedScopedId vehicleId() {
+    return vehicleId;
+  }
+
   public RealtimeVehicleBuilder withVehicleId(FeedScopedId vehicleId) {
     this.vehicleId = vehicleId;
     return this;
   }
 
+  public String label() {
+    return label;
+  }
+
   public RealtimeVehicleBuilder withLabel(String label) {
     this.label = label;
     return this;
   }
 
+  public WgsCoordinate coordinates() {
+    return coordinates;
+  }
+
   public RealtimeVehicleBuilder withCoordinates(WgsCoordinate c) {
     this.coordinates = c;
     return this;
   }
 
+  public Double speed() {
+    return speed;
+  }
+
   public RealtimeVehicleBuilder withSpeed(double speed) {
     this.speed = speed;
     return this;
   }
 
+  public Double heading() {
+    return heading;
+  }
+
   public RealtimeVehicleBuilder withHeading(double heading) {
     this.heading = heading;
     return this;
   }
 
+  public Instant time() {
+    return time;
+  }
+
   public RealtimeVehicleBuilder withTime(Instant time) {
     this.time = time;
     return this;
   }
 
+  public StopStatus stopStatus() {
+    return stopStatus;
+  }
+
   public RealtimeVehicleBuilder withStopStatus(StopStatus stopStatus) {
     this.stopStatus = stopStatus;
     return this;
   }
 
+  public StopLocation stop() {
+    return stop;
+  }
+
   public RealtimeVehicleBuilder withStop(StopLocation stop) {
     this.stop = stop;
     return this;
   }
 
+  public Trip trip() {
+    return trip;
+  }
+
   public RealtimeVehicleBuilder withTrip(Trip trip) {
     this.trip = trip;
     return this;
   }
 
+  public OccupancyStatus occupancyStatus() {
+    return occupancyStatus;
+  }
+
   public RealtimeVehicleBuilder withOccupancyStatus(OccupancyStatus occupancyStatus) {
     this.occupancyStatus = occupancyStatus;
     return this;
   }
 
   public RealtimeVehicle build() {
-    var stop = Optional
-      .ofNullable(this.stop)
-      .map(s -> new StopRelationship(s, stopStatus))
-      .orElse(null);
-    return new RealtimeVehicle(
-      vehicleId,
-      label,
-      coordinates,
-      speed,
-      heading,
-      time,
-      stop,
-      trip,
-      occupancyStatus
-    );
+    return new RealtimeVehicle(this);
   }
 }

From 865947f94a588c05820ab3ab680d35cc706db07d Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Thu, 28 Sep 2023 15:52:33 +0300
Subject: [PATCH 14/26] Include transit service in realtime vehicle service

---
 .../mapping/TripRequestMapperTest.java             |  2 +-
 .../apis/gtfs/datafetchers/TripImpl.java           |  3 +--
 .../realtimevehicles/RealtimeVehicleService.java   |  4 ++--
 .../internal/DefaultRealtimeVehicleService.java    | 14 ++++++++++----
 .../org/opentripplanner/TestServerContext.java     |  8 +++++---
 .../apis/gtfs/GraphQLIntegrationTest.java          |  2 +-
 .../apis/gtfs/mapping/RouteRequestMapperTest.java  |  2 +-
 .../transit/speed_test/SpeedTest.java              |  6 ++++--
 8 files changed, 25 insertions(+), 16 deletions(-)

diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java
index 7a2d9a64465..6034fbcd3a3 100644
--- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java
@@ -73,7 +73,7 @@ public class TripRequestMapperTest implements PlanTestConstants {
           Metrics.globalRegistry,
           RouterConfig.DEFAULT.vectorTileLayers(),
           new DefaultWorldEnvelopeService(new DefaultWorldEnvelopeRepository()),
-          new DefaultRealtimeVehicleService(),
+          new DefaultRealtimeVehicleService(transitService),
           new DefaultVehicleRentalService(),
           RouterConfig.DEFAULT.flexConfig(),
           List.of(),
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java
index 45be3238c4e..78255ce7787 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripImpl.java
@@ -374,9 +374,8 @@ public DataFetcher wheelchairAccessible(
   public DataFetcher occupancy() {
     return environment -> {
       Trip trip = getSource(environment);
-      TripPattern pattern = getTransitService(environment).getPatternForTrip(trip);
       return new TripOccupancy(
-        getRealtimeVehiclesService(environment).getVehicleOccupancyStatus(pattern, trip.getId())
+        getRealtimeVehiclesService(environment).getVehicleOccupancyStatus(trip)
       );
     };
   }
diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java
index 65d69db57cc..6424b182bf3 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java
@@ -2,9 +2,9 @@
 
 import java.util.List;
 import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle;
-import org.opentripplanner.transit.model.framework.FeedScopedId;
 import org.opentripplanner.transit.model.network.TripPattern;
 import org.opentripplanner.transit.model.timetable.OccupancyStatus;
+import org.opentripplanner.transit.model.timetable.Trip;
 
 public interface RealtimeVehicleService {
   /**
@@ -15,5 +15,5 @@ public interface RealtimeVehicleService {
   /**
    * Get the latest occupancy status for a certain trip.
    */
-  OccupancyStatus getVehicleOccupancyStatus(TripPattern pattern, FeedScopedId tripId);
+  OccupancyStatus getVehicleOccupancyStatus(Trip trip);
 }
diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
index 40485cd70ae..8539367affe 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
@@ -14,9 +14,10 @@
 import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository;
 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.TripPattern;
 import org.opentripplanner.transit.model.timetable.OccupancyStatus;
+import org.opentripplanner.transit.model.timetable.Trip;
+import org.opentripplanner.transit.service.TransitService;
 
 @Singleton
 public class DefaultRealtimeVehicleService
@@ -24,8 +25,12 @@ public class DefaultRealtimeVehicleService
 
   private final Map> vehicles = new ConcurrentHashMap<>();
 
+  private final TransitService transitService;
+
   @Inject
-  public DefaultRealtimeVehicleService() {}
+  public DefaultRealtimeVehicleService(TransitService transitService) {
+    this.transitService = transitService;
+  }
 
   @Override
   public void setRealtimeVehicles(TripPattern pattern, List updates) {
@@ -45,11 +50,12 @@ public List getRealtimeVehicles(TripPattern pattern) {
 
   @Nonnull
   @Override
-  public OccupancyStatus getVehicleOccupancyStatus(TripPattern pattern, FeedScopedId tripId) {
+  public OccupancyStatus getVehicleOccupancyStatus(Trip trip) {
+    TripPattern pattern = transitService.getPatternForTrip(trip);
     return vehicles
       .getOrDefault(pattern, List.of())
       .stream()
-      .filter(vehicle -> tripId.equals(vehicle.trip().getId()))
+      .filter(vehicle -> trip.getId().equals(vehicle.trip().getId()))
       .max(Comparator.comparing(vehicle -> vehicle.time().orElse(Instant.MIN)))
       .map(vehicle -> vehicle.occupancyStatus())
       .orElse(Optional.empty())
diff --git a/src/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java
index e6609a2b780..8e68fc7c3a0 100644
--- a/src/test/java/org/opentripplanner/TestServerContext.java
+++ b/src/test/java/org/opentripplanner/TestServerContext.java
@@ -18,6 +18,7 @@
 import org.opentripplanner.standalone.server.DefaultServerRequestContext;
 import org.opentripplanner.transit.service.DefaultTransitService;
 import org.opentripplanner.transit.service.TransitModel;
+import org.opentripplanner.transit.service.TransitService;
 
 public class TestServerContext {
 
@@ -30,6 +31,7 @@ public static OtpServerRequestContext createServerContext(
   ) {
     transitModel.index();
     final RouterConfig routerConfig = RouterConfig.DEFAULT;
+    var transitService = new DefaultTransitService(transitModel);
     DefaultServerRequestContext context = DefaultServerRequestContext.create(
       routerConfig.transitTuningConfig(),
       routerConfig.routingRequestDefaults(),
@@ -39,7 +41,7 @@ public static OtpServerRequestContext createServerContext(
       Metrics.globalRegistry,
       routerConfig.vectorTileLayers(),
       createWorldEnvelopeService(),
-      createRealtimeVehicleService(),
+      createRealtimeVehicleService(transitService),
       createVehicleRentalService(),
       routerConfig.flexConfig(),
       List.of(),
@@ -54,8 +56,8 @@ public static WorldEnvelopeService createWorldEnvelopeService() {
     return new DefaultWorldEnvelopeService(new DefaultWorldEnvelopeRepository());
   }
 
-  public static RealtimeVehicleService createRealtimeVehicleService() {
-    return new DefaultRealtimeVehicleService();
+  public static RealtimeVehicleService createRealtimeVehicleService(TransitService transitService) {
+    return new DefaultRealtimeVehicleService(transitService);
   }
 
   public static VehicleRentalService createVehicleRentalService() {
diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
index fbe047dc837..2b026fada72 100644
--- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
+++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
@@ -223,7 +223,7 @@ public TransitAlertService getTransitAlertService() {
         new DefaultFareService(),
         graph.getVehicleParkingService(),
         new DefaultVehicleRentalService(),
-        new DefaultRealtimeVehicleService(),
+        new DefaultRealtimeVehicleService(transitService),
         GraphFinder.getInstance(graph, transitService::findRegularStop),
         new RouteRequest()
       );
diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java
index 5b703eb8302..af6352839fb 100644
--- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java
+++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java
@@ -51,7 +51,7 @@ class RouteRequestMapperTest implements PlanTestConstants {
         new DefaultFareService(),
         graph.getVehicleParkingService(),
         new DefaultVehicleRentalService(),
-        new DefaultRealtimeVehicleService(),
+        new DefaultRealtimeVehicleService(transitService),
         GraphFinder.getInstance(graph, transitService::findRegularStop),
         new RouteRequest()
       );
diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java
index 9fa217903c1..519514ab9ed 100644
--- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java
+++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java
@@ -90,9 +90,11 @@ public SpeedTest(
     this.testCaseDefinitions = tcIO.readTestCaseDefinitions();
     this.expectedResultsByTcId = tcIO.readExpectedResults();
 
+    var transitService = new DefaultTransitService(transitModel);
+
     UpdaterConfigurator.configure(
       graph,
-      new DefaultRealtimeVehicleService(),
+      new DefaultRealtimeVehicleService(transitService),
       new DefaultVehicleRentalService(),
       transitModel,
       config.updatersConfig
@@ -111,7 +113,7 @@ public SpeedTest(
         timer.getRegistry(),
         List::of,
         TestServerContext.createWorldEnvelopeService(),
-        TestServerContext.createRealtimeVehicleService(),
+        TestServerContext.createRealtimeVehicleService(transitService),
         TestServerContext.createVehicleRentalService(),
         config.flexConfig,
         List.of(),

From 4ea77e8909e4094e97c06bc65812de2af6b66fe0 Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Fri, 29 Sep 2023 14:51:11 +0300
Subject: [PATCH 15/26] Fix constructor

---
 .../model/RealtimeVehicle.java                 | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java
index 98cfc6300f7..e06ae5ebaa7 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java
@@ -52,15 +52,15 @@ public class RealtimeVehicle {
       .ofNullable(builder.stop())
       .map(s -> new StopRelationship(s, builder.stopStatus()))
       .orElse(null);
-    vehicleId = builder.vehicleId();
-    label = builder().label();
-    coordinates = builder.coordinates();
-    speed = builder().speed();
-    heading = builder().heading();
-    time = builder.time();
-    stop = stopRelationship;
-    trip = builder.trip();
-    occupancyStatus = builder.occupancyStatus();
+    this.vehicleId = builder.vehicleId();
+    this.label = builder.label();
+    this.coordinates = builder.coordinates();
+    this.speed = builder.speed();
+    this.heading = builder.heading();
+    this.time = builder.time();
+    this.stop = stopRelationship;
+    this.trip = builder.trip();
+    this.occupancyStatus = builder.occupancyStatus();
   }
 
   public Optional vehicleId() {

From 00083dc8d0d54e87bdb5f645e6b3357f46525018 Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Fri, 29 Sep 2023 14:53:45 +0300
Subject: [PATCH 16/26] Add graphql tests

---
 .../apis/gtfs/GraphQLIntegrationTest.java     | 27 ++++++++++++-
 .../apis/gtfs/expectations/patterns.json      | 38 ++++++++++++++++++-
 .../apis/gtfs/queries/patterns.graphql        | 21 ++++++++++
 3 files changed, 84 insertions(+), 2 deletions(-)

diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
index 2b026fada72..c1e0d0bbccb 100644
--- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
+++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
@@ -14,10 +14,12 @@
 import static org.opentripplanner.model.plan.PlanTestConstants.T11_30;
 import static org.opentripplanner.model.plan.PlanTestConstants.T11_50;
 import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary;
+import static org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopStatus.IN_TRANSIT_TO;
 import static org.opentripplanner.test.support.JsonAssertions.assertEqualJson;
 import static org.opentripplanner.transit.model._data.TransitModelForTest.id;
 import static org.opentripplanner.transit.model.basic.TransitMode.BUS;
 import static org.opentripplanner.transit.model.basic.TransitMode.FERRY;
+import static org.opentripplanner.transit.model.timetable.OccupancyStatus.FEW_SEATS_AVAILABLE;
 
 import jakarta.ws.rs.core.Response;
 import java.io.IOException;
@@ -69,6 +71,7 @@
 import org.opentripplanner.routing.services.TransitAlertService;
 import org.opentripplanner.routing.vehicle_parking.VehicleParking;
 import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService;
+import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle;
 import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService;
 import org.opentripplanner.standalone.config.framework.json.JsonSupport;
 import org.opentripplanner.test.support.FilePatternSource;
@@ -216,6 +219,28 @@ public TransitAlertService getTransitAlertService() {
     var alerts = ListUtils.combine(List.of(alert), getTransitAlert(entitySelector));
     transitService.getTransitAlertService().setAlerts(alerts);
 
+    var realtimeVehicleService = new DefaultRealtimeVehicleService(transitService);
+    var occypancyVehicle = RealtimeVehicle
+      .builder()
+      .withTrip(trip)
+      .withTime(Instant.MAX)
+      .withVehicleId(id("vehicle-1"))
+      .withOccupancyStatus(FEW_SEATS_AVAILABLE)
+      .build();
+    var positionVehicle = RealtimeVehicle
+      .builder()
+      .withTrip(trip)
+      .withTime(Instant.MIN)
+      .withVehicleId(id("vehicle-2"))
+      .withLabel("vehicle2")
+      .withCoordinates(new WgsCoordinate(60.0, 80.0))
+      .withHeading(80.0)
+      .withSpeed(10.2)
+      .withStop(pattern.getStop(0))
+      .withStopStatus(IN_TRANSIT_TO)
+      .build();
+    realtimeVehicleService.setRealtimeVehicles(pattern, List.of(occypancyVehicle, positionVehicle));
+
     context =
       new GraphQLRequestContext(
         new TestRoutingService(List.of(i1)),
@@ -223,7 +248,7 @@ public TransitAlertService getTransitAlertService() {
         new DefaultFareService(),
         graph.getVehicleParkingService(),
         new DefaultVehicleRentalService(),
-        new DefaultRealtimeVehicleService(transitService),
+        realtimeVehicleService,
         GraphFinder.getInstance(graph, transitService::findRegularStop),
         new RouteRequest()
       );
diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/patterns.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/patterns.json
index 5f2a0e7d746..b23ced4f954 100644
--- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/patterns.json
+++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/patterns.json
@@ -47,7 +47,43 @@
                 "pickupType" : "SCHEDULED",
                 "dropoffType" : "SCHEDULED"
               }
-            ]
+            ],
+            "occupancy" : {
+              "occupancyStatus" : "FEW_SEATS_AVAILABLE"
+            }
+          }
+        ],
+        "vehiclePositions" : [
+          {
+            "vehicleId" : "F:vehicle-1",
+            "label" : null,
+            "lat" : null,
+            "lon" : null,
+            "stopRelationship" : null,
+            "speed" : null,
+            "heading" : null,
+            "lastUpdated" : 31556889864403199,
+            "trip" : {
+              "gtfsId" : "F:123"
+            }
+          },
+          {
+            "vehicleId" : "F:vehicle-2",
+            "label" : "vehicle2",
+            "lat" : 60.0,
+            "lon" : 80.0,
+            "stopRelationship" : {
+              "status" : "IN_TRANSIT_TO",
+              "stop" : {
+                "gtfsId" : "F:Stop_0"
+              }
+            },
+            "speed" : 10.2,
+            "heading" : 80.0,
+            "lastUpdated" : -31557014167219200,
+            "trip" : {
+              "gtfsId" : "F:123"
+            }
           }
         ]
       }
diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/patterns.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/patterns.graphql
index d4feaf2c8f6..32be856274e 100644
--- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/patterns.graphql
+++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/patterns.graphql
@@ -17,6 +17,27 @@
                 pickupType
                 dropoffType
             }
+            occupancy {
+                occupancyStatus
+            }
+        }
+        vehiclePositions {
+            vehicleId
+            label
+            lat
+            lon
+            stopRelationship {
+                status
+                stop {
+                    gtfsId
+                }
+            }
+            speed
+            heading
+            lastUpdated
+            trip {
+                gtfsId
+            }
         }
     }
 }
\ No newline at end of file

From 72baea8044e907eb0c9293c6a9ffeb2f3d12611c Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Fri, 29 Sep 2023 15:33:34 +0300
Subject: [PATCH 17/26] Improve service testability and docs

---
 .../RealtimeVehicleService.java               | 11 ++++++----
 .../DefaultRealtimeVehicleService.java        | 20 ++++++++++++++-----
 2 files changed, 22 insertions(+), 9 deletions(-)

diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java
index 6424b182bf3..949b196faeb 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java
@@ -1,6 +1,7 @@
 package org.opentripplanner.service.realtimevehicles;
 
 import java.util.List;
+import javax.annotation.Nonnull;
 import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle;
 import org.opentripplanner.transit.model.network.TripPattern;
 import org.opentripplanner.transit.model.timetable.OccupancyStatus;
@@ -8,12 +9,14 @@
 
 public interface RealtimeVehicleService {
   /**
-   * Get the realtime vehicles for a certain trip pattern.
+   * Get the realtime vehicles for a certain trip pattern. Service contains all the vehicles that
+   * exist in input feeds but doesn't store any historical data.
    */
-  List getRealtimeVehicles(TripPattern pattern);
+  List getRealtimeVehicles(@Nonnull TripPattern pattern);
 
   /**
-   * Get the latest occupancy status for a certain trip.
+   * Get the latest occupancy status for a certain trip. Service contains all the vehicles that
+   * exist in input feeds but doesn't store any historical data.
    */
-  OccupancyStatus getVehicleOccupancyStatus(Trip trip);
+  OccupancyStatus getVehicleOccupancyStatus(@Nonnull Trip trip);
 }
diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
index 8539367affe..401092b6058 100644
--- a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
@@ -14,6 +14,7 @@
 import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository;
 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.TripPattern;
 import org.opentripplanner.transit.model.timetable.OccupancyStatus;
 import org.opentripplanner.transit.model.timetable.Trip;
@@ -43,21 +44,30 @@ public void clearRealtimeVehicles(TripPattern pattern) {
   }
 
   @Override
-  public List getRealtimeVehicles(TripPattern pattern) {
+  public List getRealtimeVehicles(@Nonnull TripPattern pattern) {
     // the list is made immutable during insertion, so we can safely return them
     return vehicles.getOrDefault(pattern, List.of());
   }
 
   @Nonnull
   @Override
-  public OccupancyStatus getVehicleOccupancyStatus(Trip trip) {
-    TripPattern pattern = transitService.getPatternForTrip(trip);
+  public OccupancyStatus getVehicleOccupancyStatus(@Nonnull Trip trip) {
+    return getOccupancyStatus(trip.getId(), transitService.getPatternForTrip(trip));
+  }
+
+  /**
+   * Get the latest occupancy status for a certain trip. Service contains all the vehicles that
+   * exist in input feeds but doesn't store any historical data. This method is an alternative to
+   * {@link #getVehicleOccupancyStatus(Trip)} and works even when {@link TransitService} is not
+   * provided to the service.
+   */
+  public OccupancyStatus getOccupancyStatus(FeedScopedId tripId, TripPattern pattern) {
     return vehicles
       .getOrDefault(pattern, List.of())
       .stream()
-      .filter(vehicle -> trip.getId().equals(vehicle.trip().getId()))
+      .filter(vehicle -> tripId.equals(vehicle.trip().getId()))
       .max(Comparator.comparing(vehicle -> vehicle.time().orElse(Instant.MIN)))
-      .map(vehicle -> vehicle.occupancyStatus())
+      .map(RealtimeVehicle::occupancyStatus)
       .orElse(Optional.empty())
       .orElse(NO_DATA_AVAILABLE);
   }

From 91d1fb9d255f5b4971c46469a450ef1b00b917ab Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Fri, 29 Sep 2023 15:36:27 +0300
Subject: [PATCH 18/26] Update tests

---
 .../RealtimeVehicleMatcherTest.java           | 94 ++++++++++++++-----
 1 file changed, 68 insertions(+), 26 deletions(-)

diff --git a/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java
index cbd94f49322..2ddc4860679 100644
--- a/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java
+++ b/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java
@@ -2,6 +2,9 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.opentripplanner.model.plan.PlanTestConstants.T11_00;
+import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.OCCUPANCY;
+import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION;
+import static org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION;
 import static org.opentripplanner.transit.model._data.TransitModelForTest.stopTime;
 import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.TRIP_NOT_FOUND_IN_PATTERN;
 
@@ -33,12 +36,18 @@
 import org.opentripplanner.transit.model.network.Route;
 import org.opentripplanner.transit.model.network.StopPattern;
 import org.opentripplanner.transit.model.network.TripPattern;
+import org.opentripplanner.transit.model.timetable.OccupancyStatus;
 import org.opentripplanner.transit.model.timetable.Trip;
 import org.opentripplanner.transit.model.timetable.TripTimes;
 
 public class RealtimeVehicleMatcherTest {
 
-  public static final Route ROUTE = TransitModelForTest.route("1").build();
+  private static final Route ROUTE = TransitModelForTest.route("1").build();
+  private static final Set FEATURES = Set.of(
+    POSITION,
+    STOP_POSITION,
+    OCCUPANCY
+  );
   ZoneId zoneId = ZoneIds.BERLIN;
   String tripId = "trip1";
   FeedScopedId scopedTripId = TransitModelForTest.id(tripId);
@@ -49,6 +58,16 @@ public void matchRealtimeVehiclesToTrip() {
     testVehiclePositions(pos);
   }
 
+  @Test
+  public void testOccupancy() {
+    var pos = vehiclePosition(tripId);
+    var posWithOccupancy = pos
+      .toBuilder()
+      .setOccupancyStatus(VehiclePosition.OccupancyStatus.FEW_SEATS_AVAILABLE)
+      .build();
+    testVehiclePositionOccupancy(posWithOccupancy);
+  }
+
   @Test
   @DisplayName("If the vehicle position has no start_date we need to guess the service day")
   public void inferStartDate() {
@@ -65,7 +84,7 @@ public void inferStartDate() {
 
   @Test
   public void tripNotFoundInPattern() {
-    var service = new DefaultRealtimeVehicleService();
+    var service = new DefaultRealtimeVehicleService(null);
 
     final String secondTripId = "trip2";
 
@@ -84,10 +103,7 @@ public void tripNotFoundInPattern() {
       service,
       zoneId,
       null,
-      Set.of(
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION,
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION
-      )
+      FEATURES
     );
 
     var positions = List.of(vehiclePosition(secondTripId));
@@ -99,7 +115,7 @@ public void tripNotFoundInPattern() {
 
   @Test
   public void sequenceId() {
-    var service = new DefaultRealtimeVehicleService();
+    var service = new DefaultRealtimeVehicleService(null);
 
     var tripId = "trip1";
     var scopedTripId = TransitModelForTest.id(tripId);
@@ -120,10 +136,7 @@ public void sequenceId() {
       service,
       zoneId,
       null,
-      Set.of(
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION,
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION
-      )
+      FEATURES
     );
 
     var pos = VehiclePosition
@@ -139,8 +152,8 @@ public void sequenceId() {
 
     // ensure that gtfs-rt was matched to an OTP pattern correctly
     assertEquals(1, service.getRealtimeVehicles(pattern1).size());
-    var nextStop = service.getRealtimeVehicles(pattern1).get(0).stop().stop();
-    assertEquals("F:stop-20", nextStop.getId().toString());
+    var nextStop = service.getRealtimeVehicles(pattern1).get(0).stop();
+    assertEquals("F:stop-20", nextStop.get().stop().getId().toString());
   }
 
   @Test
@@ -157,7 +170,7 @@ void invalidStopSequence() {
   }
 
   private void testVehiclePositions(VehiclePosition pos) {
-    var service = new DefaultRealtimeVehicleService();
+    var service = new DefaultRealtimeVehicleService(null);
     var trip = TransitModelForTest.trip(tripId).build();
     var stopTimes = List.of(stopTime(trip, 0), stopTime(trip, 1), stopTime(trip, 2));
 
@@ -170,7 +183,7 @@ private void testVehiclePositions(VehiclePosition pos) {
     assertEquals(0, service.getRealtimeVehicles(pattern).size());
 
     // Map positions to trips in feed
-    RealtimeVehiclePatternMatcher matcher = new RealtimeVehiclePatternMatcher(
+    var matcher = new RealtimeVehiclePatternMatcher(
       TransitModelForTest.FEED_ID,
       tripForId::get,
       patternForTrip::get,
@@ -178,10 +191,7 @@ private void testVehiclePositions(VehiclePosition pos) {
       service,
       zoneId,
       null,
-      Set.of(
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION,
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION
-      )
+      FEATURES
     );
 
     var positions = List.of(pos);
@@ -195,17 +205,52 @@ private void testVehiclePositions(VehiclePosition pos) {
 
     var parsedVehicle = realtimeVehicles.get(0);
     assertEquals(tripId, parsedVehicle.trip().getId().getId());
-    assertEquals(new WgsCoordinate(1, 1), parsedVehicle.coordinates());
-    assertEquals(30, parsedVehicle.heading());
+    assertEquals(new WgsCoordinate(1, 1), parsedVehicle.coordinates().get());
+    assertEquals(30, parsedVehicle.heading().get());
 
     // if we have an empty list of updates then clear the positions from the previous update
     matcher.applyRealtimeVehicleUpdates(List.of());
     assertEquals(0, service.getRealtimeVehicles(pattern).size());
   }
 
+  private void testVehiclePositionOccupancy(VehiclePosition pos) {
+    var service = new DefaultRealtimeVehicleService(null);
+    var trip = TransitModelForTest.trip(tripId).build();
+    var stopTimes = List.of(stopTime(trip, 0), stopTime(trip, 1), stopTime(trip, 2));
+
+    TripPattern pattern = tripPattern(trip, stopTimes);
+
+    var tripForId = Map.of(scopedTripId, trip);
+    var patternForTrip = Map.of(trip, pattern);
+
+    // an untouched pattern has no vehicle positions
+    assertEquals(0, service.getRealtimeVehicles(pattern).size());
+
+    // Map positions to trips in feed
+    RealtimeVehiclePatternMatcher matcher = new RealtimeVehiclePatternMatcher(
+      TransitModelForTest.FEED_ID,
+      tripForId::get,
+      patternForTrip::get,
+      (id, time) -> patternForTrip.get(id),
+      service,
+      zoneId,
+      null,
+      FEATURES
+    );
+
+    var positions = List.of(pos);
+
+    // Execute the same match-to-pattern step as the runner
+    matcher.applyRealtimeVehicleUpdates(positions);
+
+    // Check that occupancy for the trip is as set in original position
+    var occupancy = service.getOccupancyStatus(trip.getId(), pattern);
+    assertEquals(OccupancyStatus.FEW_SEATS_AVAILABLE, occupancy);
+  }
+
   @Test
   public void clearOldTrips() {
-    var service = new DefaultRealtimeVehicleService();
+    var service = new DefaultRealtimeVehicleService(null);
 
     var tripId1 = "trip1";
     var tripId2 = "trip2";
@@ -238,10 +283,7 @@ public void clearOldTrips() {
       service,
       zoneId,
       null,
-      Set.of(
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.POSITION,
-        VehiclePositionsUpdaterConfig.VehiclePositionFeature.STOP_POSITION
-      )
+      FEATURES
     );
 
     var pos1 = vehiclePosition(tripId1);

From 292f2501ae5adc0f03296b616280bb71da3b3641 Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Fri, 29 Sep 2023 15:52:52 +0300
Subject: [PATCH 19/26] Don't use static import when mapping from one enum to
 another to avoid confusion

---
 .../gtfs/datafetchers/TripOccupancyImpl.java  | 25 ++++++-------------
 1 file changed, 8 insertions(+), 17 deletions(-)

diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripOccupancyImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripOccupancyImpl.java
index 8a623d7b0e2..b8b01eda755 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripOccupancyImpl.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/TripOccupancyImpl.java
@@ -1,14 +1,5 @@
 package org.opentripplanner.apis.gtfs.datafetchers;
 
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.CRUSHED_STANDING_ROOM_ONLY;
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.EMPTY;
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.FEW_SEATS_AVAILABLE;
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.FULL;
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.MANY_SEATS_AVAILABLE;
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.NOT_ACCEPTING_PASSENGERS;
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.NO_DATA_AVAILABLE;
-import static org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus.STANDING_ROOM_ONLY;
-
 import graphql.schema.DataFetcher;
 import graphql.schema.DataFetchingEnvironment;
 import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers;
@@ -22,14 +13,14 @@ public DataFetcher occupancyStatus() {
     return env -> {
       var occupancyStatus = getSource(env).occupancyStatus();
       return switch (occupancyStatus) {
-        case NO_DATA_AVAILABLE -> NO_DATA_AVAILABLE;
-        case EMPTY -> EMPTY;
-        case MANY_SEATS_AVAILABLE -> MANY_SEATS_AVAILABLE;
-        case FEW_SEATS_AVAILABLE -> FEW_SEATS_AVAILABLE;
-        case STANDING_ROOM_ONLY -> STANDING_ROOM_ONLY;
-        case CRUSHED_STANDING_ROOM_ONLY -> CRUSHED_STANDING_ROOM_ONLY;
-        case FULL -> FULL;
-        case NOT_ACCEPTING_PASSENGERS -> NOT_ACCEPTING_PASSENGERS;
+        case NO_DATA_AVAILABLE -> GraphQLTypes.GraphQLOccupancyStatus.NO_DATA_AVAILABLE;
+        case EMPTY -> GraphQLTypes.GraphQLOccupancyStatus.EMPTY;
+        case MANY_SEATS_AVAILABLE -> GraphQLTypes.GraphQLOccupancyStatus.MANY_SEATS_AVAILABLE;
+        case FEW_SEATS_AVAILABLE -> GraphQLTypes.GraphQLOccupancyStatus.FEW_SEATS_AVAILABLE;
+        case STANDING_ROOM_ONLY -> GraphQLTypes.GraphQLOccupancyStatus.STANDING_ROOM_ONLY;
+        case CRUSHED_STANDING_ROOM_ONLY -> GraphQLTypes.GraphQLOccupancyStatus.CRUSHED_STANDING_ROOM_ONLY;
+        case FULL -> GraphQLTypes.GraphQLOccupancyStatus.FULL;
+        case NOT_ACCEPTING_PASSENGERS -> GraphQLTypes.GraphQLOccupancyStatus.NOT_ACCEPTING_PASSENGERS;
       };
     };
   }

From 3aa86e5fa4841e54232df7eaacb881b8f3fb117a Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Fri, 6 Oct 2023 15:17:03 +0300
Subject: [PATCH 20/26] Include SIRI nordic profile docs

---
 src/ext/graphql/transmodelapi/schema.graphql  | 21 +++++++++++++++----
 .../ext/transmodelapi/model/EnumTypes.java    | 21 +++++++++++++++----
 .../apis/gtfs/generated/GraphQLTypes.java     |  8 +++----
 .../model/timetable/OccupancyStatus.java      |  7 ++++++-
 .../opentripplanner/apis/gtfs/schema.graphqls |  9 +++++++-
 5 files changed, 52 insertions(+), 14 deletions(-)

diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql
index 71aed3a2242..c62356e5dfa 100644
--- a/src/ext/graphql/transmodelapi/schema.graphql
+++ b/src/ext/graphql/transmodelapi/schema.graphql
@@ -1538,17 +1538,30 @@ enum OccupancyStatus {
   values.
   """
   empty
-  "The vehicle or carriage has a few seats available."
+  """
+  The vehicle or carriage has a few seats available.
+  SIRI nordic profile: less than ~50% of seats available.
+  """
   fewSeatsAvailable
   "The vehicle or carriage is considered full by most measures, but may still be allowing passengers to board."
   full
-  "The vehicle or carriage has a large number of seats available."
+  """
+  The vehicle or carriage has a large number of seats available.
+  SIRI nordic profile: more than ~50% of seats available.
+  """
   manySeatsAvailable
   "The vehicle or carriage doesn't have any occupancy data available."
   noData
-  "The vehicle or carriage has no seats or standing room available."
+  """
+  The vehicle or carriage has no seats or standing room available.
+  SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only allowed
+  to alight due to e.g. crowding.
+  """
   notAcceptingPassengers
-  "The vehicle or carriage only has standing room available."
+  """
+  The vehicle or carriage only has standing room available.
+  SIRI nordic profile: less than ~10% of seats available.
+  """
   standingRoomOnly
 }
 
diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java
index 3fa3ed67cc2..21befce4150 100644
--- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java
+++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java
@@ -211,17 +211,26 @@ public class EnumTypes {
     .value(
       "manySeatsAvailable",
       OccupancyStatus.MANY_SEATS_AVAILABLE,
-      "The vehicle or carriage has a large number of seats available."
+      """
+      The vehicle or carriage has a large number of seats available.
+      SIRI nordic profile: more than ~50% of seats available.
+      """
     )
     .value(
       "fewSeatsAvailable",
       OccupancyStatus.FEW_SEATS_AVAILABLE,
-      "The vehicle or carriage has a few seats available."
+      """
+      The vehicle or carriage has a few seats available.
+      SIRI nordic profile: less than ~50% of seats available.
+      """
     )
     .value(
       "standingRoomOnly",
       OccupancyStatus.STANDING_ROOM_ONLY,
-      "The vehicle or carriage only has standing room available."
+      """
+      The vehicle or carriage only has standing room available.
+      SIRI nordic profile: less than ~10% of seats available.
+      """
     )
     .value(
       "crushedStandingRoomOnly",
@@ -240,7 +249,11 @@ public class EnumTypes {
     .value(
       "notAcceptingPassengers",
       OccupancyStatus.NOT_ACCEPTING_PASSENGERS,
-      "The vehicle or carriage has no seats or standing room available."
+      """
+      The vehicle or carriage has no seats or standing room available.
+      SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only allowed
+      to alight due to e.g. crowding.
+      """
     )
     .build();
 
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 cba63739dd5..9ecf488452d 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java
@@ -917,8 +917,8 @@ public enum GraphQLPropulsionType {
   }
 
   /**
-   * Additional qualifier for a transport mode.
-   * Note that qualifiers can only be used with certain transport modes.
+   * Additional qualifier for a transport mode. Note that qualifiers can only be used with certain
+   * transport modes.
    */
   public enum GraphQLQualifier {
     ACCESS,
@@ -3373,8 +3373,8 @@ public void setGraphQLUnpreferredCost(Integer unpreferredCost) {
   }
 
   /**
-   * The state of the vehicle parking. TEMPORARILY_CLOSED and CLOSED are distinct states so that they
-   * may be represented differently to the user.
+   * The state of the vehicle parking. TEMPORARILY_CLOSED and CLOSED are distinct states so that
+   * they may be represented differently to the user.
    */
   public enum GraphQLVehicleParkingState {
     CLOSED,
diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java
index 53ef887e347..cd4abd27ced 100644
--- a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java
+++ b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java
@@ -9,7 +9,7 @@
  */
 public enum OccupancyStatus {
   /**
-   * Default. There is no occupancy-data on this departure
+   * Default. There is no occupancy-data on this departure,
    */
   NO_DATA_AVAILABLE,
   /**
@@ -27,15 +27,18 @@ public enum OccupancyStatus {
    * 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.
+   * SIRI nordic profile: more than ~50% of seats available.
    */
   FEW_SEATS_AVAILABLE,
   /**
    * The vehicle or carriage can currently accommodate only standing passengers.
+   * SIRI nordic profile: less than ~50% of seats available.
    */
   STANDING_ROOM_ONLY,
   /**
    * The vehicle or carriage can currently accommodate only standing passengers and has limited
    * space for them.
+   * SIRI nordic profile: less than ~10% of seats available.
    */
   CRUSHED_STANDING_ROOM_ONLY,
   /**
@@ -45,6 +48,8 @@ public enum OccupancyStatus {
   FULL,
   /**
    * The vehicle or carriage is not accepting passengers.
+   * SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only
+   * allowed to alight due to e.g. crowding.
    */
   NOT_ACCEPTING_PASSENGERS,
 }
diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls
index 7117acd8902..a249adaafbb 100644
--- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls
+++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls
@@ -3413,6 +3413,7 @@ enum OccupancyStatus {
   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.
+  SIRI nordic profile: more than ~50% of seats available.
   """
   MANY_SEATS_AVAILABLE
 
@@ -3420,10 +3421,14 @@ enum OccupancyStatus {
   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.
+  SIRI nordic profile: less than ~50% of seats available.
   """
   FEW_SEATS_AVAILABLE
 
-  """The vehicle or carriage can currently accommodate only standing passengers."""
+  """
+  The vehicle or carriage can currently accommodate only standing passengers.
+  SIRI nordic profile: less than ~10% of seats available.
+  """
   STANDING_ROOM_ONLY
 
   """
@@ -3441,6 +3446,8 @@ enum OccupancyStatus {
 
   """
   The vehicle or carriage is not accepting passengers.
+  SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only allowed
+  to alight due to e.g. crowding.
   """
   NOT_ACCEPTING_PASSENGERS
 }

From a6311d5a3aef451a378bb9e3808fce0599f01513 Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Fri, 13 Oct 2023 22:12:23 +0300
Subject: [PATCH 21/26] Apply suggestions from code review

Co-authored-by: Thomas Gran 
---
 .../org/opentripplanner/ext/transmodelapi/model/EnumTypes.java  | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java
index 21befce4150..65966744bdc 100644
--- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java
+++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java
@@ -206,6 +206,7 @@ public class EnumTypes {
       still accepting passengers. There isn't a big difference between this and `manySeatsAvailable`
       so it's possible to handle them as the same value, if one wants to limit the number of different
       values.
+      SIRI nordic profile: merge these into `manySeatsAvailable`.
       """
     )
     .value(
@@ -239,6 +240,7 @@ public class EnumTypes {
         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.
+        SIRI nordic profile: merge into `standingRoomOnly`.
         """
     )
     .value(

From 832f037b64f945a290aae63d7907c24f4b2b74b0 Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Fri, 13 Oct 2023 22:43:20 +0300
Subject: [PATCH 22/26] Update and refactor documentation

---
 src/ext/graphql/transmodelapi/schema.graphql  |  3 +
 .../ext/transmodelapi/model/EnumTypes.java    | 79 +++-------------
 .../apis/gtfs/generated/GraphQLTypes.java     |  8 +-
 .../model/timetable/OccupancyStatus.java      | 90 +++++++++++--------
 .../opentripplanner/apis/gtfs/schema.graphqls |  2 +
 5 files changed, 72 insertions(+), 110 deletions(-)

diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql
index c62356e5dfa..5f17dd5c16d 100644
--- a/src/ext/graphql/transmodelapi/schema.graphql
+++ b/src/ext/graphql/transmodelapi/schema.graphql
@@ -1524,11 +1524,13 @@ enum MultiModalMode {
   parent
 }
 
+"OccupancyStatus to be exposed in the API. The values are based on GTFS-RT"
 enum OccupancyStatus {
   """
   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.
+  SIRI nordic profile: merge into `standingRoomOnly`.
   """
   crushedStandingRoomOnly
   """
@@ -1536,6 +1538,7 @@ enum OccupancyStatus {
   still accepting passengers. There isn't a big difference between this and `manySeatsAvailable`
   so it's possible to handle them as the same value, if one wants to limit the number of different
   values.
+  SIRI nordic profile: merge these into `manySeatsAvailable`.
   """
   empty
   """
diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java
index 65966744bdc..2b6942d7b8a 100644
--- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java
+++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/EnumTypes.java
@@ -190,74 +190,19 @@ public class EnumTypes {
     .value("all", "all", "Both multiModal parents and their mono modal child stop places.")
     .build();
 
-  public static final GraphQLEnumType OCCUPANCY_STATUS = GraphQLEnumType
-    .newEnum()
-    .name("OccupancyStatus")
-    .value(
-      "noData",
-      OccupancyStatus.NO_DATA_AVAILABLE,
-      "The vehicle or carriage doesn't have any occupancy data available."
-    )
-    .value(
-      "empty",
-      OccupancyStatus.EMPTY,
-      """
-      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 `manySeatsAvailable`
-      so it's possible to handle them as the same value, if one wants to limit the number of different
-      values.
-      SIRI nordic profile: merge these into `manySeatsAvailable`.
-      """
-    )
-    .value(
-      "manySeatsAvailable",
-      OccupancyStatus.MANY_SEATS_AVAILABLE,
-      """
-      The vehicle or carriage has a large number of seats available.
-      SIRI nordic profile: more than ~50% of seats available.
-      """
-    )
-    .value(
-      "fewSeatsAvailable",
-      OccupancyStatus.FEW_SEATS_AVAILABLE,
-      """
-      The vehicle or carriage has a few seats available.
-      SIRI nordic profile: less than ~50% of seats available.
-      """
-    )
-    .value(
-      "standingRoomOnly",
-      OccupancyStatus.STANDING_ROOM_ONLY,
-      """
-      The vehicle or carriage only has standing room available.
-      SIRI nordic profile: less than ~10% of seats available.
-      """
-    )
-    .value(
-      "crushedStandingRoomOnly",
-      OccupancyStatus.CRUSHED_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.
-        SIRI nordic profile: merge into `standingRoomOnly`.
-        """
-    )
-    .value(
-      "full",
-      OccupancyStatus.FULL,
-      "The vehicle or carriage is considered full by most measures, but may still be allowing passengers to board."
-    )
-    .value(
-      "notAcceptingPassengers",
-      OccupancyStatus.NOT_ACCEPTING_PASSENGERS,
-      """
-      The vehicle or carriage has no seats or standing room available.
-      SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only allowed
-      to alight due to e.g. crowding.
-      """
+  public static final GraphQLEnumType OCCUPANCY_STATUS = createFromDocumentedEnum(
+    "OccupancyStatus",
+    List.of(
+      map("noData", OccupancyStatus.NO_DATA_AVAILABLE),
+      map("empty", OccupancyStatus.EMPTY),
+      map("manySeatsAvailable", OccupancyStatus.MANY_SEATS_AVAILABLE),
+      map("fewSeatsAvailable", OccupancyStatus.FEW_SEATS_AVAILABLE),
+      map("standingRoomOnly", OccupancyStatus.STANDING_ROOM_ONLY),
+      map("crushedStandingRoomOnly", OccupancyStatus.CRUSHED_STANDING_ROOM_ONLY),
+      map("full", OccupancyStatus.FULL),
+      map("notAcceptingPassengers", OccupancyStatus.NOT_ACCEPTING_PASSENGERS)
     )
-    .build();
+  );
 
   public static final GraphQLEnumType PURCHASE_WHEN = GraphQLEnumType
     .newEnum()
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 9ecf488452d..cba63739dd5 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java
@@ -917,8 +917,8 @@ public enum GraphQLPropulsionType {
   }
 
   /**
-   * Additional qualifier for a transport mode. Note that qualifiers can only be used with certain
-   * transport modes.
+   * Additional qualifier for a transport mode.
+   * Note that qualifiers can only be used with certain transport modes.
    */
   public enum GraphQLQualifier {
     ACCESS,
@@ -3373,8 +3373,8 @@ public void setGraphQLUnpreferredCost(Integer unpreferredCost) {
   }
 
   /**
-   * The state of the vehicle parking. TEMPORARILY_CLOSED and CLOSED are distinct states so that
-   * they may be represented differently to the user.
+   * The state of the vehicle parking. TEMPORARILY_CLOSED and CLOSED are distinct states so that they
+   * may be represented differently to the user.
    */
   public enum GraphQLVehicleParkingState {
     CLOSED,
diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java
index cd4abd27ced..9c32ca39394 100644
--- a/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java
+++ b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java
@@ -1,55 +1,67 @@
 package org.opentripplanner.transit.model.timetable;
 
+import org.opentripplanner.framework.doc.DocumentedEnum;
+
 /**
  * OccupancyStatus to be exposed in the API. The values are based on GTFS-RT
  * (transit_realtime.VehiclePosition.OccupancyStatus) that can be easily be mapped to the Nordic
  * SIRI-profile (SIRI 2.1)
  * 

- * Descriptions are copied from the GTFS-RT specification. + * Descriptions are copied from the GTFS-RT specification with additions of SIRI nordic profile documentation. */ -public enum OccupancyStatus { - /** - * Default. There is no occupancy-data on this departure, - */ +public enum OccupancyStatus implements DocumentedEnum { NO_DATA_AVAILABLE, - /** - * The vehicle is considered empty by most measures, and has few or no passengers onboard, but is - * still accepting passengers. - */ 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. - */ 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. - * SIRI nordic profile: more than ~50% of seats available. - */ FEW_SEATS_AVAILABLE, - /** - * The vehicle or carriage can currently accommodate only standing passengers. - * SIRI nordic profile: less than ~50% of seats available. - */ STANDING_ROOM_ONLY, - /** - * The vehicle or carriage can currently accommodate only standing passengers and has limited - * space for them. - * SIRI nordic profile: less than ~10% of seats available. - */ 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. - * SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only - * allowed to alight due to e.g. crowding. - */ - NOT_ACCEPTING_PASSENGERS, + NOT_ACCEPTING_PASSENGERS; + + @Override + public String typeDescription() { + return "OccupancyStatus to be exposed in the API. The values are based on GTFS-RT"; + } + + @Override + public String enumValueDescription() { + return switch (this) { + case NO_DATA_AVAILABLE -> "The vehicle or carriage doesn't have any occupancy data available."; + case EMPTY -> """ + 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 `manySeatsAvailable` + so it's possible to handle them as the same value, if one wants to limit the number of different + values. + SIRI nordic profile: merge these into `manySeatsAvailable`. + """; + case MANY_SEATS_AVAILABLE -> """ + The vehicle or carriage has a large number of seats available. + SIRI nordic profile: more than ~50% of seats available. + """; + case FEW_SEATS_AVAILABLE -> """ + The vehicle or carriage has a few seats available. + SIRI nordic profile: less than ~50% of seats available. + """; + case STANDING_ROOM_ONLY -> """ + The vehicle or carriage only has standing room available. + SIRI nordic profile: less than ~10% of seats available. + """; + case CRUSHED_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. + SIRI nordic profile: merge into `standingRoomOnly`. + """; + case FULL -> """ + The vehicle or carriage is considered full by most measures, but may still be allowing + passengers to board. + """; + case NOT_ACCEPTING_PASSENGERS -> """ + The vehicle or carriage has no seats or standing room available. + SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only + allowed to alight due to e.g. crowding. + """; + }; + } } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index a249adaafbb..046e581af30 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3404,6 +3404,7 @@ enum OccupancyStatus { 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. + SIRI nordic profile: merge these into `MANY_SEATS_AVAILABLE`. """ EMPTY @@ -3435,6 +3436,7 @@ enum OccupancyStatus { 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. + SIRI nordic profile: merge into `STANDING_ROOM_ONLY`. """ CRUSHED_STANDING_ROOM_ONLY From 0b724bb94079523de57742ca9f5ca1c8833f5cdd Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 13 Oct 2023 22:51:33 +0300 Subject: [PATCH 23/26] Update src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java Co-authored-by: Thomas Gran --- .../standalone/config/framework/json/ParameterBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java index 11672f99005..cd5e9a202b2 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java @@ -247,7 +247,7 @@ public > Set asEnumSet(Class enumClass, Collection de it -> parseOptionalEnum(it.asText(), enumClass) ); List result = optionalList.stream().filter(Optional::isPresent).map(Optional::get).toList(); - return result.isEmpty() ? Set.copyOf(dft) : EnumSet.copyOf(result); + return result.isEmpty() ? EnumSet.noneOf(enumClass) : EnumSet.copyOf(result); } /** From 8533c2160197df7c9d58fb5f643587feec4f0428 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 16 Oct 2023 10:50:22 +0300 Subject: [PATCH 24/26] Update src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java Co-authored-by: Leonard Ehrenfried --- .../internal/DefaultRealtimeVehicleService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java index 401092b6058..07fa48fa84f 100644 --- a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java @@ -67,8 +67,7 @@ public OccupancyStatus getOccupancyStatus(FeedScopedId tripId, TripPattern patte .stream() .filter(vehicle -> tripId.equals(vehicle.trip().getId())) .max(Comparator.comparing(vehicle -> vehicle.time().orElse(Instant.MIN))) - .map(RealtimeVehicle::occupancyStatus) - .orElse(Optional.empty()) + .flatMap(RealtimeVehicle::occupancyStatus) .orElse(NO_DATA_AVAILABLE); } } From 3aa96931f58efb0103bb9d30185eff9382e2e1ac Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 16 Oct 2023 12:35:39 +0300 Subject: [PATCH 25/26] Fix schema comment formatting --- src/ext/graphql/transmodelapi/schema.graphql | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index 5f17dd5c16d..b2bf6fb52c6 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -1528,8 +1528,8 @@ enum MultiModalMode { enum OccupancyStatus { """ 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. + 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. SIRI nordic profile: merge into `standingRoomOnly`. """ crushedStandingRoomOnly @@ -1546,7 +1546,10 @@ enum OccupancyStatus { SIRI nordic profile: less than ~50% of seats available. """ fewSeatsAvailable - "The vehicle or carriage is considered full by most measures, but may still be allowing passengers to board." + """ + The vehicle or carriage is considered full by most measures, but may still be allowing + passengers to board. + """ full """ The vehicle or carriage has a large number of seats available. @@ -1557,8 +1560,8 @@ enum OccupancyStatus { noData """ The vehicle or carriage has no seats or standing room available. - SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only allowed - to alight due to e.g. crowding. + SIRI nordic profile: if vehicle/carriage is not in use / unavailable, or passengers are only + allowed to alight due to e.g. crowding. """ notAcceptingPassengers """ From 7793f6914eebe7f34a23ea45ceda8c1bd08db86a Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 16 Oct 2023 12:35:58 +0300 Subject: [PATCH 26/26] Return immutable sets and fix default value --- .../standalone/config/framework/json/ParameterBuilder.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java index cd5e9a202b2..0302fce4d3a 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java @@ -234,7 +234,8 @@ public > Set asEnumSet(Class enumClass) { it -> parseOptionalEnum(it.asText(), enumClass) ); List result = optionalList.stream().filter(Optional::isPresent).map(Optional::get).toList(); - return result.isEmpty() ? EnumSet.noneOf(enumClass) : EnumSet.copyOf(result); + // Set is immutable + return result.isEmpty() ? Set.of() : Set.copyOf(result); } public > Set asEnumSet(Class enumClass, Collection defaultValues) { @@ -247,7 +248,8 @@ public > Set asEnumSet(Class enumClass, Collection de it -> parseOptionalEnum(it.asText(), enumClass) ); List result = optionalList.stream().filter(Optional::isPresent).map(Optional::get).toList(); - return result.isEmpty() ? EnumSet.noneOf(enumClass) : EnumSet.copyOf(result); + // Set is immutable + return result.isEmpty() ? Set.copyOf(dft) : Set.copyOf(result); } /**