Trip: org.opentripplanner.transit.model.timetable.Trip#Trip
+ TripOccupancy: org.opentripplanner.apis.gtfs.model.TripOccupancy#TripOccupancy
Unknown: org.opentripplanner.apis.gtfs.model.UnknownModel#UnknownModel
VehicleParking: org.opentripplanner.routing.vehicle_parking.VehicleParking#VehicleParking
VehicleParkingSpaces: org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces#VehicleParkingSpaces
VehicleParkingState: org.opentripplanner.routing.vehicle_parking.VehicleParkingState#VehicleParkingState
VertexType: String
SystemNotice: org.opentripplanner.model.SystemNotice#SystemNotice
- VehiclePosition: org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition#RealtimeVehiclePosition
- StopRelationship: org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition.StopRelationship#StopRelationship
+ VehiclePosition: org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle#RealtimeVehicle
+ StopRelationship: org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle.StopRelationship#StopRelationship
WheelchairBoarding: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLWheelchairBoarding
FormFactor: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLFormFactor
PropulsionType: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLPropulsionType
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/TripOccupancy.java b/src/main/java/org/opentripplanner/apis/gtfs/model/TripOccupancy.java
new file mode 100644
index 00000000000..1d4a61d3bdb
--- /dev/null
+++ b/src/main/java/org/opentripplanner/apis/gtfs/model/TripOccupancy.java
@@ -0,0 +1,8 @@
+package org.opentripplanner.apis.gtfs.model;
+
+import org.opentripplanner.transit.model.timetable.OccupancyStatus;
+
+/**
+ * Record for holding trip occupancy information.
+ */
+public record TripOccupancy(OccupancyStatus occupancyStatus) {}
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/realtimevehicles/RealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java
new file mode 100644
index 00000000000..949b196faeb
--- /dev/null
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/RealtimeVehicleService.java
@@ -0,0 +1,22 @@
+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;
+import org.opentripplanner.transit.model.timetable.Trip;
+
+public interface RealtimeVehicleService {
+ /**
+ * 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(@Nonnull TripPattern pattern);
+
+ /**
+ * 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(@Nonnull Trip 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/realtimevehicles/internal/DefaultRealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
new file mode 100644
index 00000000000..07fa48fa84f
--- /dev/null
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java
@@ -0,0 +1,73 @@
+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;
+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
+ implements RealtimeVehicleService, RealtimeVehicleRepository {
+
+ private final Map> vehicles = new ConcurrentHashMap<>();
+
+ private final TransitService transitService;
+
+ @Inject
+ public DefaultRealtimeVehicleService(TransitService transitService) {
+ this.transitService = transitService;
+ }
+
+ @Override
+ public void setRealtimeVehicles(TripPattern pattern, List updates) {
+ vehicles.put(pattern, List.copyOf(updates));
+ }
+
+ @Override
+ public void clearRealtimeVehicles(TripPattern pattern) {
+ vehicles.remove(pattern);
+ }
+
+ @Override
+ 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(@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 -> tripId.equals(vehicle.trip().getId()))
+ .max(Comparator.comparing(vehicle -> vehicle.time().orElse(Instant.MIN)))
+ .flatMap(RealtimeVehicle::occupancyStatus)
+ .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
new file mode 100644
index 00000000000..e06ae5ebaa7
--- /dev/null
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicle.java
@@ -0,0 +1,114 @@
+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;
+import org.opentripplanner.transit.model.timetable.OccupancyStatus;
+import org.opentripplanner.transit.model.timetable.Trip;
+
+/**
+ * Internal model of a realtime vehicle.
+ */
+public class RealtimeVehicle {
+
+ private final FeedScopedId vehicleId;
+
+ private final String label;
+
+ private final WgsCoordinate coordinates;
+
+ /**
+ * Speed in meters per second
+ */
+ 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.
+ */
+ private final Double heading;
+
+ /**
+ * When the realtime vehicle was recorded
+ */
+ private final Instant time;
+
+ /**
+ * Status of the vehicle, ie. if approaching the next stop or if it is there already.
+ */
+ private final StopRelationship stop;
+
+ private final Trip trip;
+
+ /**
+ * How full the vehicle is and is it still accepting passengers.
+ */
+ private final OccupancyStatus occupancyStatus;
+
+ RealtimeVehicle(RealtimeVehicleBuilder builder) {
+ var stopRelationship = Optional
+ .ofNullable(builder.stop())
+ .map(s -> new StopRelationship(s, builder.stopStatus()))
+ .orElse(null);
+ 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() {
+ 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();
+ }
+
+ public enum StopStatus {
+ INCOMING_AT,
+ STOPPED_AT,
+ IN_TRANSIT_TO,
+ }
+
+ public record StopRelationship(StopLocation stop, StopStatus status) {}
+}
diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java
new file mode 100644
index 00000000000..cc68919f41d
--- /dev/null
+++ b/src/main/java/org/opentripplanner/service/realtimevehicles/model/RealtimeVehicleBuilder.java
@@ -0,0 +1,117 @@
+package org.opentripplanner.service.realtimevehicles.model;
+
+import java.time.Instant;
+import org.opentripplanner.framework.geometry.WgsCoordinate;
+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 RealtimeVehicleBuilder {
+
+ private FeedScopedId vehicleId;
+ private String label;
+ private WgsCoordinate coordinates;
+ private Double speed = null;
+ private Double heading = null;
+ private Instant time;
+ private StopStatus stopStatus = StopStatus.IN_TRANSIT_TO;
+ private StopLocation stop;
+ 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() {
+ return new RealtimeVehicle(this);
+ }
+}
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/VehiclePositionService.java b/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java
deleted file mode 100644
index b4cbf1176ea..00000000000
--- a/src/main/java/org/opentripplanner/service/vehiclepositions/VehiclePositionService.java
+++ /dev/null
@@ -1,12 +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 VehiclePositionService {
- /**
- * 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/service/vehiclepositions/internal/DefaultVehiclePositionService.java b/src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java
deleted file mode 100644
index a9027f044d2..00000000000
--- a/src/main/java/org/opentripplanner/service/vehiclepositions/internal/DefaultVehiclePositionService.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.opentripplanner.service.vehiclepositions.internal;
-
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import org.opentripplanner.service.vehiclepositions.VehiclePositionRepository;
-import org.opentripplanner.service.vehiclepositions.VehiclePositionService;
-import org.opentripplanner.service.vehiclepositions.model.RealtimeVehiclePosition;
-import org.opentripplanner.transit.model.network.TripPattern;
-
-@Singleton
-public class DefaultVehiclePositionService
- implements VehiclePositionService, VehiclePositionRepository {
-
- private final Map> positions = new ConcurrentHashMap<>();
-
- @Inject
- public DefaultVehiclePositionService() {}
-
- @Override
- public void setVehiclePositions(TripPattern pattern, List updates) {
- positions.put(pattern, List.copyOf(updates));
- }
-
- @Override
- public void clearVehiclePositions(TripPattern pattern) {
- positions.remove(pattern);
- }
-
- @Override
- public List getVehiclePositions(TripPattern pattern) {
- // the list is made immutable during insertion, so we can safely return them
- return positions.getOrDefault(pattern, List.of());
- }
-}
diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java b/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java
deleted file mode 100644
index 56c99c445c1..00000000000
--- a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePosition.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.opentripplanner.service.vehiclepositions.model;
-
-import java.time.Instant;
-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.Trip;
-
-/**
- * Internal model of a realtime vehicle position.
- */
-public record RealtimeVehiclePosition(
- FeedScopedId vehicleId,
- String label,
- WgsCoordinate coordinates,
- /**
- * Speed in meters per second
- */
- 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,
-
- /**
- * When the realtime position was recorded
- */
- Instant time,
-
- /**
- * Status of the vehicle, ie. if approaching the next stop or if it is there already.
- */
- StopRelationship stop,
- Trip trip
-) {
- public static RealtimeVehiclePositionBuilder builder() {
- return new RealtimeVehiclePositionBuilder();
- }
-
- public enum StopStatus {
- INCOMING_AT,
- STOPPED_AT,
- IN_TRANSIT_TO,
- }
-
- public record StopRelationship(StopLocation stop, StopStatus status) {}
-}
diff --git a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java b/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java
deleted file mode 100644
index 00f2c3f7b51..00000000000
--- a/src/main/java/org/opentripplanner/service/vehiclepositions/model/RealtimeVehiclePositionBuilder.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package org.opentripplanner.service.vehiclepositions.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.transit.model.framework.FeedScopedId;
-import org.opentripplanner.transit.model.site.StopLocation;
-import org.opentripplanner.transit.model.timetable.Trip;
-
-public class RealtimeVehiclePositionBuilder {
-
- private FeedScopedId vehicleId;
- private String label;
- private WgsCoordinate coordinates;
- private Double speed = null;
- private Double heading = null;
- private Instant time;
- private StopStatus stopStatus = StopStatus.IN_TRANSIT_TO;
- private StopLocation stop;
- private Trip trip;
-
- public RealtimeVehiclePositionBuilder setVehicleId(FeedScopedId vehicleId) {
- this.vehicleId = vehicleId;
- return this;
- }
-
- public RealtimeVehiclePositionBuilder setLabel(String label) {
- this.label = label;
- return this;
- }
-
- public RealtimeVehiclePositionBuilder setCoordinates(WgsCoordinate c) {
- this.coordinates = c;
- return this;
- }
-
- public RealtimeVehiclePositionBuilder setSpeed(double speed) {
- this.speed = speed;
- return this;
- }
-
- public RealtimeVehiclePositionBuilder setHeading(double heading) {
- this.heading = heading;
- return this;
- }
-
- public RealtimeVehiclePositionBuilder setTime(Instant time) {
- this.time = time;
- return this;
- }
-
- public RealtimeVehiclePositionBuilder setStopStatus(StopStatus stopStatus) {
- this.stopStatus = stopStatus;
- return this;
- }
-
- public RealtimeVehiclePositionBuilder setStop(StopLocation stop) {
- this.stop = stop;
- return this;
- }
-
- public RealtimeVehiclePositionBuilder setTrip(Trip trip) {
- this.trip = trip;
- return this;
- }
-
- public RealtimeVehiclePosition build() {
- var stop = Optional
- .ofNullable(this.stop)
- .map(s -> new StopRelationship(s, stopStatus))
- .orElse(null);
- return new RealtimeVehiclePosition(
- vehicleId,
- label,
- coordinates,
- speed,
- heading,
- time,
- stop,
- trip
- );
- }
-}
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/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/framework/json/ParameterBuilder.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java
index 3bc66ce6ea5..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
@@ -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;
@@ -233,7 +234,22 @@ 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) {
+ 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();
+ // Set is immutable
+ return result.isEmpty() ? Set.copyOf(dft) : Set.copyOf(result);
}
/**
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..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
@@ -2,8 +2,13 @@
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;
@@ -25,7 +30,31 @@ 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 features = c
+ .of("features")
+ .since(V2_5)
+ .summary("Which features of GTFS RT vehicle positions should be loaded into OTP.")
+ .asEnumSet(VehiclePositionFeature.class, List.of(POSITION, STOP_POSITION, OCCUPANCY));
var headers = HttpHeadersConfig.headers(c, V2_3);
- return new VehiclePositionsUpdaterParameters(updaterRef, feedId, url, frequency, headers);
+ return new VehiclePositionsUpdaterParameters(
+ updaterRef,
+ feedId,
+ url,
+ frequency,
+ headers,
+ fuzzyTripMatching,
+ features
+ );
+ }
+
+ public enum VehiclePositionFeature {
+ POSITION,
+ STOP_POSITION,
+ OCCUPANCY,
}
}
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/transit/model/timetable/OccupancyStatus.java b/src/main/java/org/opentripplanner/transit/model/timetable/OccupancyStatus.java
index 8be2f468b46..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,35 +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), 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 with additions of SIRI nordic profile documentation.
*/
-public enum OccupancyStatus {
- /**
- * Default. There is no occupancy-data on this departure
- */
- NO_DATA,
- /**
- * More than ~50% of seats available
- */
+public enum OccupancyStatus implements DocumentedEnum {
+ NO_DATA_AVAILABLE,
+ EMPTY,
MANY_SEATS_AVAILABLE,
- /**
- * Less than ~50% of seats available
- */
- SEATS_AVAILABLE,
- /**
- * Less than ~10% of seats available
- */
+ FEW_SEATS_AVAILABLE,
STANDING_ROOM_ONLY,
- /**
- * Close to or at full capacity
- */
+ CRUSHED_STANDING_ROOM_ONLY,
FULL,
- /**
- * 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/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
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 31d94caeb00..bb2e27c3ce8 100644
--- a/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java
+++ b/src/main/java/org/opentripplanner/updater/vehicle_position/PollingVehiclePositionUpdater.java
@@ -5,17 +5,21 @@
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;
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;
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 {
@@ -26,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.
@@ -35,21 +39,26 @@ public class PollingVehiclePositionUpdater extends PollingGraphUpdater {
public PollingVehiclePositionUpdater(
VehiclePositionsUpdaterParameters params,
- VehiclePositionRepository vehiclePositionService,
+ RealtimeVehicleRepository realtimeVehicleRepository,
TransitModel transitModel
) {
super(params);
this.vehiclePositionSource =
new GtfsRealtimeHttpVehiclePositionSource(params.url(), params.headers());
var index = transitModel.getTransitModelIndex();
- this.vehiclePositionPatternMatcher =
- new VehiclePositionPatternMatcher(
+ var fuzzyTripMatcher = params.fuzzyTripMatching()
+ ? new GtfsRealtimeFuzzyTripMatcher(new DefaultTransitService(transitModel))
+ : null;
+ this.realtimeVehiclePatternMatcher =
+ new RealtimeVehiclePatternMatcher(
params.feedId(),
tripId -> index.getTripForId().get(tripId),
trip -> index.getPatternForTrip().get(trip),
(trip, date) -> getPatternIncludingRealtime(transitModel, trip, date),
- vehiclePositionService,
- transitModel.getTimeZone()
+ realtimeVehicleRepository,
+ transitModel.getTimeZone(),
+ fuzzyTripMatcher,
+ params.vehiclePositionFeatures()
);
LOG.info(
@@ -75,7 +84,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 61%
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 1fb05c9dcec..fe29ef35b97 100644
--- a/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionPatternMatcher.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;
@@ -30,15 +33,18 @@
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.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;
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.GtfsRealtimeFuzzyTripMatcher;
import org.opentripplanner.updater.spi.ResultLogger;
import org.opentripplanner.updater.spi.UpdateError;
import org.opentripplanner.updater.spi.UpdateResult;
@@ -50,27 +56,31 @@
* 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;
private final Function getStaticPattern;
private final BiFunction getRealtimePattern;
+ private final GtfsRealtimeFuzzyTripMatcher fuzzyTripMatcher;
+ private final Set vehiclePositionFeatures;
private Set patternsInPreviousUpdate = Set.of();
- public VehiclePositionPatternMatcher(
+ public RealtimeVehiclePatternMatcher(
String feedId,
Function getTripForId,
Function getStaticPattern,
BiFunction getRealtimePattern,
- VehiclePositionRepository repository,
- ZoneId timeZoneId
+ RealtimeVehicleRepository repository,
+ ZoneId timeZoneId,
+ GtfsRealtimeFuzzyTripMatcher fuzzyTripMatcher,
+ Set vehiclePositionFeatures
) {
this.feedId = feedId;
this.getTripForId = getTripForId;
@@ -78,28 +88,30 @@ public VehiclePositionPatternMatcher(
this.getRealtimePattern = getRealtimePattern;
this.repository = repository;
this.timeZoneId = timeZoneId;
+ this.fuzzyTripMatcher = fuzzyTripMatcher;
+ this.vehiclePositionFeatures = vehiclePositionFeatures;
}
/**
- * 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(
@@ -109,18 +121,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()) {
@@ -185,81 +197,87 @@ 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()) {
+ if (vehiclePositionFeatures.contains(POSITION) && vehiclePosition.hasPosition()) {
var position = vehiclePosition.getPosition();
- newPosition.setCoordinates(
+ newVehicle.withCoordinates(
new WgsCoordinate(position.getLatitude(), position.getLongitude())
);
if (position.hasSpeed()) {
- newPosition.setSpeed(position.getSpeed());
+ newVehicle.withSpeed(position.getSpeed());
}
if (position.hasBearing()) {
- newPosition.setHeading(position.getBearing());
+ newVehicle.withHeading(position.getBearing());
}
}
if (vehiclePosition.hasVehicle()) {
var vehicle = vehiclePosition.getVehicle();
var id = new FeedScopedId(feedId, vehicle.getId());
- newPosition
- .setVehicleId(id)
- .setLabel(Optional.ofNullable(vehicle.getLabel()).orElse(vehicle.getLicensePlate()));
+ newVehicle
+ .withVehicleId(id)
+ .withLabel(Optional.ofNullable(vehicle.getLabel()).orElse(vehicle.getLicensePlate()));
}
if (vehiclePosition.hasTimestamp()) {
- newPosition.setTime(Instant.ofEpochSecond(vehiclePosition.getTimestamp()));
+ newVehicle.withTime(Instant.ofEpochSecond(vehiclePosition.getTimestamp()));
}
- if (vehiclePosition.hasCurrentStatus()) {
- newPosition.setStopStatus(toModel(vehiclePosition.getCurrentStatus()));
- }
+ if (vehiclePositionFeatures.contains(STOP_POSITION)) {
+ if (vehiclePosition.hasCurrentStatus()) {
+ newVehicle.withStopStatus(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) {
- newPosition.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.withStop(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.withStop(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);
- newPosition.setStop(stop);
- }
- });
}
- newPosition.setTrip(trip);
+ newVehicle.withTrip(trip);
- return newPosition.build();
+ if (vehiclePositionFeatures.contains(OCCUPANCY) && vehiclePosition.hasOccupancyStatus()) {
+ newVehicle.withOccupancyStatus(occupancyStatusToModel(vehiclePosition.getOccupancyStatus()));
+ }
+
+ return newVehicle.build();
}
/**
@@ -271,7 +289,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 +297,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_ACCEPTING_PASSENGERS;
+ };
+ }
+
private static String toString(VehiclePosition vehiclePosition) {
try {
return JsonFormat.printer().omittingInsignificantWhitespace().print(vehiclePosition);
@@ -287,7 +321,12 @@ private static String toString(VehiclePosition vehiclePosition) {
}
}
- private Result toRealtimeVehiclePosition(
+ private VehiclePosition fuzzilySetTrip(VehiclePosition vehiclePosition) {
+ var trip = fuzzyTripMatcher.match(feedId, vehiclePosition.getTrip());
+ return vehiclePosition.toBuilder().setTrip(trip).build();
+ }
+
+ private Result toRealtimeVehicle(
String feedId,
VehiclePosition vehiclePosition
) {
@@ -299,7 +338,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));
@@ -317,7 +360,7 @@ private Result toRealtimeVehiclePosition
}
var serviceDate = Optional
- .of(vehiclePosition.getTrip().getStartDate())
+ .of(vehiclePositionWithTripId.getTrip().getStartDate())
.map(Strings::emptyToNull)
.flatMap(ServiceDateUtils::parseStringToOptional)
.orElseGet(() -> inferServiceDate(trip));
@@ -337,15 +380,15 @@ private Result toRealtimeVehiclePosition
}
// Add position to pattern
- var newPosition = mapVehiclePosition(
- vehiclePosition,
+ 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/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java b/src/main/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsUpdaterParameters.java
index 7d08158903a..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;
@@ -11,7 +13,9 @@ public record VehiclePositionsUpdaterParameters(
String feedId,
URI url,
Duration frequency,
- HttpHeaders headers
+ HttpHeaders headers,
+ boolean fuzzyTripMatching,
+ Set vehiclePositionFeatures
)
implements PollingGraphUpdaterParameters {
public VehiclePositionsUpdaterParameters {
diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls
index aaf312244d1..046e581af30 100644
--- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls
+++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls
@@ -3394,6 +3394,66 @@ 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.
+ SIRI nordic profile: merge these into `MANY_SEATS_AVAILABLE`.
+ """
+ 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.
+ SIRI nordic profile: more than ~50% of seats available.
+ """
+ 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: less than ~50% of seats available.
+ """
+ FEW_SEATS_AVAILABLE
+
+ """
+ The vehicle or carriage can currently accommodate only standing passengers.
+ SIRI nordic profile: less than ~10% of seats available.
+ """
+ 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 `STANDING_ROOM_ONLY`.
+ """
+ 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
+}
+
type step {
"""The distance in meters that this step takes."""
distance: Float
@@ -3972,6 +4032,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 +4064,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/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java
index 969769f93fa..8e68fc7c3a0 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;
@@ -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(),
- createVehiclePositionService(),
+ createRealtimeVehicleService(transitService),
createVehicleRentalService(),
routerConfig.flexConfig(),
List.of(),
@@ -54,8 +56,8 @@ public static WorldEnvelopeService createWorldEnvelopeService() {
return new DefaultWorldEnvelopeService(new DefaultWorldEnvelopeRepository());
}
- public static VehiclePositionService createVehiclePositionService() {
- return new DefaultVehiclePositionService();
+ 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 bd3c10f491e..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;
@@ -68,7 +70,8 @@
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.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 DefaultVehiclePositionService(),
+ realtimeVehicleService,
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 c6e69cecf4f..af6352839fb 100644
--- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java
+++ b/src/test/java/org/opentripplanner/apis/gtfs/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(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 5faffc6a606..519514ab9ed 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;
@@ -90,9 +90,11 @@ public SpeedTest(
this.testCaseDefinitions = tcIO.readTestCaseDefinitions();
this.expectedResultsByTcId = tcIO.readExpectedResults();
+ var transitService = new DefaultTransitService(transitModel);
+
UpdaterConfigurator.configure(
graph,
- new DefaultVehiclePositionService(),
+ new DefaultRealtimeVehicleService(transitService),
new DefaultVehicleRentalService(),
transitModel,
config.updatersConfig
@@ -111,7 +113,7 @@ public SpeedTest(
timer.getRegistry(),
List::of,
TestServerContext.createWorldEnvelopeService(),
- TestServerContext.createVehiclePositionService(),
+ TestServerContext.createRealtimeVehicleService(transitService),
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 66%
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 4a1a5d287bf..2ddc4860679 100644
--- a/src/test/java/org/opentripplanner/updater/vehicle_position/VehiclePositionsMatcherTest.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;
@@ -24,7 +27,8 @@
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.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;
@@ -32,22 +36,38 @@
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 VehiclePositionsMatcherTest {
+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);
@Test
- public void matchRealtimePositionsToTrip() {
+ public void matchRealtimeVehiclesToTrip() {
var pos = vehiclePosition(tripId);
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() {
@@ -64,7 +84,7 @@ public void inferStartDate() {
@Test
public void tripNotFoundInPattern() {
- var service = new DefaultVehiclePositionService();
+ var service = new DefaultRealtimeVehicleService(null);
final String secondTripId = "trip2";
@@ -75,17 +95,19 @@ 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,
(id, time) -> pattern,
service,
- zoneId
+ zoneId,
+ null,
+ FEATURES
);
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());
@@ -93,7 +115,7 @@ public void tripNotFoundInPattern() {
@Test
public void sequenceId() {
- var service = new DefaultVehiclePositionService();
+ var service = new DefaultRealtimeVehicleService(null);
var tripId = "trip1";
var scopedTripId = TransitModelForTest.id(tripId);
@@ -106,13 +128,15 @@ 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,
(id, time) -> patternForTrip.get(id),
service,
- zoneId
+ zoneId,
+ null,
+ FEATURES
);
var pos = VehiclePosition
@@ -124,12 +148,12 @@ 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("F:stop-20", nextStop.getId().toString());
+ assertEquals(1, service.getRealtimeVehicles(pattern1).size());
+ var nextStop = service.getRealtimeVehicles(pattern1).get(0).stop();
+ assertEquals("F:stop-20", nextStop.get().stop().getId().toString());
}
@Test
@@ -146,7 +170,7 @@ void invalidStopSequence() {
}
private void testVehiclePositions(VehiclePosition pos) {
- var service = new DefaultVehiclePositionService();
+ var service = new DefaultRealtimeVehicleService(null);
var trip = TransitModelForTest.trip(tripId).build();
var stopTimes = List.of(stopTime(trip, 0), stopTime(trip, 1), stopTime(trip, 2));
@@ -156,40 +180,77 @@ 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(
+ var matcher = new RealtimeVehiclePatternMatcher(
TransitModelForTest.FEED_ID,
tripForId::get,
patternForTrip::get,
(id, time) -> patternForTrip.get(id),
service,
- zoneId
+ zoneId,
+ null,
+ FEATURES
);
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().get());
+ assertEquals(30, parsedVehicle.heading().get());
// 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());
+ }
+
+ 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 DefaultVehiclePositionService();
+ var service = new DefaultRealtimeVehicleService(null);
var tripId1 = "trip1";
var tripId2 = "trip2";
@@ -210,17 +271,19 @@ 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,
(id, time) -> patternForTrip.get(id),
service,
- zoneId
+ zoneId,
+ null,
+ FEATURES
);
var pos1 = vehiclePosition(tripId1);
@@ -230,16 +293,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(
@@ -261,7 +324,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);
}
@@ -280,7 +343,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);
}
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
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
{