diff --git a/.github/workflows/performance-test.yml b/.github/workflows/performance-test.yml index f4e2d021d21..ddc730fbeae 100644 --- a/.github/workflows/performance-test.yml +++ b/.github/workflows/performance-test.yml @@ -39,12 +39,6 @@ jobs: # extended locations that are run only after merging to dev-2.x - # Hamburg is disabled because of https://github.com/opentripplanner/OpenTripPlanner/issues/6430 - # - location: hamburg # German city - # iterations: 1 - # jfr-delay: "50s" - # profile: extended - - location: baden-wuerttemberg # German state of Baden-Württemberg: https://en.wikipedia.org/wiki/Baden-W%C3%BCrttemberg iterations: 1 jfr-delay: "50s" diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index f0932f007ab..fff11b02076 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -19,8 +19,8 @@ jobs: locations: - name: seattle sleep: 30 - - name: atlanta - sleep: 15 + #- name: atlanta + # sleep: 15 - name: houston sleep: 30 - name: denver diff --git a/application/pom.xml b/application/pom.xml index a54d76e9458..a9c495ca082 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -296,12 +296,6 @@ onebusaway-gtfs 11.2.2 - - - org.processing - core - 2.2.1 - net.java.dev.jets3t @@ -323,7 +317,7 @@ com.graphql-java graphql-java - 25.0 + 26.0 com.graphql-java @@ -333,7 +327,7 @@ org.apache.httpcomponents.client5 httpclient5 - 5.6 + 5.6.1 commons-cli @@ -349,7 +343,7 @@ com.hivemq hivemq-mqtt-client - 1.3.12 + 1.3.13 io.github.ci-cmg diff --git a/application/src/client/index.html b/application/src/client/index.html index ec630252590..12e8ecb4b28 100644 --- a/application/src/client/index.html +++ b/application/src/client/index.html @@ -5,8 +5,8 @@ OTP Debug - - + +
diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java index ef15793c74f..6f3ac78704e 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolEstimatedVehicleJourneyData.java @@ -19,7 +19,9 @@ import uk.org.siri.siri21.EstimatedVehicleJourney; import uk.org.siri.siri21.NaturalLanguageStringStructure; import uk.org.siri.siri21.OperatorRefStructure; +import uk.org.siri.siri21.PassengerCapacityStructure; import uk.org.siri.siri21.StopAssignmentStructure; +import uk.org.siri.siri21.VehicleOccupancyStructure; public class CarpoolEstimatedVehicleJourneyData { @@ -174,6 +176,139 @@ static EstimatedCall forPolygon(String posList) { return call; } + public static EstimatedVehicleJourney journeyWithTotalCapacity(int capacity) { + var journey = minimalCompleteJourney(); + for (var call : journey.getEstimatedCalls().getEstimatedCalls()) { + addTotalCapacity(call, capacity); + } + return journey; + } + + public static EstimatedVehicleJourney journeyWithDifferentCapacitiesPerCall( + int firstCapacity, + int lastCapacity + ) { + var journey = minimalCompleteJourney(); + var calls = journey.getEstimatedCalls().getEstimatedCalls(); + addTotalCapacity(calls.getFirst(), firstCapacity); + addTotalCapacity(calls.getLast(), lastCapacity); + return journey; + } + + public static EstimatedVehicleJourney journeyWithOnboardCounts(int... onboardCounts) { + var journey = minimalCompleteJourney(); + var calls = journey.getEstimatedCalls().getEstimatedCalls(); + for (int i = 0; i < Math.min(onboardCounts.length, calls.size()); i++) { + addOnboardCount(calls.get(i), onboardCounts[i]); + } + return journey; + } + + private static void addTotalCapacity(EstimatedCall call, int totalCapacity) { + var capacity = new PassengerCapacityStructure(); + capacity.setTotalCapacity(BigInteger.valueOf(totalCapacity)); + call.getExpectedDepartureCapacities().add(capacity); + } + + public static EstimatedVehicleJourney journeyWithLatestExpectedArrivalTime( + int expectedArrivalMinutes, + int latestExpectedArrivalMinutes + ) { + var journey = minimalCompleteJourney(); + var lastStop = journey.getEstimatedCalls().getEstimatedCalls().getLast(); + var base = lastStop.getAimedArrivalTime(); + lastStop.setExpectedArrivalTime(base.plusMinutes(expectedArrivalMinutes)); + lastStop.setLatestExpectedArrivalTime(base.plusMinutes(latestExpectedArrivalMinutes)); + return journey; + } + + public static EstimatedVehicleJourney journeyWithLatestExpectedArrivalTimeAimedOnly( + int latestExpectedArrivalMinutes + ) { + var journey = minimalCompleteJourney(); + var lastStop = journey.getEstimatedCalls().getEstimatedCalls().getLast(); + var base = lastStop.getAimedArrivalTime(); + lastStop.setExpectedArrivalTime(null); + lastStop.setLatestExpectedArrivalTime(base.plusMinutes(latestExpectedArrivalMinutes)); + return journey; + } + + /** + * Builds a 3-stop journey (origin, intermediate, destination) where the intermediate and + * destination stops each get their own {@code expectedArrivalTime} and + * {@code latestExpectedArrivalTime}, enabling assertions on per-stop deviation budgets. + * Arrival times are offset from {@code now} in minutes. + */ + public static EstimatedVehicleJourney journeyWithPerStopLatestExpectedArrivalTimes( + int intermediateExpectedArrivalMinutes, + int intermediateLatestExpectedArrivalMinutes, + int lastExpectedArrivalMinutes, + int lastLatestExpectedArrivalMinutes + ) { + var base = ZonedDateTime.now(); + + var origin = forPoint(OSLO_EAST); + origin.setAimedDepartureTime(base); + addStopName(origin, "Origin"); + + var intermediate = createArrivalStop( + OSLO_NORTH, + "Intermediate", + base, + intermediateExpectedArrivalMinutes, + intermediateLatestExpectedArrivalMinutes + ); + intermediate.setAimedDepartureTime(base.plusMinutes(intermediateExpectedArrivalMinutes)); + + var last = createArrivalStop( + OSLO_NORTH, + "Last", + base, + lastExpectedArrivalMinutes, + lastLatestExpectedArrivalMinutes + ); + + var journey = new EstimatedVehicleJourney(); + var operator = new OperatorRefStructure(); + operator.setValue("TESTOPERATOR"); + journey.setEstimatedVehicleJourneyCode("unittest"); + journey.setOperatorRef(operator); + journey.setEstimatedCalls(new EstimatedVehicleJourney.EstimatedCalls()); + journey.getEstimatedCalls().getEstimatedCalls().add(origin); + journey.getEstimatedCalls().getEstimatedCalls().add(intermediate); + journey.getEstimatedCalls().getEstimatedCalls().add(last); + + return journey; + } + + private static EstimatedCall createArrivalStop( + WgsCoordinate coordinate, + String name, + ZonedDateTime base, + int expectedArrivalMinutes, + int latestExpectedArrivalMinutes + ) { + var arrivalTime = base.plusMinutes(expectedArrivalMinutes); + var call = forPoint(coordinate); + call.setAimedArrivalTime(arrivalTime); + call.setExpectedArrivalTime(arrivalTime); + call.setLatestExpectedArrivalTime(base.plusMinutes(latestExpectedArrivalMinutes)); + addStopName(call, name); + return call; + } + + private static void addStopName(EstimatedCall call, String name) { + var nameStruct = new NaturalLanguageStringStructure(); + nameStruct.setValue(name); + call.getStopPointNames().add(nameStruct); + } + + private static void addOnboardCount(EstimatedCall call, int onboardCount) { + var occupancy = new VehicleOccupancyStructure(); + occupancy.setOnboardCount(BigInteger.valueOf(onboardCount)); + call.getExpectedDepartureOccupancies().add(occupancy); + } + static AimedFlexibleArea poslistToAimedFlexibleArea(String coordinates) { var gmlFactory = new ObjectFactory(); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolGraphPathBuilder.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolGraphPathBuilder.java index c5a9d22dd79..27b7647b30c 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolGraphPathBuilder.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolGraphPathBuilder.java @@ -18,9 +18,6 @@ public class CarpoolGraphPathBuilder { // Walking speed in m/s (OTP default from WalkPreferences) private static final double WALKING_SPEED_MPS = 1.33; - // Default number of edges to distribute duration across - private static final int DEFAULT_NUM_EDGES = 3; - /** * Creates a GraphPath with default 5-minute duration. */ @@ -30,6 +27,10 @@ public static GraphPath createGraphPath() { /** * Creates a GraphPath with specified duration using State chain. + * Uses a single edge with floor distance to avoid rounding errors: the edge traversal + * applies ceiling when converting to milliseconds, and State.getTime() applies ceiling + * when converting to seconds, so floor distance ensures the final second-precision + * duration matches the requested value. * * @param duration Total duration for the path * @return GraphPath with real State objects and accurate timing @@ -37,17 +38,9 @@ public static GraphPath createGraphPath() { public static GraphPath createGraphPath(Duration duration) { var builder = TestStateBuilder.ofWalking(); - // Calculate distance needed for target duration - double totalDistanceMeters = duration.toSeconds() * WALKING_SPEED_MPS; - - // Distribute across multiple edges for realistic path - int numEdges = DEFAULT_NUM_EDGES; - int distancePerEdge = (int) Math.ceil(totalDistanceMeters / numEdges); + int distanceMeters = (int) (duration.toSeconds() * WALKING_SPEED_MPS); - // Build state chain with calculated distances - for (int i = 0; i < numEdges; i++) { - builder.streetEdge("segment-" + i, distancePerEdge); - } + builder.streetEdge("segment-0", distanceMeters); return new GraphPath<>(builder.build()); } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java index 23f30b9dd71..11674add0d3 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/CarpoolTripTestData.java @@ -4,11 +4,10 @@ import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.carpooling.model.CarpoolStop; -import org.opentripplanner.ext.carpooling.model.CarpoolStopType; import org.opentripplanner.ext.carpooling.model.CarpoolTrip; +import org.opentripplanner.ext.carpooling.model.CarpoolTripBuilder; import org.opentripplanner.street.geometry.WgsCoordinate; /** @@ -16,16 +15,17 @@ */ public class CarpoolTripTestData { - private static final AtomicInteger ID_COUNTER = new AtomicInteger(0); - private static final AtomicInteger AREA_STOP_COUNTER = new AtomicInteger(0); + private static int idCounter = 0; + + private static final int DEFAULT_TOTAL_CAPACITY = CarpoolTrip.DEFAULT_TOTAL_CAPACITY; + private static final Duration DEFAULT_DEVIATION_BUDGET = Duration.ofMinutes(10); /** - * Creates a simple trip with origin and destination stops, default capacity of 4. + * Creates a simple trip with origin and destination stops. */ public static CarpoolTrip createSimpleTrip(WgsCoordinate boarding, WgsCoordinate alighting) { - var origin = createOriginStop(boarding); - var destination = createDestinationStop(alighting, 1); - return createTripWithCapacity(4, List.of(origin, destination)); + var stops = List.of(createOriginStop(boarding), createDestinationStop(alighting)); + return buildTrip(DEFAULT_TOTAL_CAPACITY, null, stops); } /** @@ -36,14 +36,11 @@ public static CarpoolTrip createSimpleTripWithTime( WgsCoordinate alighting, ZonedDateTime startTime ) { - var origin = createOriginStopWithTime(boarding, startTime, startTime); - var destination = createDestinationStopWithTime( - alighting, - 1, - startTime.plusHours(1), - startTime.plusHours(1) + var stops = List.of( + createOriginStopWithTime(boarding, startTime, startTime), + createDestinationStopWithTime(alighting, startTime.plusHours(1), startTime.plusHours(1)) ); - return createTripWithTime(startTime, 4, List.of(origin, destination)); + return buildTrip(DEFAULT_TOTAL_CAPACITY, startTime, stops); } /** @@ -57,160 +54,201 @@ public static CarpoolTrip createTripWithStops( List allStops = new ArrayList<>(); allStops.add(createOriginStop(boarding)); - // Renumber intermediate stops to account for origin at position 0 for (int i = 0; i < intermediateStops.size(); i++) { CarpoolStop intermediate = intermediateStops.get(i); allStops.add( - CarpoolStop.of(intermediate.getId(), () -> intermediate.getIndex() + 1) + CarpoolStop.of(intermediate.getId()) .withCoordinate(intermediate.getCoordinate()) - .withCarpoolStopType(intermediate.getCarpoolStopType()) .withExpectedDepartureTime(intermediate.getExpectedDepartureTime()) + .withAimedDepartureTime(intermediate.getAimedDepartureTime()) + .withExpectedArrivalTime(intermediate.getExpectedArrivalTime()) .withAimedArrivalTime(intermediate.getAimedArrivalTime()) + .withOnboardCount(intermediate.getOnboardCount()) + .withDeviationBudget(intermediate.getDeviationBudget()) + .build() + ); + } + + allStops.add(createDestinationStop(alighting)); + return buildTrip(DEFAULT_TOTAL_CAPACITY, null, allStops); + } + + /** + * Creates a trip with origin, intermediate stops, and destination. The deviation budget is applied + * to the origin and destination stops, while intermediate stops retain their own deviation budget. + */ + public static CarpoolTrip createTripWithStops( + WgsCoordinate boarding, + List intermediateStops, + WgsCoordinate alighting, + Duration deviationBudget + ) { + List allStops = new ArrayList<>(); + allStops.add(createOriginStopWithDeviationBudget(boarding, deviationBudget)); + + for (int i = 0; i < intermediateStops.size(); i++) { + CarpoolStop intermediate = intermediateStops.get(i); + allStops.add( + CarpoolStop.of(intermediate.getId()) + .withCoordinate(intermediate.getCoordinate()) + .withExpectedDepartureTime(intermediate.getExpectedDepartureTime()) + .withAimedDepartureTime(intermediate.getAimedDepartureTime()) .withExpectedArrivalTime(intermediate.getExpectedArrivalTime()) - .withAimedArrivalTime(intermediate.getAimedDepartureTime()) - .withSequenceNumber(intermediate.getSequenceNumber() + 1) - .withPassengerDelta(intermediate.getPassengerDelta()) + .withAimedArrivalTime(intermediate.getAimedArrivalTime()) + .withOnboardCount(intermediate.getOnboardCount()) + .withDeviationBudget(intermediate.getDeviationBudget()) .build() ); } - allStops.add(createDestinationStop(alighting, allStops.size())); - return createTripWithCapacity(4, allStops); + allStops.add(createDestinationStopWithDeviationBudget(alighting, deviationBudget)); + return buildTrip(DEFAULT_TOTAL_CAPACITY, null, allStops); } /** * Creates a trip with specified capacity and all stops (including origin/destination). */ - public static CarpoolTrip createTripWithCapacity(int seats, List stops) { - return createTripWithDeviationBudget(Duration.ofMinutes(10), seats, stops); + public static CarpoolTrip createTripWithCapacity(int capacity, List stops) { + return buildTrip(capacity, null, stops); } /** - * Creates a trip with specified deviation budget. + * Creates a trip with specified deviation budget on all stops. */ public static CarpoolTrip createTripWithDeviationBudget( Duration deviationBudget, WgsCoordinate boarding, WgsCoordinate alighting ) { - var origin = createOriginStop(boarding); - var destination = createDestinationStop(alighting, 1); - return createTripWithDeviationBudget(deviationBudget, 4, List.of(origin, destination)); + var origin = createOriginStopWithDeviationBudget(boarding, deviationBudget); + var destination = createDestinationStopWithDeviationBudget(alighting, deviationBudget); + return buildTrip(DEFAULT_TOTAL_CAPACITY, null, List.of(origin, destination)); } /** - * Creates a trip with all parameters specified. + * Creates a trip with specific start time. */ - public static CarpoolTrip createTripWithDeviationBudget( - Duration deviationBudget, - int seats, + public static CarpoolTrip createTripWithTime( + ZonedDateTime startTime, + int capacity, List stops ) { - return new org.opentripplanner.ext.carpooling.model.CarpoolTripBuilder( - FeedScopedId.ofNullable("TEST", "trip-" + ID_COUNTER.incrementAndGet()) - ) - .withStops(stops) - .withAvailableSeats(seats) - .withStartTime(ZonedDateTime.now()) - .withDeviationBudget(deviationBudget) - .build(); + return buildTrip(capacity, startTime, stops); } /** - * Creates a trip with specific start time and all other parameters. - * End time is calculated as startTime + 1 hour. + * Creates a CarpoolStop with specified onboard count. */ - public static CarpoolTrip createTripWithTime( - ZonedDateTime startTime, - int seats, - List stops - ) { - return new org.opentripplanner.ext.carpooling.model.CarpoolTripBuilder( - FeedScopedId.ofNullable("TEST", "trip-" + ID_COUNTER.incrementAndGet()) - ) - .withStops(stops) - .withAvailableSeats(seats) - .withStartTime(startTime) - .withEndTime(startTime.plusHours(1)) - .withDeviationBudget(Duration.ofMinutes(10)) - .build(); + public static CarpoolStop createStop(int onboardCount) { + return createStopAt(onboardCount, CarpoolTestCoordinates.OSLO_CENTER); } /** - * Creates a CarpoolStop with specified sequence (0-based) and passenger delta. + * Creates a CarpoolStop at a specific location with onboardCount=1 (driver only). */ - public static CarpoolStop createStop(int zeroBasedSequence, int passengerDelta) { - return createStopAt(zeroBasedSequence, passengerDelta, CarpoolTestCoordinates.OSLO_CENTER); + public static CarpoolStop createStopAt(WgsCoordinate location) { + return createStopAt(1, location); } /** - * Creates a CarpoolStop at a specific location. + * Creates a CarpoolStop at a specific location with a specific deviation budget. */ - public static CarpoolStop createStopAt(int sequence, WgsCoordinate location) { - return createStopAt(sequence, 0, location); + public static CarpoolStop createStopAt(WgsCoordinate location, Duration deviationBudget) { + return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-" + ++idCounter)) + .withCoordinate(location) + .withOnboardCount(1) + .withDeviationBudget(deviationBudget) + .build(); } /** * Creates a CarpoolStop with all parameters. */ - public static CarpoolStop createStopAt(int sequence, int passengerDelta, WgsCoordinate location) { - return CarpoolStop.of( - FeedScopedId.ofNullable("TEST", "area-" + AREA_STOP_COUNTER.incrementAndGet()), - AREA_STOP_COUNTER::getAndIncrement - ) + public static CarpoolStop createStopAt(int onboardCount, WgsCoordinate location) { + return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-" + ++idCounter)) .withCoordinate(location) - .withSequenceNumber(sequence) - .withPassengerDelta(passengerDelta) + .withOnboardCount(onboardCount) + .withDeviationBudget(DEFAULT_DEVIATION_BUDGET) .build(); } - /** - * Creates an origin stop (first stop, PICKUP_ONLY, passengerDelta=0, departure times only). - */ public static CarpoolStop createOriginStop(WgsCoordinate location) { return createOriginStopWithTime(location, null, null); } - /** - * Creates an origin stop with specific departure times. - */ public static CarpoolStop createOriginStopWithTime( WgsCoordinate location, ZonedDateTime expectedDepartureTime, ZonedDateTime aimedDepartureTime ) { - return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-0"), () -> 0) + return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-" + ++idCounter)) .withCoordinate(location) + .withOnboardCount(1) .withExpectedDepartureTime(expectedDepartureTime) .withAimedDepartureTime(aimedDepartureTime) + .withDeviationBudget(DEFAULT_DEVIATION_BUDGET) .build(); } /** - * Creates a destination stop (last stop, DROP_OFF_ONLY, passengerDelta=0, arrival times only). + * Creates an origin stop with specific deviation budget. */ - public static CarpoolStop createDestinationStop(WgsCoordinate location, int sequenceNumber) { - return createDestinationStopWithTime(location, sequenceNumber, null, null); + public static CarpoolStop createOriginStopWithDeviationBudget( + WgsCoordinate location, + Duration deviationBudget + ) { + return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-" + ++idCounter)) + .withCoordinate(location) + .withOnboardCount(1) + .withDeviationBudget(deviationBudget) + .build(); + } + + public static CarpoolStop createDestinationStop(WgsCoordinate location) { + return createDestinationStopWithTime(location, null, null); } - /** - * Creates a destination stop with specific arrival times. - */ public static CarpoolStop createDestinationStopWithTime( WgsCoordinate location, - int sequenceNumber, ZonedDateTime expectedArrivalTime, ZonedDateTime aimedArrivalTime ) { - return CarpoolStop.of( - FeedScopedId.ofNullable("TEST", "area-" + AREA_STOP_COUNTER.incrementAndGet()), - AREA_STOP_COUNTER::getAndIncrement - ) + return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-" + ++idCounter)) .withCoordinate(location) - .withCarpoolStopType(CarpoolStopType.DROP_OFF_ONLY) - .withSequenceNumber(sequenceNumber) + .withOnboardCount(1) .withExpectedArrivalTime(expectedArrivalTime) .withAimedArrivalTime(aimedArrivalTime) + .withDeviationBudget(DEFAULT_DEVIATION_BUDGET) .build(); } + + /** + * Creates a destination stop with specific deviation budget. + */ + public static CarpoolStop createDestinationStopWithDeviationBudget( + WgsCoordinate location, + Duration deviationBudget + ) { + return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "area-" + ++idCounter)) + .withCoordinate(location) + .withOnboardCount(1) + .withDeviationBudget(deviationBudget) + .build(); + } + + private static CarpoolTrip buildTrip( + int capacity, + ZonedDateTime startTime, + List stops + ) { + var actualStartTime = startTime != null ? startTime : ZonedDateTime.now(); + var builder = new CarpoolTripBuilder(FeedScopedId.ofNullable("TEST", "trip-" + ++idCounter)) + .withStops(stops) + .withTotalCapacity(capacity) + .withStartTime(actualStartTime); + if (startTime != null) { + builder.withEndTime(startTime.plusHours(1)); + } + return builder.build(); + } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraintsTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraintsTest.java index 8364132d2e5..a29b8b62686 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraintsTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraintsTest.java @@ -1,140 +1,164 @@ package org.opentripplanner.ext.carpooling.constraints; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.ext.carpooling.util.GraphPathUtils.calculateCumulativeDurations; import java.time.Duration; -import org.junit.jupiter.api.BeforeEach; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; import org.opentripplanner.astar.model.GraphPath; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.carpooling.CarpoolGraphPathBuilder; +import org.opentripplanner.ext.carpooling.model.CarpoolStop; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.state.State; class PassengerDelayConstraintsTest { - private PassengerDelayConstraints constraints; + private static final AtomicInteger STOP_COUNTER = new AtomicInteger(0); + private static final Duration FIVE_MINUTES = Duration.ofMinutes(5); - @BeforeEach - void setup() { - constraints = new PassengerDelayConstraints(); + private static CarpoolStop stopWithBudget(Duration budget) { + return CarpoolStop.of(FeedScopedId.ofNullable("TEST", "stop-" + STOP_COUNTER.incrementAndGet())) + .withCoordinate(new org.opentripplanner.street.geometry.WgsCoordinate(59.9, 10.7)) + .withDeviationBudget(budget) + .build(); } @Test - void satisfiesConstraints_noExistingStops_alwaysAccepts() { - Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10) }; + void satisfiesConstraints_delayWellUnderBudget_accepts() { + Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(5), Duration.ofMinutes(15) }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - // Modified route with passenger inserted + // Stop1 delay: 7min - 5min = 2min (within 5min budget) + // Destination delay: 17min - 15min = 2min (within 5min budget) GraphPath[] modifiedSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(4)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(7)), }; - // Should accept - no existing passengers to protect assertTrue( - constraints.satisfiesConstraints( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, - 2 + 3, + stops ) ); } @Test - void satisfiesConstraints_delayWellUnderThreshold_accepts() { - // Original timings: 0min -> 5min -> 15min - Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(5), Duration.ofMinutes(15) }; + void satisfiesConstraints_delayExactlyAtBudget_accepts() { + Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - // Modified route: boarding -> pickup -> stop1 -> dropoff -> alighting - // Timings: 0min -> 3min -> 7min -> 12min -> 17min - // Stop1 delay: 7min - 5min = 2min (well under 5min threshold) + // Stop1 delay: 15min - 10min = 5min (exactly at 5min budget) GraphPath[] modifiedSegments = new GraphPath[] { - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(4)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), }; assertTrue( - constraints.satisfiesConstraints( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, - 3 + 3, + stops ) ); } @Test - void satisfiesConstraints_delayExactlyAtThreshold_accepts() { - // Original route with one stop + void satisfiesConstraints_delayOverBudget_rejects() { Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - // Modified route where stop1 is delayed by exactly 5 minutes - // Timings: 0min -> 5min -> 15min -> 20min -> 25min - // Stop1 delay: 15min - 10min = 5min (exactly at threshold) + // Stop1 delay: 16min - 10min = 6min (exceeds 5min budget) GraphPath[] modifiedSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), }; - assertTrue( - constraints.satisfiesConstraints( + assertFalse( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, - 3 + 3, + stops ) ); } @Test - void satisfiesConstraints_delayOverThreshold_rejects() { - // Original route with one stop + void satisfiesConstraints_destinationOverBudget_rejects() { Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(Duration.ofMinutes(20)), + stopWithBudget(FIVE_MINUTES) + ); - // Modified route where stop1 is delayed by 6 minutes (over 5min threshold) - // Timings: 0min -> 5min -> 16min -> 21min -> 26min - // Stop1 delay: 16min - 10min = 6min (exceeds threshold) + // Stop1 delay: 12min - 10min = 2min (within 20min budget) + // Destination delay: 27min - 20min = 7min (exceeds 5min budget) GraphPath[] modifiedSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(7)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), }; assertFalse( - constraints.satisfiesConstraints( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, - 3 + 3, + stops ) ); } @Test - void satisfiesConstraints_multipleStops_oneOverThreshold_rejects() { - // Original route: boarding -> stop1 -> stop2 -> alighting + void satisfiesConstraints_multipleStops_oneOverBudget_rejects() { Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20), Duration.ofMinutes(30), }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - // Modified route where stop1 is ok (3min delay) but stop2 exceeds (7min delay) - // Timings: 0min -> 5min -> 13min -> 18min -> 27min -> 32min - // Stop1 delay: 13min - 10min = 3min ✓ - // Stop2 delay: 27min - 20min = 7min ✗ + // Stop1 delay: 13min - 10min = 3min ok + // Stop2 delay: 27min - 20min = 7min exceeds GraphPath[] modifiedSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(8)), @@ -144,29 +168,31 @@ void satisfiesConstraints_multipleStops_oneOverThreshold_rejects() { }; assertFalse( - constraints.satisfiesConstraints( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, - 3 + 3, + stops ) ); } @Test - void satisfiesConstraints_multipleStops_allUnderThreshold_accepts() { - // Original route: boarding -> stop1 -> stop2 -> alighting + void satisfiesConstraints_multipleStops_allUnderBudget_accepts() { Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20), Duration.ofMinutes(30), }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - // Modified route where both stops have acceptable delays - // Timings: 0min -> 5min -> 12min -> 17min -> 24min -> 34min - // Stop1 delay: 12min - 10min = 2min ✓ - // Stop2 delay: 24min - 20min = 4min ✓ GraphPath[] modifiedSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(7)), @@ -176,294 +202,325 @@ void satisfiesConstraints_multipleStops_allUnderThreshold_accepts() { }; assertTrue( - constraints.satisfiesConstraints( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, - 3 + 3, + stops ) ); } @Test - void satisfiesConstraints_passengerBeforeAllStops_checksAllStops() { - // Original route: boarding -> stop1 -> stop2 -> alighting - Duration[] originalTimes = { - Duration.ZERO, - Duration.ofMinutes(10), - Duration.ofMinutes(20), - Duration.ofMinutes(30), - }; + void satisfiesConstraints_differentBudgetsPerStop() { + Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + // Stop 1 has 2min budget, destination has 10min budget + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(Duration.ofMinutes(2)), + stopWithBudget(Duration.ofMinutes(10)) + ); - // Passenger inserted at very beginning (pickup at 1, dropoff at 2) - // Modified: boarding -> pickup -> dropoff -> stop1 -> stop2 -> alighting - // Mapping: stop1 (orig 1) -> mod 3, stop2 (orig 2) -> mod 4 - // Timings: 0min -> 3min -> 5min -> 13min -> 24min -> 34min - // Stop1 delay: 13min - 10min = 3min ✓ - // Stop2 delay: 24min - 20min = 4min ✓ + // Stop1 delay: 13min - 10min = 3min (exceeds 2min budget) GraphPath[] modifiedSegments = new GraphPath[] { - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(2)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(8)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), }; - assertTrue( - constraints.satisfiesConstraints( + assertFalse( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, - 2 + 3, + stops ) ); } @Test - void satisfiesConstraints_passengerAfterAllStops_checksAllStops() { - // Original route: boarding -> stop1 -> stop2 -> alighting - Duration[] originalTimes = { - Duration.ZERO, - Duration.ofMinutes(10), - Duration.ofMinutes(20), - Duration.ofMinutes(30), - }; + void satisfiesConstraints_noDelay_accepts() { + Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - // Passenger inserted at very end (pickup at 3, dropoff at 4) - // Modified: boarding -> stop1 -> stop2 -> pickup -> dropoff -> alighting - // Mapping: stop1 (orig 1) -> mod 1, stop2 (orig 2) -> mod 2 - // Even though passenger comes after, routing to pickup might cause delays - // Timings: 0min -> 11min -> 22min -> 27min -> 30min -> 40min - // Stop1 delay: 11min - 10min = 1min ✓ - // Stop2 delay: 22min - 20min = 2min ✓ GraphPath[] modifiedSegments = new GraphPath[] { - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(4)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), }; assertTrue( - constraints.satisfiesConstraints( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), + 1, 3, - 4 + stops ) ); } @Test - void satisfiesConstraints_passengerBetweenStops_checksAllStops() { - // Original route: boarding -> stop1 -> stop2 -> alighting - Duration[] originalTimes = { - Duration.ZERO, - Duration.ofMinutes(10), - Duration.ofMinutes(20), - Duration.ofMinutes(30), - }; + void satisfiesConstraints_zeroBudget_rejectsAnyDelay() { + Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + var stops = List.of( + stopWithBudget(Duration.ZERO), + stopWithBudget(Duration.ZERO), + stopWithBudget(Duration.ZERO) + ); - // Passenger inserted between stops (pickup at 2, dropoff at 3) - // Modified: boarding -> stop1 -> pickup -> dropoff -> stop2 -> alighting - // Mapping: stop1 (orig 1) -> mod 1, stop2 (orig 2) -> mod 4 - // Timings: 0min -> 11min -> 14min -> 17min -> 24min -> 34min - // Stop1 delay: 11min - 10min = 1min ✓ - // Stop2 delay: 24min - 20min = 4min ✓ + // Stop1 delay: 10min + 1s - 10min = 1s (exceeds zero budget) GraphPath[] modifiedSegments = new GraphPath[] { - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(7)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5).plusSeconds(1)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), }; - assertTrue( - constraints.satisfiesConstraints( + assertFalse( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), - 2, - 3 + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), + 1, + 3, + stops ) ); } @Test - void customMaxDelay_acceptsWithinCustomThreshold() { - var customConstraints = new PassengerDelayConstraints(Duration.ofMinutes(10)); - - Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + void satisfiesConstraints_zeroBudget_noDelay_accepts() { + var stops = List.of( + stopWithBudget(Duration.ZERO), + stopWithBudget(Duration.ZERO), + stopWithBudget(Duration.ZERO) + ); - // Stop1 delayed by 8 minutes (within 10min custom threshold) + // Use the same GraphPaths to derive both original and modified times + // so there is truly zero delay (avoids rounding from GraphPath construction) GraphPath[] modifiedSegments = new GraphPath[] { - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(13)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(4)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), }; + Duration[] cumulativeDurations = calculateCumulativeDurations(modifiedSegments, Duration.ZERO); + + // originalTimes = modified times at the original stop positions + // With pickup=1, dropoff=3: original indices [0,1,2] map to modified [0,2,4] + Duration[] originalTimes = { + cumulativeDurations[0], + cumulativeDurations[2], + cumulativeDurations[4], + }; assertTrue( - customConstraints.satisfiesConstraints( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + cumulativeDurations, 1, - 3 + 3, + stops ) ); } @Test - void customMaxDelay_rejectsOverCustomThreshold() { - var customConstraints = new PassengerDelayConstraints(Duration.ofMinutes(2)); - + void satisfiesConstraints_largeBudget_acceptsLargeDelay() { Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + var stops = List.of( + stopWithBudget(Duration.ZERO), + stopWithBudget(Duration.ofHours(1)), + stopWithBudget(Duration.ofHours(1)) + ); - // Stop1 delayed by 3 minutes (over 2min custom threshold) + // Stop1 delay: 40min - 10min = 30min (within 60min budget) GraphPath[] modifiedSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(8)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(35)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), }; - assertFalse( - customConstraints.satisfiesConstraints( + assertTrue( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, - 3 + 3, + stops ) ); } @Test - void customMaxDelay_zeroTolerance_rejectsAnyDelay() { - var strictConstraints = new PassengerDelayConstraints(Duration.ZERO); - + void satisfiesConstraints_tightAndPermissiveStops_respectsEachBudget() { Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + // Stop 1 is strict (3min), destination is permissive (30min) + var stops = List.of( + stopWithBudget(Duration.ZERO), + stopWithBudget(Duration.ofMinutes(3)), + stopWithBudget(Duration.ofMinutes(30)) + ); - // Stop1 delayed by even 1 second + // Stop1 delay: 12min - 10min = 2min (within 3min budget, ok) + // Destination delay: 47min - 20min = 27min (within 30min budget, ok) GraphPath[] modifiedSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5).plusSeconds(1)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(7)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(30)), }; - assertFalse( - strictConstraints.satisfiesConstraints( + assertTrue( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, - 3 + 3, + stops ) ); } @Test - void customMaxDelay_veryPermissive_acceptsLargeDelays() { - var permissiveConstraints = new PassengerDelayConstraints(Duration.ofHours(1)); - - Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; + void satisfiesConstraints_passengerBeforeAllStops_checksAllStops() { + Duration[] originalTimes = { + Duration.ZERO, + Duration.ofMinutes(10), + Duration.ofMinutes(20), + Duration.ofMinutes(30), + }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - // Stop1 delayed by 30 minutes (well within 1 hour threshold) + // Passenger inserted at very beginning (pickup at 1, dropoff at 2) + // Stop1 delay: 13min - 10min = 3min ok + // Stop2 delay: 24min - 20min = 4min ok + // Destination delay: 36min - 30min = 6min exceeds 5min budget GraphPath[] modifiedSegments = new GraphPath[] { - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(35)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(2)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(8)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(12)), }; - assertTrue( - permissiveConstraints.satisfiesConstraints( + assertFalse( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), 1, - 3 + 2, + stops ) ); } @Test - void getMaxDelay_returnsConfiguredValue() { - assertEquals(Duration.ofMinutes(5), constraints.getMaxDelay()); + void satisfiesConstraints_nonZeroStopDuration_countsDwellAtIntermediateStops() { + // Uses a non-zero stopDuration to verify dwell at intermediate stops is included in the + // budget check. The modified route has 2 extra dwells vs. the baseline (4 segments vs. 2), + // which alone accounts for 2 of the 6-minute destination delay that pushes it over budget. + Duration stopDuration = Duration.ofMinutes(1); + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - var customConstraints = new PassengerDelayConstraints(Duration.ofMinutes(10)); - assertEquals(Duration.ofMinutes(10), customConstraints.getMaxDelay()); - } + // Baseline: 2 segments of 10min. With 1-min dwell: cumulative = [0, 10, 21] + GraphPath[] baselineSegments = new GraphPath[] { + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), + }; + Duration[] originalTimes = calculateCumulativeDurations(baselineSegments, stopDuration); - @Test - void defaultMaxDelay_isFiveMinutes() { - assertEquals(Duration.ofMinutes(5), PassengerDelayConstraints.DEFAULT_MAX_DELAY); - } + // Modified (pickup=1, dropoff=3): 4 segments of 6min. With 1-min dwell: cumulative = [0, 6, 13, 20, 27] + // Destination delay: 27 - 21 = 6min, exceeds 5min budget. + GraphPath[] overBudgetSegments = new GraphPath[] { + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), + }; - @Test - void constructor_negativeDelay_throwsException() { - assertThrows(IllegalArgumentException.class, () -> - new PassengerDelayConstraints(Duration.ofMinutes(-1)) + assertFalse( + PassengerDelayConstraints.satisfiesConstraints( + originalTimes, + calculateCumulativeDurations(overBudgetSegments, stopDuration), + 1, + 3, + stops + ) ); - } - - @Test - void satisfiesConstraints_noDelay_accepts() { - // Route where insertion doesn't add any delay - Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20) }; - // Modified route where stop1 arrives at exactly the same time - // (perfect routing somehow) - GraphPath[] modifiedSegments = new GraphPath[] { - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(4)), + // Shortening one segment to 5min: cumulative = [0, 6, 12, 19, 26] + // Destination delay: 26 - 21 = 5min, exactly at budget → accepts. + GraphPath[] atBudgetSegments = new GraphPath[] { CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(5)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(6)), }; assertTrue( - constraints.satisfiesConstraints( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(atBudgetSegments, stopDuration), 1, - 3 + 3, + stops ) ); } @Test - void satisfiesConstraints_tripWithManyStops_checksAll() { - // Original route with 5 stops + void satisfiesConstraints_passengerBetweenStops_checksAllStops() { Duration[] originalTimes = { Duration.ZERO, Duration.ofMinutes(10), Duration.ofMinutes(20), Duration.ofMinutes(30), - Duration.ofMinutes(40), - Duration.ofMinutes(50), - Duration.ofMinutes(60), }; + var stops = List.of( + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES), + stopWithBudget(FIVE_MINUTES) + ); - // Insert passenger between stop2 and stop3 (positions 3, 4) - // All stops should have delays <= 5 minutes - // Modified indices: 0,1,2,pickup@3,dropoff@4,3,4,5,6 - // Note: With real State objects, durations will be slightly longer due to rounding - // (typically 1-3 seconds per path). We use slightly shorter durations to ensure - // the cumulative delays stay within the 5-minute threshold. + // Passenger inserted between stops (pickup at 2, dropoff at 3) + // Stop1 delay: 11min - 10min = 1min ok + // Stop2 delay: 24min - 20min = 4min ok + // Destination delay: 36min - 30min = 6min exceeds 5min budget GraphPath[] modifiedSegments = new GraphPath[] { - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(11)), CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(2)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(8)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), - CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(10)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(3)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(7)), + CarpoolGraphPathBuilder.createGraphPath(Duration.ofMinutes(12)), }; - assertTrue( - constraints.satisfiesConstraints( + assertFalse( + PassengerDelayConstraints.satisfiesConstraints( originalTimes, - calculateCumulativeDurations(modifiedSegments), + calculateCumulativeDurations(modifiedSegments, Duration.ZERO), + 2, 3, - 4 + stops ) ); } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/CapacityFilterTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/CapacityFilterTest.java deleted file mode 100644 index ca741e20b73..00000000000 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/CapacityFilterTest.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.opentripplanner.ext.carpooling.filter; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_CENTER; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_EAST; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_NORTH; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_SOUTH; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_WEST; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createDestinationStop; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createOriginStop; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createStop; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createTripWithCapacity; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createTripWithStops; - -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class CapacityFilterTest { - - private CapacityFilter filter; - - @BeforeEach - void setup() { - filter = new CapacityFilter(); - } - - @Test - void accepts_tripWithCapacity_returnsTrue() { - var trip = createTripWithStops(OSLO_CENTER, List.of(), OSLO_NORTH); - - assertTrue(filter.accepts(trip, OSLO_EAST, OSLO_WEST)); - } - - @Test - void accepts_tripAtFullCapacity_returnsTrue() { - // CapacityFilter only checks configured capacity, not actual occupancy - // Detailed capacity validation happens in the validator layer - // All 4 seats taken - var stop1 = createStop(0, 4); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); - - // Filter accepts because trip has capacity configured (even if currently full) - assertTrue(filter.accepts(trip, OSLO_EAST, OSLO_WEST)); - } - - @Test - void accepts_tripWithOneOpenSeat_returnsTrue() { - // 3 of 4 seats taken - var stop1 = createStop(0, 3); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); - - assertTrue(filter.accepts(trip, OSLO_EAST, OSLO_WEST)); - } - - @Test - void accepts_zeroCapacityTrip_returnsFalse() { - var stops = List.of(createOriginStop(OSLO_CENTER), createDestinationStop(OSLO_NORTH, 1)); - var trip = createTripWithCapacity(0, stops); - - assertFalse(filter.accepts(trip, OSLO_EAST, OSLO_WEST)); - } - - @Test - void accepts_passengerCoordinatesIgnored() { - // Filter only checks if ANY capacity exists, not position-specific - var stops = List.of(createOriginStop(OSLO_CENTER), createDestinationStop(OSLO_NORTH, 1)); - var trip = createTripWithCapacity(2, stops); - - // Should accept regardless of passenger coordinates - assertTrue(filter.accepts(trip, OSLO_SOUTH, OSLO_EAST)); - assertTrue(filter.accepts(trip, OSLO_NORTH, OSLO_SOUTH)); - } - - @Test - void accepts_tripWithFluctuatingCapacity_checksOverallAvailability() { - // 2 passengers - var stop1 = createStop(0, 2); - // Dropoff 2 - var stop2 = createStop(1, -2); - // Pickup 1 - var stop3 = createStop(2, 1); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2, stop3), OSLO_NORTH); - - // At some point there's capacity (positions 0, 2+) - assertTrue(filter.accepts(trip, OSLO_EAST, OSLO_WEST)); - } - - @Test - void accepts_tripAlwaysAtCapacity_returnsTrue() { - // CapacityFilter only checks configured capacity, not actual occupancy - // Fill to capacity - var stop1 = createStop(0, 4); - // Drop 1 - var stop2 = createStop(1, -1); - // Pick 1 (back to full) - var stop3 = createStop(2, 1); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2, stop3), OSLO_NORTH); - - // Filter accepts because trip has capacity configured - // The validator will determine if there's actual room for insertion - assertTrue(filter.accepts(trip, OSLO_EAST, OSLO_WEST)); - } -} diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilterTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilterTest.java deleted file mode 100644 index d27b492490e..00000000000 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilterTest.java +++ /dev/null @@ -1,185 +0,0 @@ -package org.opentripplanner.ext.carpooling.filter; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.LAKE_EAST; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.LAKE_NORTH; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.LAKE_SOUTH; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.LAKE_WEST; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_CENTER; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_EAST; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_NORTH; -import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_NORTHEAST; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createSimpleTrip; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createStopAt; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createTripWithStops; - -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.opentripplanner.street.geometry.WgsCoordinate; - -class DirectionalCompatibilityFilterTest { - - private DirectionalCompatibilityFilter filter; - - @BeforeEach - void setup() { - filter = new DirectionalCompatibilityFilter(); - } - - @Test - void accepts_passengerAlignedWithTrip_returnsTrue() { - // Trip goes north - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger also going north - var passengerPickup = OSLO_EAST; - // Northeast - var passengerDropoff = new WgsCoordinate(59.9549, 10.7922); - - assertTrue(filter.accepts(trip, passengerPickup, passengerDropoff)); - } - - @Test - void accepts_passengerOppositeDirection_returnsFalse() { - // Trip goes north - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger going south - var passengerPickup = OSLO_EAST; - var passengerDropoff = OSLO_CENTER; - - assertFalse(filter.accepts(trip, passengerPickup, passengerDropoff)); - } - - @Test - void accepts_tripAroundLake_passengerOnSegment_returnsTrue() { - // Trip goes around a lake: North → East → South → West - var stop1 = createStopAt(0, LAKE_EAST); - var stop2 = createStopAt(1, LAKE_SOUTH); - var trip = createTripWithStops(LAKE_NORTH, List.of(stop1, stop2), LAKE_WEST); - - // Passenger aligned with the southward segment (East → South) - // East side - var passengerPickup = new WgsCoordinate(59.9339, 10.7922); - // South of east - var passengerDropoff = new WgsCoordinate(59.9139, 10.7922); - - // Should accept because passenger aligns with East→South segment - assertTrue(filter.accepts(trip, passengerPickup, passengerDropoff)); - } - - @Test - void accepts_passengerFarFromRoute_butDirectionallyAligned_returnsTrue() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger far to the east but directionally aligned (both going north) - // Way east - var passengerPickup = new WgsCoordinate(59.9139, 11.0000); - var passengerDropoff = new WgsCoordinate(59.9439, 11.0000); - - // Should accept - only checks direction, not distance (that's DistanceBasedFilter's job) - assertTrue(filter.accepts(trip, passengerPickup, passengerDropoff)); - } - - @Test - void accepts_passengerPartiallyAligned_withinTolerance_returnsTrue() { - // Going north - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger going northeast (~45° off) - // Should accept within default tolerance (60°) - assertTrue(filter.accepts(trip, OSLO_CENTER, OSLO_NORTHEAST)); - } - - @Test - void accepts_passengerPerpendicularToTrip_returnsFalse() { - // Going north - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger going east (90° perpendicular) - // Should reject (exceeds 60° tolerance) - assertFalse(filter.accepts(trip, OSLO_CENTER, OSLO_EAST)); - } - - @Test - void accepts_complexRoute_multipleSegments_findsCompatibleSegment() { - // Trip with multiple segments going different directions - // Go east first - var stop1 = createStopAt(0, OSLO_EAST); - // Then northeast - var stop2 = createStopAt(1, OSLO_NORTHEAST); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); - - // Passenger going northeast (aligns with second segment) - var passengerPickup = new WgsCoordinate(59.9289, 10.7722); - var passengerDropoff = new WgsCoordinate(59.9389, 10.7822); - - assertTrue(filter.accepts(trip, passengerPickup, passengerDropoff)); - } - - @Test - void accepts_tripWithSingleStop_checksAllSegments() { - var stop1 = createStopAt(0, OSLO_EAST); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); - - // Passenger aligned with first segment (Center → East) - var passengerPickup = new WgsCoordinate(59.9139, 10.7622); - var passengerDropoff = new WgsCoordinate(59.9139, 10.7822); - - assertTrue(filter.accepts(trip, passengerPickup, passengerDropoff)); - } - - @Test - void accepts_passengerWithinCorridorButWrongDirection_returnsFalse() { - // Going north - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger nearby but going opposite direction - // North - var passengerPickup = new WgsCoordinate(59.9239, 10.7522); - // South (backtracking) - var passengerDropoff = new WgsCoordinate(59.9139, 10.7522); - - assertFalse(filter.accepts(trip, passengerPickup, passengerDropoff)); - } - - @Test - void customBearingTolerance_acceptsWithinCustomTolerance() { - // Custom filter with 90° tolerance (very permissive) - var customFilter = new DirectionalCompatibilityFilter(90.0); - - // Going north - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger going east (90° perpendicular) - // Should accept with 90° tolerance (default 60° would reject) - assertTrue(customFilter.accepts(trip, OSLO_CENTER, OSLO_EAST)); - } - - @Test - void customBearingTolerance_rejectsOutsideCustomTolerance() { - // Custom filter with 30° tolerance (strict) - var customFilter = new DirectionalCompatibilityFilter(30.0); - - // Going north - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger going northeast (~45° off) - // Should reject with 30° tolerance (default 60° would accept) - assertFalse(customFilter.accepts(trip, OSLO_CENTER, OSLO_NORTHEAST)); - } - - @Test - void getBearingToleranceDegrees_returnsConfiguredValue() { - var customFilter = new DirectionalCompatibilityFilter(45.0); - assertEquals(45.0, customFilter.getBearingToleranceDegrees()); - } - - @Test - void defaultBearingTolerance_is60Degrees() { - assertEquals(60.0, filter.getBearingToleranceDegrees()); - } -} diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DistanceBasedFilterTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DistanceBasedFilterTest.java index c2f8e43b4f8..ddcaf570ae4 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DistanceBasedFilterTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/DistanceBasedFilterTest.java @@ -214,8 +214,8 @@ void accepts_horizontalRoute_passengerAlongRoute_returnsTrue() { @Test void accepts_tripWithMultipleStops_passengerNearAnySegment() { // Trip with multiple stops - filter checks ALL segments - var stop1 = createStopAt(0, LAKE_EAST); - var stop2 = createStopAt(1, LAKE_SOUTH); + var stop1 = createStopAt(LAKE_EAST); + var stop2 = createStopAt(LAKE_SOUTH); var trip = createTripWithStops(LAKE_NORTH, java.util.List.of(stop1, stop2), LAKE_WEST); // Passenger journey near the LAKE_SOUTH to LAKE_WEST segment diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/FilterChainTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/FilterChainTest.java index b36b690fe46..bed4e6d9cc9 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/FilterChainTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/filter/FilterChainTest.java @@ -6,10 +6,7 @@ import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_EAST; import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_NORTH; import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_WEST; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createDestinationStop; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createOriginStop; import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createSimpleTrip; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createTripWithCapacity; import java.util.List; import org.junit.jupiter.api.Test; @@ -73,30 +70,6 @@ void accepts_firstFilterRejects_doesNotCallOthers() { assertFalse(filter2Called[0], "Filter2 should not have been called due to short-circuit"); } - @Test - void standard_includesAllStandardFilters() { - var chain = FilterChain.standard(); - - // Should contain CapacityFilter and DirectionalCompatibilityFilter - // Verify by testing behavior with a trip that has no capacity - var stops = List.of(createOriginStop(OSLO_CENTER), createDestinationStop(OSLO_NORTH, 1)); - var emptyTrip = createTripWithCapacity(0, stops); - - // Should reject due to capacity filter - assertFalse(chain.accepts(emptyTrip, OSLO_EAST, OSLO_WEST)); - } - - @Test - void standard_checksDirectionalCompatibility() { - var chain = FilterChain.standard(); - - // Trip going north, passenger going south - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Should reject due to directional filter - assertFalse(chain.accepts(trip, OSLO_EAST, OSLO_CENTER)); - } - @Test void emptyChain_acceptsAll() { var chain = new FilterChain(List.of()); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java index a7c3521331b..a468587379c 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilderTest.java @@ -3,13 +3,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_CENTER; import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_NORTH; -import static org.opentripplanner.ext.carpooling.model.CarpoolStopType.DROP_OFF_ONLY; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; -import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; public class CarpoolStopBuilderTest { @@ -57,70 +54,44 @@ public class CarpoolStopBuilderTest { @Test void buildFromValues_usingWith_buildToCorrectValues() { - var builder = new CarpoolStopBuilder(new FeedScopedId("feed", "id"), () -> -1); + var builder = new CarpoolStopBuilder(new FeedScopedId("feed", "id")); builder - .withSequenceNumber(1) - .withPassengerDelta(2) + .withOnboardCount(2) .withCoordinate(OSLO_NORTH) - .withCarpoolStopType(DROP_OFF_ONLY) .withAimedArrivalTime(AIMED_ARRIVAL_TIME) .withExpectedArrivalTime(EXPECTED_ARRIVAL_TIME) .withAimedDepartureTime(AIMED_DEPARTURE_TIME) - .withExpectedDepartureTime(EXPECTED_DEPARTURE_TIME) - .withName(new NonLocalizedString("name")) - .withDescription(new NonLocalizedString("description")) - .withUrl(new NonLocalizedString("http://url.value")); + .withExpectedDepartureTime(EXPECTED_DEPARTURE_TIME); var stop = builder.buildFromValues(); - assertEquals(-1, stop.getIndex()); - assertEquals(1, stop.getSequenceNumber()); - assertEquals(2, stop.getPassengerDelta()); + assertEquals(2, stop.getOnboardCount()); assertEquals(OSLO_NORTH, stop.getCoordinate()); - assertEquals(DROP_OFF_ONLY, stop.getCarpoolStopType()); assertEquals(AIMED_ARRIVAL_TIME, stop.getAimedArrivalTime()); assertEquals(EXPECTED_ARRIVAL_TIME, stop.getExpectedArrivalTime()); assertEquals(AIMED_DEPARTURE_TIME, stop.getAimedDepartureTime()); assertEquals(EXPECTED_DEPARTURE_TIME, stop.getExpectedDepartureTime()); - assertEquals("name", stop.getName().toString()); - assertEquals("description", stop.getDescription().toString()); - assertEquals("http://url.value", stop.getUrl().toString()); } @Test void buildFromValues_usingCarPoolStop_buildsCorrectValues() { - var i = new AtomicInteger(0); - var originalBuilder = new CarpoolStopBuilder( - new FeedScopedId("feed", "id"), - i::incrementAndGet - ); + var originalBuilder = new CarpoolStopBuilder(new FeedScopedId("feed", "id")); originalBuilder - .withSequenceNumber(2) - .withPassengerDelta(3) + .withOnboardCount(3) .withCoordinate(OSLO_CENTER) - .withCarpoolStopType(DROP_OFF_ONLY) .withAimedArrivalTime(AIMED_ARRIVAL_TIME) .withExpectedArrivalTime(EXPECTED_ARRIVAL_TIME) .withAimedDepartureTime(AIMED_DEPARTURE_TIME) - .withExpectedDepartureTime(EXPECTED_DEPARTURE_TIME) - .withName(new NonLocalizedString("name value")) - .withDescription(new NonLocalizedString("description value")) - .withUrl(new NonLocalizedString("http://url.value")); + .withExpectedDepartureTime(EXPECTED_DEPARTURE_TIME); var original = originalBuilder.buildFromValues(); var copyBuilder = new CarpoolStopBuilder(original); var copy = copyBuilder.buildFromValues(); - assertEquals(1, copy.getIndex()); - assertEquals(original.getSequenceNumber(), copy.getSequenceNumber()); - assertEquals(original.getPassengerDelta(), copy.getPassengerDelta()); + assertEquals(original.getOnboardCount(), copy.getOnboardCount()); assertEquals(original.getCoordinate(), copy.getCoordinate()); - assertEquals(original.getCarpoolStopType(), copy.getCarpoolStopType()); assertEquals(original.getAimedArrivalTime(), copy.getAimedArrivalTime()); assertEquals(original.getExpectedArrivalTime(), copy.getExpectedArrivalTime()); assertEquals(original.getAimedDepartureTime(), copy.getAimedDepartureTime()); assertEquals(original.getExpectedDepartureTime(), copy.getExpectedDepartureTime()); - assertEquals(original.getName(), copy.getName()); - assertEquals(original.getDescription(), copy.getDescription()); - assertEquals(original.getUrl(), copy.getUrl()); } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java index 5a0fc8f7207..1ead1a0f674 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilderTest.java @@ -6,9 +6,7 @@ import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createSimpleTrip; import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createStopAt; -import java.time.Duration; import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; import java.util.List; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; @@ -19,21 +17,19 @@ public class CarpoolTripBuilderTest { void buildFromValues_fromId_buildToCorrectValues() { var startTime = ZonedDateTime.now(); var endTime = ZonedDateTime.now().plusMinutes(45); - var stop = createStopAt(1, OSLO_EAST); + var stop = createStopAt(OSLO_EAST); var builder = new CarpoolTripBuilder(new FeedScopedId("feed", "id")); var trip = builder - .withAvailableSeats(2) + .withTotalCapacity(2) .withProvider("UNIT") - .withDeviationBudget(Duration.of(8, ChronoUnit.MINUTES)) .withStartTime(startTime) .withEndTime(endTime) .withStops(List.of(stop)) .buildFromValues(); - assertEquals(2, trip.availableSeats()); + assertEquals(2, trip.totalCapacity()); assertEquals("UNIT", trip.provider()); - assertEquals(Duration.of(8, ChronoUnit.MINUTES), trip.deviationBudget()); assertEquals(startTime, trip.startTime()); assertEquals(endTime, trip.endTime()); assertEquals(stop, trip.stops().getFirst()); @@ -46,9 +42,8 @@ void buildFromValues_fromOriginal_buildToCorrectValues() { var builder = new CarpoolTripBuilder(original); var trip = builder.buildFromValues(); - assertEquals(original.availableSeats(), trip.availableSeats()); + assertEquals(original.totalCapacity(), trip.totalCapacity()); assertEquals(original.provider(), trip.provider()); - assertEquals(original.deviationBudget(), trip.deviationBudget()); assertEquals(original.startTime(), trip.startTime()); assertEquals(original.endTime(), trip.endTime()); assertEquals(original.stops().getFirst(), trip.stops().getFirst()); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java index 2f7c5c43469..aadd58b84ab 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/model/CarpoolTripCapacityTest.java @@ -15,181 +15,152 @@ /** * Tests for capacity checking methods on {@link CarpoolTrip}. + *

+ * All trips created via {@code createTripWithStops} have totalCapacity=5. + * The method wraps intermediate stops with an Origin (onboard=1) at the front + * and a Destination (onboard=1) at the end. + *

+ * {@code pickupPosition} and {@code dropoffPosition} in {@code hasCapacityForInsertion} + * are 0-based indices of the passenger's stops in the modified route (the route after the + * passenger's pickup and dropoff have been inserted into the carpool trip). */ class CarpoolTripCapacityTest { + // -- getPassengerCountAtDepartureOfStop tests -- + @Test - void getPassengerCountAtPosition_noStops_allZeros() { + void getPassengerCountAtDepartureOfStop_driverOnly() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - // Boarding - assertEquals(0, trip.getPassengerCountAtPosition(0)); - // Beyond stops - assertEquals(0, trip.getPassengerCountAtPosition(1)); + assertEquals(1, trip.getPassengerCountAtDepartureOfStop(0)); + assertEquals(1, trip.getPassengerCountAtDepartureOfStop(1)); } @Test - void getPassengerCountAtPosition_onePickupStop_incrementsAtStop() { - // Pickup 1 passenger, then drop off 1 passenger - var stop1 = createStop(0, 1); - var stop2 = createStop(1, -1); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); - - // Position 0: Before origin stop - assertEquals(0, trip.getPassengerCountAtPosition(0)); - // Position 1: After origin stop (passengerDelta=0) - assertEquals(0, trip.getPassengerCountAtPosition(1)); - // Position 2: After pickup stop (passengerDelta=1) - assertEquals(1, trip.getPassengerCountAtPosition(2)); - // Position 3: After dropoff stop (passengerDelta=-1) - assertEquals(0, trip.getPassengerCountAtPosition(3)); - // Position 4: After destination stop (passengerDelta=0) - assertEquals(0, trip.getPassengerCountAtPosition(4)); + void getPassengerCountAtDepartureOfStop_withIntermediateStops() { + // Stops: [Origin(1), A(2), B(1), Destination(1)] + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(2), createStop(1)), OSLO_NORTH); + + assertEquals(1, trip.getPassengerCountAtDepartureOfStop(0)); + assertEquals(2, trip.getPassengerCountAtDepartureOfStop(1)); + assertEquals(1, trip.getPassengerCountAtDepartureOfStop(2)); + assertEquals(1, trip.getPassengerCountAtDepartureOfStop(3)); } @Test - void getPassengerCountAtPosition_pickupAndDropoff_incrementsThenDecrements() { - // Pickup 2 passengers - var stop1 = createStop(0, 2); - // Dropoff 1 passenger - var stop2 = createStop(1, -1); - // Dropoff remaining passenger - var stop3 = createStop(2, -1); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2, stop3), OSLO_NORTH); - - // Position 0: Before origin stop - assertEquals(0, trip.getPassengerCountAtPosition(0)); - // Position 1: After origin stop (passengerDelta=0) - assertEquals(0, trip.getPassengerCountAtPosition(1)); - // Position 2: After first intermediate stop (passengerDelta=2) - assertEquals(2, trip.getPassengerCountAtPosition(2)); - // Position 3: After second intermediate stop (passengerDelta=-1) - assertEquals(1, trip.getPassengerCountAtPosition(3)); - // Position 4: After third intermediate stop (passengerDelta=-1) - assertEquals(0, trip.getPassengerCountAtPosition(4)); - // Position 5: After destination stop (passengerDelta=0) - assertEquals(0, trip.getPassengerCountAtPosition(5)); + void getPassengerCountAtDepartureOfStop_negativeIndex_throwsException() { + var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + assertThrows(IllegalArgumentException.class, () -> trip.getPassengerCountAtDepartureOfStop(-1)); } @Test - void getPassengerCountAtPosition_multipleStops_cumulativeCount() { - var stop1 = createStop(0, 1); - var stop2 = createStop(1, 2); - var stop3 = createStop(2, -1); - var stop4 = createStop(3, 1); - var stop5 = createStop(4, -3); - var trip = createTripWithStops( - OSLO_CENTER, - List.of(stop1, stop2, stop3, stop4, stop5), - OSLO_NORTH - ); - - // Position 0: Before origin - assertEquals(0, trip.getPassengerCountAtPosition(0)); - // Position 1: After origin (passengerDelta=0) - assertEquals(0, trip.getPassengerCountAtPosition(1)); - // Position 2: After stop1 (0 + 1) - assertEquals(1, trip.getPassengerCountAtPosition(2)); - // Position 3: After stop2 (1 + 2) - assertEquals(3, trip.getPassengerCountAtPosition(3)); - // Position 4: After stop3 (3 - 1) - assertEquals(2, trip.getPassengerCountAtPosition(4)); - // Position 5: After stop4 (2 + 1) - assertEquals(3, trip.getPassengerCountAtPosition(5)); - // Position 6: After stop5 (3 - 3) - assertEquals(0, trip.getPassengerCountAtPosition(6)); - // Position 7: After destination (passengerDelta=0) - assertEquals(0, trip.getPassengerCountAtPosition(7)); + void getPassengerCountAtDepartureOfStop_indexTooLarge_throwsException() { + var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + // Trip has 2 stops (Origin, Destination), valid indices are 0 and 1 + assertThrows(IllegalArgumentException.class, () -> trip.getPassengerCountAtDepartureOfStop(2)); } @Test - void getPassengerCountAtPosition_negativePosition_throwsException() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + void adjacentPositions_onlyChecksStopBeforePickup() { + // Original stops: [Origin(1), A(5), Destination(1)] totalCapacity=5 + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(5)), OSLO_NORTH); - assertThrows(IllegalArgumentException.class, () -> trip.getPassengerCountAtPosition(-1)); + // Modified route: [Origin, Pickup, Dropoff, A, Destination] + // 0 1 2 3 4 + // Checked original stops: Origin (index 0, onboard=1). A is NOT checked. + assertTrue(trip.hasCapacityForInsertion(1, 2, 4)); } @Test - void getPassengerCountAtPosition_positionTooLarge_throwsException() { - var stop1 = createStop(0, 1); - var stop2 = createStop(1, 1); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); - - // Trip has: origin (0), stop1 (1), stop2 (2), destination (3) = 4 stops total - // Valid positions are 0 to 4 (0 to stops.size()) - // Position 5 should throw - assertThrows(IllegalArgumentException.class, () -> trip.getPassengerCountAtPosition(5)); - // Position 999 should also throw - assertThrows(IllegalArgumentException.class, () -> trip.getPassengerCountAtPosition(999)); + void adjacentPositions_fullAtStopBeforePickup() { + // Original stops: [Origin(1), A(5), Destination(1)] totalCapacity=5 + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(5)), OSLO_NORTH); + + // Modified route: [Origin, A, Pickup, Dropoff, Destination] + // 0 1 2 3 4 + // Checked original stops: A (index 1, onboard=5). No room. + assertFalse(trip.hasCapacityForInsertion(2, 3, 1)); } @Test - void hasCapacityForInsertion_noPassengers_hasCapacity() { - var trip = createTripWithStops(OSLO_CENTER, List.of(), OSLO_NORTH); + void widerGap_checksAllOriginalStopsBetweenPickupAndDropoff() { + // Original stops: [Origin(1), A(2), B(4), C(1), Destination(1)] totalCapacity=5 + var trip = createTripWithStops( + OSLO_CENTER, + List.of(createStop(2), createStop(4), createStop(1)), + OSLO_NORTH + ); - assertTrue(trip.hasCapacityForInsertion(1, 2, 1)); - // Can fit all 4 seats - assertTrue(trip.hasCapacityForInsertion(1, 2, 4)); + // Modified route: [Origin, Pickup, A, B, Dropoff, C, Destination] + // 0 1 2 3 4 5 6 + // Checked original stops: Origin(1), A(2), B(4). Max is 4, room for 1. + assertTrue(trip.hasCapacityForInsertion(1, 4, 1)); + assertFalse(trip.hasCapacityForInsertion(1, 4, 2)); } @Test - void hasCapacityForInsertion_fullCapacity_noCapacity() { - // Fill all 4 seats - var stop1 = createStop(0, 4); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); - - // No room for additional passenger after stop 1 - assertFalse(trip.hasCapacityForInsertion(2, 3, 1)); + void stopAfterDropoff_isNotChecked() { + // Original stops: [Origin(1), A(1), B(5), Destination(1)] totalCapacity=5 + // B is full, but the passenger is dropped off before B. + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(1), createStop(5)), OSLO_NORTH); + + // Modified route: [Origin, Pickup, A, Dropoff, B, Destination] + // 0 1 2 3 4 5 + // Checked original stops: Origin(1), A(1). B is after the dropoff. + assertTrue(trip.hasCapacityForInsertion(1, 3, 3)); } @Test - void hasCapacityForInsertion_partialCapacity_hasCapacityForOne() { - // 3 of 4 seats taken - var stop1 = createStop(0, 3); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); + void pickupNearEnd_limitedByStopBeforePickup() { + // Original stops: [Origin(1), A(4), Destination(1)] totalCapacity=5 + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(4)), OSLO_NORTH); - // Room for 1 + // Modified route: [Origin, A, Pickup, Dropoff, Destination] + // 0 1 2 3 4 + // Checked original stops: A (index 1, onboard=4). Room for 1. assertTrue(trip.hasCapacityForInsertion(2, 3, 1)); - // No room for 2 assertFalse(trip.hasCapacityForInsertion(2, 3, 2)); } @Test - void hasCapacityForInsertion_acrossMultiplePositions_checksAll() { - var stop1 = createStop(0, 2); - // Total 3 passengers at position 3 - var stop2 = createStop(1, 1); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); - - // Trip positions: 0 (before origin), 1 (after origin=0), 2 (after stop1=2), 3 (after stop2=3), 4 (after dest=0) - // Range 2-4 includes position 3 with 3 passengers, so only 1 seat available - assertTrue(trip.hasCapacityForInsertion(2, 4, 1)); - assertFalse(trip.hasCapacityForInsertion(2, 4, 2)); + void fullRangeInsertion_checksAllOriginalStops() { + // Original stops: [Origin(2), A(2), Destination(2)] totalCapacity=5 + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(2)), OSLO_NORTH); + + // Modified route: [Origin, Pickup, A, Dropoff, Destination] + // 0 1 2 3 4 + // Checked original stops: Origin(2), A(2). Both onboard=2, room for 3. + assertTrue(trip.hasCapacityForInsertion(1, 3, 3)); + assertFalse(trip.hasCapacityForInsertion(1, 3, 4)); } @Test - void hasCapacityForInsertion_rangeBeforeStop_usesInitialCapacity() { - // Fill capacity at position 1 - var stop1 = createStop(0, 4); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); - - // Pickup at position 1, dropoff at position 1 - only checks capacity at boarding (position 0) - // At boarding there are no passengers yet, so we have full capacity - assertTrue(trip.hasCapacityForInsertion(1, 1, 4)); + void fullStopBeforePickup_notInCheckedRange() { + // Original stops: [Origin(1), A(5), Destination(1)] totalCapacity=5 + // A is full, but pickup and dropoff are inserted after A. + var trip = createTripWithStops(OSLO_CENTER, List.of(createStop(5)), OSLO_NORTH); + + // Modified route: [Origin, A, Destination, Pickup, Dropoff] + // 0 1 2 3 4 + // firstOriginalStop = 3 - 1 = 2, lastOriginalStop = 4 - 2 = 2 + // Checked original stop: Destination (index 2, onboard=1). A is outside the range. + assertTrue(trip.hasCapacityForInsertion(3, 4, 4)); + assertFalse(trip.hasCapacityForInsertion(3, 4, 5)); } @Test - void hasCapacityForInsertion_capacityFreesUpInRange_checksMaxInRange() { - // 3 passengers - var stop1 = createStop(0, 3); - // 2 dropoff, leaving 1 - var stop2 = createStop(1, -2); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); - - // Range includes both positions - max passengers is 3 (at position 1) - // 4 total - 3 max = 1 available - assertTrue(trip.hasCapacityForInsertion(1, 3, 1)); - // Not enough - assertFalse(trip.hasCapacityForInsertion(1, 3, 2)); + void bottleneckInMiddle_limitsCapacity() { + // Original stops: [Origin(1), A(1), B(4), C(1), D(1), Destination(1)] totalCapacity=5 + var trip = createTripWithStops( + OSLO_CENTER, + List.of(createStop(1), createStop(4), createStop(1), createStop(1)), + OSLO_NORTH + ); + + // Modified route: [Origin, Pickup, A, B, C, Dropoff, D, Destination] + // 0 1 2 3 4 5 6 7 + // Checked original stops: Origin(1), A(1), B(4), C(1). Max is 4 at B, room for 1. + assertTrue(trip.hasCapacityForInsertion(1, 5, 1)); + assertFalse(trip.hasCapacityForInsertion(1, 5, 2)); } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java index e4770c3b231..d5d3ada4d17 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidateTest.java @@ -1,114 +1,42 @@ package org.opentripplanner.ext.carpooling.routing; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.ext.carpooling.CarpoolGraphPathBuilder.createGraphPath; import static org.opentripplanner.ext.carpooling.CarpoolGraphPathBuilder.createGraphPaths; import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_CENTER; import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_NORTH; import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createSimpleTrip; -import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createTripWithDeviationBudget; import java.time.Duration; +import java.util.List; import org.junit.jupiter.api.Test; +import org.opentripplanner.ext.carpooling.util.GraphPathUtils; class InsertionCandidateTest { - @Test - void additionalDuration_calculatesCorrectly() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - // 3 segments - var segments = createGraphPaths(3); - - var candidate = new InsertionCandidate( - trip, - 1, - 2, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), - null - ); - - assertEquals(Duration.ofMinutes(5), candidate.additionalDuration()); - } + private static final Duration STOP_DURATION = Duration.ofMinutes(2); @Test - void additionalDuration_zeroAdditional_returnsZero() { + void totalTripDuration_calculatesFromSegments() { + // Simple trip origin → destination, with passenger pickup and dropoff inserted: + // origin → pickup (5 min) → dropoff (10 min) → destination (8 min) var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - var segments = createGraphPaths(2); + var originToPickup = createGraphPath(Duration.ofMinutes(5)); + var pickupToDropoff = createGraphPath(Duration.ofMinutes(10)); + var dropoffToDestination = createGraphPath(Duration.ofMinutes(8)); var candidate = new InsertionCandidate( trip, 1, 2, - segments, - Duration.ofMinutes(10), - // Same as baseline - Duration.ofMinutes(10), + List.of(originToPickup, pickupToDropoff, dropoffToDestination), + STOP_DURATION, null ); - assertEquals(Duration.ZERO, candidate.additionalDuration()); - } - - @Test - void isWithinDeviationBudget_withinBudget_returnsTrue() { - var trip = createTripWithDeviationBudget(Duration.ofMinutes(10), OSLO_CENTER, OSLO_NORTH); - var segments = createGraphPaths(2); - - var candidate = new InsertionCandidate( - trip, - 1, - 2, - segments, - // baseline - Duration.ofMinutes(10), - // total (8 min additional, within 10 min budget) - Duration.ofMinutes(18), - null - ); - - assertTrue(candidate.isWithinDeviationBudget()); - } - - @Test - void isWithinDeviationBudget_exceedsBudget_returnsFalse() { - var trip = createTripWithDeviationBudget(Duration.ofMinutes(5), OSLO_CENTER, OSLO_NORTH); - var segments = createGraphPaths(2); - - var candidate = new InsertionCandidate( - trip, - 1, - 2, - segments, - // baseline - Duration.ofMinutes(10), - // total (10 min additional, exceeds 5 min budget) - Duration.ofMinutes(20), - null - ); - - assertFalse(candidate.isWithinDeviationBudget()); - } - - @Test - void isWithinDeviationBudget_exactlyAtBudget_returnsTrue() { - var trip = createTripWithDeviationBudget(Duration.ofMinutes(5), OSLO_CENTER, OSLO_NORTH); - var segments = createGraphPaths(2); - - var candidate = new InsertionCandidate( - trip, - 1, - 2, - segments, - Duration.ofMinutes(10), - // Exactly 5 min additional - Duration.ofMinutes(15), - null - ); - - assertTrue(candidate.isWithinDeviationBudget()); + // 5 + 2 (stop) + 10 + 2 (stop) + 8 = 27 minutes + assertEquals(Duration.ofMinutes(27), candidate.totalTripDuration()); } @Test @@ -116,18 +44,9 @@ void getPickupSegments_returnsCorrectRange() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); var segments = createGraphPaths(5); - var candidate = new InsertionCandidate( - trip, - 2, - 4, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), - null - ); + var candidate = new InsertionCandidate(trip, 2, 4, segments, STOP_DURATION, null); var pickupSegments = candidate.getPickupSegments(); - // Segments 0-1 (before position 2) assertEquals(2, pickupSegments.size()); assertEquals(segments.subList(0, 2), pickupSegments); } @@ -137,15 +56,7 @@ void getPickupSegments_positionZero_returnsEmpty() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); var segments = createGraphPaths(3); - var candidate = new InsertionCandidate( - trip, - 0, - 2, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), - null - ); + var candidate = new InsertionCandidate(trip, 0, 2, segments, STOP_DURATION, null); var pickupSegments = candidate.getPickupSegments(); assertTrue(pickupSegments.isEmpty()); @@ -156,18 +67,9 @@ void getSharedSegments_returnsCorrectRange() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); var segments = createGraphPaths(5); - var candidate = new InsertionCandidate( - trip, - 1, - 3, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), - null - ); + var candidate = new InsertionCandidate(trip, 1, 3, segments, STOP_DURATION, null); var sharedSegments = candidate.getSharedSegments(); - // Segments 1-2 (positions 1 to 3) assertEquals(2, sharedSegments.size()); assertEquals(segments.subList(1, 3), sharedSegments); } @@ -177,15 +79,7 @@ void getSharedSegments_adjacentPositions_returnsSingleSegment() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); var segments = createGraphPaths(3); - var candidate = new InsertionCandidate( - trip, - 1, - 2, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), - null - ); + var candidate = new InsertionCandidate(trip, 1, 2, segments, STOP_DURATION, null); var sharedSegments = candidate.getSharedSegments(); assertEquals(1, sharedSegments.size()); @@ -196,18 +90,9 @@ void getDropoffSegments_returnsCorrectRange() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); var segments = createGraphPaths(5); - var candidate = new InsertionCandidate( - trip, - 1, - 3, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), - null - ); + var candidate = new InsertionCandidate(trip, 1, 3, segments, STOP_DURATION, null); var dropoffSegments = candidate.getDropoffSegments(); - // Segments 3-4 (after position 3) assertEquals(2, dropoffSegments.size()); assertEquals(segments.subList(3, 5), dropoffSegments); } @@ -217,15 +102,7 @@ void getDropoffSegments_atEnd_returnsEmpty() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); var segments = createGraphPaths(3); - var candidate = new InsertionCandidate( - trip, - 1, - 3, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), - null - ); + var candidate = new InsertionCandidate(trip, 1, 3, segments, STOP_DURATION, null); var dropoffSegments = candidate.getDropoffSegments(); assertTrue(dropoffSegments.isEmpty()); @@ -236,21 +113,128 @@ void toString_includesKeyInformation() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); var segments = createGraphPaths(3); + var candidate = new InsertionCandidate(trip, 1, 2, segments, STOP_DURATION, null); + + var str = candidate.toString(); + assertTrue(str.contains("pickup@1")); + assertTrue(str.contains("dropoff@2")); + assertTrue(str.contains("duration=")); + assertTrue(str.contains("segments=3")); + } + + /** + * No pickup segments → durationUntilPickup is zero and no boarding dwell is added to the ride. + * Single shared segment → passengerRideDuration is just the segment duration. + */ + @Test + void durations_noPickupSegments_singleSharedSegment() { + var stopDuration = Duration.ofMinutes(2); + var sharedPath = createGraphPath(Duration.ofMinutes(10)); + var sharedDuration = GraphPathUtils.calculateDuration(sharedPath); + + var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + var candidate = new InsertionCandidate(trip, 0, 1, List.of(sharedPath), stopDuration, null); + + assertEquals(Duration.ofMinutes(10), sharedDuration); + assertEquals(Duration.ZERO, candidate.getDurationUntilPickupArrival()); + assertEquals(sharedDuration, candidate.getPassengerRideDuration()); + } + + /** + * Single pickup segment → durationUntilPickup = segment duration (boarding excluded). + * Single shared segment → passengerRideDuration = boarding time + segment duration. + */ + @Test + void durations_onePickupSegment_singleSharedSegment() { + var stopDuration = Duration.ofMinutes(3); + var pickupPath = createGraphPath(Duration.ofMinutes(8)); + var sharedPath = createGraphPath(Duration.ofMinutes(15)); + + var pickupDuration = GraphPathUtils.calculateDuration(pickupPath); + var sharedDuration = GraphPathUtils.calculateDuration(sharedPath); + + var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); var candidate = new InsertionCandidate( trip, 1, 2, - segments, - Duration.ofMinutes(10), - Duration.ofMinutes(15), + List.of(pickupPath, sharedPath), + stopDuration, null ); - var str = candidate.toString(); - assertTrue(str.contains("pickup@1")); - assertTrue(str.contains("dropoff@2")); - // 5 min = 300s additional - assertTrue(str.contains("300s")); - assertTrue(str.contains("segments=3")); + assertEquals(pickupDuration, candidate.getDurationUntilPickupArrival()); + assertEquals(stopDuration.plus(sharedDuration), candidate.getPassengerRideDuration()); + } + + /** + * Two pickup segments → travel + 1 intermediate stop, no boarding dwell. + * Two shared segments → boarding dwell + travel + 1 intermediate stop. + */ + @Test + void durations_multiplePickupAndSharedSegments() { + var stopDuration = Duration.ofMinutes(2); + var pickup0 = createGraphPath(Duration.ofMinutes(5)); + var pickup1 = createGraphPath(Duration.ofMinutes(7)); + var shared0 = createGraphPath(Duration.ofMinutes(10)); + var shared1 = createGraphPath(Duration.ofMinutes(12)); + + var pickup0Duration = GraphPathUtils.calculateDuration(pickup0); + var pickup1Duration = GraphPathUtils.calculateDuration(pickup1); + var shared0Duration = GraphPathUtils.calculateDuration(shared0); + var shared1Duration = GraphPathUtils.calculateDuration(shared1); + + var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + var candidate = new InsertionCandidate( + trip, + 2, + 4, + List.of(pickup0, pickup1, shared0, shared1), + stopDuration, + null + ); + + // 2 pickup segments: travel + 1 intermediate stop (boarding now belongs to the ride) + var expectedPickup = pickup0Duration.plus(stopDuration).plus(pickup1Duration); + assertEquals(expectedPickup, candidate.getDurationUntilPickupArrival()); + + // 2 shared segments: boarding dwell + travel + 1 intermediate stop delay + var expectedRide = stopDuration.plus(shared0Duration).plus(stopDuration).plus(shared1Duration); + assertEquals(expectedRide, candidate.getPassengerRideDuration()); + } + + /** + * Larger stop duration scales the durations proportionally. + */ + @Test + void durations_scaleWithStopDuration() { + var shared0 = createGraphPath(Duration.ofMinutes(10)); + var shared1 = createGraphPath(Duration.ofMinutes(10)); + + var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + + var candidateSmall = new InsertionCandidate( + trip, + 0, + 2, + List.of(shared0, shared1), + Duration.ofMinutes(1), + null + ); + var candidateLarge = new InsertionCandidate( + trip, + 0, + 2, + List.of(shared0, shared1), + Duration.ofMinutes(5), + null + ); + + // Pickup at origin (no pickup segments) → no boarding dwell, so only the 1 intermediate + // stop between the 2 shared segments scales: 1x stopDuration difference. + var difference = candidateLarge + .getPassengerRideDuration() + .minus(candidateSmall.getPassengerRideDuration()); + assertEquals(Duration.ofMinutes(4), difference); } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java index badc0b555bb..b4e593bb316 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluatorTest.java @@ -27,7 +27,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opentripplanner.astar.model.GraphPath; -import org.opentripplanner.ext.carpooling.constraints.PassengerDelayConstraints; import org.opentripplanner.ext.carpooling.model.CarpoolTrip; import org.opentripplanner.ext.carpooling.util.BeelineEstimator; import org.opentripplanner.ext.carpooling.util.StreetVertexUtils; @@ -42,7 +41,6 @@ class InsertionEvaluatorTest { - private PassengerDelayConstraints delayConstraints; private InsertionPositionFinder positionFinder; private LinkingContext linkingContext; private StreetVertexUtils streetVertexUtils; @@ -50,8 +48,7 @@ class InsertionEvaluatorTest { @BeforeEach void setup() { - delayConstraints = new PassengerDelayConstraints(); - positionFinder = new InsertionPositionFinder(delayConstraints, new BeelineEstimator()); + positionFinder = new InsertionPositionFinder(new BeelineEstimator()); vertexMap = new HashMap<>(); Map> locationVertices = new HashMap<>(); @@ -110,7 +107,8 @@ private InsertionCandidate findOptimalInsertion( List viablePositions = positionFinder.findViablePositions( trip, passengerPickup, - passengerDropoff + passengerDropoff, + Duration.ZERO ); if (viablePositions.isEmpty()) { @@ -118,10 +116,10 @@ private InsertionCandidate findOptimalInsertion( } var evaluator = new InsertionEvaluator( - delayConstraints, linkingContext, streetVertexUtils, - carpoolRouter + carpoolRouter, + Duration.ZERO ); return evaluator.findBestInsertion( tripWithVertices, @@ -140,10 +138,15 @@ private WgsCoordinate getCoordinateBetween(WgsCoordinate coordinate1, WgsCoordin @Test void findOptimalInsertion_onDeviationBudgetExceeded_returnsNull() { + var deviationBudget = Duration.ofMinutes(5); var trip = createTripWithStops( OSLO_SOUTH, - List.of(createStopAt(0, OSLO_CENTER), createStopAt(0, OSLO_NORTHEAST)), - OSLO_NORTH + List.of( + createStopAt(OSLO_CENTER, deviationBudget), + createStopAt(OSLO_NORTHEAST, deviationBudget) + ), + OSLO_NORTH, + deviationBudget ); var mockPath = createGraphPath(Duration.ofMinutes(4)); @@ -161,7 +164,7 @@ void findOptimalInsertion_onDeviationBudgetExceeded_returnsNull() { @Test void findOptimalInsertion_noValidPositions_returnsNull() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + var trip = createTripWithDeviationBudget(Duration.ofMinutes(20), OSLO_CENTER, OSLO_NORTH); // Routing function returns null (simulating routing failure) // This causes evaluator to skip all positions CarpoolRouter routingFunction = (from, to) -> null; @@ -173,7 +176,7 @@ void findOptimalInsertion_noValidPositions_returnsNull() { @Test void findOptimalInsertion_oneValidPosition_returnsCandidate() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + var trip = createTripWithDeviationBudget(Duration.ofMinutes(20), OSLO_CENTER, OSLO_NORTH); var mockPath = createGraphPath(); @@ -189,7 +192,7 @@ void findOptimalInsertion_oneValidPosition_returnsCandidate() { @Test void findOptimalInsertion_routingFails_skipsPosition() { // Use a trip with one stop to have multiple viable insertion positions - var stop1 = createStopAt(0, OSLO_EAST); + var stop1 = createStopAt(OSLO_EAST); var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); var mockPath = createGraphPath(Duration.ofMinutes(3)); @@ -241,8 +244,8 @@ void findOptimalInsertion_exceedsDeviationBudget_returnsNull() { @Test void findOptimalInsertion_tripWithStops_evaluatesAllPositions() { - var stop1 = createStopAt(0, OSLO_EAST); - var stop2 = createStopAt(1, OSLO_WEST); + var stop1 = createStopAt(OSLO_EAST); + var stop2 = createStopAt(OSLO_WEST); var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); var mockPath = createGraphPath(); @@ -256,7 +259,7 @@ void findOptimalInsertion_tripWithStops_evaluatesAllPositions() { @Test void findOptimalInsertion_baselineDurationCalculationFails_returnsNull() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + var trip = createTripWithDeviationBudget(Duration.ofMinutes(20), OSLO_CENTER, OSLO_NORTH); CarpoolRouter routingFunction = (from, to) -> null; @@ -265,20 +268,44 @@ void findOptimalInsertion_baselineDurationCalculationFails_returnsNull() { assertNull(result); } + /** + * Given two viable insertion positions with different total trip durations, + * the evaluator should select the one with the shorter total. + * + * Trip: SOUTH → CENTER → NORTH (baseline: 10 + 10 = 20 min) + * Passenger: pickup at EAST, dropoff at WEST + * + * Position (1,2) modified route: SOUTH → EAST → WEST → CENTER → NORTH + * segments: 8 + 4 + 9 + 10(reused) = 31 min + * + * Position (2,3) modified route: SOUTH → CENTER → EAST → WEST → NORTH + * segments: 10(reused) + 3 + 4 + 5 = 22 min ← shorter, should be selected + */ @Test - void findOptimalInsertion_selectsMinimumAdditionalDuration() { - var trip = createTripWithDeviationBudget(Duration.ofMinutes(20), OSLO_CENTER, OSLO_NORTH); + void findBestInsertion_selectsShorterTotalTripDuration() { + var stop = createStopAt(OSLO_CENTER, Duration.ofMinutes(30)); + var trip = createTripWithStops(OSLO_SOUTH, List.of(stop), OSLO_NORTH, Duration.ofMinutes(30)); + var tripWithVertices = createTripWithVertices(trip); final Map, GraphPath> pathsMap = new HashMap<>( Map.of( + // Baseline segments + new Pair<>(OSLO_SOUTH, OSLO_CENTER), + createGraphPath(Duration.ofMinutes(10)), new Pair<>(OSLO_CENTER, OSLO_NORTH), createGraphPath(Duration.ofMinutes(10)), - new Pair<>(OSLO_CENTER, OSLO_EAST), - createGraphPath(Duration.ofMinutes(4)), + // Position (1,2) new segments + new Pair<>(OSLO_SOUTH, OSLO_EAST), + createGraphPath(Duration.ofMinutes(8)), new Pair<>(OSLO_EAST, OSLO_WEST), - createGraphPath(Duration.ofMinutes(5)), + createGraphPath(Duration.ofMinutes(4)), + new Pair<>(OSLO_WEST, OSLO_CENTER), + createGraphPath(Duration.ofMinutes(9)), + // Position (2,3) new segments + new Pair<>(OSLO_CENTER, OSLO_EAST), + createGraphPath(Duration.ofMinutes(3)), new Pair<>(OSLO_WEST, OSLO_NORTH), - createGraphPath(Duration.ofMinutes(6)) + createGraphPath(Duration.ofMinutes(5)) ) ); @@ -286,18 +313,30 @@ void findOptimalInsertion_selectsMinimumAdditionalDuration() { CarpoolRouter routingFunction = (from, to) -> pathsMap.get(new Pair<>(getCoordinate(from), getCoordinate(to))); - var result = findOptimalInsertion(trip, OSLO_EAST, OSLO_WEST, routingFunction); + var viablePositions = List.of(new InsertionPosition(1, 2), new InsertionPosition(2, 3)); + + var evaluator = new InsertionEvaluator( + linkingContext, + streetVertexUtils, + routingFunction, + Duration.ZERO + ); + var result = evaluator.findBestInsertion( + tripWithVertices, + viablePositions, + OSLO_EAST, + OSLO_WEST + ); assertNotNull(result); - // Should have selected one of the evaluated insertions - // The exact additional duration depends on which position was evaluated first - assertTrue(result.additionalDuration().compareTo(Duration.ofMinutes(20)) <= 0); - assertTrue(result.additionalDuration().compareTo(Duration.ZERO) > 0); + assertEquals(2, result.pickupPosition()); + assertEquals(3, result.dropoffPosition()); + assertEquals(Duration.ofMinutes(22), result.totalTripDuration()); } @Test void findOptimalInsertion_simpleTrip_hasExpectedStructure() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + var trip = createTripWithDeviationBudget(Duration.ofMinutes(20), OSLO_CENTER, OSLO_NORTH); var mockPath = createGraphPath(); @@ -366,85 +405,25 @@ void findOptimalInsertion_insertBetweenTwoPoints_routesAllSegments() { // Note: With real State objects, exact durations will have minor rounding differences // (typically 1-2 seconds per edge due to millisecond rounding in StreetEdge.doTraverse()) - // The baseline should be approximately 10 minutes (within 10 seconds tolerance) - assertTrue( - Math.abs(result.durationBetweenOriginAndDestination().toSeconds() - 600) < 10, - "Baseline should be approximately 10 min (within 10s), got " + - result.durationBetweenOriginAndDestination() - ); - - // CRITICAL: Total duration should be sum of NEW segments, NOT baseline duration + // CRITICAL: Total driving duration should be sum of NEW segments, NOT baseline duration // Total = 3 + 2 + 4 = 9 minutes (approximately, with rounding) // If bug exists, segment A→C would incorrectly use baseline (10 min) → total would be wrong assertTrue( - Math.abs(result.totalDuration().toSeconds() - 540) < 10, - "Total duration should be approximately 9 min (within 10s), got " + result.totalDuration() - ); - - // Additional duration should be negative (this insertion is actually faster!) - // This is realistic for insertions that "shortcut" part of the baseline route - assertTrue( - result.additionalDuration().isNegative(), - "Additional duration should be negative (insertion is faster), got " + - result.additionalDuration() + Math.abs(result.totalTripDuration().toSeconds() - 540) < 10, + "Total driving duration should be approximately 9 min (within 10s), got " + + result.totalTripDuration() ); // Routing was called at least 4 times (1 baseline + 3 new segments minimum) assertTrue(callCount[0] >= 4, "Should have called routing at least 4 times"); } - @Test - void findOptimalInsertion_insertAtEnd_reusesMostSegments() { - // This test verifies that segment reuse optimization still works correctly - // Scenario: Trip A→B→C, insert passenger that allows some segment reuse - // Expected: Segments that have matching endpoints should be REUSED - - var stop1 = createStopAt(0, OSLO_EAST); - var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTH); - - // Baseline has 2 segments: CENTER→EAST, EAST→NORTH - var mockPath = createGraphPath(Duration.ofMinutes(3)); - - final int[] callCount = { 0 }; - CarpoolRouter carpoolRouter = (from, to) -> { - callCount[0]++; - return mockPath; - }; - - // Insert passenger - the algorithm will find the best position - var result = findOptimalInsertion(trip, OSLO_WEST, OSLO_SOUTH, carpoolRouter); - - assertNotNull(result, "Should find valid insertion"); - - // Duration between start and stop should be calculated correctly - assertTrue( - Duration.ofMinutes(3).minus(result.durationBetweenOriginAndDestination()).toSeconds() < 10, - "Baseline should be approximately 3 min (within 10s), got " + - result.durationBetweenOriginAndDestination() - ); - - // The modified route should have more segments than baseline - assertTrue( - result.routeSegments().size() >= 2, - "Modified route should have at least baseline segments" - ); - - // Additional duration should be positive (adding detour) - assertTrue( - result.additionalDuration().compareTo(Duration.ZERO) > 0, - "Adding passenger should increase duration" - ); - - // Routing was called for baseline and new segments - assertTrue(callCount[0] >= 2, "Should have called routing at least 2 times"); - } - @Test void findOptimalInsertion_pickupAtExistingPoint_handlesCorrectly() { // Scenario: Trip A→B→C, passenger pickup at B (existing point), dropoff at new point // Expected: Segment A→B should be reused, B→dropoff and dropoff→C should be routed - var stop1 = createStopAt(0, OSLO_EAST); + var stop1 = createStopAt(OSLO_EAST); var trip = createTripWithStops(OSLO_CENTER, List.of(stop1), OSLO_NORTHEAST); var mockPath = createGraphPath(Duration.ofMinutes(3)); @@ -473,7 +452,7 @@ void findOptimalInsertion_singleSegmentTrip_routesAllNewSegments() { // Edge case: Simplest possible trip (2 points, 1 segment) // Any insertion will require routing all new segments - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); + var trip = createTripWithDeviationBudget(Duration.ofMinutes(20), OSLO_CENTER, OSLO_NORTH); var mockPath = createGraphPath(Duration.ofMinutes(5)); @@ -491,8 +470,7 @@ void findOptimalInsertion_singleSegmentTrip_routesAllNewSegments() { // Routing was called for baseline and new segments assertTrue(callCount[0] >= 4, "Should have called routing at least 4 times"); - // Total duration should be positive - assertTrue(result.totalDuration().compareTo(Duration.ZERO) > 0); - assertTrue(result.durationBetweenOriginAndDestination().compareTo(Duration.ZERO) > 0); + // Total driving duration should be positive + assertTrue(result.totalTripDuration().compareTo(Duration.ZERO) > 0); } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java index 0ae740b984b..ca1a46a9761 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinderTest.java @@ -19,12 +19,10 @@ import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.opentripplanner.ext.carpooling.constraints.PassengerDelayConstraints; -import org.opentripplanner.ext.carpooling.util.BeelineEstimator; /** * Tests for {@link InsertionPositionFinder}. - * Focuses on heuristic validation: capacity, directional compatibility, and beeline delays. + * Focuses on heuristic validation: capacity and beeline delays. */ class InsertionPositionFinderTest { @@ -39,33 +37,19 @@ void setup() { void findViablePositions_simpleTrip_findsPositions() { var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - var viablePositions = finder.findViablePositions(trip, OSLO_EAST, OSLO_WEST); + // Passenger picked up east of route, dropped off at destination — small compatible detour + var viablePositions = finder.findViablePositions(trip, OSLO_EAST, OSLO_NORTH, Duration.ZERO); assertFalse(viablePositions.isEmpty()); - // Simple trip (2 points) allows insertions at positions (1,2) and (1,3) - assertTrue(viablePositions.size() >= 1); - } - - @Test - void findViablePositions_incompatibleDirection_rejectsPosition() { - var trip = createSimpleTrip(OSLO_CENTER, OSLO_NORTH); - - // Passenger going perpendicular (EAST to WEST when trip is CENTER to NORTH) - // This should result in some positions being rejected by directional checks - var viablePositions = finder.findViablePositions(trip, OSLO_SOUTH, OSLO_CENTER); - - // May not be completely empty, but should have fewer positions than compatible directions - // The directional check filters out positions that cause too much backtracking - assertNotNull(viablePositions); } @Test void findViablePositions_noCapacity_rejectsPosition() { // Create a trip with 0 available seats - var stops = List.of(createOriginStop(OSLO_CENTER), createDestinationStop(OSLO_NORTH, 1)); + var stops = List.of(createOriginStop(OSLO_CENTER), createDestinationStop(OSLO_NORTH)); var trip = createTripWithCapacity(0, stops); - var viablePositions = finder.findViablePositions(trip, OSLO_EAST, OSLO_WEST); + var viablePositions = finder.findViablePositions(trip, OSLO_EAST, OSLO_WEST, Duration.ZERO); // Should reject all positions due to capacity assertTrue(viablePositions.isEmpty()); @@ -73,37 +57,31 @@ void findViablePositions_noCapacity_rejectsPosition() { @Test void findViablePositions_exceedsBeelineDelay_rejectsPosition() { - // Create finder with very restrictive delay constraints - var restrictiveConstraints = new PassengerDelayConstraints(Duration.ofSeconds(1)); - var restrictiveFinder = new InsertionPositionFinder( - restrictiveConstraints, - new BeelineEstimator() + // Create stops with very restrictive deviation budgets (1 second) + var restrictiveBudget = Duration.ofSeconds(1); + var trip = createTripWithStops( + OSLO_CENTER, + List.of(createStopAt(OSLO_EAST, restrictiveBudget)), + OSLO_NORTH, + restrictiveBudget ); - var trip = createTripWithStops(OSLO_CENTER, List.of(createStopAt(0, OSLO_EAST)), OSLO_NORTH); - - // Try to insert passenger that would cause significant detour - // Far from route - // Even farther - var viablePositions = restrictiveFinder.findViablePositions(trip, OSLO_WEST, OSLO_SOUTH); + // Passenger going opposite direction (WEST→SOUTH) with 1s budget — all positions should be rejected + var viablePositions = finder.findViablePositions(trip, OSLO_WEST, OSLO_SOUTH, Duration.ZERO); - // With very restrictive constraints, positions causing significant detours should be rejected - // However, the beeline check only applies if there are existing stops (routePoints.size() > 2) - // With CENTER, EAST, NORTH we have 3 points, so the check should apply - // The result depends on the actual distances and heuristics - assertNotNull(viablePositions); + assertTrue(viablePositions.isEmpty()); } @Test void findViablePositions_multipleStops_checksAllCombinations() { - var stop1 = createStopAt(0, OSLO_EAST); - var stop2 = createStopAt(1, OSLO_WEST); + var stop1 = createStopAt(OSLO_EAST); + var stop2 = createStopAt(OSLO_WEST); var trip = createTripWithStops(OSLO_CENTER, List.of(stop1, stop2), OSLO_NORTH); - var viablePositions = finder.findViablePositions(trip, OSLO_SOUTH, OSLO_NORTH); + var viablePositions = finder.findViablePositions(trip, OSLO_SOUTH, OSLO_NORTH, Duration.ZERO); // Should evaluate multiple pickup/dropoff combinations - // Exact count depends on directional and beeline filtering + // Exact count depends on beeline filtering assertNotNull(viablePositions); } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java index a9fc0d84658..e2aab820ea1 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceAccessEgressTest.java @@ -21,13 +21,12 @@ import org.opentripplanner.ext.carpooling.internal.DefaultCarpoolingRepository; import org.opentripplanner.ext.carpooling.routing.CarpoolAccessEgress; import org.opentripplanner.ext.carpooling.routing.CarpoolTreeStreetRouter; -import org.opentripplanner.graph_builder.module.nearbystops.SiteRepositoryResolver; -import org.opentripplanner.graph_builder.module.nearbystops.StopResolver; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.algorithm.GraphRoutingTest; import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.request.StreetRequest; +import org.opentripplanner.routing.graphfinder.TransitServiceResolver; import org.opentripplanner.routing.linking.LinkingContext; import org.opentripplanner.routing.linking.VertexLinkerTestFactory; import org.opentripplanner.street.geometry.WgsCoordinate; @@ -80,7 +79,7 @@ class DefaultCarpoolingServiceAccessEgressTest extends GraphRoutingTest { private DefaultCarpoolingService service; private CarpoolingRepository repository; - private StopResolver stopResolver; + private TransitServiceResolver transitServiceResolver; private LinkingContext linkingContext; private TransitStopVertex stopT1; @@ -177,9 +176,9 @@ public void build() { Graph graph = model.graph(); var timetableRepository = model.timetableRepository(); - stopResolver = new SiteRepositoryResolver(timetableRepository.getSiteRepository()); VertexLinker vertexLinker = VertexLinkerTestFactory.of(graph); TransitService transitService = new DefaultTransitService(timetableRepository); + transitServiceResolver = new TransitServiceResolver(transitService); repository = new DefaultCarpoolingRepository(); // LinkingContext: P1 -> {iP1}, P2 -> {iP2}, P3 -> {iP3} @@ -256,7 +255,7 @@ void returnsEmptyWhenAccessModeIsNotCarpool() { request, new StreetRequest(StreetMode.WALK), AccessEgressType.ACCESS, - stopResolver, + transitServiceResolver, linkingContext, SEARCH_TIME ); @@ -276,7 +275,7 @@ void returnsEmptyWhenEgressModeIsNotCarpool() { request, new StreetRequest(StreetMode.WALK), AccessEgressType.EGRESS, - stopResolver, + transitServiceResolver, linkingContext, SEARCH_TIME ); @@ -292,7 +291,7 @@ void returnsEmptyWhenNoCarpoolTripsInRepository() { request, new StreetRequest(StreetMode.CARPOOL), AccessEgressType.ACCESS, - stopResolver, + transitServiceResolver, linkingContext, SEARCH_TIME ); @@ -312,7 +311,7 @@ void returnsEmptyWhenTripsFailTimeFilter() { request, new StreetRequest(StreetMode.CARPOOL), AccessEgressType.ACCESS, - stopResolver, + transitServiceResolver, linkingContext, SEARCH_TIME ); @@ -333,7 +332,7 @@ void findsAccessResultsForCompatibleTrip() { request, new StreetRequest(StreetMode.CARPOOL), AccessEgressType.ACCESS, - stopResolver, + transitServiceResolver, linkingContext, SEARCH_TIME ); @@ -348,8 +347,8 @@ void findsAccessResultsForCompatibleTrip() { assertFalse(accessEgress.getSegments().isEmpty(), "Segments should not be empty"); } - int stopT3Index = stopResolver.getRegularStop(stopT3.getId()).getIndex(); - int stopT4Index = stopResolver.getRegularStop(stopT4.getId()).getIndex(); + int stopT3Index = transitServiceResolver.getStop(stopT3.getId()).getIndex(); + int stopT4Index = transitServiceResolver.getStop(stopT4.getId()).getIndex(); assertTrue( results.stream().anyMatch(r -> r.stop() == stopT3Index), "At least one access result should include stopT3" @@ -373,7 +372,7 @@ void findsEgressResultsForCompatibleTrip() { request, new StreetRequest(StreetMode.CARPOOL), AccessEgressType.EGRESS, - stopResolver, + transitServiceResolver, linkingContext, SEARCH_TIME ); @@ -385,8 +384,8 @@ void findsEgressResultsForCompatibleTrip() { assertNotNull(accessEgress.getSegments(), "Segments should not be null"); } - int stopT1Index = stopResolver.getRegularStop(stopT1.getId()).getIndex(); - int stopT2Index = stopResolver.getRegularStop(stopT2.getId()).getIndex(); + int stopT1Index = transitServiceResolver.getStop(stopT1.getId()).getIndex(); + int stopT2Index = transitServiceResolver.getStop(stopT2.getId()).getIndex(); assertTrue( results.stream().anyMatch(r -> r.stop() == stopT1Index), "At least one egress result should include stopT1" @@ -410,7 +409,7 @@ void accessResultsHaveMatchingArrivalDepartureAndDuration() { request, new StreetRequest(StreetMode.CARPOOL), AccessEgressType.ACCESS, - stopResolver, + transitServiceResolver, linkingContext, transitSearchTimeZero ); @@ -436,8 +435,8 @@ void accessResultsHaveMatchingArrivalDepartureAndDuration() { ); } - int stopT3Index = stopResolver.getRegularStop(stopT3.getId()).getIndex(); - int stopT4Index = stopResolver.getRegularStop(stopT4.getId()).getIndex(); + int stopT3Index = transitServiceResolver.getStop(stopT3.getId()).getIndex(); + int stopT4Index = transitServiceResolver.getStop(stopT4.getId()).getIndex(); assertTrue( results.stream().anyMatch(r -> r.stop() == stopT3Index), "At least one access result should include stopT3" @@ -465,7 +464,7 @@ void twoTripsReturnTwoResultsPerStop() { request, new StreetRequest(StreetMode.CARPOOL), AccessEgressType.ACCESS, - stopResolver, + transitServiceResolver, linkingContext, SEARCH_TIME ); @@ -482,8 +481,8 @@ void twoTripsReturnTwoResultsPerStop() { ); } - int stopT3Index = stopResolver.getRegularStop(stopT3.getId()).getIndex(); - int stopT4Index = stopResolver.getRegularStop(stopT4.getId()).getIndex(); + int stopT3Index = transitServiceResolver.getStop(stopT3.getId()).getIndex(); + int stopT4Index = transitServiceResolver.getStop(stopT4.getId()).getIndex(); assertTrue( results.stream().anyMatch(r -> r.stop() == stopT3Index), "At least one access result should include stopT3" @@ -502,11 +501,10 @@ void tripWithIntermediateStopsProducesResults() { 4, List.of( CarpoolTripTestData.createOriginStopWithTime(coordA, departureTime, departureTime), - CarpoolTripTestData.createStopAt(1, coordB), - CarpoolTripTestData.createStopAt(2, coordC), + CarpoolTripTestData.createStopAt(coordB), + CarpoolTripTestData.createStopAt(coordC), CarpoolTripTestData.createDestinationStopWithTime( coordD, - 3, departureTime.plusHours(1), departureTime.plusHours(1) ) @@ -521,15 +519,15 @@ void tripWithIntermediateStopsProducesResults() { request, new StreetRequest(StreetMode.CARPOOL), AccessEgressType.ACCESS, - stopResolver, + transitServiceResolver, linkingContext, SEARCH_TIME ); assertFalse(results.isEmpty(), "Trip with intermediate stops should produce results"); - int stopT3Index = stopResolver.getRegularStop(stopT3.getId()).getIndex(); - int stopT4Index = stopResolver.getRegularStop(stopT4.getId()).getIndex(); + int stopT3Index = transitServiceResolver.getStop(stopT3.getId()).getIndex(); + int stopT4Index = transitServiceResolver.getStop(stopT4.getId()).getIndex(); assertTrue( results.stream().anyMatch(r -> r.stop() == stopT3Index), "At least one access result should include stopT3" @@ -552,7 +550,7 @@ void earliestDepartureTimeRespectsRequestedDepartureTime() { request, new StreetRequest(StreetMode.CARPOOL), AccessEgressType.ACCESS, - stopResolver, + transitServiceResolver, linkingContext, SEARCH_TIME ); @@ -570,8 +568,8 @@ void earliestDepartureTimeRespectsRequestedDepartureTime() { ); } - int stopT3Index = stopResolver.getRegularStop(stopT3.getId()).getIndex(); - int stopT4Index = stopResolver.getRegularStop(stopT4.getId()).getIndex(); + int stopT3Index = transitServiceResolver.getStop(stopT3.getId()).getIndex(); + int stopT4Index = transitServiceResolver.getStop(stopT4.getId()).getIndex(); assertTrue( results.stream().anyMatch(r -> r.stop() == stopT3Index), "At least one access result should include stopT3" @@ -632,20 +630,24 @@ void accessDepartureAndArrivalTimesMatchIndependentRouting() { request, new StreetRequest(StreetMode.CARPOOL), AccessEgressType.ACCESS, - stopResolver, + transitServiceResolver, linkingContext, transitSearchTimeZero ); assertFalse(results.isEmpty(), "Should find access results"); + // Departure time of the passenger is when the car arrives at the pickup (P2). The + // boarding dwell at P2 is part of the CarpoolAccessEgress duration, not added before + // the departure time. + var pickupTime = RouteRequest.defaultValue().preferences().car().pickupTime(); var expectedDeparture = (int) Duration.between( transitSearchTimeZero.toInstant(), departureTime.plus(drivingDurationAToP2).toInstant() ).getSeconds(); - int stopT3Index = stopResolver.getRegularStop(stopT3.getId()).getIndex(); - int stopT4Index = stopResolver.getRegularStop(stopT4.getId()).getIndex(); + int stopT3Index = transitServiceResolver.getStop(stopT3.getId()).getIndex(); + int stopT4Index = transitServiceResolver.getStop(stopT4.getId()).getIndex(); var targetStopIndices = Set.of(stopT3Index, stopT4Index); Map expectedDrivingP2ToStop = Map.of( @@ -681,15 +683,15 @@ void accessDepartureAndArrivalTimesMatchIndependentRouting() { ); var drivingP2ToStop = expectedDrivingP2ToStop.get(accessEgress.stop()); - // The service adds CARPOOL_STOP_DURATION (1 min) for the passenger pickup at P2 + // Boarding dwell at P2 is now part of the passenger's ride duration, so the arrival + // time is the departure (arrival at P2) plus boarding plus driving from P2 to the stop. var expectedArrival = - expectedDeparture + - (int) drivingP2ToStop.getSeconds() + - (int) DefaultCarpoolingService.CARPOOL_STOP_DURATION.getSeconds(); + expectedDeparture + (int) pickupTime.getSeconds() + (int) drivingP2ToStop.getSeconds(); assertEquals( expectedArrival, accessEgress.getArrivalTimeOfPassenger(), - "Arrival time should equal passenger departure plus driving time from P2 to stop plus pickup duration" + "Arrival time should equal passenger departure plus boarding dwell plus driving " + + "time from P2 to stop" ); } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java index 226b0662c85..1dfd2482fbb 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingServiceDirectTest.java @@ -42,17 +42,27 @@ * These tests use a real street graph to verify the full direct routing pipeline * including filtering, position finding, insertion evaluation, and itinerary mapping. *

- * Graph layout (going east, ~2km total): + * Graph layout (main road going east, P and Q sit south of the road): *

- *   A ---- B ----------- C ---- D
- *   0m   500m          1500m  2000m
- *    \                         /
- *     P ------(shortcut)------Q
+ *           500m         1000m         500m
+ *      A ---------- B ----------- C ---------- D
+ *      |\          /               \          /
+ *      | \        / 255         255 \        / 255
+ *      |  \      /                   \      /
+ *      |   P ---------- 1400 ---------- Q
+ *      |            (P-Q shortcut)    /
+ *      +-------------- 1500 ----------+
+ *               (direct A-Q bypass)
  *
- *   A = tripStart, D = tripEnd (graph intersections)
- *   P-Q direct shortcut edge exists (bypasses B and C)
- *   P = passenger pickup (south, between A and B, linked via LinkingContext)
- *   Q = passenger dropoff (south, between C and D, linked via LinkingContext)
+ *   A = tripStart, D = tripEnd
+ *   P = passenger pickup, connected to both A and B (255m each)
+ *   Q = passenger dropoff, connected to both C and D (255m each)
+ *   P-Q direct shortcut (1400m) beats the main-road P-B-C-Q path (1510m) by 110m,
+ *   so the carpool's shared segment (pickup -> dropoff) routes over it.
+ *   A-Q direct bypass (1500m) is shorter than A-P-Q (255 + 1400 = 1655m), so the
+ *   shortest A->Q path skips the pickup. The carpool itself is still forced to drive
+ *   A->P->Q to pick up the passenger; this edge exists so the test cannot rely on
+ *   "route tripStart to dropoff directly" as a proxy for what the carpool drives.
  * 
*/ class DefaultCarpoolingServiceDirectTest extends GraphRoutingTest { @@ -123,7 +133,8 @@ public void build() { biStreet(B, P, 255); biStreet(C, Q, 255); biStreet(D, Q, 255); - biStreet(P, Q, 1500); + biStreet(P, Q, 1400); + biStreet(A, Q, 1500); biStreet(Q, F, (int) DistanceBasedFilter.DEFAULT_MAX_DISTANCE_METERS + 10000); } } @@ -270,26 +281,6 @@ void returnsEmptyWhenDropoffExceedsMaxDistance() { ); } - @Test - void returnsAtMostMaxDirectResults() { - int maxResults = DefaultCarpoolingService.DEFAULT_MAX_CARPOOL_DIRECT_RESULTS; - for (int i = 0; i < maxResults + 2; i++) { - var departureTime = SEARCH_TIME.plusMinutes(5 + i * 5); - var trip = CarpoolTripTestData.createSimpleTripWithTime(tripStart, tripEnd, departureTime); - repository.upsertCarpoolTrip(trip); - } - - var request = buildDirectCarpoolRequest(passengerPickup, passengerDropoff, SEARCH_TIME); - - var results = service.routeDirect(request, linkingContext); - - assertEquals( - maxResults, - results.size(), - "Should return exactly DEFAULT_MAX_CARPOOL_DIRECT_RESULTS results" - ); - } - @Test void twoTripsReturnTwoResults() { var departureTime1 = SEARCH_TIME.plusMinutes(10); @@ -316,11 +307,10 @@ void tripWithIntermediateStopsProducesResults() { 4, List.of( CarpoolTripTestData.createOriginStopWithTime(tripStart, departureTime, departureTime), - CarpoolTripTestData.createStopAt(1, coordB), - CarpoolTripTestData.createStopAt(2, coordC), + CarpoolTripTestData.createStopAt(coordB), + CarpoolTripTestData.createStopAt(coordC), CarpoolTripTestData.createDestinationStopWithTime( tripEnd, - 3, departureTime.plusHours(1), departureTime.plusHours(1) ) @@ -348,11 +338,10 @@ void routeFollowsIntermediateStopsInsteadOfDirectPath() { 4, List.of( CarpoolTripTestData.createOriginStopWithTime(tripStart, departureTime, departureTime), - CarpoolTripTestData.createStopAt(1, coordB), - CarpoolTripTestData.createStopAt(2, coordC), + CarpoolTripTestData.createStopAt(coordB), + CarpoolTripTestData.createStopAt(coordC), CarpoolTripTestData.createDestinationStopWithTime( tripEnd, - 3, departureTime.plusHours(1), departureTime.plusHours(1) ) @@ -401,15 +390,84 @@ void routeFollowsIntermediateStopsInsteadOfDirectPath() { ); } + @Test + void itineraryReflectsDriverScheduleWhenTripDepartsBeforeRequestTime() { + // Trip starts 10 min before the passenger's requested time but within the 30-min + // TimeBasedFilter window, so the trip is accepted. The driver arrives at the pickup + // well before the requested time — the question is what the returned itinerary says. + var departureTime = SEARCH_TIME.minusMinutes(10); + var trip = CarpoolTripTestData.createSimpleTripWithTime(tripStart, tripEnd, departureTime); + repository.upsertCarpoolTrip(trip); + + var router = new CarpoolTreeStreetRouter(); + router.addVertex(vertexTripStart, CarpoolTreeStreetRouter.Direction.FROM, Duration.ofHours(2)); + router.addVertex(vertexPickup, CarpoolTreeStreetRouter.Direction.FROM, Duration.ofHours(2)); + + var pathToPickup = router.route(vertexTripStart, vertexPickup); + assertNotNull(pathToPickup); + var drivingToPickup = Duration.between( + pathToPickup.states.getFirst().getTime(), + pathToPickup.states.getLast().getTime() + ); + + var pathPickupToDropoff = router.route(vertexPickup, vertexDropoff); + assertNotNull(pathPickupToDropoff); + var drivingPickupToDropoff = Duration.between( + pathPickupToDropoff.states.getFirst().getTime(), + pathPickupToDropoff.states.getLast().getTime() + ); + + var request = buildDirectCarpoolRequest(passengerPickup, passengerDropoff, SEARCH_TIME); + var stopDuration = request.preferences().car().pickupTime(); + + // The driver's pickup arrival time is fixed by the trip's schedule. It does NOT shift + // forward just because the passenger requested a later departure — the driver cannot + // wait (committed schedule / other passengers). + var actualPickupArrivalTime = departureTime.plus(drivingToPickup); + + // Guard the premise of this test: the requested time is after the real pickup arrival. + assertTrue( + request.dateTime().isAfter(actualPickupArrivalTime.toInstant()), + "Test premise: request time must be after the driver's real pickup arrival time" + ); + + // Itinerary start time is when the car arrives at the pickup; the boarding dwell is part + // of the leg's duration, so it shows up in the end time. + var expectedStartTime = actualPickupArrivalTime; + var expectedEndTime = expectedStartTime.plus(stopDuration).plus(drivingPickupToDropoff); + + var results = service.routeDirect(request, linkingContext); + + assertFalse(results.isEmpty(), "Trip within search window should produce a result"); + + var itinerary = results.getFirst(); + assertEquals( + expectedStartTime.toInstant(), + itinerary.startTime().toInstant(), + "Itinerary start time must match the driver's pickup arrival time, not the passenger's " + + "requested time — the driver cannot wait for the passenger" + ); + assertEquals( + expectedEndTime.toInstant(), + itinerary.endTime().toInstant(), + "Itinerary end time must match the driver's real dropoff time" + ); + } + @Test void resultItinerariesHaveValidStartAndEndTimes() { var departureTime = SEARCH_TIME.plusMinutes(10); var trip = CarpoolTripTestData.createSimpleTripWithTime(tripStart, tripEnd, departureTime); repository.upsertCarpoolTrip(trip); - // Independently compute driving durations to derive expected start/end times + // The carpool is forced to route via the pickup, so we sum the two segments it actually + // drives (tripStart -> pickup, then pickup -> dropoff) rather than routing tripStart -> dropoff + // directly. The graph includes an A-Q bypass edge whose shortest path skips the pickup, so a + // test that used router.route(tripStart, dropoff) here would not match what the carpool + // drives; this guards against regressing to that shortcut-in-the-test. var router = new CarpoolTreeStreetRouter(); router.addVertex(vertexTripStart, CarpoolTreeStreetRouter.Direction.FROM, Duration.ofHours(2)); + router.addVertex(vertexPickup, CarpoolTreeStreetRouter.Direction.FROM, Duration.ofHours(2)); var pathToPickup = router.route(vertexTripStart, vertexPickup); assertNotNull(pathToPickup, "Should route from trip start to pickup"); @@ -418,17 +476,19 @@ void resultItinerariesHaveValidStartAndEndTimes() { pathToPickup.states.getLast().getTime() ); - var pathToDropoff = router.route(vertexTripStart, vertexDropoff); - assertNotNull(pathToDropoff, "Should route from trip start to dropoff"); - var drivingToDropoff = Duration.between( - pathToDropoff.states.getFirst().getTime(), - pathToDropoff.states.getLast().getTime() + var pathPickupToDropoff = router.route(vertexPickup, vertexDropoff); + assertNotNull(pathPickupToDropoff, "Should route from pickup to dropoff"); + var drivingPickupToDropoff = Duration.between( + pathPickupToDropoff.states.getFirst().getTime(), + pathPickupToDropoff.states.getLast().getTime() ); - var expectedStartTime = departureTime.plus(drivingToPickup); - var expectedEndTime = departureTime.plus(drivingToDropoff); - var request = buildDirectCarpoolRequest(passengerPickup, passengerDropoff, SEARCH_TIME); + var stopDuration = request.preferences().car().pickupTime(); + // Start time is when the car arrives at the pickup. The boarding dwell is part of the + // leg's duration, so it is included in the end time rather than before the start. + var expectedStartTime = departureTime.plus(drivingToPickup); + var expectedEndTime = expectedStartTime.plus(stopDuration).plus(drivingPickupToDropoff); var results = service.routeDirect(request, linkingContext); @@ -441,12 +501,13 @@ void resultItinerariesHaveValidStartAndEndTimes() { assertEquals( expectedStartTime.toInstant(), itinerary.startTime().toInstant(), - "Start time should equal trip departure plus driving time to pickup" + "Start time should equal trip departure plus driving time to pickup (arrival at pickup)" ); assertEquals( expectedEndTime.toInstant(), itinerary.endTime().toInstant(), - "End time should equal trip departure plus driving time to dropoff" + "End time should equal start time plus boarding dwell plus driving time from pickup " + + "to dropoff" ); } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java index 99686794eef..d685fc0747d 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapperTest.java @@ -4,13 +4,23 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.arrivalIsAfterDepartureTime; +import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.journeyWithDifferentCapacitiesPerCall; +import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.journeyWithLatestExpectedArrivalTime; +import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.journeyWithLatestExpectedArrivalTimeAimedOnly; +import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.journeyWithOnboardCounts; +import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.journeyWithPerStopLatestExpectedArrivalTimes; +import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.journeyWithTotalCapacity; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.lessThanTwoStops; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.minimalCompleteJourney; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.minimalCompleteJourneyWithPolygon; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.stopTimesAreOutOfOrder; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.tripHasAimedTimesOnly; import static org.opentripplanner.ext.carpooling.CarpoolEstimatedVehicleJourneyData.tripHasExpectedTimesOnly; +import static org.opentripplanner.ext.carpooling.model.CarpoolStop.DEFAULT_DEVIATION_BUDGET; +import static org.opentripplanner.ext.carpooling.model.CarpoolStop.DEFAULT_ONBOARD_COUNT; +import static org.opentripplanner.ext.carpooling.model.CarpoolTrip.DEFAULT_TOTAL_CAPACITY; +import java.time.Duration; import org.junit.jupiter.api.Test; import uk.org.siri.siri21.EstimatedCall; @@ -19,14 +29,14 @@ public class CarpoolSiriMapperTest { private final CarpoolSiriMapper mapper = new CarpoolSiriMapper(); @Test - void mapSiriToCarpoolTrip_arrivalIsAfterDepartureTime_trowsIllegalArgumentException() { + void mapSiriToCarpoolTrip_arrivalIsAfterDepartureTime_throwsIllegalArgumentException() { assertThrows(IllegalArgumentException.class, () -> mapper.mapSiriToCarpoolTrip(arrivalIsAfterDepartureTime()) ); } @Test - void mapSiriToCarpoolTrip_lessThanTwoStops_trowsIllegalArgumentException() { + void mapSiriToCarpoolTrip_lessThanTwoStops_throwsIllegalArgumentException() { assertThrows(IllegalArgumentException.class, () -> mapper.mapSiriToCarpoolTrip(lessThanTwoStops()) ); @@ -103,9 +113,131 @@ void mapSiriToCarpoolTrip_tripHasOnlyExpectedTimes_mapsOk() { } @Test - void mapSiriToCarpoolTrip_stopTimesAreOutOfOrder_trowsIllegalArgumentException() { + void mapSiriToCarpoolTrip_stopTimesAreOutOfOrder_throwsIllegalArgumentException() { assertThrows(IllegalArgumentException.class, () -> mapper.mapSiriToCarpoolTrip(stopTimesAreOutOfOrder()) ); } + + // -- extractTotalCapacity tests -- + + @Test + void mapSiriToCarpoolTrip_noCapacityData_returnsDefaultCapacity() { + var mapped = mapper.mapSiriToCarpoolTrip(minimalCompleteJourney()); + assertEquals(DEFAULT_TOTAL_CAPACITY, mapped.totalCapacity()); + } + + @Test + void mapSiriToCarpoolTrip_withCapacityData_usesProvidedCapacity() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithTotalCapacity(3)); + assertEquals(3, mapped.totalCapacity()); + } + + @Test + void mapSiriToCarpoolTrip_zeroCapacity_returnsDefaultCapacity() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithTotalCapacity(0)); + assertEquals(DEFAULT_TOTAL_CAPACITY, mapped.totalCapacity()); + } + + @Test + void mapSiriToCarpoolTrip_negativeCapacity_returnsDefaultCapacity() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithTotalCapacity(-1)); + assertEquals(DEFAULT_TOTAL_CAPACITY, mapped.totalCapacity()); + } + + @Test + void mapSiriToCarpoolTrip_differentCapacitiesPerCall_usesFirstValue() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithDifferentCapacitiesPerCall(3, 7)); + assertEquals(3, mapped.totalCapacity()); + } + + @Test + void mapSiriToCarpoolTrip_consistentCapacitiesPerCall_usesValue() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithDifferentCapacitiesPerCall(4, 4)); + assertEquals(4, mapped.totalCapacity()); + } + + // -- extractOnboardCount tests -- + + @Test + void mapSiriToCarpoolTrip_noOccupancyData_returnsDefaultOnboardCount() { + var mapped = mapper.mapSiriToCarpoolTrip(minimalCompleteJourney()); + for (var stop : mapped.stops()) { + assertEquals(DEFAULT_ONBOARD_COUNT, stop.getOnboardCount()); + } + } + + @Test + void mapSiriToCarpoolTrip_withOccupancyData_usesProvidedOnboardCount() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithOnboardCounts(2, 3)); + assertEquals(2, mapped.stops().getFirst().getOnboardCount()); + assertEquals(3, mapped.stops().getLast().getOnboardCount()); + } + + @Test + void mapSiriToCarpoolTrip_zeroOnboardCount_returnsDefaultOnboardCount() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithOnboardCounts(0, 0)); + for (var stop : mapped.stops()) { + assertEquals(DEFAULT_ONBOARD_COUNT, stop.getOnboardCount()); + } + } + + @Test + void mapSiriToCarpoolTrip_negativeOnboardCount_returnsDefaultOnboardCount() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithOnboardCounts(-1, -1)); + for (var stop : mapped.stops()) { + assertEquals(DEFAULT_ONBOARD_COUNT, stop.getOnboardCount()); + } + } + + // -- extractDeviationBudget tests -- + + @Test + void mapSiriToCarpoolTrip_noLatestExpectedArrivalTime_returnsDefaultDeviationBudget() { + var mapped = mapper.mapSiriToCarpoolTrip(minimalCompleteJourney()); + assertEquals(Duration.ZERO, mapped.stops().getFirst().getDeviationBudget()); + assertEquals(DEFAULT_DEVIATION_BUDGET, mapped.stops().getLast().getDeviationBudget()); + } + + @Test + void mapSiriToCarpoolTrip_withLatestExpectedArrivalTime_computesDeviationBudget() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithLatestExpectedArrivalTime(0, 10)); + var lastStop = mapped.stops().getLast(); + assertEquals(Duration.ofMinutes(10), lastStop.getDeviationBudget()); + } + + @Test + void mapSiriToCarpoolTrip_withLatestExpectedArrivalTimeNoExpected_usesAimedArrivalTime() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithLatestExpectedArrivalTimeAimedOnly(20)); + var lastStop = mapped.stops().getLast(); + assertEquals(Duration.ofMinutes(20), lastStop.getDeviationBudget()); + } + + @Test + void mapSiriToCarpoolTrip_originStop_hasZeroDeviationBudget() { + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithLatestExpectedArrivalTime(0, 10)); + assertEquals(Duration.ZERO, mapped.stops().getFirst().getDeviationBudget()); + } + + @Test + void mapSiriToCarpoolTrip_latestBeforeExpected_returnsZeroDeviationBudget() { + // latestExpectedArrival is before expectedArrival — schedule has slipped past commitment, + // no further deviation is acceptable + var mapped = mapper.mapSiriToCarpoolTrip(journeyWithLatestExpectedArrivalTime(10, 5)); + var lastStop = mapped.stops().getLast(); + assertEquals(Duration.ZERO, lastStop.getDeviationBudget()); + } + + @Test + void mapSiriToCarpoolTrip_multiStopWithDifferingBudgets_eachStopHasOwnBudget() { + // 3-stop journey. Intermediate arrives at +20 with latest +23 (3 min slack), + // last arrives at +45 with latest +55 (10 min slack). + var mapped = mapper.mapSiriToCarpoolTrip( + journeyWithPerStopLatestExpectedArrivalTimes(20, 23, 45, 55) + ); + assertEquals(3, mapped.stops().size()); + assertEquals(Duration.ZERO, mapped.stops().get(0).getDeviationBudget()); + assertEquals(Duration.ofMinutes(3), mapped.stops().get(1).getDeviationBudget()); + assertEquals(Duration.ofMinutes(10), mapped.stops().get(2).getDeviationBudget()); + } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/util/BeelineEstimatorTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/util/BeelineEstimatorTest.java index d3c2fb48d54..61023d92683 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/util/BeelineEstimatorTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/util/BeelineEstimatorTest.java @@ -73,7 +73,7 @@ void calculateCumulativeTimes_simpleRoute_calculatesCorrectly() { // Route: Oslo Center → Oslo East → Oslo North List points = List.of(OSLO_CENTER, OSLO_EAST, OSLO_NORTH); - Duration[] times = estimator.calculateCumulativeTimes(points); + Duration[] times = estimator.calculateCumulativeTimes(points, Duration.ZERO); assertEquals(3, times.length); // Start at 0 @@ -94,7 +94,7 @@ void calculateCumulativeTimes_simpleRoute_calculatesCorrectly() { void calculateCumulativeTimes_singlePoint_returnsZero() { List points = List.of(OSLO_CENTER); - Duration[] times = estimator.calculateCumulativeTimes(points); + Duration[] times = estimator.calculateCumulativeTimes(points, Duration.ZERO); assertEquals(1, times.length); assertEquals(Duration.ZERO, times[0]); @@ -104,7 +104,7 @@ void calculateCumulativeTimes_singlePoint_returnsZero() { void calculateCumulativeTimes_emptyList_returnsEmptyArray() { List points = List.of(); - Duration[] times = estimator.calculateCumulativeTimes(points); + Duration[] times = estimator.calculateCumulativeTimes(points, Duration.ZERO); assertEquals(0, times.length); } @@ -120,7 +120,7 @@ void calculateCumulativeTimes_multipleStops_timesAreMonotonic() { OSLO_NORTHWEST ); - Duration[] times = estimator.calculateCumulativeTimes(points); + Duration[] times = estimator.calculateCumulativeTimes(points, Duration.ZERO); // Times should be strictly increasing for (int i = 1; i < times.length; i++) { @@ -257,7 +257,7 @@ void estimateDuration_longDistance_scalesCorrectly() { void calculateCumulativeTimes_twoPoints_calculatesCorrectly() { List points = List.of(OSLO_CENTER, OSLO_NORTH); - Duration[] times = estimator.calculateCumulativeTimes(points); + Duration[] times = estimator.calculateCumulativeTimes(points, Duration.ZERO); assertEquals(2, times.length); assertEquals(Duration.ZERO, times[0]); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/carpooling/util/GraphPathUtilsTest.java b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/util/GraphPathUtilsTest.java new file mode 100644 index 00000000000..8984f7366e4 --- /dev/null +++ b/application/src/ext-test/java/org/opentripplanner/ext/carpooling/util/GraphPathUtilsTest.java @@ -0,0 +1,102 @@ +package org.opentripplanner.ext.carpooling.util; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.Duration; +import org.junit.jupiter.api.Test; + +class GraphPathUtilsTest { + + private static final Duration TEN_MINUTES = Duration.ofMinutes(10); + private static final Duration ONE_MINUTE = Duration.ofMinutes(1); + + @Test + void calculateCumulativeDurations_sixSegmentsWithStopDelay() { + Duration[] segments = { + TEN_MINUTES, + TEN_MINUTES, + TEN_MINUTES, + TEN_MINUTES, + TEN_MINUTES, + TEN_MINUTES, + }; + + Duration[] result = GraphPathUtils.calculateCumulativeDurations(segments, ONE_MINUTE); + + assertArrayEquals( + new Duration[] { + Duration.ofMinutes(0), + Duration.ofMinutes(10), + Duration.ofMinutes(21), + Duration.ofMinutes(32), + Duration.ofMinutes(43), + Duration.ofMinutes(54), + Duration.ofMinutes(65), + }, + result + ); + } + + @Test + void calculateCumulativeDurations_noStopDelay() { + Duration[] segments = { TEN_MINUTES, TEN_MINUTES, TEN_MINUTES }; + + Duration[] result = GraphPathUtils.calculateCumulativeDurations(segments, Duration.ZERO); + + assertArrayEquals( + new Duration[] { + Duration.ofMinutes(0), + Duration.ofMinutes(10), + Duration.ofMinutes(20), + Duration.ofMinutes(30), + }, + result + ); + } + + @Test + void calculateCumulativeDurations_singleSegment_noStopDelayApplied() { + Duration[] segments = { TEN_MINUTES }; + + Duration[] result = GraphPathUtils.calculateCumulativeDurations(segments, ONE_MINUTE); + + assertArrayEquals(new Duration[] { Duration.ZERO, TEN_MINUTES }, result); + } + + @Test + void calculateCumulativeDurations_twoSegments_stopDelayOnlyAtSecondPoint() { + Duration[] segments = { TEN_MINUTES, TEN_MINUTES }; + + Duration[] result = GraphPathUtils.calculateCumulativeDurations(segments, ONE_MINUTE); + + assertArrayEquals( + new Duration[] { Duration.ofMinutes(0), Duration.ofMinutes(10), Duration.ofMinutes(21) }, + result + ); + } + + @Test + void calculateCumulativeDurations_noSegments() { + Duration[] segments = {}; + + Duration[] result = GraphPathUtils.calculateCumulativeDurations(segments, ONE_MINUTE); + + assertArrayEquals(new Duration[] { Duration.ZERO }, result); + } + + @Test + void calculateCumulativeDurations_varyingSegmentDurations() { + Duration[] segments = { Duration.ofMinutes(5), Duration.ofMinutes(15), Duration.ofMinutes(10) }; + + Duration[] result = GraphPathUtils.calculateCumulativeDurations( + segments, + Duration.ofMinutes(2) + ); + + assertEquals(Duration.ofMinutes(0), result[0]); + assertEquals(Duration.ofMinutes(5), result[1]); + assertEquals(Duration.ofMinutes(22), result[2]); + assertEquals(Duration.ofMinutes(34), result[3]); + } +} diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java index bb2e2014530..fc79e22ae73 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/FaresFilterTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.fares; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/GtfsFaresServiceTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/GtfsFaresServiceTest.java index e534fe1404c..ff7db8c76ee 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/GtfsFaresServiceTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/GtfsFaresServiceTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.Duration; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceTest.java index 5a275126330..0645a9530aa 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceTest.java @@ -2,8 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.FEED_ID; import java.time.Duration; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/AreasTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/AreasTest.java index 5d4e9f8af03..244c9014a2e 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/AreasTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/AreasTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import com.google.common.collect.Multimaps; import java.util.Map; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferAcrossNetworksTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferAcrossNetworksTest.java index 194618bba6d..03bfaf576fc 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferAcrossNetworksTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferAcrossNetworksTest.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Set; import org.junit.jupiter.api.Test; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.ext.fares.model.FareLegRule; import org.opentripplanner.ext.fares.model.FareTestConstants; import org.opentripplanner.ext.fares.model.FareTransferRule; @@ -38,26 +39,26 @@ class CostedTransferAcrossNetworksTest implements PlanTestConstants, FareTestCon List.of( // transferring from A to A is free FareTransferRule.of() - .withId(TimetableRepositoryForTest.id("t1")) + .withId(FeedScopedIdForTestFactory.id("t1")) .withFromLegGroup(LEG_GROUP_A) .withToLegGroup(LEG_GROUP_A) .build(), // transferring from B to B is also free FareTransferRule.of() - .withId(TimetableRepositoryForTest.id("t2")) + .withId(FeedScopedIdForTestFactory.id("t2")) .withFromLegGroup(LEG_GROUP_B) .withToLegGroup(LEG_GROUP_B) .build(), // transferring from A to B costs one EUR FareTransferRule.of() - .withId(TimetableRepositoryForTest.id("t3")) + .withId(FeedScopedIdForTestFactory.id("t3")) .withFromLegGroup(LEG_GROUP_A) .withToLegGroup(LEG_GROUP_B) .withFareProducts(TRANSFER_1) .build(), // transferring from B to A is free FareTransferRule.of() - .withId(TimetableRepositoryForTest.id("t4")) + .withId(FeedScopedIdForTestFactory.id("t4")) .withFromLegGroup(LEG_GROUP_B) .withToLegGroup(LEG_GROUP_A) .build() diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferInNetworkTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferInNetworkTest.java index 8330460cd39..0801c35b9a4 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferInNetworkTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/CostedTransferInNetworkTest.java @@ -2,8 +2,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import java.util.Set; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToArrivalTimeLimitTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToArrivalTimeLimitTest.java index 783b6df108b..da80500a699 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToArrivalTimeLimitTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToArrivalTimeLimitTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.utils.time.TimeUtils.time; import java.time.Duration; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToDepartureTimeLimitTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToDepartureTimeLimitTest.java index 57fc5b89ac4..56e8d3df822 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToDepartureTimeLimitTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/DepartureToDepartureTimeLimitTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.utils.time.TimeUtils.time; import java.time.Duration; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FlexLegTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FlexLegTest.java index 860edb4b7bc..5c9927b93a8 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FlexLegTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FlexLegTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import com.google.common.collect.ImmutableMultimap; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferAcrossNetworksTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferAcrossNetworksTest.java index b814c074fa7..dfdb3ed2f13 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferAcrossNetworksTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferAcrossNetworksTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferInNetworkTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferInNetworkTest.java index 0f6ee6a5449..31b512727bf 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferInNetworkTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferInNetworkTest.java @@ -2,8 +2,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.groupOfRoutes; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferTimeLimitTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferTimeLimitTest.java index b21a0e28295..626e5c29604 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferTimeLimitTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/FreeTransferTimeLimitTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.utils.time.TimeUtils.time; import java.time.Duration; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/GtfsFaresV2ServiceTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/GtfsFaresV2ServiceTest.java index f292926ecbf..74139d94164 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/GtfsFaresV2ServiceTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/GtfsFaresV2ServiceTest.java @@ -2,8 +2,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.FEED_ID; import java.util.Set; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyFromTimeframeMatcherTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyFromTimeframeMatcherTest.java index 46494d6f5e3..2bd9c448731 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyFromTimeframeMatcherTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyFromTimeframeMatcherTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import com.google.common.collect.ImmutableMultimap; import java.time.LocalTime; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyToTimeframeMatcherTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyToTimeframeMatcherTest.java index 6eb4fe5fe8e..17e6c4ef648 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyToTimeframeMatcherTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OnlyToTimeframeMatcherTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import com.google.common.collect.ImmutableMultimap; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframeTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframeTest.java index 1f0c69c9604..39f69869b16 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframeTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframeTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframesWithPriorityTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframesWithPriorityTest.java index 0324be08e10..44b57d05280 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframesWithPriorityTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/OverlappingTimeframesWithPriorityTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/SameGroupIdPriorityTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/SameGroupIdPriorityTest.java index 1ebb383b359..3bd8803e352 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/SameGroupIdPriorityTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/SameGroupIdPriorityTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeMatcherTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeMatcherTest.java index ec19683355f..9d48c8a7721 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeMatcherTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeMatcherTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import com.google.common.collect.ImmutableMultimap; import java.util.List; @@ -10,10 +10,10 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.ext.fares.model.FareLegRule; import org.opentripplanner.ext.fares.model.FareTestConstants; import org.opentripplanner.model.plan.TestTransitLeg; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; class TimeframeMatcherTest implements FareTestConstants { diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeTest.java index d7362cd62b0..243ce47d1c6 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TimeframeTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountTest.java index e0912de9bce..5a9d1856dbb 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountWithTimeLimitTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountWithTimeLimitTest.java index d4843095991..94ce64c5713 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountWithTimeLimitTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/TransferCountWithTimeLimitTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/WildcardNetworkTransferTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/WildcardNetworkTransferTest.java index 893a46c3fb0..83cc4e8e460 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/WildcardNetworkTransferTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/WildcardNetworkTransferTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.service.gtfs.v2; import static com.google.common.truth.Truth.assertThat; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.Duration; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactoryTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactoryTest.java index 3ddb47ff5c9..4dc5017193c 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactoryTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactoryTest.java @@ -1,13 +1,13 @@ package org.opentripplanner.ext.fares.service.gtfs.v2.custom; import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.fares.service.gtfs.v2.custom.OregonHopFareFactory.ADULT_REGIONAL_SINGLE_RIDE; import static org.opentripplanner.ext.fares.service.gtfs.v2.custom.OregonHopFareFactory.CATEGORY_ADULT; import static org.opentripplanner.ext.fares.service.gtfs.v2.custom.OregonHopFareFactory.HOP_FASTPASS; import static org.opentripplanner.ext.fares.service.gtfs.v2.custom.OregonHopFareFactory.LG_CTRAN_REGIONAL; import static org.opentripplanner.ext.fares.service.gtfs.v2.custom.OregonHopFareFactory.LG_TRIMET_TRIMET; import static org.opentripplanner.ext.fares.service.gtfs.v2.custom.OregonHopFareFactory.TRIMET_ADULT_SINGLE_RIDE; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.util.List; import java.util.Set; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java index f0ee672b247..d8c923cd437 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIndexTest.java @@ -1,9 +1,12 @@ package org.opentripplanner.ext.flex; +import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.model.FlexStopTimesFactory.area; +import static org.opentripplanner.model.FlexStopTimesFactory.groupStop; +import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.trip; import java.time.LocalDate; import java.util.Collection; @@ -13,17 +16,21 @@ import org.opentripplanner.ext.flex.trip.UnscheduledTrip; import org.opentripplanner.model.calendar.CalendarServiceData; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.site.GroupStop; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.service.TimetableRepository; class FlexIndexTest { + public static final Route ROUTE_2 = TimetableRepositoryForTest.route("r2").build(); + @Test void testFlexTripSpanningMidnight() { TimetableRepository repo = new TimetableRepository(); FeedScopedId serviceId = id("S1"); - Trip trip = TimetableRepositoryForTest.trip("T1").withServiceId(serviceId).build(); + Trip trip = trip("T1").withServiceId(serviceId).build(); UnscheduledTrip flexTrip = UnscheduledTrip.of(id("FT1")) .withTrip(trip) @@ -65,7 +72,7 @@ void testFlexTripSpanningMidnight() { void testFlexTripStartingAfterMidnight() { TimetableRepository repo = new TimetableRepository(); FeedScopedId serviceId = id("S2"); - Trip trip = TimetableRepositoryForTest.trip("T2").withServiceId(serviceId).build(); + Trip trip = trip("T2").withServiceId(serviceId).build(); UnscheduledTrip flexTrip = UnscheduledTrip.of(id("FT2")) .withTrip(trip) @@ -90,4 +97,50 @@ void testFlexTripStartingAfterMidnight() { assertEquals(1, tripsOnNextDay.size(), "Should have 1 trip on next day"); assertEquals(serviceDate, tripsOnNextDay.iterator().next().serviceDate()); } + + @Test + void routesAtArea() { + var repo = new TimetableRepository(); + + var st1 = area("10:00", "12:00"); + var st2 = area("14:00", "16:00"); + + var flexTrip = UnscheduledTrip.of(id("T2")) + .withTrip(trip("T2").withRoute(ROUTE_2).build()) + .withStopTimes(List.of(st1, st2)) + .build(); + + repo.addFlexTrip(flexTrip.getId(), flexTrip); + + var index = new FlexIndex(repo); + + assertThat(index.findRoutes(st1.getStop())).containsExactly(ROUTE_2); + assertThat(index.findRoutes(st2.getStop())).containsExactly(ROUTE_2); + } + + @Test + void routesAtGroup() { + var repo = new TimetableRepository(); + + var st1 = groupStop("10:00", "12:00"); + var st2 = groupStop("14:00", "16:00"); + + var flexTrip = UnscheduledTrip.of(id("T2")) + .withTrip(trip("T2").withRoute(ROUTE_2).build()) + .withStopTimes(List.of(st1, st2)) + .build(); + + repo.addFlexTrip(flexTrip.getId(), flexTrip); + + var index = new FlexIndex(repo); + + var groupStop = (GroupStop) st1.getStop(); + assertThat(groupStop.getChildLocations()).isNotEmpty(); + groupStop + .getChildLocations() + .forEach(child -> { + assertThat(index.findRoutes(child)).containsExactly(ROUTE_2); + assertThat(index.findRoutes(child)).containsExactly(ROUTE_2); + }); + } } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java index 3f7fa497cf2..939286c451c 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.TestOtpModel; import org.opentripplanner.TestServerContext; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.core.model.time.LocalDateInterval; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; @@ -123,7 +124,7 @@ void shouldReturnARouteTransferringFromBusToFlex() { @Test void shouldReturnARouteWithTwoTransfers() { - var from = GenericLocation.fromStopId("ALEX DR@ALEX WAY", "MARTA", "97266"); + var from = GenericLocation.fromStopId(new FeedScopedId("MARTA", "97266"), "ALEX DR@ALEX WAY"); var to = GenericLocation.fromCoordinate(33.86701256815635, -84.61787939071655); var itin = getItinerary(from, to, 3); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexTransferIndexTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexTransferIndexTest.java index 9b1d51c5fac..12f5c1ac799 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexTransferIndexTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexTransferIndexTest.java @@ -2,7 +2,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java index cd843b3d18e..f836fb36ce7 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexibleTransitLegTest.java @@ -3,8 +3,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.fares.model.FareModelForTest.ANY_FARE_OFFER; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.time.Duration; import java.time.LocalDate; @@ -26,8 +26,8 @@ class FlexibleTransitLegTest implements PlanTestConstants { private static final FlexTripEdge EDGE = new FlexTripEdge( StreetModelForTest.intersectionVertex(1, 1), StreetModelForTest.intersectionVertex(2, 2), - A.stop, - B.stop, + A.stop.getId(), + B.stop.getId(), null, 1, 2, @@ -48,6 +48,8 @@ void listsAreInitialized() { .withStartTime(START_TIME) .withEndTime(END_TIME) .withFlexTripEdge(EDGE) + .withFromStop(A.stop) + .withToStop(B.stop) .build(); assertNotNull(leg.fareOffers()); assertNotNull(leg.listTransitAlerts()); @@ -61,6 +63,8 @@ void everythingIsNonNull() { assertThrows(expectedType, () -> new FlexibleTransitLegBuilder().withFlexTripEdge(null).build() ); + assertThrows(expectedType, () -> new FlexibleTransitLegBuilder().withFromStop(null).build()); + assertThrows(expectedType, () -> new FlexibleTransitLegBuilder().withToStop(null).build()); assertThrows(expectedType, () -> new FlexibleTransitLegBuilder().withAlerts(null).build()); assertThrows(expectedType, () -> new FlexibleTransitLegBuilder().withFareProducts(null).build() @@ -73,6 +77,8 @@ void copyOf() { .withStartTime(START_TIME) .withEndTime(END_TIME) .withFlexTripEdge(EDGE) + .withFromStop(A.stop) + .withToStop(B.stop) .withFareProducts(List.of(ANY_FARE_OFFER)) .withAlerts(Set.of(ALERT)) .withEmissionPerPerson(EMISSION) @@ -94,6 +100,8 @@ void timeShift() { .withStartTime(START_TIME) .withEndTime(END_TIME) .withFlexTripEdge(EDGE) + .withFromStop(A.stop) + .withToStop(B.stop) .withFareProducts(List.of(ANY_FARE_OFFER)) .withAlerts(Set.of(ALERT)) .build(); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/filter/FilterMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/filter/FilterMapperTest.java index e2ebad57302..6836a2ce493 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/filter/FilterMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/filter/FilterMapperTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.flex.filter; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java index d35ee6436d0..391da6b3a94 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java @@ -1,11 +1,11 @@ package org.opentripplanner.ext.flex.flexpathcalculator; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStop; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.model.FlexStopTimesFactory.area; +import static org.opentripplanner.model.FlexStopTimesFactory.regularStop; import static org.opentripplanner.street.model.StreetModelForTest.V1; import static org.opentripplanner.street.model.StreetModelForTest.V2; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.time.Duration; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java index d0213119f0a..4cfb9d8ba0c 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/template/ClosestTripTest.java @@ -2,8 +2,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.model.FlexStopTimesFactory.area; import java.time.Instant; import java.time.LocalDate; @@ -61,7 +61,7 @@ public Collection getTransfersToStop(StopLocation stop) { } @Override - public Collection> getFlexTripsByStop(StopLocation stopLocation) { + public Collection> getFlexTripsByStopId(FeedScopedId stopLocationId) { return List.of(FLEX_TRIP); } @@ -96,7 +96,7 @@ void filter() { private static Collection closestTrips(Matcher matcher) { return ClosestTrip.of( ADAPTER, - List.of(new NearbyStop(STOP, 100, List.of(), null)), + List.of(new NearbyStop(STOP.getId(), 100, List.of(), null)), matcher, List.of(FSD), true diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/template/FlexTemplateFactoryTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/template/FlexTemplateFactoryTest.java index 448b2adc5b2..7a1e1c397c2 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/template/FlexTemplateFactoryTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/template/FlexTemplateFactoryTest.java @@ -330,7 +330,7 @@ void testCreateEgressTemplateForScheduledDeviatedTrip() { private static NearbyStop nearbyStop(StopLocation transferPoint) { var id = "NearbyStop:" + transferPoint.getId().getId(); return new NearbyStop( - transferPoint, + transferPoint.getId(), 0, List.of(), new State( diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/FlexTripsMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/FlexTripsMapperTest.java index a236b87e967..a23cb83a679 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/FlexTripsMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/FlexTripsMapperTest.java @@ -2,8 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; import static org.opentripplanner.graph_builder.issue.api.DataImportIssueStore.NOOP; +import static org.opentripplanner.model.FlexStopTimesFactory.area; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripIntegrationTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripIntegrationTest.java index 2a6b6783fee..f35d159c0bb 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripIntegrationTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripIntegrationTest.java @@ -101,11 +101,13 @@ void flexTripInTransitMode() { ); // from zone 3 to zone 2 - var from = GenericLocation.fromStopId("Transfer Point for Route 30", feedId, "cujv"); + var from = GenericLocation.fromStopId( + new FeedScopedId(feedId, "cujv"), + "Transfer Point for Route 30" + ); var to = GenericLocation.fromStopId( - "Zone 1 - PUBLIX Super Market,Zone 1 Collection Point", - feedId, - "yz85" + new FeedScopedId(feedId, "yz85"), + "Zone 1 - PUBLIX Super Market,Zone 1 Collection Point" ); var itineraries = getItineraries(from, to, serverContext); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java index 43f7c3e1451..f7abcccf82d 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java @@ -3,11 +3,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.areaWithContinuousStopping; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStop; -import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStopWithContinuousStopping; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.model.FlexStopTimesFactory.area; +import static org.opentripplanner.model.FlexStopTimesFactory.areaWithContinuousStopping; +import static org.opentripplanner.model.FlexStopTimesFactory.regularStop; +import static org.opentripplanner.model.FlexStopTimesFactory.regularStopWithContinuousStopping; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java index 61ea07c20bd..4f78826f347 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java @@ -1,17 +1,17 @@ package org.opentripplanner.ext.flex.trip; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.street.model.StreetModelForTest.V1; import static org.opentripplanner.street.model.StreetModelForTest.V2; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.time.Duration; import java.util.List; import org.junit.jupiter.api.Test; import org.opentripplanner._support.geometry.LineStrings; -import org.opentripplanner.ext.flex.FlexStopTimesForTest; import org.opentripplanner.ext.flex.flexpathcalculator.FlexPath; import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator; +import org.opentripplanner.model.FlexStopTimesFactory; import org.opentripplanner.model.StopTime; import org.opentripplanner.routing.api.request.framework.TimePenalty; @@ -23,7 +23,7 @@ class UnscheduledDrivingDurationTest { boardStopPosition, alightStopPosition ) -> new FlexPath(10_000, (int) Duration.ofMinutes(10).toSeconds(), () -> LineStrings.SIMPLE); - private static final StopTime STOP_TIME = FlexStopTimesForTest.area("10:00", "18:00"); + private static final StopTime STOP_TIME = FlexStopTimesFactory.area("10:00", "18:00"); @Test void noPenalty() { diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index d5760946a8e..65d3b10e421 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -3,11 +3,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.flex.trip.UnscheduledTrip.isUnscheduledTrip; import static org.opentripplanner.ext.flex.trip.UnscheduledTripTest.TestCase.tc; import static org.opentripplanner.model.PickDrop.NONE; import static org.opentripplanner.model.StopTime.MISSING_VALUE; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.util.Collections; import java.util.List; @@ -17,7 +17,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import org.opentripplanner.ext.flex.FlexStopTimesForTest; +import org.opentripplanner.model.FlexStopTimesFactory; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; @@ -46,18 +46,18 @@ class UnscheduledTripTest { @Nested class IsUnscheduledTrip { - private static final StopTime SCHEDULED_STOP = FlexStopTimesForTest.regularStop("10:00"); - private static final StopTime UNSCHEDULED_STOP = FlexStopTimesForTest.area("10:10", "10:20"); + private static final StopTime SCHEDULED_STOP = FlexStopTimesFactory.regularStop("10:00"); + private static final StopTime UNSCHEDULED_STOP = FlexStopTimesFactory.area("10:10", "10:20"); private static final StopTime CONTINUOUS_PICKUP_STOP = - FlexStopTimesForTest.regularStopWithContinuousPickup("10:30"); + FlexStopTimesFactory.regularStopWithContinuousPickup("10:30"); private static final StopTime CONTINUOUS_DROP_OFF_STOP = - FlexStopTimesForTest.regularStopWithContinuousDropOff("10:40"); + FlexStopTimesFactory.regularStopWithContinuousDropOff("10:40"); // disallowed by the GTFS spec private static final StopTime FLEX_AND_CONTINUOUS_PICKUP_STOP = - FlexStopTimesForTest.areaWithContinuousPickup("10:50"); + FlexStopTimesFactory.areaWithContinuousPickup("10:50"); private static final StopTime FLEX_AND_CONTINUOUS_DROP_OFF_STOP = - FlexStopTimesForTest.areaWithContinuousDropOff("11:00"); + FlexStopTimesFactory.areaWithContinuousDropOff("11:00"); static List> notUnscheduled() { return List.of( @@ -103,8 +103,8 @@ void isUnscheduled(List stopTimes) { @Test void testMaxSpanDays() { var stopTimes = List.of( - FlexStopTimesForTest.area("10:10", "14:10"), - FlexStopTimesForTest.area("11:10", "15:10") + FlexStopTimesFactory.area("10:10", "14:10"), + FlexStopTimesFactory.area("11:10", "15:10") ); var trip = UnscheduledTrip.of(id("1")).withStopTimes(stopTimes).build(); @@ -114,8 +114,8 @@ void testMaxSpanDays() { @Test void testMaxSpanDaysOvernight() { var stopTimes = List.of( - FlexStopTimesForTest.area("10:10", "14:10"), - FlexStopTimesForTest.area("21:10", "26:10") + FlexStopTimesFactory.area("10:10", "14:10"), + FlexStopTimesFactory.area("21:10", "26:10") ); var trip = UnscheduledTrip.of(id("1")).withStopTimes(stopTimes).build(); assertEquals(1, trip.maxSpanDays()); @@ -124,8 +124,8 @@ void testMaxSpanDaysOvernight() { @Test void testMaxSpanDaysNextDay() { var stopTimes = List.of( - FlexStopTimesForTest.area("24:00", "26:00"), - FlexStopTimesForTest.area("24:00", "26:00") + FlexStopTimesFactory.area("24:00", "26:00"), + FlexStopTimesFactory.area("24:00", "26:00") ); var trip = UnscheduledTrip.of(id("1")).withStopTimes(stopTimes).build(); assertEquals(1, trip.maxSpanDays()); @@ -566,11 +566,11 @@ void boardingAlighting() { .build() .trip(); - assertTrue(trip.isBoardingPossible(AREA_STOP1)); - assertFalse(trip.isAlightingPossible(AREA_STOP1)); + assertTrue(trip.isBoardingPossible(AREA_STOP1.getId())); + assertFalse(trip.isAlightingPossible(AREA_STOP1.getId())); - assertFalse(trip.isBoardingPossible(AREA_STOP2)); - assertTrue(trip.isAlightingPossible(AREA_STOP2)); + assertFalse(trip.isBoardingPossible(AREA_STOP2.getId())); + assertTrue(trip.isAlightingPossible(AREA_STOP2.getId())); } private static String timeToString(int time) { diff --git a/application/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/application/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 33f8c29f948..54ee43e8f85 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -2,7 +2,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model.basic.TransitMode.BUS; import static org.opentripplanner.transit.model.basic.TransitMode.FERRY; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/geocoder/SerializationTest.java b/application/src/ext-test/java/org/opentripplanner/ext/geocoder/SerializationTest.java index 220566888ac..bedfd6c7f96 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/geocoder/SerializationTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/geocoder/SerializationTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.geocoder; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.geocoder.StopCluster.LocationType.STOP; import static org.opentripplanner.test.support.JsonAssertions.assertEqualJson; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.util.List; import java.util.Set; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/ojp/mapping/RouteRequestMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/ojp/mapping/RouteRequestMapperTest.java index 069a059cacf..73dd4b4828e 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/ojp/mapping/RouteRequestMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/ojp/mapping/RouteRequestMapperTest.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import de.vdv.ojp20.LineDirectionFilterStructure; import de.vdv.ojp20.ModeAndModeOfOperationFilterStructure; @@ -45,10 +45,10 @@ void mapWithCoordinates() { var routeRequest = mapper.map(tripRequest); - assertEquals(47.3769, routeRequest.from().lat); - assertEquals(8.5417, routeRequest.from().lng); - assertEquals(46.9480, routeRequest.to().lat); - assertEquals(7.4474, routeRequest.to().lng); + assertEquals(47.3769, routeRequest.from().wgsCoordinate().latitude()); + assertEquals(8.5417, routeRequest.from().wgsCoordinate().longitude()); + assertEquals(46.9480, routeRequest.to().wgsCoordinate().latitude()); + assertEquals(7.4474, routeRequest.to().wgsCoordinate().longitude()); assertTransitFilters("[ALLOW_ALL]", routeRequest); assertEquals(StreetMode.WALK, routeRequest.journey().access().mode()); @@ -62,8 +62,8 @@ void mapWithStopPlaceRef() { var routeRequest = mapper.map(tripRequest); - assertEquals(id("stop1"), routeRequest.from().stopId); - assertEquals(id("stop2"), routeRequest.to().stopId); + assertEquals(id("stop1"), routeRequest.from().stopId()); + assertEquals(id("stop2"), routeRequest.to().stopId()); } @Test @@ -75,8 +75,8 @@ void mapWithStopPointRef() { var routeRequest = mapper.map(tripRequest); assertNotNull(routeRequest.to()); - assertEquals(id("stopPoint1"), routeRequest.from().stopId); - assertEquals(id("stopPoint2"), routeRequest.to().stopId); + assertEquals(id("stopPoint1"), routeRequest.from().stopId()); + assertEquals(id("stopPoint2"), routeRequest.to().stopId()); } @Test @@ -87,9 +87,9 @@ void mapWithMixedLocationTypes() { var routeRequest = mapper.map(tripRequest); - assertEquals(47.3769, routeRequest.from().lat); - assertEquals(8.5417, routeRequest.from().lng); - assertEquals(id("stop1"), routeRequest.to().stopId); + assertEquals(47.3769, routeRequest.from().wgsCoordinate().latitude()); + assertEquals(8.5417, routeRequest.from().wgsCoordinate().longitude()); + assertEquals(id("stop1"), routeRequest.to().stopId()); } @Test diff --git a/application/src/ext-test/java/org/opentripplanner/ext/ojp/resource/OjpMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/ojp/resource/OjpMapperTest.java index 2846522b2e0..4fc06b20fff 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/ojp/resource/OjpMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/ojp/resource/OjpMapperTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.ojp.resource; import static jakarta.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import de.vdv.ojp20.OJP; import jakarta.xml.bind.JAXBContext; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java b/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java index e70c0dd21db..7efe12961e1 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/CallAtStopServiceTest.java @@ -3,7 +3,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.util.List; @@ -102,7 +102,12 @@ void coordinates() { @Override public List findClosestStops(Coordinate coordinate, double radiusMeters) { return List.of( - new NearbyStop(STOP_A, 100, List.of(), TestStateBuilder.ofWalking().streetEdge().build()) + new NearbyStop( + STOP_A.getId(), + 100, + List.of(), + TestStateBuilder.ofWalking().streetEdge().build() + ) ); } diff --git a/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/StopEventParamsMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/StopEventParamsMapperTest.java index 4511aca913f..fc9d47aae5a 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/StopEventParamsMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/ojp/service/StopEventParamsMapperTest.java @@ -3,7 +3,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model.basic.TransitMode.BUS; import static org.opentripplanner.transit.model.basic.TransitMode.FERRY; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationRepositoryTest.java b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationRepositoryTest.java index 7bf87b4c89c..98cc695228e 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationRepositoryTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationRepositoryTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilderTest.java b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilderTest.java index b3a983850bd..288f568b8e5 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilderTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLegBuilderTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.ext.stopconsolidation.model; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.fares.model.FareModelForTest.ANY_FARE_OFFER; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.time.ZonedDateTime; import java.util.List; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stations/DigitransitStationPropertyMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stations/DigitransitStationPropertyMapperTest.java index 3c9a3fb5a59..00eb659805c 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stations/DigitransitStationPropertyMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stations/DigitransitStationPropertyMapperTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.vectortiles.layers.stations; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.HashMap; import java.util.Locale; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java index faf6f8766df..eb10dc6fa03 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.vectortiles.layers.stops; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.HashMap; import java.util.Locale; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java index 0f8d40c7d76..8e3dec8266a 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java @@ -16,6 +16,7 @@ import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.i18n.TranslatedString; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.ext.vectortiles.VectorTilesResource; import org.opentripplanner.inspector.vector.KeyValue; import org.opentripplanner.inspector.vector.LayerParameters; @@ -28,11 +29,10 @@ import org.opentripplanner.service.vehicleparking.model.VehicleParkingState; import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; public class VehicleParkingGroupsLayerTest { - private static final FeedScopedId ID = TimetableRepositoryForTest.id("id"); + private static final FeedScopedId ID = FeedScopedIdForTestFactory.id("id"); private VehicleParkingGroup vehicleParkingGroup; private VehicleParking vehicleParking; diff --git a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java index de4d16f799d..994373288ed 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java @@ -22,6 +22,7 @@ import org.locationtech.jts.geom.Geometry; import org.opentripplanner.core.model.i18n.TranslatedString; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingRepository; import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingService; import org.opentripplanner.service.vehicleparking.model.VehicleParking; @@ -30,12 +31,11 @@ import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.street.model.openinghours.OpeningHoursCalendarService; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; public class VehicleParkingsLayerTest { - private static final FeedScopedId ID = TimetableRepositoryForTest.id("id"); + private static final FeedScopedId ID = FeedScopedIdForTestFactory.id("id"); private VehicleParking vehicleParking; diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/Architecture.md b/application/src/ext/java/org/opentripplanner/ext/carpooling/Architecture.md index 7ffa3b64ee7..3baadb83d06 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/Architecture.md +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/Architecture.md @@ -18,15 +18,12 @@ org.opentripplanner.ext.carpooling/ │ └── CarpoolStreetRouter # Street routing for carpooling ├── filter/ # Trip pre-filtering │ ├── TripFilter # Filter interface -│ ├── CapacityFilter # Checks available capacity │ ├── TimeBasedFilter # Time window filtering -│ ├── DistanceBasedFilter # Geographic distance checks -│ └── DirectionalCompatibilityFilter # Directional alignment +│ └── DistanceBasedFilter # Geographic distance checks ├── constraints/ # Post-routing constraints │ └── PassengerDelayConstraints # Protects existing passengers ├── util/ # Utilities -│ ├── BeelineEstimator # Fast travel time estimates -│ └── DirectionalCalculator # Geographic bearing calculations +│ └── BeelineEstimator # Fast travel time estimates ├── updater/ # Real-time updates │ ├── SiriETCarpoolingUpdater # SIRI-ET integration │ └── CarpoolSiriMapper # Maps SIRI to domain model @@ -44,7 +41,6 @@ Fast pre-screening to eliminate incompatible trips: - **Capacity Filter**: Checks if any seats are available - **Time-Based Filter**: Ensures departure time compatibility - **Distance-Based Filter**: Validates pickup/dropoff are within 50km of driver's route -- **Directional Compatibility Filter**: Verifies passenger direction aligns with trip route ### 2. Routing Phase Optimal insertion point calculation: @@ -55,7 +51,6 @@ Optimal insertion point calculation: ### 3. Constraint Validation - **Capacity constraints**: Ensures vehicle capacity is not exceeded -- **Directional constraints**: Prevents backtracking (90° tolerance) - **Passenger delay constraints**: Protects existing passengers (max 5 minutes additional delay) - **Deviation budget**: Respects driver's maximum acceptable detour time diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/CarpoolingService.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/CarpoolingService.java index 995ae8bb286..1ea1a7b4b6a 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/CarpoolingService.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/CarpoolingService.java @@ -3,11 +3,11 @@ import java.time.ZonedDateTime; import java.util.List; import org.opentripplanner.ext.carpooling.routing.CarpoolAccessEgress; -import org.opentripplanner.graph_builder.module.nearbystops.StopResolver; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.request.StreetRequest; +import org.opentripplanner.routing.graphfinder.TransitServiceResolver; import org.opentripplanner.routing.linking.LinkingContext; /** @@ -24,8 +24,7 @@ public interface CarpoolingService { * * @param request the routing request containing passenger origin, destination, and preferences * @param linkingContext linking context with pre-linked vertices for the request - * @return list of carpool itineraries, sorted by quality (additional travel time), may be empty - * if no compatible trips found. Results are limited to avoid overwhelming users. + * @return list of carpool itineraries, may be empty if no compatible trips found * @throws IllegalArgumentException if request is null */ List routeDirect(RouteRequest request, LinkingContext linkingContext); @@ -34,7 +33,7 @@ List routeAccessEgress( RouteRequest request, StreetRequest streetRequest, AccessEgressType accessOrEgress, - StopResolver stopResolver, + TransitServiceResolver transitServiceResolver, LinkingContext linkingContext, ZonedDateTime transitSearchTimeZero ); diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md b/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md index dff8c5d4461..6689cc46afc 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/README.md @@ -35,14 +35,12 @@ The carpooling extension enables OpenTripPlanner to find carpool trip options by │ 1. Filter Phase (FilterChain) │ │ - Capacity check │ │ - Time window check │ -│ - Direction check │ │ - Distance check │ │ │ │ 2. Insertion Phase │ │ 2a. Position Pre-screening │ │ (InsertionPositionFinder) │ │ - Capacity check │ -│ - Directional check │ │ - Beeline delay heuristic │ │ │ │ 2b. Routing & Selection │ @@ -54,7 +52,6 @@ The carpooling extension enables OpenTripPlanner to find carpool trip options by │ │ │ 3. Validation Phase (CompositeValidator) │ │ - Capacity timeline check │ -│ - Directional consistency check │ │ - Deviation budget check │ │ │ └────────┬───────────────────────────────────┘ @@ -84,9 +81,7 @@ org.opentripplanner.ext.carpooling/ │ ├── filter/ # Pre-screening filters │ ├── FilterChain.java # Composite filter -│ ├── CapacityFilter.java # Seat availability check │ ├── TimeBasedFilter.java # Time window check -│ ├── DirectionalCompatibilityFilter.java # Direction check │ └── DistanceBasedFilter.java # Distance check │ ├── routing/ # Insertion optimization @@ -97,8 +92,7 @@ org.opentripplanner.ext.carpooling/ │ ├── validation/ # Constraint validation │ ├── CompositeValidator.java # Composite validator -│ ├── CapacityValidator.java # Capacity timeline check -│ └── DirectionalValidator.java # Backtracking check +│ └── CapacityValidator.java # Capacity timeline check │ ├── internal/ # Implementation details │ ├── DefaultCarpoolingRepository.java # In-memory repository @@ -108,8 +102,7 @@ org.opentripplanner.ext.carpooling/ │ └── SiriETCarpoolingUpdater.java # SIRI-ET message processing │ ├── util/ # Utilities -│ ├── BeelineEstimator.java # Straight-line distance estimation -│ └── DirectionalCalculator.java # Bearing and direction calculations +│ └── BeelineEstimator.java # Straight-line distance estimation │ ├── constraints/ # Constraint definitions │ └── PassengerDelayConstraints.java # Delay limits for passengers @@ -124,10 +117,8 @@ org.opentripplanner.ext.carpooling/ Filters eliminate obviously incompatible trips **without any street routing**: -1. **CapacityFilter**: Does the vehicle have available seats? -2. **TimeBasedFilter**: Is the trip timing compatible with passenger request? -3. **DirectionalCompatibilityFilter**: Are driver and passenger heading the same direction? -4. **DistanceBasedFilter**: Is the passenger's journey within reasonable distance of driver route? +1. **TimeBasedFilter**: Is the trip timing compatible with passenger request? +2. **DistanceBasedFilter**: Is the passenger's journey within reasonable distance of driver route? **Performance**: O(n) where n = number of active trips. @@ -142,20 +133,18 @@ Fast heuristic checks eliminate impossible positions **before any A* routing**: ``` For each remaining trip: 1. Generate all position combinations (pickup, dropoff) where: - - Pickup: between any two consecutive stops (1-indexed) + - Pickup: between any two consecutive stops (0-based index in modified route) - Dropoff: after pickup position 2. For each position pair, check: a. Capacity: Does insertion exceed vehicle capacity at any point? - b. Direction: Does insertion cause backtracking or U-turns? - c. Beeline delay: Do straight-line estimates exceed delay threshold? + b. Beeline delay: Do straight-line estimates exceed delay threshold? 3. Return only "viable" positions that pass all checks ``` **Key optimizations**: - **Capacity validation**: Uses `CarpoolTrip.hasCapacityForInsertion()` to check entire journey range -- **Directional filtering**: Prevents insertions that deviate >90° from route bearing - **Beeline heuristic**: Optimistic straight-line estimates eliminate positions early - **No routing yet**: All checks use geometric calculations only @@ -193,11 +182,7 @@ Ensures the proposed insertion satisfies all constraints: - Tracks passenger count at each stop - Ensures capacity never exceeds vehicle limit -2. **DirectionalValidator**: Ensures no backtracking - - Computes bearings between consecutive stops - - Rejects if bearing changes > threshold (indicates backtracking) - -3. **Deviation Budget Check**: Ensures additional time ≤ driver's stated willingness +2. **Deviation Budget Check**: Ensures additional time ≤ driver's stated willingness **All validators must pass** for an insertion to be considered valid. @@ -292,13 +277,10 @@ Configure the SIRI-ET updater to receive trip updates: Represents a driver's journey offering carpool seats: - **id**: Unique trip identifier -- **boardingArea**: Start zone for driver journey -- **alightingArea**: End zone for driver journey -- **startTime**: When driver departs -- **endTime**: When driver arrives (includes deviation budget) -- **deviationBudget**: Extra time driver is willing to spend for passengers -- **availableSeats**: Current remaining capacity -- **stops**: Ordered list of waypoints (includes booked passenger stops) +- **startTime**: When the driver departs +- **endTime**: When the driver arrives +- **totalCapacity**: Number of seats in the car, including the driver seat +- **stops**: Ordered list of waypoints; the first stop is the origin, the last is the destination, and booked passenger stops are inserted in between - **provider**: Source system identifier ### CarpoolStop @@ -306,31 +288,32 @@ Represents a driver's journey offering carpool seats: Waypoint along a carpool route: - **coordinate**: Geographic location -- **sequenceNumber**: Order in route (0-indexed) -- **estimatedArrivalTime**: When driver expects to arrive -- **stopType**: PICKUP or DROPOFF -- **passengerDelta**: Change in passenger count (+1 for pickup, -1 for dropoff) +- **aimedArrivalTime**: Planned arrival time (null for the origin stop) +- **expectedArrivalTime**: Currently expected arrival time, updated via real-time (null for the origin stop) +- **latestExpectedArrivalTime**: Latest arrival time the driver commits to (null if not provided); used to derive `deviationBudget` +- **aimedDepartureTime**: Planned departure time (null for the destination stop) +- **expectedDepartureTime**: Currently expected departure time (null for the destination stop) +- **deviationBudget**: Extra time the driver is willing to spend on deviations before reaching this stop +- **onboardCount**: Number of passengers onboard (including the driver) when departing this stop ### InsertionPosition Represents a viable pickup/dropoff position pair: -- **pickupPos**: Position to insert passenger pickup (1-indexed) -- **dropoffPos**: Position to insert passenger dropoff (1-indexed) - -Note: Positions are 1-indexed to match insertion semantics (insert between existing points). +- **pickupPos**: 0-based index of the passenger's pickup in the modified route +- **dropoffPos**: 0-based index of the passenger's dropoff in the modified route ### InsertionCandidate Result of finding optimal passenger insertion: - **trip**: The original carpool trip -- **pickupPosition**: Where to insert passenger pickup (index) -- **dropoffPosition**: Where to insert passenger dropoff (index) -- **segments**: Routed path segments for modified route -- **baselineDuration**: Original trip duration -- **totalDuration**: Modified trip duration (with passenger) -- **additionalDuration**: Extra time added (= totalDuration - baselineDuration) +- **pickupPosition**: 0-based index of the passenger's pickup in the modified route +- **dropoffPosition**: 0-based index of the passenger's dropoff in the modified route +- **routeSegments**: Routed path segments forming the complete modified route +- **stopDuration**: Dwell time added at each intermediate stop (from the car routing preferences' `pickupTime`) +- **transitStop**: Passenger's access/egress stop, if any +- **totalTripDuration**: Total trip duration including driving and stop delays, computed from `routeSegments` and `stopDuration` ## Performance Characteristics @@ -374,7 +357,6 @@ public class CustomFilter implements TripFilter { // Add to filter chain FilterChain chain = FilterChain.of( - new CapacityFilter(), new TimeBasedFilter(), new CustomFilter() ); @@ -405,17 +387,12 @@ Test individual components in isolation: ```java @Test -void testCapacityFilter() { - var filter = new CapacityFilter(); - var trip = createTripWithSeats(2); // 2 available seats +void testTimeBasedFilter() { + var filter = new TimeBasedFilter(); + var trip = createSimpleTrip(origin, destination); - // Should pass - within capacity + // Should pass - within time window assertTrue(filter.accepts(trip, pickup, dropoff, now())); - - var fullTrip = createTripWithSeats(0); // No seats - - // Should fail - no capacity - assertFalse(filter.accepts(fullTrip, pickup, dropoff, now())); } ``` diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java index fe3ffa17b95..f7564ac4df7 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/constraints/PassengerDelayConstraints.java @@ -1,110 +1,70 @@ package org.opentripplanner.ext.carpooling.constraints; import java.time.Duration; +import java.util.List; +import org.opentripplanner.ext.carpooling.model.CarpoolStop; import org.opentripplanner.ext.carpooling.routing.InsertionPosition; -import org.opentripplanner.utils.time.DurationUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Validates that inserting a new passenger does not cause excessive delays - * for existing passengers in a carpool trip. + * for existing stops in a carpool trip. *

- * Ensures that no existing passenger experiences: - * - More than {@code maxDelay} additional wait time at their pickup location - * - More than {@code maxDelay} later arrival at their dropoff location + * Ensures that no existing stop experiences more delay than its own + * {@link CarpoolStop#getDeviationBudget() deviationBudget} allows. *

* This protects the rider experience by preventing situations where accepting * one more passenger significantly inconveniences existing bookings. */ -public class PassengerDelayConstraints { +public final class PassengerDelayConstraints { private static final Logger LOG = LoggerFactory.getLogger(PassengerDelayConstraints.class); - /** - * Default maximum delay: 5 minutes. - * No existing passenger should wait more than 5 minutes longer or arrive - * more than 5 minutes later due to a new passenger insertion. - */ - public static final Duration DEFAULT_MAX_DELAY = Duration.ofMinutes(5); - - private final Duration maxDelay; - - /** - * Creates constraints with default 5-minute maximum delay. - */ - public PassengerDelayConstraints() { - this(DEFAULT_MAX_DELAY); - } - - /** - * Creates constraints with custom maximum delay. - * - * @param maxDelay Maximum acceptable delay for existing passengers - */ - public PassengerDelayConstraints(Duration maxDelay) { - this.maxDelay = DurationUtils.requireNonNegative(maxDelay); - } + private PassengerDelayConstraints() {} /** - * Checks if a passenger insertion satisfies delay constraints. + * Checks if a passenger insertion satisfies delay constraints for all existing stops. + * Each stop is checked against its own deviation budget. * * @param originalCumulativeDurations Cumulative duration to each point in original route * @param modifiedCumulativeDurations Cumulative duration to each point in modified route - * @param pickupPos Position where passenger pickup is inserted (1-indexed) - * @param dropoffPos Position where passenger dropoff is inserted (1-indexed) - * @return true if all existing passengers experience acceptable delays + * @param pickupPos 0-based index of the passenger's pickup in the modified route + * @param dropoffPos 0-based index of the passenger's dropoff in the modified route + * @param stops The ordered list of stops in the original trip + * @return true if all existing stops experience acceptable delays */ - public boolean satisfiesConstraints( + public static boolean satisfiesConstraints( Duration[] originalCumulativeDurations, Duration[] modifiedCumulativeDurations, int pickupPos, - int dropoffPos + int dropoffPos, + List stops ) { - // If no existing stops (only boarding and alighting), no constraint to check - if (originalCumulativeDurations.length <= 2) { - return true; - } - - // Check delay at each existing stop (exclude boarding at 0 and alighting at end) + // Check delay at each existing stop (exclude origin at index 0) for ( int originalIndex = 1; - originalIndex < originalCumulativeDurations.length - 1; + originalIndex < originalCumulativeDurations.length; originalIndex++ ) { int modifiedIndex = InsertionPosition.mapOriginalIndex(originalIndex, pickupPos, dropoffPos); - Duration originalTime = originalCumulativeDurations[originalIndex]; - Duration modifiedTime = modifiedCumulativeDurations[modifiedIndex]; - Duration delay = modifiedTime.minus(originalTime); + Duration delay = modifiedCumulativeDurations[modifiedIndex].minus( + originalCumulativeDurations[originalIndex] + ); + Duration stopBudget = stops.get(originalIndex).getDeviationBudget(); - if (delay.compareTo(maxDelay) > 0) { + if (delay.compareTo(stopBudget) > 0) { LOG.debug( - "Insertion rejected: stop at position {} delayed by {}s (max: {}s)", + "Stop at position {} delayed by {}s exceeds budget of {}s", originalIndex, delay.getSeconds(), - maxDelay.getSeconds() + stopBudget.getSeconds() ); return false; } - - LOG.trace( - "Stop at position {} delay: {}s (acceptable, max: {}s)", - originalIndex, - delay.getSeconds(), - maxDelay.getSeconds() - ); } return true; } - - /** - * Gets the configured maximum delay. - * - * @return Maximum delay duration - */ - public Duration getMaxDelay() { - return maxDelay; - } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/CapacityFilter.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/CapacityFilter.java deleted file mode 100644 index 1d296cc0956..00000000000 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/CapacityFilter.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.opentripplanner.ext.carpooling.filter; - -import java.time.Duration; -import java.time.Instant; -import org.opentripplanner.ext.carpooling.model.CarpoolTrip; -import org.opentripplanner.street.geometry.WgsCoordinate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Filters trips based on available capacity. - *

- * This is a fast pre-filter that checks if the trip has any capacity at all. - * More detailed per-position capacity checking happens during insertion validation. - */ -public class CapacityFilter implements TripFilter { - - private static final Logger LOG = LoggerFactory.getLogger(CapacityFilter.class); - - private boolean accepts(CarpoolTrip trip) { - boolean hasCapacity = trip.availableSeats() > 0; - - if (!hasCapacity) { - LOG.debug("Trip {} rejected by capacity filter: no available seats", trip.getId()); - } - - return hasCapacity; - } - - @Override - public boolean accepts( - CarpoolTrip trip, - WgsCoordinate passengerPickup, - WgsCoordinate passengerDropoff - ) { - return accepts(trip); - } - - @Override - public boolean acceptsAccessEgress( - CarpoolTrip trip, - WgsCoordinate coordinateOfPassenger, - Instant passengerDepartureTime, - Duration searchWindow - ) { - return accepts(trip); - } -} diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilter.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilter.java deleted file mode 100644 index 9cd14a1c2a1..00000000000 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/DirectionalCompatibilityFilter.java +++ /dev/null @@ -1,140 +0,0 @@ -package org.opentripplanner.ext.carpooling.filter; - -import java.time.Duration; -import java.time.Instant; -import java.util.List; -import org.opentripplanner.ext.carpooling.model.CarpoolTrip; -import org.opentripplanner.street.geometry.DirectionUtils; -import org.opentripplanner.street.geometry.WgsCoordinate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Filters trips based on directional compatibility with the passenger journey. - *

- * This prevents carpooling from becoming a taxi service by ensuring trips and - * passengers are going in generally the same direction. Uses optimized segment-based - * analysis to handle routes that take detours (e.g., driving around a lake). - *

- */ -public class DirectionalCompatibilityFilter implements TripFilter { - - private static final Logger LOG = LoggerFactory.getLogger(DirectionalCompatibilityFilter.class); - - /** - * Default maximum bearing difference for compatibility. - * 60° allows for reasonable detours while preventing perpendicular or opposite directions. - */ - public static final double DEFAULT_BEARING_TOLERANCE_DEGREES = 60.0; - - private final double bearingToleranceDegrees; - - public DirectionalCompatibilityFilter() { - this(DEFAULT_BEARING_TOLERANCE_DEGREES); - } - - public DirectionalCompatibilityFilter(double bearingToleranceDegrees) { - this.bearingToleranceDegrees = bearingToleranceDegrees; - } - - @Override - public boolean accepts( - CarpoolTrip trip, - WgsCoordinate passengerPickup, - WgsCoordinate passengerDropoff - ) { - List routePoints = trip.routePoints(); - - if (routePoints.size() < 2) { - LOG.warn("Trip {} has fewer than 2 route points, rejecting", trip.getId()); - return false; - } - - double passengerBearing = DirectionUtils.getAzimuth( - passengerPickup.asJtsCoordinate(), - passengerDropoff.asJtsCoordinate() - ); - - for (int i = 0; i < routePoints.size() - 1; i++) { - if (isSegmentCompatible(routePoints.get(i), routePoints.get(i + 1), passengerBearing)) { - LOG.debug( - "Trip {} accepted: passenger journey aligns with segment {} ({} to {})", - trip.getId(), - i, - routePoints.get(i), - routePoints.get(i + 1) - ); - return true; - } - } - - // Check full route as fallback - if (isSegmentCompatible(routePoints.getFirst(), routePoints.getLast(), passengerBearing)) { - LOG.debug( - "Trip {} accepted: passenger journey aligns with full route ({} to {})", - trip.getId(), - routePoints.getFirst(), - routePoints.getLast() - ); - return true; - } - - LOG.debug( - "Trip {} rejected by directional filter: passenger journey (bearing {}°) not aligned with any route segments", - trip.getId(), - Math.round(passengerBearing) - ); - return false; - } - - @Override - public boolean acceptsAccessEgress( - CarpoolTrip trip, - WgsCoordinate coordinateOfPassenger, - Instant passengerDepartureTime, - Duration searchWindow - ) { - var tripStartCoordinate = trip.routePoints().getFirst().asJtsCoordinate(); - var tripEndCoordinate = trip.routePoints().getLast().asJtsCoordinate(); - var passengerCoordJts = coordinateOfPassenger.asJtsCoordinate(); - - var tripBearing = DirectionUtils.getAzimuth(tripStartCoordinate, tripEndCoordinate); - var startToPassengerBearing = DirectionUtils.getAzimuth(tripStartCoordinate, passengerCoordJts); - var endToPassengerBearing = DirectionUtils.getAzimuth(passengerCoordJts, tripEndCoordinate); - - return ( - bearingsAreWithinTolerance(tripBearing, startToPassengerBearing) && - bearingsAreWithinTolerance(tripBearing, endToPassengerBearing) - ); - } - - double getBearingToleranceDegrees() { - return bearingToleranceDegrees; - } - - /** - * Checks if a segment is directionally compatible with the passenger journey. - * - * @param segmentStart Start coordinate of the segment - * @param segmentEnd End coordinate of the segment - * @param passengerBearing Bearing of passenger journey - * @return true if segment bearing is within tolerance of passenger bearing - */ - private boolean isSegmentCompatible( - WgsCoordinate segmentStart, - WgsCoordinate segmentEnd, - double passengerBearing - ) { - double segmentBearing = DirectionUtils.getAzimuth( - segmentStart.asJtsCoordinate(), - segmentEnd.asJtsCoordinate() - ); - - return bearingsAreWithinTolerance(segmentBearing, passengerBearing); - } - - private boolean bearingsAreWithinTolerance(double bearing1, double bearing2) { - double bearingDiff = DirectionUtils.bearingDifference(bearing1, bearing2); - return bearingDiff <= bearingToleranceDegrees; - } -} diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/FilterChain.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/FilterChain.java index 8031328095c..f3caa61d9f9 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/FilterChain.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/FilterChain.java @@ -13,10 +13,8 @@ * as soon as one filter rejects a trip, evaluation stops. *

* The standard filter chain includes (in order of performance impact): - * 1. CapacityFilter - Very fast (O(1)) - * 2. TimeBasedFilter - Very fast (O(1)) - * 3. DistanceBasedFilter - Fast (O(1) with 4 distance calculations) - * 4. DirectionalCompatibilityFilter - Medium (O(n) with n = number of stops) + * 1. TimeBasedFilter - Very fast (O(1)) + * 2. DistanceBasedFilter - Fast (O(1) with 4 distance calculations) */ public class FilterChain implements TripFilter { @@ -33,14 +31,7 @@ public FilterChain(List filters) { * the benefit of short-circuit evaluation. */ public static FilterChain standard() { - return new FilterChain( - List.of( - new CapacityFilter(), - new TimeBasedFilter(), - new DistanceBasedFilter(), - new DirectionalCompatibilityFilter() - ) - ); + return new FilterChain(List.of(new TimeBasedFilter(), new DistanceBasedFilter())); } @Override diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/TripFilter.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/TripFilter.java index 92086bf3ff5..a8a011c3c6a 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/TripFilter.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/filter/TripFilter.java @@ -9,7 +9,7 @@ * Interface for filtering carpool trips before expensive routing calculations. *

* Filters are applied as a pre-screening mechanism to quickly eliminate - * incompatible trips based on various criteria (direction, capacity, time, distance, etc.). + * incompatible trips based on various criteria (capacity, time, distance, etc.). *

* Supports both direct routing (pickup + dropoff) and access/egress routing * (single passenger coordinate near a transit stop). diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java index a08cc8c9fa3..2347a58d1e7 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/internal/CarpoolItineraryMapper.java @@ -1,7 +1,5 @@ package org.opentripplanner.ext.carpooling.internal; -import java.time.Duration; -import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; import javax.annotation.Nullable; @@ -11,10 +9,8 @@ import org.opentripplanner.ext.carpooling.model.CarpoolLeg; import org.opentripplanner.ext.carpooling.routing.CarpoolAccessEgress; import org.opentripplanner.ext.carpooling.routing.InsertionCandidate; -import org.opentripplanner.framework.time.ZoneIdFallback; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Place; -import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.street.geometry.GeometryUtils; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.Vertex; @@ -42,14 +38,12 @@ * *

Time Calculation

*

- * The passenger's start time is the later of: - *

    - *
  1. The passenger's requested departure time
  2. - *
  3. When the driver arrives at the pickup location
  4. - *
- *

- * This ensures the itinerary reflects realistic timing: passengers can't board before the - * driver arrives, but they also won't board earlier than they wanted to depart. + * The passenger's start time is the moment the driver arrives at the pickup location + * (trip start + pickup travel); the boarding dwell is included in the leg's duration, not + * added before it. The start time is not shifted to match the passenger's requested + * departure time: the driver is on a committed schedule and cannot wait. Whether the + * passenger should show up early, or whether a trip starting before the requested time + * should be matched at all, is a filtering concern and lives upstream of this mapper. * *

Geometry and Cost

*

@@ -71,25 +65,17 @@ */ public class CarpoolItineraryMapper { - private final ZoneId timeZone; private final ZonedDateTime transitSearchTimeZero; /** - * Creates a new carpool itinerary mapper with the specified timezone. - *

- * The timezone is used to convert passenger requested departure times from Instant to - * ZonedDateTime for comparison with driver pickup times. - *

- * @param timeZone the timezone for time conversions, typically from TransitService.getTimeZone() - * @param transitSearchTimeZero the base time for access egress requests. It is not used for access / egress + * @param transitSearchTimeZero the base time for access egress requests; not used for direct */ - public CarpoolItineraryMapper(ZoneId timeZone, ZonedDateTime transitSearchTimeZero) { - this.timeZone = ZoneIdFallback.zoneId(timeZone); + public CarpoolItineraryMapper(ZonedDateTime transitSearchTimeZero) { this.transitSearchTimeZero = transitSearchTimeZero; } - public CarpoolItineraryMapper(ZoneId timeZone) { - this(timeZone, null); + public CarpoolItineraryMapper() { + this(null); } /** @@ -101,11 +87,15 @@ public CarpoolItineraryMapper(ZoneId timeZone) { * *

Time Calculation Details

*

- * The method calculates three key times: + * Start and end times come entirely from the driver's schedule: *

    - *
  1. Driver pickup arrival: Driver's start time + pickup segment durations
  2. - *
  3. Passenger start: max(requested time, driver arrival time)
  4. - *
  5. Passenger end: start time + shared segment durations
  6. + *
  7. Start: {@code trip.startTime() +} + * {@link InsertionCandidate#getDurationUntilPickupArrival()} — the moment the driver + * arrives at the pickup point.
  8. + *
  9. End: {@code start +} + * {@link InsertionCandidate#getPassengerRideDuration()}, which already includes the + * boarding dwell at the pickup and any intermediate stop delays along the shared + * segments.
  10. *
* *

Null Return Cases

@@ -113,41 +103,20 @@ public CarpoolItineraryMapper(ZoneId timeZone) { * Returns {@code null} if the candidate has no shared segments, which should never happen * for valid insertion candidates but serves as a safety check. * - * @param request the original routing request containing passenger preferences and timing * @param candidate the insertion candidate containing route segments and trip details * @return an itinerary with a single carpool leg, or null if shared segments are empty * (should not occur for valid candidates) */ @Nullable - public Itinerary toItinerary(RouteRequest request, InsertionCandidate candidate) { + public Itinerary toItinerary(InsertionCandidate candidate) { var sharedSegments = candidate.getSharedSegments(); if (sharedSegments.isEmpty()) { return null; } - var pickupSegments = candidate.getPickupSegments(); - Duration pickupDuration = Duration.ZERO; - for (var segment : pickupSegments) { - pickupDuration = pickupDuration.plus( - Duration.between(segment.states.getFirst().getTime(), segment.states.getLast().getTime()) - ); - } - - var driverPickupTime = candidate.trip().startTime().plus(pickupDuration); + var startTime = candidate.trip().startTime().plus(candidate.getDurationUntilPickupArrival()); - var startTime = request.dateTime().isAfter(driverPickupTime.toInstant()) - ? request.dateTime().atZone(timeZone) - : driverPickupTime; - - // Calculate shared journey duration - Duration carpoolDuration = Duration.ZERO; - for (var segment : sharedSegments) { - carpoolDuration = carpoolDuration.plus( - Duration.between(segment.states.getFirst().getTime(), segment.states.getLast().getTime()) - ); - } - - var endTime = startTime.plus(carpoolDuration); + var endTime = startTime.plus(candidate.getPassengerRideDuration()); var firstSegment = sharedSegments.getFirst(); var lastSegment = sharedSegments.getLast(); @@ -193,7 +162,6 @@ public Itinerary toItinerary(CarpoolAccessEgress accessEgress) { .withEndTime(endTime) .withFrom(Place.normal(fromVertex, new NonLocalizedString("Carpool boarding"))) .withTo(Place.normal(toVertex, new NonLocalizedString("Carpool alighting"))) - .withGeometry(GeometryUtils.concatenateLineStrings(allEdges, Edge::getGeometry)) .withDistanceMeters(allEdges.stream().mapToDouble(Edge::getDistanceMeters).sum()) .withGeneralizedCost((int) cost) .withGeometry(geometry) diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java index d395025a740..bc8147a8dae 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStop.java @@ -1,141 +1,67 @@ package org.opentripplanner.ext.carpooling.model; +import java.time.Duration; import java.time.ZonedDateTime; import java.util.Objects; -import java.util.function.IntSupplier; import javax.annotation.Nullable; -import org.locationtech.jts.geom.Geometry; -import org.opentripplanner.core.model.i18n.I18NString; -import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.transit.model.framework.AbstractTransitEntity; -import org.opentripplanner.transit.model.site.StopLocation; -import org.opentripplanner.transit.model.site.StopType; /** - * Represents a stop along a carpool trip route with passenger pickup/drop-off information. - * Each stop tracks the passenger delta (number of passengers picked up or dropped off). + * Represents a stop along a carpool trip route with occupancy and timing information. * Stops are ordered sequentially along the route. */ -public class CarpoolStop - extends AbstractTransitEntity - implements StopLocation { - - private final int index; - private final I18NString name; - private final I18NString description; - private final I18NString url; +public class CarpoolStop extends AbstractTransitEntity { + + /** Default onboard count per stop (1 = driver only) when no occupancy information is provided. */ + public static final int DEFAULT_ONBOARD_COUNT = 1; + + /** + * Default per-stop deviation budget used when the SIRI feed does not supply a + * {@code latestExpectedArrivalTime} for the stop. + */ + public static final Duration DEFAULT_DEVIATION_BUDGET = Duration.ofMinutes(15); + private final WgsCoordinate coordinate; - private final Geometry geometry; - private final CarpoolStopType carpoolStopType; - private final ZonedDateTime expectedArrivalTime; private final ZonedDateTime aimedArrivalTime; - private final ZonedDateTime expectedDepartureTime; + private final ZonedDateTime expectedArrivalTime; + private final ZonedDateTime latestExpectedArrivalTime; private final ZonedDateTime aimedDepartureTime; - private final int sequenceNumber; - private final int passengerDelta; + private final ZonedDateTime expectedDepartureTime; + private final int onboardCount; + private final Duration deviationBudget; public CarpoolStop(CarpoolStopBuilder builder) { super(builder.getId()); - this.index = builder.createIndex(); - // According to the spec, stop location names are optional for flex zones, so we set the ID as the dummy name. - if (builder.name() == null) { - this.name = new NonLocalizedString(builder.getId().toString()); - } else { - this.name = builder.name(); - } - this.description = builder.description(); - this.url = builder.url(); this.coordinate = Objects.requireNonNull(builder.coordinate()); - this.geometry = builder.geometry(); - this.carpoolStopType = builder.carpoolStopType(); this.expectedArrivalTime = builder.expectedArrivalTime(); this.aimedArrivalTime = builder.aimedArrivalTime(); + this.latestExpectedArrivalTime = builder.latestExpectedArrivalTime(); this.expectedDepartureTime = builder.expectedDepartureTime(); this.aimedDepartureTime = builder.aimedDepartureTime(); - this.sequenceNumber = builder.sequenceNumber(); - this.passengerDelta = builder.passengerDelta(); + this.onboardCount = builder.onboardCount(); + this.deviationBudget = builder.deviationBudget(); } - public static CarpoolStopBuilder of(FeedScopedId id, IntSupplier indexCounter) { - return new CarpoolStopBuilder(id, indexCounter); + public static CarpoolStopBuilder of(FeedScopedId id) { + return new CarpoolStopBuilder(id); } public static CarpoolStopBuilder of(CarpoolStop carpoolStop) { return new CarpoolStopBuilder(carpoolStop); } - // StopLocation interface implementation - delegate to the underlying AreaStop - - @Override - public int getIndex() { - return index; - } - - @Override - @Nullable - public I18NString getName() { - return name; - } - - @Override - @Nullable - public I18NString getDescription() { - return description; - } - - @Override - @Nullable - public I18NString getUrl() { - return url; - } - - @Override - public StopType getStopType() { - return StopType.REGULAR; - } - - @Override - @Nullable - public String getCode() { - return null; - } - - @Override - @Nullable - public String getPlatformCode() { - return null; - } - - @Override public WgsCoordinate getCoordinate() { return coordinate; } - @Override - @Nullable - public Geometry getGeometry() { - return geometry; - } - - @Override - public boolean isPartOfStation() { - return false; - } - - @Override - public boolean isPartOfSameStationAs(StopLocation alternativeStop) { - return false; - } - - // Carpool-specific methods - /** - * @return The type of carpool operation allowed at this stop + * @return The aimed arrival time, or null if not applicable (e.g., origin stop) */ - public CarpoolStopType getCarpoolStopType() { - return carpoolStopType; + @Nullable + public ZonedDateTime getAimedArrivalTime() { + return aimedArrivalTime; } /** @@ -147,46 +73,45 @@ public ZonedDateTime getExpectedArrivalTime() { } /** - * @return The aimed arrival time, or null if not applicable (e.g., origin stop) + * @return The latest expected arrival time, or null if not provided */ @Nullable - public ZonedDateTime getAimedArrivalTime() { - return aimedArrivalTime; + public ZonedDateTime getLatestExpectedArrivalTime() { + return latestExpectedArrivalTime; } /** - * @return The expected departure time, or null if not applicable (e.g., destination stop) + * @return The aimed departure time, or null if not applicable (e.g., destination stop) */ @Nullable - public ZonedDateTime getExpectedDepartureTime() { - return expectedDepartureTime; - } - - public int getSequenceNumber() { - return sequenceNumber; + public ZonedDateTime getAimedDepartureTime() { + return aimedDepartureTime; } /** - * @return The aimed departure time, or null if not applicable (e.g., destination stop) + * @return The expected departure time, or null if not applicable (e.g., destination stop) */ @Nullable - public ZonedDateTime getAimedDepartureTime() { - return aimedDepartureTime; + public ZonedDateTime getExpectedDepartureTime() { + return expectedDepartureTime; } /** - * Returns the primary timing for this stop, preferring aimed arrival time. - * This provides backward compatibility for code that expects a single time value. - * - * @return The aimed arrival time if set, otherwise aimed departure time + * @return The number of passengers onboard (including the driver) when departing this stop */ - @Nullable - public ZonedDateTime getEstimatedTime() { - return aimedArrivalTime != null ? aimedArrivalTime : aimedDepartureTime; + public int getOnboardCount() { + return onboardCount; } - public int getPassengerDelta() { - return passengerDelta; + /** + * Returns the remaining slack the carpool may consume before this stop without breaking the + * driver's commitment to passengers already onboard. This is not the original + * commitment from the SIRI feed: as the trip is updated with additional SIRI messages, + * the budget shrinks as prior detours eat into it. + * A value of {@link Duration#ZERO} means no further deviation is acceptable here. + */ + public Duration getDeviationBudget() { + return deviationBudget; } @Override diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java index 0bfc5518311..c8b496f88a4 100755 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopBuilder.java @@ -1,55 +1,42 @@ package org.opentripplanner.ext.carpooling.model; +import java.time.Duration; import java.time.ZonedDateTime; import java.util.Objects; -import java.util.function.IntSupplier; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.GeometryFactory; -import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.transit.model.framework.AbstractEntityBuilder; +/** + * Builder for {@link CarpoolStop} instances. + */ public class CarpoolStopBuilder extends AbstractEntityBuilder { - private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); - private final IntSupplier indexCounter; - private I18NString name; - private I18NString description; - private I18NString url; private WgsCoordinate coordinate; - private Geometry geometry; - private CarpoolStopType carpoolStopType; private ZonedDateTime expectedArrivalTime; private ZonedDateTime aimedArrivalTime; + private ZonedDateTime latestExpectedArrivalTime; private ZonedDateTime expectedDepartureTime; private ZonedDateTime aimedDepartureTime; - private int sequenceNumber; - private int passengerDelta; + private int onboardCount = CarpoolStop.DEFAULT_ONBOARD_COUNT; - CarpoolStopBuilder(FeedScopedId id, IntSupplier indexCounter) { + private Duration deviationBudget = CarpoolStop.DEFAULT_DEVIATION_BUDGET; + + CarpoolStopBuilder(FeedScopedId id) { super(id); - this.indexCounter = Objects.requireNonNull(indexCounter); } CarpoolStopBuilder(CarpoolStop original) { super(original); - this.indexCounter = original::getIndex; - // Optional fields - this.name = original.getName(); - this.description = original.getDescription(); - this.url = original.getUrl(); this.coordinate = original.getCoordinate(); - this.geometry = original.getGeometry(); - this.sequenceNumber = original.getSequenceNumber(); - - this.carpoolStopType = original.getCarpoolStopType(); this.expectedArrivalTime = original.getExpectedArrivalTime(); this.aimedArrivalTime = original.getAimedArrivalTime(); + this.latestExpectedArrivalTime = original.getLatestExpectedArrivalTime(); this.expectedDepartureTime = original.getExpectedDepartureTime(); this.aimedDepartureTime = original.getAimedDepartureTime(); - this.passengerDelta = original.getPassengerDelta(); + this.onboardCount = original.getOnboardCount(); + this.deviationBudget = original.getDeviationBudget(); } @Override @@ -57,29 +44,8 @@ protected CarpoolStop buildFromValues() { return new CarpoolStop(this); } - public CarpoolStopBuilder withName(I18NString name) { - this.name = name; - return this; - } - - public CarpoolStopBuilder withDescription(I18NString description) { - this.description = description; - return this; - } - - public CarpoolStopBuilder withUrl(I18NString url) { - this.url = url; - return this; - } - public CarpoolStopBuilder withCoordinate(WgsCoordinate coordinate) { this.coordinate = coordinate; - this.geometry = toGeometry(coordinate); - return this; - } - - public CarpoolStopBuilder withCarpoolStopType(CarpoolStopType carpoolStopType) { - this.carpoolStopType = carpoolStopType; return this; } @@ -93,6 +59,11 @@ public CarpoolStopBuilder withAimedArrivalTime(ZonedDateTime aimedArrivalTime) { return this; } + public CarpoolStopBuilder withLatestExpectedArrivalTime(ZonedDateTime latestExpectedArrivalTime) { + this.latestExpectedArrivalTime = latestExpectedArrivalTime; + return this; + } + public CarpoolStopBuilder withExpectedDepartureTime(ZonedDateTime expectedDepartureTime) { this.expectedDepartureTime = expectedDepartureTime; return this; @@ -103,44 +74,29 @@ public CarpoolStopBuilder withAimedDepartureTime(ZonedDateTime aimedDepartureTim return this; } - public CarpoolStopBuilder withSequenceNumber(int sequenceNumber) { - this.sequenceNumber = sequenceNumber; + public CarpoolStopBuilder withOnboardCount(int onboardCount) { + this.onboardCount = onboardCount; return this; } - public CarpoolStopBuilder withPassengerDelta(int passengerDelta) { - this.passengerDelta = passengerDelta; + /** + * Sets the per-stop deviation budget. See {@link CarpoolStop#getDeviationBudget()} for the + * semantics of the value. + * + * @param deviationBudget remaining slack at this stop; must be non-null. Use + * {@link Duration#ZERO} for stops where no further deviation is + * acceptable (always for the trip origin). + * @throws NullPointerException if {@code deviationBudget} is null + */ + public CarpoolStopBuilder withDeviationBudget(Duration deviationBudget) { + this.deviationBudget = Objects.requireNonNull(deviationBudget); return this; } - int createIndex() { - return indexCounter.getAsInt(); - } - - public I18NString name() { - return name; - } - - public I18NString description() { - return description; - } - - public I18NString url() { - return url; - } - public WgsCoordinate coordinate() { return coordinate; } - public Geometry geometry() { - return geometry; - } - - public CarpoolStopType carpoolStopType() { - return carpoolStopType; - } - public ZonedDateTime expectedArrivalTime() { return expectedArrivalTime; } @@ -149,6 +105,10 @@ public ZonedDateTime aimedArrivalTime() { return aimedArrivalTime; } + public ZonedDateTime latestExpectedArrivalTime() { + return latestExpectedArrivalTime; + } + public ZonedDateTime expectedDepartureTime() { return expectedDepartureTime; } @@ -157,15 +117,11 @@ public ZonedDateTime aimedDepartureTime() { return aimedDepartureTime; } - public int sequenceNumber() { - return sequenceNumber; - } - - public int passengerDelta() { - return passengerDelta; + public int onboardCount() { + return onboardCount; } - private Geometry toGeometry(WgsCoordinate coordinate) { - return GEOMETRY_FACTORY.createPoint(coordinate.asJtsCoordinate()); + public Duration deviationBudget() { + return deviationBudget; } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopType.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopType.java deleted file mode 100644 index 2235fd78280..00000000000 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolStopType.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.opentripplanner.ext.carpooling.model; - -/** - * The type of carpool stop operation. - */ -public enum CarpoolStopType { - /** Only passengers can be picked up at this stop */ - PICKUP_ONLY, - /** Only passengers can be dropped off at this stop */ - DROP_OFF_ONLY, - /** Both pickup and drop-off are allowed */ - PICKUP_AND_DROP_OFF, -} diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTrip.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTrip.java index bb6d0e1d09b..f20e5ec4691 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTrip.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTrip.java @@ -1,6 +1,5 @@ package org.opentripplanner.ext.carpooling.model; -import java.time.Duration; import java.time.ZonedDateTime; import java.util.Collections; import java.util.List; @@ -14,7 +13,7 @@ * Represents a driver's carpool journey with planned route, timing, and passenger capacity. *

* A carpool trip models a driver offering their vehicle journey for passengers to join. It includes - * the driver's planned route as a sequence of stops, available seating capacity, and timing + * the driver's planned route as a sequence of stops, total vehicle capacity, and timing * constraints including a deviation budget that allows the driver to slightly adjust their route * to accommodate passengers. * @@ -23,9 +22,7 @@ *

  • Origin/Destination Areas: Start and end zones for the driver's journey
  • *
  • Stops: Ordered sequence of waypoints along the route where passengers * can be picked up or dropped off. Stops are dynamically updated as bookings occur.
  • - *
  • Deviation Budget: Maximum additional time the driver is willing to spend - * to pick up/drop off passengers (e.g., 5 minutes). This represents the driver's flexibility.
  • - *
  • Available Seats: Current passenger capacity remaining in the vehicle
  • + *
  • Total Capacity: Number of seats in the car, including the driver seat
  • * * *

    Data Source

    @@ -57,14 +54,13 @@ public class CarpoolTrip extends AbstractTransitEntity implements LogInfo { + /** Default total capacity (including driver) when no capacity information is provided. */ + public static final int DEFAULT_TOTAL_CAPACITY = 5; + private final ZonedDateTime startTime; private final ZonedDateTime endTime; private final String provider; - - // The amount of time the trip can deviate from the scheduled time in order to pick up or drop off - // a passenger. - private final Duration deviationBudget; - private final int availableSeats; + private final int totalCapacity; // Ordered list of stops along the carpool route where passengers can be picked up or dropped off private final List stops; @@ -74,8 +70,7 @@ public CarpoolTrip(CarpoolTripBuilder builder) { this.startTime = builder.startTime(); this.endTime = builder.endTime(); this.provider = builder.provider(); - this.availableSeats = builder.availableSeats(); - this.deviationBudget = builder.deviationBudget(); + this.totalCapacity = builder.totalCapacity(); this.stops = Collections.unmodifiableList(builder.stops()); } @@ -117,23 +112,22 @@ public String provider() { return provider; } - public Duration deviationBudget() { - return deviationBudget; - } - - public int availableSeats() { - return availableSeats; + /** + * @return Total number of seats in the vehicle, including the driver seat + */ + public int totalCapacity() { + return totalCapacity; } /** * Returns the ordered sequence of stops along the carpool route. *

    * Stops include both the driver's originally planned stops and any dynamically added stops - * for passenger pickups and dropoffs. The list is ordered by sequence number, representing - * the order in which stops are visited along the route. + * for passenger pickups and dropoffs. The list is ordered by visit order along the route: + * the first element is the origin and the last is the destination. * - * @return an immutable list of stops along the carpool route, ordered by sequence number, - * never null but may be empty for trips with no intermediate stops + * @return an immutable list of stops along the carpool route, in visit order; never null, + * and always contains at least the origin and destination */ public List stops() { return stops; @@ -152,65 +146,67 @@ public List routePoints() { } /** - * Calculates the number of passengers in the vehicle after visiting the specified position. - *

    - * Position semantics: - * - Position 0: Before any stops → 0 passengers - * - Position N: After Nth stop → cumulative passenger delta up to stop N + * Returns the number of passengers onboard the vehicle when departing the given stop. * - * @param position The position index (0 = before any stops, 1 = after first stop, etc.) - * @return Number of passengers after this position - * @throws IllegalArgumentException if position is negative or greater than stops.size() + * @param stopIndex The 0-based index of the stop in the stop list + * @return Number of passengers onboard when departing from this stop + * @throws IllegalArgumentException if stopIndex is out of bounds */ - public int getPassengerCountAtPosition(int position) { - if (position < 0) { - throw new IllegalArgumentException("Position must be non-negative, got: " + position); - } - - if (position > stops.size()) { + public int getPassengerCountAtDepartureOfStop(int stopIndex) { + if (stopIndex < 0 || stopIndex >= stops.size()) { throw new IllegalArgumentException( - "Position " + position + " exceeds valid range (0 to " + stops.size() + ")" + "Stop index " + stopIndex + " is out of bounds (0 to " + (stops.size() - 1) + ")" ); } - // Position 0 is before any stops - if (position == 0) { - return 0; - } - - // Accumulate passenger deltas up to this position - int count = 0; - for (int i = 0; i < position; i++) { - count += stops.get(i).getPassengerDelta(); - } - - return count; + return stops.get(stopIndex).getOnboardCount(); } /** - * Checks if there's capacity to add passengers throughout a range of positions. + * Checks if there's capacity to insert a passenger at the given pickup and dropoff positions + * in the modified route. + *

    + * The positions are 0-based indices of the passenger's pickup and dropoff stops in the + * modified route (the route after the passenger's stops have been inserted). For example, + * with original stops [Origin, A, B, Destination] and pickupPosition=1, dropoffPosition=3: + * the modified route is [Origin, Pickup, A, Dropoff, B, Destination]. + * All stops between (inclusive) pickupPosition - 1 and dropoffPosition - 2 are checked for capacity. + * In the example this is between stops 0 and 1, meaning that stops Origin and A need to have sufficient + * capacity for {@code additionalPassengers} extra passengers. *

    - * This validates that adding passengers won't exceed vehicle capacity at any point - * between pickup and dropoff positions. * - * @param pickupPosition The pickup position (1-indexed, inclusive) - * @param dropoffPosition The dropoff position (1-indexed, exclusive) + * @param pickupPosition 0-based index of the passenger's pickup in the modified route. + * Must be >= 1 (position 0 is the driver's origin). + * @param dropoffPosition 0-based index of the passenger's dropoff in the modified route. + * Must be > pickupPosition. * @param additionalPassengers Number of passengers to add (typically 1) * @return true if capacity is available throughout the entire range, false otherwise + * @throws IllegalArgumentException if pickupPosition < 1 or dropoffPosition <= pickupPosition */ public boolean hasCapacityForInsertion( int pickupPosition, int dropoffPosition, int additionalPassengers ) { - int pickupPassengers = getPassengerCountAtPosition(pickupPosition - 1); - if (pickupPassengers + additionalPassengers > availableSeats) { - return false; + if (pickupPosition < 1) { + throw new IllegalArgumentException( + "pickupPosition must be >= 1 (position 0 is the driver's origin), got: " + pickupPosition + ); } + if (dropoffPosition <= pickupPosition) { + throw new IllegalArgumentException( + "dropoffPosition must be > pickupPosition, got: pickupPosition=" + + pickupPosition + + ", dropoffPosition=" + + dropoffPosition + ); + } + + int firstOriginalStop = pickupPosition - 1; + int lastOriginalStop = dropoffPosition - 2; - for (int pos = pickupPosition; pos < dropoffPosition; pos++) { - int currentPassengers = getPassengerCountAtPosition(pos); - if (currentPassengers + additionalPassengers > availableSeats) { + for (int i = firstOriginalStop; i <= lastOriginalStop; i++) { + if (getPassengerCountAtDepartureOfStop(i) + additionalPassengers > totalCapacity) { return false; } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java index 106232ff62e..b9969b5b430 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/model/CarpoolTripBuilder.java @@ -1,20 +1,20 @@ package org.opentripplanner.ext.carpooling.model; -import java.time.Duration; import java.time.ZonedDateTime; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.transit.model.framework.AbstractEntityBuilder; +/** + * Builder for {@link CarpoolTrip} instances. + */ public class CarpoolTripBuilder extends AbstractEntityBuilder { private ZonedDateTime startTime; private ZonedDateTime endTime; private String provider; - private Duration deviationBudget = Duration.ofMinutes(15); - private int availableSeats = 1; + private int totalCapacity = CarpoolTrip.DEFAULT_TOTAL_CAPACITY; private List stops = new ArrayList<>(); public CarpoolTripBuilder(FeedScopedId id) { @@ -26,8 +26,7 @@ public CarpoolTripBuilder(CarpoolTrip original) { this.startTime = original.startTime(); this.endTime = original.endTime(); this.provider = original.provider(); - this.deviationBudget = original.deviationBudget(); - this.availableSeats = original.availableSeats(); + this.totalCapacity = original.totalCapacity(); this.stops = new ArrayList<>(original.stops()); } @@ -46,13 +45,8 @@ public CarpoolTripBuilder withProvider(String provider) { return this; } - public CarpoolTripBuilder withDeviationBudget(Duration deviationBudget) { - this.deviationBudget = deviationBudget; - return this; - } - - public CarpoolTripBuilder withAvailableSeats(int availableSeats) { - this.availableSeats = availableSeats; + public CarpoolTripBuilder withTotalCapacity(int totalCapacity) { + this.totalCapacity = totalCapacity; return this; } @@ -68,12 +62,8 @@ public String provider() { return provider; } - public Duration deviationBudget() { - return deviationBudget; - } - - public int availableSeats() { - return availableSeats; + public int totalCapacity() { + return totalCapacity; } public CarpoolTripBuilder withStops(List stops) { @@ -81,18 +71,6 @@ public CarpoolTripBuilder withStops(List stops) { return this; } - public CarpoolTripBuilder addStop(CarpoolStop stop) { - this.stops.add(stop); - // Sort stops by sequence number to maintain order - this.stops.sort(Comparator.comparingInt(CarpoolStop::getSequenceNumber)); - return this; - } - - public CarpoolTripBuilder clearStops() { - this.stops.clear(); - return this; - } - public List stops() { return stops; } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/CarpoolAccessEgress.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/CarpoolAccessEgress.java index 35643aa1755..2e629ffd5e1 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/CarpoolAccessEgress.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/CarpoolAccessEgress.java @@ -14,12 +14,19 @@ public class CarpoolAccessEgress implements RoutingAccessEgress { /** - * The departure time of the passenger in seconds since transitSearchTimeZero. + * The Raptor departure time of this access/egress leg, in seconds since + * {@code transitSearchTimeZero}. For a carpool leg this is the moment the car arrives at the + * pickup: the passenger must be ready by this instant, since the driver is on a committed + * schedule and cannot wait. The boarding dwell at the pickup is part of + * {@link #durationInSeconds}, not of the time before departure. */ private final int departureTimeOfPassenger; /** - * The arrival time of the passenger in seconds since transitSearchTimeZero. + * The Raptor arrival time of this access/egress leg, in seconds since + * {@code transitSearchTimeZero}. For a carpool leg this is the moment the car reaches the + * dropoff (the transit stop for access, the passenger's destination for egress), after + * boarding dwell and shared travel. */ private final int arrivalTimeOfPassenger; private final int stop; @@ -112,7 +119,7 @@ public RoutingAccessEgress withPenalty(TimeAndCost penalty) { It is never used for instances of CarpoolAccessEgress, but this might change in the future. */ @Override - public State getLastState() { + public State getFinalState() { throw new UnsupportedOperationException( "Fetching last state of CarpoolAccessEgress is not yet implemented" ); diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java index 43f3472cc02..1ffb28491c8 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionCandidate.java @@ -4,6 +4,7 @@ import java.util.List; import org.opentripplanner.astar.model.GraphPath; import org.opentripplanner.ext.carpooling.model.CarpoolTrip; +import org.opentripplanner.ext.carpooling.util.GraphPathUtils; import org.opentripplanner.routing.graphfinder.NearbyStop; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.Vertex; @@ -14,31 +15,51 @@ *

    * Contains all information needed to construct an itinerary, including: * - The original trip - * - Insertion positions (where pickup and dropoff occur in the route) + * - Insertion positions (where pickup and dropoff occur in the modified route) * - Route segments (all GraphPaths forming the complete modified route) - * - Timing information (baseline and total duration, deviation) + * - Timing information + *

    + * {@code pickupPosition} and {@code dropoffPosition} are 0-based indices of the passenger's + * pickup and dropoff stops in the modified route (the route after the passenger's stops have + * been inserted into the carpool trip). */ public record InsertionCandidate( CarpoolTrip trip, int pickupPosition, int dropoffPosition, List> routeSegments, - Duration durationBetweenOriginAndDestination, - Duration totalDuration, - NearbyStop transitStop + Duration stopDuration, + NearbyStop transitStop, + Duration totalTripDuration ) { - /** - * Calculates the additional duration caused by inserting this passenger. - */ - public Duration additionalDuration() { - return totalDuration.minus(durationBetweenOriginAndDestination); + public InsertionCandidate( + CarpoolTrip trip, + int pickupPosition, + int dropoffPosition, + List> routeSegments, + Duration stopDuration, + NearbyStop transitStop + ) { + this( + trip, + pickupPosition, + dropoffPosition, + routeSegments, + stopDuration, + transitStop, + computeTotalTripDuration(routeSegments, stopDuration) + ); } - /** - * Checks if this insertion is within the trip's deviation budget. - */ - public boolean isWithinDeviationBudget() { - return additionalDuration().compareTo(trip.deviationBudget()) <= 0; + private static Duration computeTotalTripDuration( + List> routeSegments, + Duration stopDuration + ) { + Duration[] cumulativeDurations = GraphPathUtils.calculateCumulativeDurations( + routeSegments.toArray(new GraphPath[0]), + stopDuration + ); + return cumulativeDurations[cumulativeDurations.length - 1]; } /** @@ -71,14 +92,49 @@ public List> getDropoffSegments() { return routeSegments.subList(dropoffPosition, routeSegments.size()); } + /** + * Calculates the duration from trip start until the car arrives at the passenger's pickup. + * Includes travel time through pickup segments and intermediate stop delays between them, but + * excludes the boarding dwell at the pickup itself — that is accounted for in + * {@link #getPassengerRideDuration()}. + * Returns {@link Duration#ZERO} when the passenger boards at the trip origin (no pickup segments). + */ + public Duration getDurationUntilPickupArrival() { + return totalSegmentDuration(getPickupSegments(), stopDuration); + } + + /** + * Calculates the duration of the passenger's ride from pickup arrival to dropoff. + * Includes the boarding dwell at the pickup (when there are pickup segments preceding it), + * travel time through shared segments, and stop delays at intermediate stops between shared + * segments. The no-pickup-segments case (passenger boarding at the trip origin) cannot occur + * today — the search never places {@code pickupPosition == 0} — but the branch guards against + * it by omitting the boarding dwell. + */ + public Duration getPassengerRideDuration() { + Duration boardingDwell = pickupPosition == 0 ? Duration.ZERO : stopDuration; + return totalSegmentDuration(getSharedSegments(), stopDuration).plus(boardingDwell); + } + + private static Duration totalSegmentDuration( + List> segments, + Duration stopDuration + ) { + return segments + .stream() + .map(GraphPathUtils::calculateDuration) + .reduce(Duration.ZERO, Duration::plus) + .plus(stopDuration.multipliedBy(Math.max(0, segments.size() - 1))); + } + @Override public String toString() { return String.format( - "InsertionCandidate{trip=%s, pickup@%d, dropoff@%d, additional=%ds, segments=%d}", + "InsertionCandidate{trip=%s, pickup@%d, dropoff@%d, duration=%ds, segments=%d}", trip.getId(), pickupPosition, dropoffPosition, - additionalDuration().getSeconds(), + totalTripDuration.getSeconds(), routeSegments.size() ); } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java index f50b3a324da..195ca0ae8eb 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionEvaluator.java @@ -1,7 +1,6 @@ package org.opentripplanner.ext.carpooling.routing; import static org.opentripplanner.ext.carpooling.util.GraphPathUtils.calculateCumulativeDurations; -import static org.opentripplanner.ext.carpooling.util.GraphPathUtils.calculateDuration; import java.time.Duration; import java.util.ArrayList; @@ -36,29 +35,27 @@ public class InsertionEvaluator { private static final Logger LOG = LoggerFactory.getLogger(InsertionEvaluator.class); - private static final Duration INITIAL_ADDITIONAL_DURATION = Duration.ofDays(1); - - private final PassengerDelayConstraints delayConstraints; private final LinkingContext linkingContext; private final StreetVertexUtils streetVertexUtils; private final CarpoolRouter carpoolRouter; + private final Duration stopDuration; /** - * Creates an evaluator with the specified routing function, delay constraints, and linking context. + * Creates an evaluator with the specified routing function and linking context. * - * @param delayConstraints Constraints for acceptable passenger delays * @param linkingContext Linking context with pre-linked vertices for routing + * @param stopDuration Duration added at each intermediate stop (from car pickupTime preference) */ public InsertionEvaluator( - PassengerDelayConstraints delayConstraints, LinkingContext linkingContext, StreetVertexUtils streetVertexUtils, - CarpoolRouter carpoolRouter + CarpoolRouter carpoolRouter, + Duration stopDuration ) { - this.delayConstraints = delayConstraints; this.linkingContext = linkingContext; this.streetVertexUtils = streetVertexUtils; this.carpoolRouter = carpoolRouter; + this.stopDuration = stopDuration; } /** @@ -102,23 +99,7 @@ public List findBestInsertions( return List.of(); } - Duration[] cumulativeDurations = calculateCumulativeDurations(baselineSegments); - - GraphPath pathBetweenOriginAndDestination = carpoolRouter.route( - tripWithVertices.vertices().getFirst(), - tripWithVertices.vertices().getLast() - ); - if (pathBetweenOriginAndDestination == null) { - LOG.error( - "Could not route between origin and destination for trip {}", - tripWithVertices.trip().getId() - ); - return List.of(); - } - - Duration durationBetweenOriginAndDestination = calculateDuration( - pathBetweenOriginAndDestination - ); + Duration[] cumulativeDurations = calculateCumulativeDurations(baselineSegments, stopDuration); return tripWithViableAccessEgress .viableAccessEgress() @@ -138,7 +119,6 @@ public List findBestInsertions( dropOffVertex, baselineSegments, cumulativeDurations, - durationBetweenOriginAndDestination, viableAccessEgress.transitStop() ); }) @@ -182,23 +162,7 @@ public InsertionCandidate findBestInsertion( linkingContext ); - Duration[] cumulativeDurations = calculateCumulativeDurations(baselineSegments); - - GraphPath pathBetweenOriginAndDestination = carpoolRouter.route( - tripWithVertices.vertices().getFirst(), - tripWithVertices.vertices().getLast() - ); - if (pathBetweenOriginAndDestination == null) { - LOG.error( - "Could not route between origin and destination for trip {}", - tripWithVertices.trip().getId() - ); - return null; - } - - Duration durationBetweenOriginAndDestination = calculateDuration( - pathBetweenOriginAndDestination - ); + Duration[] cumulativeDurations = calculateCumulativeDurations(baselineSegments, stopDuration); return findBestInsertion( tripWithVertices, @@ -207,7 +171,6 @@ public InsertionCandidate findBestInsertion( passengerDropoffVertex, baselineSegments, cumulativeDurations, - durationBetweenOriginAndDestination, null ); } @@ -220,11 +183,9 @@ private InsertionCandidate findBestInsertion( Vertex passengerDropoff, GraphPath[] baselineSegments, Duration[] cumulativeDurations, - Duration durationBetweenOriginAndDestination, NearbyStop transitStop ) { InsertionCandidate bestCandidate = null; - Duration minAdditionalDuration = INITIAL_ADDITIONAL_DURATION; for (InsertionPosition position : viablePositions) { InsertionCandidate candidate = evaluateInsertion( @@ -235,7 +196,6 @@ private InsertionCandidate findBestInsertion( passengerDropoff, baselineSegments, cumulativeDurations, - durationBetweenOriginAndDestination, transitStop ); @@ -243,20 +203,16 @@ private InsertionCandidate findBestInsertion( continue; } - Duration additionalDuration = candidate.additionalDuration(); - - // Check if this is the best so far and within deviation budget if ( - additionalDuration.compareTo(minAdditionalDuration) < 0 && - additionalDuration.compareTo(tripWithVertices.trip().deviationBudget()) <= 0 + bestCandidate == null || + candidate.totalTripDuration().compareTo(bestCandidate.totalTripDuration()) < 0 ) { - minAdditionalDuration = additionalDuration; bestCandidate = candidate; LOG.debug( - "New best insertion: pickup@{}, dropoff@{}, additional={}s", + "New best insertion: pickup@{}, dropoff@{}, duration={}s", position.pickupPos(), position.dropoffPos(), - additionalDuration.getSeconds() + candidate.totalTripDuration().getSeconds() ); } } @@ -276,7 +232,6 @@ private InsertionCandidate evaluateInsertion( Vertex passengerDropoff, GraphPath[] baselineSegments, Duration[] originalCumulativeDurations, - Duration durationBetweenOriginAndDestination, NearbyStop transitStop ) { List> modifiedSegments = buildModifiedSegments( @@ -292,23 +247,18 @@ private InsertionCandidate evaluateInsertion( return null; } - // Calculate total duration - Duration totalDuration = Duration.ZERO; - for (GraphPath segment : modifiedSegments) { - totalDuration = totalDuration.plus( - Duration.between(segment.states.getFirst().getTime(), segment.states.getLast().getTime()) - ); - } - // Check passenger delay constraints + Duration[] modifiedCumulativeDurations = calculateCumulativeDurations( + modifiedSegments.toArray(new GraphPath[modifiedSegments.size()]), + stopDuration + ); if ( - !delayConstraints.satisfiesConstraints( + !PassengerDelayConstraints.satisfiesConstraints( originalCumulativeDurations, - calculateCumulativeDurations( - modifiedSegments.toArray(new GraphPath[modifiedSegments.size()]) - ), + modifiedCumulativeDurations, pickupPos, - dropoffPos + dropoffPos, + tripWithVertices.trip().stops() ) ) { LOG.trace( @@ -324,8 +274,7 @@ private InsertionCandidate evaluateInsertion( pickupPos, dropoffPos, modifiedSegments, - durationBetweenOriginAndDestination, - totalDuration, + stopDuration, transitStop ); } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPosition.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPosition.java index 2471e39599a..16d875dd026 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPosition.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPosition.java @@ -9,11 +9,12 @@ public class InsertionPosition { * Represents a pickup and dropoff position pair that passed heuristic validation. *

    * This is an intermediate value used between finding viable positions (via heuristics) - * and evaluating them (via A* routing). Positions are 1-indexed to match the insertion - * point semantics in the route modification algorithm. + * and evaluating them (via A* routing). Positions are 0-based indices of the passenger's + * pickup and dropoff stops in the modified route (the route after the passenger's stops + * have been inserted into the carpool trip). * - * @param pickupPos Position to insert passenger pickup (1-indexed) - * @param dropoffPos Position to insert passenger dropoff (1-indexed, always > pickupPos) + * @param pickupPos 0-based index of the passenger's pickup in the modified route + * @param dropoffPos 0-based index of the passenger's dropoff in the modified route (always > pickupPos) */ public InsertionPosition(int pickupPos, int dropoffPos) { if (dropoffPos <= pickupPos) { @@ -41,8 +42,8 @@ public int dropoffPos() { * indices shift. This method calculates the new index for an original route point. * * @param originalIndex Index in original route (before passenger insertion) - * @param pickupPos Position where pickup was inserted (1-indexed) - * @param dropoffPos Position where dropoff was inserted (1-indexed) + * @param pickupPos 0-based index of the passenger's pickup in the modified route + * @param dropoffPos 0-based index of the passenger's dropoff in the modified route * @return Corresponding index in modified route (after passenger insertion) */ public static int mapOriginalIndex(int originalIndex, int pickupPos, int dropoffPos) { diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java index ffc69cd4591..25e094a63cd 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/routing/InsertionPositionFinder.java @@ -6,7 +6,6 @@ import org.opentripplanner.ext.carpooling.constraints.PassengerDelayConstraints; import org.opentripplanner.ext.carpooling.model.CarpoolTrip; import org.opentripplanner.ext.carpooling.util.BeelineEstimator; -import org.opentripplanner.street.geometry.DirectionUtils; import org.opentripplanner.street.geometry.WgsCoordinate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,7 +17,6 @@ * are worth evaluating with expensive A* routing. It validates positions using: *

      *
    • Capacity constraints - ensures available seats throughout the journey
    • - *
    • Directional compatibility - prevents backtracking and U-turns
    • *
    • Beeline delay heuristic - optimistic straight-line time estimates
    • *
    *

    @@ -29,30 +27,21 @@ public class InsertionPositionFinder { private static final Logger LOG = LoggerFactory.getLogger(InsertionPositionFinder.class); - /** Maximum bearing deviation allowed for forward progress (90° allows detours, prevents U-turns) */ - private static final double FORWARD_PROGRESS_TOLERANCE_DEGREES = 90.0; - - private final PassengerDelayConstraints delayConstraints; private final BeelineEstimator beelineEstimator; /** - * Creates a finder with default constraints and estimator. + * Creates a finder with default estimator. */ public InsertionPositionFinder() { - this(new PassengerDelayConstraints(), new BeelineEstimator()); + this(new BeelineEstimator()); } /** - * Creates a finder with specified constraints and estimator. + * Creates a finder with specified estimator. * - * @param delayConstraints Constraints for acceptable passenger delays * @param beelineEstimator Estimator for beeline travel times */ - public InsertionPositionFinder( - PassengerDelayConstraints delayConstraints, - BeelineEstimator beelineEstimator - ) { - this.delayConstraints = delayConstraints; + public InsertionPositionFinder(BeelineEstimator beelineEstimator) { this.beelineEstimator = beelineEstimator; } @@ -63,22 +52,27 @@ public InsertionPositionFinder( * @param trip The carpool trip being evaluated * @param passengerPickup Passenger's pickup location * @param passengerDropoff Passenger's dropoff location + * @param stopDuration Dwell time added at each intermediate stop; used by the beeline delay + * heuristic so its cumulative-time estimates match the per-stop budget + * check used downstream * @return List of viable insertion positions (may be empty) */ public List findViablePositions( CarpoolTrip trip, WgsCoordinate passengerPickup, - WgsCoordinate passengerDropoff + WgsCoordinate passengerDropoff, + Duration stopDuration ) { List routePoints = trip.routePoints(); - Duration[] beelineTimes = beelineEstimator.calculateCumulativeTimes(routePoints); + Duration[] beelineTimes = beelineEstimator.calculateCumulativeTimes(routePoints, stopDuration); List viable = new ArrayList<>(); - // Pickup positions: 1 to routePoints.size()-1 (cannot pick up at position 0/origin) + // pickupPos/dropoffPos are 0-based indices of the passenger's stops in the modified route. + // Pickup cannot be at index 0 (that's the driver's origin). for (int pickupPos = 1; pickupPos < routePoints.size(); pickupPos++) { - // Dropoff positions: pickupPos+1 to routePoints.size() (can drop off up to and including destination) + // Dropoff must be after pickup. Max is routePoints.size() (appended after all original stops except the last). for (int dropoffPos = pickupPos + 1; dropoffPos <= routePoints.size(); dropoffPos++) { if (!trip.hasCapacityForInsertion(pickupPos, dropoffPos, 1)) { LOG.trace( @@ -90,42 +84,25 @@ public List findViablePositions( } if ( - !insertionMaintainsForwardProgress( + !passesBeelineDelayCheck( routePoints, + beelineTimes, + passengerPickup, + passengerDropoff, pickupPos, dropoffPos, - passengerPickup, - passengerDropoff + trip, + stopDuration ) ) { LOG.trace( - "Insertion at pickup={}, dropoff={} rejected by directional check", + "Insertion at pickup={}, dropoff={} rejected by beeline delay heuristic", pickupPos, dropoffPos ); continue; } - if (routePoints.size() > 2) { - if ( - !passesBeelineDelayCheck( - routePoints, - beelineTimes, - passengerPickup, - passengerDropoff, - pickupPos, - dropoffPos - ) - ) { - LOG.trace( - "Insertion at pickup={}, dropoff={} rejected by beeline delay heuristic", - pickupPos, - dropoffPos - ); - continue; - } - } - viable.add(new InsertionPosition(pickupPos, dropoffPos)); } } @@ -133,95 +110,6 @@ public List findViablePositions( return viable; } - /** - * Checks if inserting pickup/dropoff points maintains forward progress. - * Prevents backtracking by ensuring insertions don't cause the route - * to deviate too far from its intended direction. - * - * @param routePoints Current route points - * @param pickupPos Position to insert pickup (1-indexed) - * @param dropoffPos Position to insert dropoff (1-indexed) - * @param passengerPickup Passenger pickup coordinate - * @param passengerDropoff Passenger dropoff coordinate - * @return true if insertion maintains forward progress - */ - private boolean insertionMaintainsForwardProgress( - List routePoints, - int pickupPos, - int dropoffPos, - WgsCoordinate passengerPickup, - WgsCoordinate passengerDropoff - ) { - if (pickupPos > 0 && pickupPos < routePoints.size()) { - WgsCoordinate prevPoint = routePoints.get(pickupPos - 1); - WgsCoordinate nextPoint = routePoints.get(pickupPos); - - if (!maintainsForwardProgress(prevPoint, passengerPickup, nextPoint)) { - return false; - } - } - - if (dropoffPos > 0 && dropoffPos <= routePoints.size()) { - WgsCoordinate prevPoint; - if (dropoffPos == pickupPos) { - prevPoint = passengerPickup; - } else if (dropoffPos - 1 < routePoints.size()) { - prevPoint = routePoints.get(dropoffPos - 1); - } else { - return true; - } - - if (dropoffPos < routePoints.size()) { - WgsCoordinate nextPoint = routePoints.get(dropoffPos); - - return maintainsForwardProgress(prevPoint, passengerDropoff, nextPoint); - } - } - - return true; - } - - /** - * Checks if inserting a new point maintains forward progress. - */ - private boolean maintainsForwardProgress( - WgsCoordinate previous, - WgsCoordinate newPoint, - WgsCoordinate next - ) { - // Skip check if inserting at an existing point (newPoint equals next or previous) - // This avoids undefined bearing calculations from a point to itself - if (newPoint.equals(next) || newPoint.equals(previous)) { - return true; - } - - // Calculate intended direction (previous → next) - double intendedBearing = DirectionUtils.getAzimuth( - previous.asJtsCoordinate(), - next.asJtsCoordinate() - ); - - // Calculate detour directions - double bearingToNew = DirectionUtils.getAzimuth( - previous.asJtsCoordinate(), - newPoint.asJtsCoordinate() - ); - double bearingFromNew = DirectionUtils.getAzimuth( - newPoint.asJtsCoordinate(), - next.asJtsCoordinate() - ); - - // Check deviations - double deviationToNew = DirectionUtils.bearingDifference(intendedBearing, bearingToNew); - double deviationFromNew = DirectionUtils.bearingDifference(intendedBearing, bearingFromNew); - - // Allow some deviation but not complete reversal - return ( - deviationToNew <= FORWARD_PROGRESS_TOLERANCE_DEGREES && - deviationFromNew <= FORWARD_PROGRESS_TOLERANCE_DEGREES - ); - } - /** * Checks if an insertion position passes the beeline delay heuristic. * This is a fast, optimistic check using straight-line distance estimates. @@ -232,8 +120,9 @@ private boolean maintainsForwardProgress( * @param originalBeelineTimes Beeline cumulative times for original route * @param passengerPickup Passenger pickup location * @param passengerDropoff Passenger dropoff location - * @param pickupPos Pickup insertion position (1-indexed) - * @param dropoffPos Dropoff insertion position (1-indexed) + * @param pickupPos 0-based index of the passenger's pickup in the modified route + * @param dropoffPos 0-based index of the passenger's dropoff in the modified route + * @param trip The carpool trip being evaluated * @return true if insertion might satisfy delay constraints (proceed with A* routing) */ private boolean passesBeelineDelayCheck( @@ -242,7 +131,9 @@ private boolean passesBeelineDelayCheck( WgsCoordinate passengerPickup, WgsCoordinate passengerDropoff, int pickupPos, - int dropoffPos + int dropoffPos, + CarpoolTrip trip, + Duration stopDuration ) { // Build modified coordinate list with passenger stops inserted List modifiedCoords = new ArrayList<>(originalCoords); @@ -250,28 +141,18 @@ private boolean passesBeelineDelayCheck( modifiedCoords.add(dropoffPos, passengerDropoff); // Calculate beeline times for modified route - Duration[] modifiedBeelineTimes = beelineEstimator.calculateCumulativeTimes(modifiedCoords); - - // Check delays at each existing stop (exclude boarding at 0 and alighting at end) - for (int originalIndex = 1; originalIndex < originalCoords.size() - 1; originalIndex++) { - int modifiedIndex = InsertionPosition.mapOriginalIndex(originalIndex, pickupPos, dropoffPos); - - Duration originalTime = originalBeelineTimes[originalIndex]; - Duration modifiedTime = modifiedBeelineTimes[modifiedIndex]; - Duration beelineDelay = modifiedTime.minus(originalTime); - - // If even the optimistic beeline estimate exceeds threshold, actual routing will too - if (beelineDelay.compareTo(delayConstraints.getMaxDelay()) > 0) { - LOG.trace( - "Stop at position {} has beeline delay {}s (exceeds {}s threshold)", - originalIndex, - beelineDelay.getSeconds(), - delayConstraints.getMaxDelay().getSeconds() - ); - return false; - } - } + Duration[] modifiedBeelineTimes = beelineEstimator.calculateCumulativeTimes( + modifiedCoords, + stopDuration + ); - return true; + // If even the optimistic beeline estimate exceeds a stop's budget, actual routing will too + return PassengerDelayConstraints.satisfiesConstraints( + originalBeelineTimes, + modifiedBeelineTimes, + pickupPos, + dropoffPos, + trip.stops() + ); } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java index 6cbe2eb276d..202a33b4750 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/service/DefaultCarpoolingService.java @@ -3,15 +3,12 @@ import java.time.Duration; import java.time.ZonedDateTime; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.Set; -import org.opentripplanner.astar.model.GraphPath; import org.opentripplanner.ext.carpooling.CarpoolingRepository; import org.opentripplanner.ext.carpooling.CarpoolingService; -import org.opentripplanner.ext.carpooling.constraints.PassengerDelayConstraints; import org.opentripplanner.ext.carpooling.filter.FilterChain; import org.opentripplanner.ext.carpooling.internal.CarpoolItineraryMapper; import org.opentripplanner.ext.carpooling.routing.CarpoolAccessEgress; @@ -27,7 +24,6 @@ import org.opentripplanner.ext.carpooling.util.BeelineEstimator; import org.opentripplanner.ext.carpooling.util.StreetVertexUtils; import org.opentripplanner.framework.model.TimeAndCost; -import org.opentripplanner.graph_builder.module.nearbystops.StopResolver; import org.opentripplanner.graph_builder.module.nearbystops.StreetNearbyStopFinder; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.model.plan.Itinerary; @@ -39,14 +35,13 @@ import org.opentripplanner.routing.api.response.RoutingErrorCode; import org.opentripplanner.routing.error.RoutingValidationException; import org.opentripplanner.routing.graphfinder.NearbyStop; +import org.opentripplanner.routing.graphfinder.TransitServiceResolver; import org.opentripplanner.routing.linking.LinkingContext; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.street.linking.TemporaryVerticesContainer; import org.opentripplanner.street.linking.VertexLinker; import org.opentripplanner.street.model.StreetMode; -import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.Vertex; -import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.service.StreetLimitationParametersService; import org.opentripplanner.transit.model.site.AreaStop; import org.opentripplanner.transit.service.TransitService; @@ -67,10 +62,10 @@ * The service executes routing requests in three distinct phases: *

      *
    1. Pre-filtering ({@link FilterChain}): Quickly eliminates incompatible - * trips based on capacity, time windows, direction, and distance.
    2. + * trips based on capacity, time windows, and distance. *
    3. Position Finding ({@link InsertionPositionFinder}): For trips that * pass filtering, identifies viable pickup/dropoff position pairs using fast heuristics - * (capacity, direction, beeline delay estimates). No routing is performed in this phase.
    4. + * (capacity, beeline delay estimates). No routing is performed in this phase. *
    5. Insertion Evaluation ({@link InsertionEvaluator}): For viable positions, * computes actual routes using A* street routing. Evaluates all feasible insertion positions * and selects the one minimizing additional travel time while satisfying delay constraints.
    6. @@ -95,17 +90,9 @@ public class DefaultCarpoolingService implements CarpoolingService { private static final Logger LOG = LoggerFactory.getLogger(DefaultCarpoolingService.class); - static final int DEFAULT_MAX_CARPOOL_DIRECT_RESULTS = 3; private static final Duration DEFAULT_SEARCH_WINDOW = Duration.ofMinutes(30); // How far away in time a carpooling trip can be from the requested departure time to be considered private static final Duration ACCESS_EGRESS_SEARCH_WINDOW = Duration.ofHours(12); - /* - The time it takes to pick up or drop off a passenger and start driving again. - It is only used for access/egress, and is a temporary solution. - The implementation will be changed for both direct and access/egress when implementing the field - latestExpectedArrivalTime from siri. - */ - static final Duration CARPOOL_STOP_DURATION = Duration.ofMinutes(1); /* This is needed for managing computational complexity unless we find a smarter way of searching for nearby stops. @@ -116,7 +103,6 @@ public class DefaultCarpoolingService implements CarpoolingService { private final StreetLimitationParametersService streetLimitationParametersService; private final FilterChain preFilters; private final CarpoolItineraryMapper itineraryMapper; - private final PassengerDelayConstraints delayConstraints; private final InsertionPositionFinder positionFinder; private final VertexLinker vertexLinker; @@ -142,9 +128,8 @@ public DefaultCarpoolingService( this.repository = repository; this.streetLimitationParametersService = streetLimitationParametersService; this.preFilters = FilterChain.standard(); - this.itineraryMapper = new CarpoolItineraryMapper(transitService.getTimeZone()); - this.delayConstraints = new PassengerDelayConstraints(); - this.positionFinder = new InsertionPositionFinder(delayConstraints, new BeelineEstimator()); + this.itineraryMapper = new CarpoolItineraryMapper(); + this.positionFinder = new InsertionPositionFinder(new BeelineEstimator()); this.vertexLinker = vertexLinker; } @@ -154,21 +139,18 @@ public DefaultCarpoolingService( * This method executes the full three-phase carpooling algorithm: *
        *
      1. Pre-filtering: All trips from the repository are filtered by capacity, - * time window, direction, and distance to quickly eliminate incompatible matches.
      2. + * time window, and distance to quickly eliminate incompatible matches. *
      3. Position finding: For each surviving trip, viable pickup/dropoff * insertion positions are identified using beeline heuristics (no routing).
      4. *
      5. Insertion evaluation: Viable positions are evaluated with A* street * routing to find the insertion that minimizes additional driver travel time while * respecting delay constraints.
      6. *
      - *

      - * Results are sorted by additional travel time and limited to - * {@value #DEFAULT_MAX_CARPOOL_DIRECT_RESULTS} itineraries. * * @param request the routing request. Must have {@link StreetMode#CARPOOL} as the direct mode. * @param linkingContext pre-linked vertices for the passenger's origin and destination - * @return a list of carpool itineraries sorted by additional travel time, or an empty list - * if no viable matches are found or the direct mode is not CARPOOL + * @return a list of carpool itineraries, or an empty list if no viable matches are found + * or the direct mode is not CARPOOL * @throws RoutingValidationException if origin or destination coordinates are missing */ @Override @@ -226,11 +208,13 @@ public List routeDirect(RouteRequest request, LinkingContext linkingC var streetVertexUtils = new StreetVertexUtils(this.vertexLinker, temporaryVerticesContainer); + var stopDuration = request.preferences().car().pickupTime(); + var insertionEvaluator = new InsertionEvaluator( - delayConstraints, linkingContext, streetVertexUtils, - router + router, + stopDuration ); // Find optimal insertions for remaining trips @@ -240,7 +224,8 @@ public List routeDirect(RouteRequest request, LinkingContext linkingC List viablePositions = positionFinder.findViablePositions( trip, passengerPickup, - passengerDropoff + passengerDropoff, + stopDuration ); if (viablePositions.isEmpty()) { @@ -274,15 +259,13 @@ public List routeDirect(RouteRequest request, LinkingContext linkingC ); }) .filter(Objects::nonNull) - .sorted(Comparator.comparing(InsertionCandidate::additionalDuration)) - .limit(DEFAULT_MAX_CARPOOL_DIRECT_RESULTS) .toList(); LOG.debug("Found {} viable insertion candidates", insertionCandidates.size()); itineraries = insertionCandidates .stream() - .map(candidate -> itineraryMapper.toItinerary(request, candidate)) + .map(itineraryMapper::toItinerary) .filter(Objects::nonNull) .toList(); } @@ -314,7 +297,7 @@ public List routeDirect(RouteRequest request, LinkingContext linkingC * @param streetRequest * @param accessOrEgress whether this is an access leg (origin to transit) or egress leg * (transit to destination) - * @param stopResolver used for nearby stop search + * @param transitServiceResolver used for nearby stop search * @param linkingContext pre-linked vertices for the passenger's origin and destination * @param transitSearchTimeZero the reference time for computing relative start/end times * used by Raptor @@ -327,7 +310,7 @@ public List routeAccessEgress( RouteRequest request, StreetRequest streetRequest, AccessEgressType accessOrEgress, - StopResolver stopResolver, + TransitServiceResolver transitServiceResolver, LinkingContext linkingContext, ZonedDateTime transitSearchTimeZero ) throws RoutingValidationException { @@ -353,10 +336,7 @@ public List routeAccessEgress( or the passenger's destination if the request is for egress */ GenericLocation passengerLocation = accessOrEgress.isAccess() ? request.from() : request.to(); - WgsCoordinate passengerCoordinates = new WgsCoordinate( - passengerLocation.lat, - passengerLocation.lng - ); + WgsCoordinate passengerCoordinates = passengerLocation.wgsCoordinate(); var passengerDepartureTime = request.dateTime(); @@ -391,7 +371,6 @@ public List routeAccessEgress( } var streetNearbyStopFinder = StreetNearbyStopFinder.of( - stopResolver, MAX_SEARCH_DURATION_FOR_NEARBY_STOPS_FOR_ACCESS_EGRESS, 0 ); @@ -405,7 +384,7 @@ public List routeAccessEgress( accessOrEgress.isEgress() ) .stream() - .filter(stop -> !(stop.stop instanceof AreaStop)) + .filter(stop -> !(transitServiceResolver.getStopLocation(stop.stopId) instanceof AreaStop)) .toList(); var nearbyStopsWithVertices = new HashMap(); @@ -449,11 +428,13 @@ public List routeAccessEgress( }); }); + var stopDuration = request.preferences().car().pickupTime(); + var insertionEvaluator = new InsertionEvaluator( - delayConstraints, linkingContext, streetVertexUtils, - carpoolTreeVertexRouter + carpoolTreeVertexRouter, + stopDuration ); var candidateTripsWithViableStopsAndPositions = candidateTripsWithVertices @@ -463,17 +444,19 @@ public List routeAccessEgress( .keySet() .stream() .map(nearbyStop -> { + var stop = transitServiceResolver.getStopLocation(nearbyStop.stopId); var pickUpCoord = accessOrEgress.isAccess() ? passengerCoordinates - : nearbyStop.stop.getCoordinate(); + : stop.getCoordinate(); var dropOffCoord = accessOrEgress.isAccess() - ? nearbyStop.stop.getCoordinate() + ? stop.getCoordinate() : passengerCoordinates; var viablePositions = positionFinder.findViablePositions( tripWithVertices.trip(), pickUpCoord, - dropOffCoord + dropOffCoord, + stopDuration ); return new ViableAccessEgress( nearbyStop, @@ -498,6 +481,7 @@ public List routeAccessEgress( .stream() .map(it -> createCarpoolAccessEgress( + transitServiceResolver, it, transitSearchTimeZero, /* @@ -514,55 +498,30 @@ public List routeAccessEgress( private void validateRequest(RouteRequest request) throws RoutingValidationException { Objects.requireNonNull(request.from()); Objects.requireNonNull(request.to()); - if (request.from().lat == null || request.from().lng == null) { + if (request.from().wgsCoordinate() == null) { throw new RoutingValidationException( List.of(new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.FROM_PLACE)) ); } - if (request.to().lat == null || request.to().lng == null) { + if (request.to().wgsCoordinate() == null) { throw new RoutingValidationException( List.of(new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.TO_PLACE)) ); } } - private Duration getTotalDurationOfSegments( - List> segments, - Duration extraTimeForStop - ) { - return segments - .stream() - .map(it -> Duration.between(it.states.getFirst().getTime(), it.states.getLast().getTime())) - .reduce(Duration.ZERO, Duration::plus) - .plus(extraTimeForStop.multipliedBy(segments.size() - 1)); - } - private CarpoolAccessEgress createCarpoolAccessEgress( + TransitServiceResolver transitServiceResolver, InsertionCandidate insertionCandidate, ZonedDateTime transitSearchTimeZero, Double carpoolReluctance ) { - var pickUpIndex = insertionCandidate.pickupPosition(); - var dropOffIndex = insertionCandidate.dropoffPosition() - 1; - - var segmentsBeforeInsertion = insertionCandidate.routeSegments().subList(0, pickUpIndex); - var segmentsWithPassenger = insertionCandidate - .routeSegments() - .subList(pickUpIndex, dropOffIndex + 1); + var sharedSegments = insertionCandidate.getSharedSegments(); + var durationUntilPickup = insertionCandidate.getDurationUntilPickupArrival(); + var passengerRideDuration = insertionCandidate.getPassengerRideDuration(); - var durationBeforeInsertion = getTotalDurationOfSegments( - segmentsBeforeInsertion, - CARPOOL_STOP_DURATION - ); - - // Adding an extra CARPOOL_STOP_DURATION for the time it takes to pick up the passenger - var durationWithPassenger = getTotalDurationOfSegments( - segmentsWithPassenger, - CARPOOL_STOP_DURATION - ).plus(CARPOOL_STOP_DURATION); - - var startTimeOfSegment = insertionCandidate.trip().startTime().plus(durationBeforeInsertion); - var endTimeOfSegment = startTimeOfSegment.plus(durationWithPassenger); + var startTimeOfSegment = insertionCandidate.trip().startTime().plus(durationUntilPickup); + var endTimeOfSegment = startTimeOfSegment.plus(passengerRideDuration); var relativeStartTime = TimeUtils.toTransitTimeSeconds( transitSearchTimeZero, @@ -573,16 +532,14 @@ private CarpoolAccessEgress createCarpoolAccessEgress( endTimeOfSegment.toInstant() ); - var accessEgress = new CarpoolAccessEgress( - insertionCandidate.transitStop().stop.getIndex(), - durationWithPassenger, + return new CarpoolAccessEgress( + transitServiceResolver.getStopLocation(insertionCandidate.transitStop().stopId).getIndex(), + passengerRideDuration, relativeStartTime, relativeEndTime, - segmentsWithPassenger, + sharedSegments, TimeAndCost.ZERO, carpoolReluctance ); - - return accessEgress; } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java index 0f1536eb5b6..246ca26ffdf 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/updater/CarpoolSiriMapper.java @@ -1,5 +1,9 @@ package org.opentripplanner.ext.carpooling.updater; +import static org.opentripplanner.ext.carpooling.model.CarpoolStop.DEFAULT_ONBOARD_COUNT; +import static org.opentripplanner.ext.carpooling.model.CarpoolTrip.DEFAULT_TOTAL_CAPACITY; + +import java.math.BigInteger; import java.time.Duration; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -10,10 +14,8 @@ import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LinearRing; import org.locationtech.jts.geom.Polygon; -import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.carpooling.model.CarpoolStop; -import org.opentripplanner.ext.carpooling.model.CarpoolStopType; import org.opentripplanner.ext.carpooling.model.CarpoolTrip; import org.opentripplanner.ext.carpooling.model.CarpoolTripBuilder; import org.opentripplanner.street.geometry.WgsCoordinate; @@ -24,18 +26,16 @@ import uk.org.siri.siri21.EstimatedCall; import uk.org.siri.siri21.EstimatedVehicleJourney; +/** + * Maps SIRI EstimatedVehicleJourney messages to {@link CarpoolTrip} instances. + * Extracts stop geometry, timing, capacity and occupancy from the SIRI data. + */ public class CarpoolSiriMapper { private static final Logger LOG = LoggerFactory.getLogger(CarpoolSiriMapper.class); private static final String FEED_ID = "ENT"; private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); - private static final int DEFAULT_AVAILABLE_SEATS = 5; - private static final Duration DEFAULT_DEVIATION_BUDGET = Duration.ofMinutes(15); - // INDEX is not relevant for our stop type. Also set index to a hard coded value to avoid - // run-away memory use if it by error ends up in global repositories. - public static final int CARPOOLING_DUMMY_INDEX = -9_999; - public CarpoolTrip mapSiriToCarpoolTrip(EstimatedVehicleJourney journey) { var calls = journey.getEstimatedCalls().getEstimatedCalls(); if (calls.size() < 2) { @@ -70,14 +70,13 @@ public CarpoolTrip mapSiriToCarpoolTrip(EstimatedVehicleJourney journey) { ? lastStop.getExpectedArrivalTime() : lastStop.getAimedArrivalTime(); + int totalCapacity = extractTotalCapacity(tripId, calls); + return new CarpoolTripBuilder(new FeedScopedId(FEED_ID, tripId)) .withStartTime(startTime) .withEndTime(endTime) .withProvider(journey.getOperatorRef().getValue()) - // TODO: Find a better way to exchange deviation budget with providers. - .withDeviationBudget(DEFAULT_DEVIATION_BUDGET) - // TODO: Make available seats dynamic based on EstimatedVehicleJourney data - .withAvailableSeats(DEFAULT_AVAILABLE_SEATS) + .withTotalCapacity(totalCapacity) .withStops(stops) .build(); } @@ -87,7 +86,7 @@ public CarpoolTrip mapSiriToCarpoolTrip(EstimatedVehicleJourney journey) { * * @param call The SIRI EstimatedCall containing stop information * @param tripId The trip ID for generating unique stop IDs - * @param sequenceNumber The 0-based sequence number of this stop + * @param stopIndex The 0-based index of this stop in the call list * @param isFirst true if this is the first stop (origin) * @param isLast true if this is the last stop (destination) * @return A CarpoolStop representing the stop @@ -95,7 +94,7 @@ public CarpoolTrip mapSiriToCarpoolTrip(EstimatedVehicleJourney journey) { private CarpoolStop buildCarpoolStopForPosition( EstimatedCall call, String tripId, - int sequenceNumber, + int stopIndex, boolean isFirst, boolean isLast ) { @@ -103,49 +102,130 @@ private CarpoolStop buildCarpoolStopForPosition( ? tripId + "_trip_origin" : isLast ? tripId + "_trip_destination" - : tripId + "_stop_" + sequenceNumber; + : tripId + "_stop_" + stopIndex; - return toCarpoolStop(call, stopId, isFirst, isLast); + return toCarpoolStop(call, stopId, tripId, isFirst, isLast); } /** - * Determine the carpool stop type from the EstimatedCall data. + * Extracts the total capacity from the EstimatedCalls' ExpectedDepartureCapacities. + * Only the first element of each call's capacities list is inspected; additional + * entries are ignored. Uses the value from the first call that has it. Logs a warning + * if different calls report different capacity values. Returns + * {@link CarpoolTrip#DEFAULT_TOTAL_CAPACITY} if no call has capacity data or if the value is invalid. */ - private CarpoolStopType determineCarpoolStopType(EstimatedCall call) { - boolean hasArrival = - call.getExpectedArrivalTime() != null || call.getAimedArrivalTime() != null; - boolean hasDeparture = - call.getExpectedDepartureTime() != null || call.getAimedDepartureTime() != null; - - if (hasArrival && hasDeparture) { - return CarpoolStopType.PICKUP_AND_DROP_OFF; - } else if (hasDeparture) { - return CarpoolStopType.PICKUP_ONLY; - } else if (hasArrival) { - return CarpoolStopType.DROP_OFF_ONLY; - } else { - return CarpoolStopType.PICKUP_AND_DROP_OFF; + private int extractTotalCapacity(String tripId, List calls) { + Integer firstCapacity = null; + int firstCapacityIndex = -1; + + for (int i = 0; i < calls.size(); i++) { + var capacities = calls.get(i).getExpectedDepartureCapacities(); + if (capacities == null || capacities.isEmpty()) { + continue; + } + BigInteger value = capacities.getFirst().getTotalCapacity(); + if (value == null) { + continue; + } + int intValue = value.intValue(); + if (firstCapacity == null) { + firstCapacity = intValue; + firstCapacityIndex = i; + } else if (intValue != firstCapacity) { + LOG.warn( + "Trip {}: totalCapacity differs between calls (call {} has {}, call {} has {})", + tripId, + firstCapacityIndex, + firstCapacity, + i, + intValue + ); + } + } + + if (firstCapacity == null) { + return DEFAULT_TOTAL_CAPACITY; + } + if (firstCapacity <= 0) { + LOG.warn( + "Trip {}: invalid totalCapacity {} at call {}, using default {}", + tripId, + firstCapacity, + firstCapacityIndex, + DEFAULT_TOTAL_CAPACITY + ); + return DEFAULT_TOTAL_CAPACITY; } + return firstCapacity; } /** - * Calculate the passenger delta (change in passenger count) from the EstimatedCall. + * Extracts the onboard count from the EstimatedCall's ExpectedDepartureOccupancies. + * Only the first element of the occupancies list is inspected; additional entries are + * ignored. Returns {@link CarpoolStop#DEFAULT_ONBOARD_COUNT} if not present or if the value is invalid. */ - private int calculatePassengerDelta(EstimatedCall call, CarpoolStopType stopType) { - // This is a placeholder implementation - adapt based on SIRI ET data structure - // SIRI ET may have passenger count changes, boarding/alighting numbers, etc. - - // For now, return a default value of 1 passenger pickup/dropoff - if (stopType == CarpoolStopType.DROP_OFF_ONLY) { - // Assume 1 passenger drop-off - return -1; - } else if (stopType == CarpoolStopType.PICKUP_ONLY) { - // Assume 1 passenger pickup - return 1; - } else { - // No net change for both pickup and drop-off - return 0; + private int extractOnboardCount(String tripId, EstimatedCall call) { + var occupancies = call.getExpectedDepartureOccupancies(); + if (occupancies != null && !occupancies.isEmpty()) { + BigInteger onboardCount = occupancies.getFirst().getOnboardCount(); + if (onboardCount != null) { + int value = onboardCount.intValue(); + if (value <= 0) { + LOG.warn( + "Trip {}: invalid onboardCount {}, using default {}", + tripId, + value, + DEFAULT_ONBOARD_COUNT + ); + return DEFAULT_ONBOARD_COUNT; + } + return value; + } + } + return DEFAULT_ONBOARD_COUNT; + } + + /** + * Extracts the deviation budget from the EstimatedCall by computing the difference between + * {@code latestExpectedArrivalTime} and the arrival time ({@code expectedArrivalTime} if + * present, otherwise {@code aimedArrivalTime}). + *

      + * The result is the remaining slack at this stop, not an initial contract: + * {@code expectedArrivalTime} already reflects detours committed by prior passenger + * insertions, and {@code latestExpectedArrivalTime} is the unchanged commitment to the + * passenger at this stop. Each time this mapper runs against a fresh SIRI snapshot, the + * extracted value therefore shrinks in step with the consumed slack. + *

      + * Fallbacks: + *

        + *
      • Returns {@link CarpoolStop#DEFAULT_DEVIATION_BUDGET} if either timestamp is missing. + * This is intentionally permissive — the absence of a commitment should not block + * insertions.
      • + *
      • Returns {@link Duration#ZERO} (and logs a warning) if {@code latestExpectedArrivalTime} + * is before the arrival time — the schedule has slipped past the commitment, so no + * further deviation is acceptable.
      • + *
      + */ + private Duration extractDeviationBudget(EstimatedCall call) { + var latestExpected = call.getLatestExpectedArrivalTime(); + var arrivalTime = call.getExpectedArrivalTime() != null + ? call.getExpectedArrivalTime() + : call.getAimedArrivalTime(); + + if (latestExpected == null || arrivalTime == null) { + return CarpoolStop.DEFAULT_DEVIATION_BUDGET; + } + + Duration budget = Duration.between(arrivalTime, latestExpected); + if (budget.isNegative()) { + LOG.warn( + "latestExpectedArrivalTime ({}) is before arrivalTime ({}), using zero deviation budget", + latestExpected, + arrivalTime + ); + return Duration.ZERO; } + return budget; } /** @@ -201,9 +281,15 @@ private void validateEstimatedCallOrder(List calls) { } } + /** + * Builds a {@link CarpoolStop} from a SIRI call. The origin (when {@code isFirst} is true) + * always gets {@link Duration#ZERO} as its deviation budget — the trip cannot start later + * than scheduled — regardless of any value extracted from the call. + */ private CarpoolStop toCarpoolStop( EstimatedCall call, String id, + String tripId, boolean isFirst, boolean isLast ) { @@ -214,24 +300,15 @@ private CarpoolStop toCarpoolStop( ? toWgsCoordinate(toPolygon(legacyGeometry)) : toWgsCoordinate(circleLocation); - CarpoolStopType stopType; - if (isFirst) { - stopType = CarpoolStopType.PICKUP_ONLY; - } else if (isLast) { - stopType = CarpoolStopType.DROP_OFF_ONLY; - } else { - stopType = determineCarpoolStopType(call); - } - - return CarpoolStop.of(new FeedScopedId(FEED_ID, id), () -> CARPOOLING_DUMMY_INDEX) - .withName(I18NString.of(call.getStopPointNames().getFirst().getValue())) + return CarpoolStop.of(new FeedScopedId(FEED_ID, id)) .withCoordinate(centroid) - .withCarpoolStopType(stopType) .withAimedDepartureTime(isLast ? null : call.getAimedDepartureTime()) .withExpectedDepartureTime(isLast ? null : call.getExpectedDepartureTime()) .withAimedArrivalTime(isFirst ? null : call.getAimedArrivalTime()) .withExpectedArrivalTime(isFirst ? null : call.getExpectedArrivalTime()) - .withPassengerDelta(isLast ? 0 : calculatePassengerDelta(call, stopType)) + .withLatestExpectedArrivalTime(isFirst ? null : call.getLatestExpectedArrivalTime()) + .withOnboardCount(extractOnboardCount(tripId, call)) + .withDeviationBudget(isFirst ? Duration.ZERO : extractDeviationBudget(call)) .build(); } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/util/BeelineEstimator.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/util/BeelineEstimator.java index 8b00c19f681..96fd7f1da72 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/util/BeelineEstimator.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/util/BeelineEstimator.java @@ -85,25 +85,22 @@ public Duration estimateDuration(WgsCoordinate from, WgsCoordinate to) { } /** - * Calculates cumulative travel times to each point in a route. - * Returns an array where index i contains the cumulative duration from the start to point i. + * Calculates cumulative travel times to each point in a route, including stop duration + * at each intermediate point. * * @param points Route points in order + * @param stopDuration Duration added at each intermediate stop (not at the first point) * @return Array of cumulative durations (first element is always Duration.ZERO) */ - public Duration[] calculateCumulativeTimes(List points) { + public Duration[] calculateCumulativeTimes(List points, Duration stopDuration) { if (points.isEmpty()) { return new Duration[0]; } - Duration[] cumulativeTimes = new Duration[points.size()]; - cumulativeTimes[0] = Duration.ZERO; - - for (int i = 0; i < points.size() - 1; i++) { - Duration segmentDuration = estimateDuration(points.get(i), points.get(i + 1)); - cumulativeTimes[i + 1] = cumulativeTimes[i].plus(segmentDuration); + Duration[] segmentDurations = new Duration[points.size() - 1]; + for (int i = 0; i < segmentDurations.length; i++) { + segmentDurations[i] = estimateDuration(points.get(i), points.get(i + 1)); } - - return cumulativeTimes; + return GraphPathUtils.calculateCumulativeDurations(segmentDurations, stopDuration); } } diff --git a/application/src/ext/java/org/opentripplanner/ext/carpooling/util/GraphPathUtils.java b/application/src/ext/java/org/opentripplanner/ext/carpooling/util/GraphPathUtils.java index 12bb9449238..169408af479 100644 --- a/application/src/ext/java/org/opentripplanner/ext/carpooling/util/GraphPathUtils.java +++ b/application/src/ext/java/org/opentripplanner/ext/carpooling/util/GraphPathUtils.java @@ -6,18 +6,59 @@ import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.state.State; -public class GraphPathUtils { +public final class GraphPathUtils { + + private GraphPathUtils() {} + + /** + * Calculates cumulative durations from pre-routed segments, including stop duration + * at each intermediate stop. + * + * @param segments Pre-routed segments + * @param stopDuration Duration added at each intermediate stop + */ + public static Duration[] calculateCumulativeDurations( + GraphPath[] segments, + Duration stopDuration + ) { + Duration[] segmentDurations = new Duration[segments.length]; + for (int i = 0; i < segments.length; i++) { + segmentDurations[i] = calculateDuration(segments[i]); + } + return calculateCumulativeDurations(segmentDurations, stopDuration); + } /** - * Calculates cumulative durations from pre-routed segments. + * Calculates cumulative arrival times from segment durations, including a stop delay + * at each intermediate point. The stop delay is added before each segment + * except the first, modelling time spent at an intermediate stop before departing + * to the next point. No delay is added at the origin (before segment 0) or after the + * final segment. + *

      + * Given N segments, the result has N+1 entries: + *

      +   *   result[0] = 0                                           (origin)
      +   *   result[1] = segment[0]                                  (no preceding stop delay)
      +   *   result[2] = segment[0] + stopDuration + segment[1]
      +   *   result[k] = result[k-1] + stopDuration + segment[k-1]  (for k >= 2)
      +   * 
      + * Example: 6 segments of 10 min each, 1 min stop duration: + * {@code [0, 10, 21, 32, 43, 54, 65]} + * + * @param segmentDurations Duration of each segment + * @param stopDuration Duration added before each segment except the first + * @return Array of cumulative durations with length segmentDurations.length + 1 */ - public static Duration[] calculateCumulativeDurations(GraphPath[] segments) { - Duration[] cumulativeDurations = new Duration[segments.length + 1]; + public static Duration[] calculateCumulativeDurations( + Duration[] segmentDurations, + Duration stopDuration + ) { + Duration[] cumulativeDurations = new Duration[segmentDurations.length + 1]; cumulativeDurations[0] = Duration.ZERO; - for (int i = 0; i < segments.length; i++) { - Duration segmentDuration = calculateDuration(segments[i]); - cumulativeDurations[i + 1] = cumulativeDurations[i].plus(segmentDuration); + for (int i = 0; i < segmentDurations.length; i++) { + Duration stopDelay = i > 0 ? stopDuration : Duration.ZERO; + cumulativeDurations[i + 1] = cumulativeDurations[i].plus(stopDelay).plus(segmentDurations[i]); } return cumulativeDurations; diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/FaresConfiguration.java b/application/src/ext/java/org/opentripplanner/ext/fares/FaresConfiguration.java index c520685ff5f..bd226e30727 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/FaresConfiguration.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/FaresConfiguration.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.opentripplanner.ext.fares.service.NoopFareServiceFactory; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.ext.fares.service.gtfs.v1.custom.AtlantaFareServiceFactory; import org.opentripplanner.ext.fares.service.gtfs.v1.custom.CombineInterlinedLegsFactory; import org.opentripplanner.ext.fares.service.gtfs.v1.custom.HSLFareServiceFactory; @@ -58,7 +58,7 @@ public static FareServiceFactory fromConfig(JsonNode config) { } if (type == null) { - type = "default"; + type = "off"; } FareServiceFactory factory = createFactory(type); @@ -68,8 +68,8 @@ public static FareServiceFactory fromConfig(JsonNode config) { private static FareServiceFactory createFactory(String type) { return switch (type) { - case "default" -> new DefaultFareServiceFactory(); case "off" -> new NoopFareServiceFactory(); + case "gtfs" -> new GtfsFareServiceFactory(); case "highest-fare-in-free-transfer-window", "highestFareInFreeTransferWindow" -> new HighestFareInFreeTransferWindowFareServiceFactory(); diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/DefaultFareServiceFactory.java b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/GtfsFareServiceFactory.java similarity index 96% rename from application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/DefaultFareServiceFactory.java rename to application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/GtfsFareServiceFactory.java index 2ebb69b99b4..4334a2104bd 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/DefaultFareServiceFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/GtfsFareServiceFactory.java @@ -30,9 +30,9 @@ * as well as Fares V2. * */ -public class DefaultFareServiceFactory implements FareServiceFactory { +public class GtfsFareServiceFactory implements FareServiceFactory { - private static final Logger LOG = LoggerFactory.getLogger(DefaultFareServiceFactory.class); + private static final Logger LOG = LoggerFactory.getLogger(GtfsFareServiceFactory.class); protected Map regularFareRules = new HashMap<>(); diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/AtlantaFareServiceFactory.java b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/AtlantaFareServiceFactory.java index c5a26e096a2..35983699092 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/AtlantaFareServiceFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/AtlantaFareServiceFactory.java @@ -6,10 +6,10 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.ext.fares.model.FareRulesData; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.routing.fares.FareService; -public class AtlantaFareServiceFactory extends DefaultFareServiceFactory { +public class AtlantaFareServiceFactory extends GtfsFareServiceFactory { protected Map regularFareRules = new HashMap<>(); diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/CombineInterlinedLegsFactory.java b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/CombineInterlinedLegsFactory.java index c35dd3cc817..00f3960f410 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/CombineInterlinedLegsFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/CombineInterlinedLegsFactory.java @@ -1,13 +1,13 @@ package org.opentripplanner.ext.fares.service.gtfs.v1.custom; import com.fasterxml.jackson.databind.JsonNode; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.ext.fares.service.gtfs.v1.custom.CombinedInterlinedLegsFareService.CombinationMode; import org.opentripplanner.routing.core.FareType; import org.opentripplanner.routing.fares.FareService; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; -public class CombineInterlinedLegsFactory extends DefaultFareServiceFactory { +public class CombineInterlinedLegsFactory extends GtfsFareServiceFactory { private CombinationMode mode = CombinationMode.ALWAYS; diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HSLFareServiceFactory.java b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HSLFareServiceFactory.java index 1861a568b3c..7572ad3f6b4 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HSLFareServiceFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HSLFareServiceFactory.java @@ -6,14 +6,14 @@ import org.opentripplanner.ext.fares.model.FareAttribute; import org.opentripplanner.ext.fares.model.FareRule; import org.opentripplanner.ext.fares.model.FareRuleSet; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.routing.core.FareType; import org.opentripplanner.routing.fares.FareService; import org.opentripplanner.transit.model.network.Route; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class HSLFareServiceFactory extends DefaultFareServiceFactory { +public class HSLFareServiceFactory extends GtfsFareServiceFactory { private static final Logger LOG = LoggerFactory.getLogger(HSLFareService.class); diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceFactory.java b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceFactory.java index 281466d26f8..61eb89dd3d0 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/HighestFareInFreeTransferWindowFareServiceFactory.java @@ -9,7 +9,7 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.ext.fares.model.FareRulesData; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.routing.fares.FareService; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; @@ -26,7 +26,7 @@ *

      * This calculator is maintained by IBI Group. */ -public class HighestFareInFreeTransferWindowFareServiceFactory extends DefaultFareServiceFactory { +public class HighestFareInFreeTransferWindowFareServiceFactory extends GtfsFareServiceFactory { protected Map regularFareRules = new HashMap<>(); diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/OrcaFareFactory.java b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/OrcaFareFactory.java index a1bad5c376a..988297745ce 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/OrcaFareFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v1/custom/OrcaFareFactory.java @@ -6,10 +6,10 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.ext.fares.model.FareRulesData; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.routing.fares.FareService; -public class OrcaFareFactory extends DefaultFareServiceFactory { +public class OrcaFareFactory extends GtfsFareServiceFactory { protected Map regularFareRules = new HashMap<>(); diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactory.java b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactory.java index 24e13a98862..8d94371693e 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/service/gtfs/v2/custom/OregonHopFareFactory.java @@ -11,7 +11,7 @@ import org.opentripplanner.ext.fares.model.TimeLimitType; import org.opentripplanner.ext.fares.service.gtfs.GtfsFaresService; import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareService; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.ext.fares.service.gtfs.v2.GtfsFaresV2Service; import org.opentripplanner.model.fare.FareMedium; import org.opentripplanner.model.fare.FareProduct; @@ -20,7 +20,7 @@ import org.opentripplanner.routing.fares.FareService; import org.opentripplanner.transit.model.basic.Money; -public class OregonHopFareFactory extends DefaultFareServiceFactory { +public class OregonHopFareFactory extends GtfsFareServiceFactory { static final FeedScopedId TRIMET_ADULT_SINGLE_RIDE = trimetId("TRIMET_ADULT_SINGLE_RIDE"); static final FeedScopedId TRIMET_HC_SINGLE_RIDE = trimetId("TRIMET_HC_SINGLE_RIDE"); diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java b/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java index d230cf28e3b..e353d1b4672 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java @@ -1,6 +1,7 @@ package org.opentripplanner.ext.flex; import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Multimap; import java.time.LocalDate; import java.util.ArrayList; @@ -17,7 +18,7 @@ public class FlexIndex { - private final Multimap> flexTripsByStop = HashMultimap.create(); + private final Multimap> flexTripsByStopId = HashMultimap.create(); private final Map routeById = new HashMap<>(); @@ -25,17 +26,24 @@ public class FlexIndex { private final Map> flexTripsRunningOnDate = new HashMap<>(); + private final Multimap routeByStop; + public FlexIndex(TimetableRepository timetableRepository) { + var routeByStopBuilder = ImmutableSetMultimap.builder(); + for (FlexTrip flexTrip : timetableRepository.getAllFlexTrips()) { - routeById.put(flexTrip.getTrip().getRoute().getId(), flexTrip.getTrip().getRoute()); + var route = flexTrip.getTrip().getRoute(); + routeById.put(route.getId(), route); tripById.put(flexTrip.getTrip().getId(), flexTrip); for (StopLocation stop : flexTrip.getStops()) { if (stop instanceof GroupStop groupStop) { for (StopLocation stopElement : groupStop.getChildLocations()) { - flexTripsByStop.put(stopElement, flexTrip); + flexTripsByStopId.put(stopElement.getId(), flexTrip); + routeByStopBuilder.put(stopElement, route); } } else { - flexTripsByStop.put(stop, flexTrip); + flexTripsByStopId.put(stop.getId(), flexTrip); + routeByStopBuilder.put(stop, route); } } @@ -55,10 +63,12 @@ public FlexIndex(TimetableRepository timetableRepository) { }); }); } + + routeByStop = routeByStopBuilder.build(); } - public Collection> getFlexTripsByStop(StopLocation stopLocation) { - return flexTripsByStop.get(stopLocation); + public Collection> getFlexTripsByStopId(FeedScopedId stopLocationId) { + return flexTripsByStopId.get(stopLocationId); } public boolean contains(Route route) { @@ -77,6 +87,15 @@ public Collection getAllFlexRoutes() { return tripById.values(); } + /** + * For a given stop location returns the flex routes that visit it, taking care to resolve + * members of group stops (which are visited "transitively", ie. not directly but by + * them being members of the group stop). + */ + public Collection findRoutes(StopLocation stop) { + return routeByStop.get(stop); + } + /** * Returns flex trips for the given running date. Running date is not necessarily the same as the * service date. A Trip "runs through" a date if any of its arrivals or departures is happening on diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java b/application/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java index dc69510691d..65bd2c55a95 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java @@ -242,8 +242,8 @@ public Collection getTransfersToStop(StopLocation stop) { } @Override - public Collection> getFlexTripsByStop(StopLocation stopLocation) { - return flexIndex.getFlexTripsByStop(stopLocation); + public Collection> getFlexTripsByStopId(FeedScopedId stopLocationId) { + return flexIndex.getFlexTripsByStopId(stopLocationId); } @Override diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java b/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java index a362844ef84..33ef420ad9f 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java @@ -25,6 +25,7 @@ import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.organization.Agency; import org.opentripplanner.transit.model.organization.Operator; +import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.booking.BookingInfo; import org.opentripplanner.utils.lang.DoubleUtils; @@ -39,6 +40,10 @@ public class FlexibleTransitLeg implements TransitLeg { private final FlexTripEdge edge; + private final StopLocation fromStop; + + private final StopLocation toStop; + private final ZonedDateTime startTime; private final ZonedDateTime endTime; @@ -53,6 +58,8 @@ public class FlexibleTransitLeg implements TransitLeg { FlexibleTransitLeg(FlexibleTransitLegBuilder builder) { this.edge = Objects.requireNonNull(builder.flexTripEdge()); + this.fromStop = Objects.requireNonNull(builder.fromStop()); + this.toStop = Objects.requireNonNull(builder.toStop()); this.startTime = TimeUtils.normalize(builder.startTime()); this.endTime = TimeUtils.normalize(builder.endTime()); this.generalizedCost = builder.generalizedCost(); @@ -150,12 +157,12 @@ public LocalDate serviceDate() { @Override public Place from() { - return Place.forFlexStop(edge.s1(), edge.getFromVertex()); + return Place.forFlexStop(fromStop, edge.getFromVertex()); } @Override public Place to() { - return Place.forFlexStop(edge.s2(), edge.getToVertex()); + return Place.forFlexStop(toStop, edge.getToVertex()); } @Override @@ -270,6 +277,14 @@ public String toString() { .toString(); } + StopLocation fromStop() { + return fromStop; + } + + StopLocation toStop() { + return toStop; + } + FlexTripEdge flexTripEdge() { return edge; } diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilder.java b/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilder.java index 328c1a2d169..ff97d2fda70 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilder.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLegBuilder.java @@ -10,10 +10,13 @@ import org.opentripplanner.model.fare.FareOffer; import org.opentripplanner.model.plan.Emission; import org.opentripplanner.routing.alertpatch.TransitAlert; +import org.opentripplanner.transit.model.site.StopLocation; public class FlexibleTransitLegBuilder { private FlexTripEdge flexTripEdge; + private StopLocation fromStop; + private StopLocation toStop; private ZonedDateTime startTime; private ZonedDateTime endTime; private int generalizedCost; @@ -25,6 +28,8 @@ public class FlexibleTransitLegBuilder { FlexibleTransitLegBuilder(FlexibleTransitLeg original) { flexTripEdge = original.flexTripEdge(); + fromStop = original.fromStop(); + toStop = original.toStop(); startTime = original.startTime(); endTime = original.endTime(); generalizedCost = original.generalizedCost(); @@ -42,6 +47,24 @@ public FlexTripEdge flexTripEdge() { return flexTripEdge; } + public FlexibleTransitLegBuilder withFromStop(StopLocation stop) { + this.fromStop = stop; + return this; + } + + public StopLocation fromStop() { + return fromStop; + } + + public FlexibleTransitLegBuilder withToStop(StopLocation stop) { + this.toStop = stop; + return this; + } + + public StopLocation toStop() { + return toStop; + } + public FlexibleTransitLegBuilder withStartTime(ZonedDateTime startTime) { this.startTime = startTime; return this; diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/edgetype/FlexTripEdge.java b/application/src/ext/java/org/opentripplanner/ext/flex/edgetype/FlexTripEdge.java index 98af1e27534..ec03e9dbaa6 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/edgetype/FlexTripEdge.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/edgetype/FlexTripEdge.java @@ -4,6 +4,7 @@ import java.util.Objects; import org.locationtech.jts.geom.LineString; import org.opentripplanner.core.model.i18n.I18NString; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.flex.flexpathcalculator.FlexPath; import org.opentripplanner.ext.flex.trip.FlexTrip; import org.opentripplanner.street.model.edge.Edge; @@ -11,15 +12,14 @@ import org.opentripplanner.street.search.TraverseMode; import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.state.StateEditor; -import org.opentripplanner.transit.model.site.StopLocation; /** * Flex trips edges are not connected to the graph. */ public class FlexTripEdge extends Edge { - private final StopLocation s1; - private final StopLocation s2; + private final FeedScopedId fromStopId; + private final FeedScopedId toStopId; private final FlexTrip trip; private final int boardStopPosInPattern; private final int alightStopPosInPattern; @@ -29,8 +29,8 @@ public class FlexTripEdge extends Edge { public FlexTripEdge( Vertex v1, Vertex v2, - StopLocation s1, - StopLocation s2, + FeedScopedId fromStopId, + FeedScopedId toStopId, FlexTrip trip, int boardStopPosInPattern, int alightStopPosInPattern, @@ -38,8 +38,8 @@ public FlexTripEdge( FlexPath flexPath ) { super(v1, v2); - this.s1 = s1; - this.s2 = s2; + this.fromStopId = fromStopId; + this.toStopId = toStopId; this.trip = trip; this.boardStopPosInPattern = boardStopPosInPattern; this.alightStopPosInPattern = alightStopPosInPattern; @@ -47,12 +47,12 @@ public FlexTripEdge( this.flexPath = Objects.requireNonNull(flexPath); } - public StopLocation s1() { - return s1; + public FeedScopedId fromStopId() { + return fromStopId; } - public StopLocation s2() { - return s2; + public FeedScopedId toStopId() { + return toStopId; } public int boardStopPosInPattern() { diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java index 4382000582c..12ed02161c2 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java @@ -8,6 +8,7 @@ import java.util.Objects; import java.util.stream.Stream; import javax.annotation.Nullable; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.flex.FlexAccessEgress; import org.opentripplanner.ext.flex.FlexPathDurations; import org.opentripplanner.ext.flex.edgetype.FlexTripEdge; @@ -85,12 +86,12 @@ abstract class AbstractFlexTemplate { this.maxTransferDuration = maxTransferDuration; } - StopLocation getTransferStop() { - return transferStop; + FeedScopedId getTransferStopId() { + return transferStop.getId(); } - StopLocation getAccessEgressStop() { - return accessEgress.stop; + FeedScopedId getAccessEgressStopId() { + return accessEgress.stopId; } /** @@ -174,7 +175,7 @@ protected abstract FlexPathDurations calculateFlexPathDurations( * Get the FlexTripEdge for the flex ride. */ @Nullable - protected abstract FlexTripEdge getFlexEdge(Vertex flexFromVertex, StopLocation transferStop); + protected abstract FlexTripEdge getFlexEdge(Vertex flexFromVertex, FeedScopedId transferStopId); @Nullable private FlexAccessEgress createFlexAccessEgress( @@ -182,7 +183,7 @@ private FlexAccessEgress createFlexAccessEgress( Vertex flexVertex, RegularStop stop ) { - var flexEdge = getFlexEdge(flexVertex, transferStop); + var flexEdge = getFlexEdge(flexVertex, transferStop.getId()); // Drop non-routable and very short(<10s) trips if (flexEdge == null || flexEdge.getTimeInSeconds() < MIN_FLEX_TRIP_DURATION_SECONDS) { diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/ClosestTrip.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/ClosestTrip.java index ff1dd7836db..91774b60164 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/template/ClosestTrip.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/ClosestTrip.java @@ -83,9 +83,9 @@ public FlexServiceDate activeDate() { ) { var map = new HashMap, ClosestTrip>(); for (NearbyStop nearbyStop : nearbyStops) { - var stop = nearbyStop.stop; - for (var trip : callbackService.getFlexTripsByStop(stop)) { - int stopPos = pickup ? trip.findBoardIndex(stop) : trip.findAlightIndex(stop); + var stopId = nearbyStop.stopId; + for (var trip : callbackService.getFlexTripsByStopId(stopId)) { + int stopPos = pickup ? trip.findBoardIndex(stopId) : trip.findAlightIndex(stopId); if (stopPos != FlexTrip.STOP_INDEX_NOT_FOUND && matcher.match(trip.getTrip())) { var existing = map.get(trip); if (existing == null || nearbyStop.isBetter(existing.nearbyStop())) { diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressCallbackAdapter.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressCallbackAdapter.java index 60633d88230..4c47b920907 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressCallbackAdapter.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressCallbackAdapter.java @@ -27,7 +27,7 @@ public interface FlexAccessEgressCallbackAdapter { Collection getTransfersToStop(StopLocation stop); /** Adapter, look at implementing service for documentation. */ - Collection> getFlexTripsByStop(StopLocation stopLocation); + Collection> getFlexTripsByStopId(FeedScopedId stopLocation); /** * Return true if date is an active service date for the given trip, and can be used for diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessTemplate.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessTemplate.java index cc85b169709..fe66116de81 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessTemplate.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessTemplate.java @@ -3,6 +3,7 @@ import java.time.Duration; import java.util.Collection; import java.util.List; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.flex.FlexPathDurations; import org.opentripplanner.ext.flex.edgetype.FlexTripEdge; import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator; @@ -69,7 +70,7 @@ protected FlexPathDurations calculateFlexPathDurations(FlexTripEdge flexEdge, St ); } - protected FlexTripEdge getFlexEdge(Vertex flexToVertex, StopLocation transferStop) { + protected FlexTripEdge getFlexEdge(Vertex flexToVertex, FeedScopedId transferStopId) { var flexPath = calculator.calculateFlexPath( accessEgress.state.getVertex(), flexToVertex, @@ -84,8 +85,8 @@ protected FlexTripEdge getFlexEdge(Vertex flexToVertex, StopLocation transferSto return new FlexTripEdge( accessEgress.state.getVertex(), flexToVertex, - accessEgress.stop, - transferStop, + accessEgress.stopId, + transferStopId, trip, boardStopPosition, alightStopPosition, diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java index f7d5e34ff17..80a0409837d 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java @@ -9,13 +9,13 @@ import java.util.Collection; import java.util.List; import java.util.Optional; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator; import org.opentripplanner.routing.graphfinder.NearbyStop; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.state.EdgeTraverser; import org.opentripplanner.street.search.state.State; import org.opentripplanner.transit.model.filter.expr.Matcher; -import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.booking.RoutingBookingInfo; @@ -64,11 +64,11 @@ public Collection calculateDirectFlexPaths( matcher ).calculateFlexEgressTemplates(streetEgresses, dates); - Multimap streetEgressByStop = HashMultimap.create(); - streetEgresses.forEach(it -> streetEgressByStop.put(it.stop, it)); + Multimap streetEgressByStopId = HashMultimap.create(); + streetEgresses.forEach(it -> streetEgressByStopId.put(it.stopId, it)); for (FlexAccessTemplate template : flexAccessTemplates) { - StopLocation transferStop = template.getTransferStop(); + var transferStopId = template.getTransferStopId(); // TODO: Document or reimplement this. Why are we using the egress to see if the // access-transfer-stop (last-stop) is used by at least one egress-template? @@ -80,9 +80,9 @@ public Collection calculateDirectFlexPaths( // Problem: Any asymmetrical restriction which apply/do not apply to the egress, // but do not apply/apply to the access, like booking-notice. if ( - flexEgressTemplates.stream().anyMatch(t -> t.getAccessEgressStop().equals(transferStop)) + flexEgressTemplates.stream().anyMatch(t -> t.getAccessEgressStopId().equals(transferStopId)) ) { - for (NearbyStop egress : streetEgressByStop.get(transferStop)) { + for (NearbyStop egress : streetEgressByStopId.get(transferStopId)) { createDirectGraphPath(template, egress, arriveBy, requestTime).ifPresent( directFlexPaths::add ); @@ -111,7 +111,7 @@ private Optional createDirectGraphPath( return Optional.empty(); } - var flexEdge = accessTemplate.getFlexEdge(flexToVertex, egress.stop); + var flexEdge = accessTemplate.getFlexEdge(flexToVertex, egress.stopId); if (flexEdge == null) { return Optional.empty(); diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexEgressTemplate.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexEgressTemplate.java index 9dbbd9c0c79..58aaea058c1 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexEgressTemplate.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexEgressTemplate.java @@ -4,6 +4,7 @@ import java.time.Duration; import java.util.Collection; import java.util.List; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.flex.FlexPathDurations; import org.opentripplanner.ext.flex.edgetype.FlexTripEdge; import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator; @@ -70,7 +71,7 @@ protected FlexPathDurations calculateFlexPathDurations(FlexTripEdge flexEdge, St ); } - protected FlexTripEdge getFlexEdge(Vertex flexFromVertex, StopLocation transferStop) { + protected FlexTripEdge getFlexEdge(Vertex flexFromVertex, FeedScopedId transferStopId) { var flexPath = calculator.calculateFlexPath( flexFromVertex, accessEgress.state.getVertex(), @@ -85,8 +86,8 @@ protected FlexTripEdge getFlexEdge(Vertex flexFromVertex, StopLocation transferS return new FlexTripEdge( flexFromVertex, accessEgress.state.getVertex(), - transferStop, - accessEgress.stop, + transferStopId, + accessEgress.stopId, trip, boardStopPosition, alightStopPosition, diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/trip/FlexTrip.java b/application/src/ext/java/org/opentripplanner/ext/flex/trip/FlexTrip.java index 99185993414..4cc1e25f561 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/trip/FlexTrip.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/trip/FlexTrip.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; @@ -119,25 +120,25 @@ public Trip getTrip() { public abstract PickDrop getAlightRule(int i); - public abstract boolean isBoardingPossible(StopLocation stop); + public abstract boolean isBoardingPossible(FeedScopedId stopId); - public abstract boolean isAlightingPossible(StopLocation stop); + public abstract boolean isAlightingPossible(FeedScopedId stopId); /** - * Find the first stop-position matching the given {@code fromStop} where + * Find the first stop-position matching the given {@code fromStopId} where * boarding is allowed. * * @return stop position in the pattern or {@link #STOP_INDEX_NOT_FOUND} if not found. */ - public abstract int findBoardIndex(StopLocation fromStop); + public abstract int findBoardIndex(FeedScopedId fromStopId); /** - * Find the first stop-position matching the given {@code toStop} where + * Find the first stop-position matching the given {@code toStopId} where * alighting is allowed. * * @return the stop position in the pattern or {@link #STOP_INDEX_NOT_FOUND} if not found. */ - public abstract int findAlightIndex(StopLocation toStop); + public abstract int findAlightIndex(FeedScopedId fromStopId); /** * Allow each FlexTrip type to decorate or replace the router defaultCalculator. diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java b/application/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java index d8a9eee239e..5d9cfb7c743 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java @@ -151,13 +151,13 @@ public PickDrop getAlightRule(int i) { } @Override - public boolean isBoardingPossible(StopLocation fromStop) { - return findBoardIndex(fromStop) != STOP_INDEX_NOT_FOUND; + public boolean isBoardingPossible(FeedScopedId fromStopId) { + return findBoardIndex(fromStopId) != STOP_INDEX_NOT_FOUND; } @Override - public boolean isAlightingPossible(StopLocation toStop) { - return findAlightIndex(toStop) != STOP_INDEX_NOT_FOUND; + public boolean isAlightingPossible(FeedScopedId toStopId) { + return findAlightIndex(toStopId) != STOP_INDEX_NOT_FOUND; } @Override @@ -176,18 +176,23 @@ public TransitBuilder copy( } @Override - public int findBoardIndex(StopLocation fromStop) { + public int findBoardIndex(FeedScopedId fromStopId) { for (int i = 0; i < stopTimes.length; i++) { if (getBoardRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop; if (stop instanceof GroupStop groupStop) { - if (groupStop.getChildLocations().contains(fromStop)) { + if ( + groupStop + .getChildLocations() + .stream() + .anyMatch(childStop -> childStop.getId().equals(fromStopId)) + ) { return i; } } else { - if (stop.equals(fromStop)) { + if (stop.getId().equals(fromStopId)) { return i; } } @@ -196,18 +201,23 @@ public int findBoardIndex(StopLocation fromStop) { } @Override - public int findAlightIndex(StopLocation toStop) { + public int findAlightIndex(FeedScopedId toStopId) { for (int i = stopTimes.length - 1; i >= 0; i--) { if (getAlightRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop; if (stop instanceof GroupStop groupStop) { - if (groupStop.getChildLocations().contains(toStop)) { + if ( + groupStop + .getChildLocations() + .stream() + .anyMatch(childStop -> childStop.getId().equals(toStopId)) + ) { return i; } } else { - if (stop.equals(toStop)) { + if (stop.getId().equals(toStopId)) { return i; } } diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/application/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index 23fa17891b5..48db3ca8c67 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -195,13 +195,13 @@ public PickDrop getAlightRule(int stopIndex) { } @Override - public boolean isBoardingPossible(StopLocation stop) { - return findBoardIndex(stop) != STOP_INDEX_NOT_FOUND; + public boolean isBoardingPossible(FeedScopedId stopId) { + return findBoardIndex(stopId) != STOP_INDEX_NOT_FOUND; } @Override - public boolean isAlightingPossible(StopLocation stop) { - return findAlightIndex(stop) != STOP_INDEX_NOT_FOUND; + public boolean isAlightingPossible(FeedScopedId stopId) { + return findAlightIndex(stopId) != STOP_INDEX_NOT_FOUND; } @Override @@ -220,18 +220,23 @@ public TransitBuilder copy() { } @Override - public int findBoardIndex(StopLocation fromStop) { + public int findBoardIndex(FeedScopedId fromStopId) { for (int i = 0; i < stopTimes.length; i++) { if (getBoardRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop(); if (stop instanceof GroupStop groupStop) { - if (groupStop.getChildLocations().contains(fromStop)) { + if ( + groupStop + .getChildLocations() + .stream() + .anyMatch(childStop -> childStop.getId().equals(fromStopId)) + ) { return i; } } else { - if (stop.equals(fromStop)) { + if (stop.getId().equals(fromStopId)) { return i; } } @@ -240,18 +245,23 @@ public int findBoardIndex(StopLocation fromStop) { } @Override - public int findAlightIndex(StopLocation toStop) { + public int findAlightIndex(FeedScopedId toStopId) { for (int i = stopTimes.length - 1; i >= 0; i--) { if (getAlightRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop(); if (stop instanceof GroupStop groupStop) { - if (groupStop.getChildLocations().contains(toStop)) { + if ( + groupStop + .getChildLocations() + .stream() + .anyMatch(childStop -> childStop.getId().equals(toStopId)) + ) { return i; } } else { - if (stop.equals(toStop)) { + if (stop.getId().equals(toStopId)) { return i; } } diff --git a/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/OptionsView.java b/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/OptionsView.java index 08907346a76..316ac2b8843 100644 --- a/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/OptionsView.java +++ b/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/OptionsView.java @@ -16,7 +16,6 @@ class OptionsView { private final JCheckBox buildTransitGraphChk; private final JCheckBox saveGraphChk; private final JCheckBox startOptServerChk; - private final JCheckBox startOptVisualizerChk; private final StartupModel model; OptionsView(StartupModel model) { @@ -25,7 +24,6 @@ class OptionsView { this.buildTransitGraphChk = new JCheckBox("Transit graph", model.isBuildTransit()); this.saveGraphChk = new JCheckBox("Save graph", model.isSaveGraph()); this.startOptServerChk = new JCheckBox("Serve graph", model.isServeGraph()); - this.startOptVisualizerChk = new JCheckBox("Visualizer", model.isVisualizer()); panel.add(Box.createGlue()); addComp(createBuildBox(), panel); @@ -36,7 +34,6 @@ class OptionsView { // Toggle [ ] save on/off buildStreetGraphChk.addActionListener(e -> onBuildGraphChkChanged()); buildTransitGraphChk.addActionListener(e -> onBuildGraphChkChanged()); - startOptServerChk.addActionListener(e -> onStartOptServerChkChanged()); //addSectionDoubleSpace(panel); bindCheckBoxesToModel(); @@ -58,7 +55,6 @@ private JComponent createActionBox() { addVerticalSectionSpace(actionBox); addComp(saveGraphChk, actionBox); addComp(startOptServerChk, actionBox); - addComp(startOptVisualizerChk, actionBox); return actionBox; } @@ -79,7 +75,6 @@ private void bindCheckBoxesToModel() { bind(buildTransitGraphChk, model::setBuildTransit); bind(saveGraphChk, model::setSaveGraph); bind(startOptServerChk, model::setServeGraph); - bind(startOptVisualizerChk, model::setVisualizer); } private boolean buildStreet() { @@ -93,12 +88,5 @@ private boolean buildTransit() { private void onBuildGraphChkChanged() { saveGraphChk.setEnabled(buildStreet() || buildTransit()); startOptServerChk.setEnabled(buildTransit() || !buildStreet()); - startOptVisualizerChk.setEnabled(buildTransit() || !buildStreet()); - } - - private void onStartOptServerChkChanged() { - startOptVisualizerChk.setEnabled( - startOptServerChk.isEnabled() && startOptServerChk.isSelected() - ); } } diff --git a/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StartupModel.java b/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StartupModel.java index 7416b4a14d0..d9dde7201ea 100644 --- a/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StartupModel.java +++ b/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/startup/StartupModel.java @@ -22,7 +22,6 @@ public class StartupModel { private boolean buildTransit = true; private boolean saveGraph = false; private boolean serveGraph = true; - private boolean visualizer = false; public void subscribeCmdLineUpdates(Consumer commandLineChange) { this.commandLineChange = commandLineChange; @@ -108,15 +107,6 @@ public void setServeGraph(boolean serveGraph) { notifyChangeListener(); } - public boolean isVisualizer() { - return visualizer; - } - - public void setVisualizer(boolean visualizer) { - this.visualizer = visualizer; - notifyChangeListener(); - } - @Override public String toString() { return String.join("", asOtpArgs()); @@ -159,10 +149,6 @@ public String[] asOtpArgs() { if (serveGraph && !buildStreetOnly()) { args.add("--serve"); } - if (serveGraph && !buildStreetOnly() && visualizer) { - args.add("--visualize"); - } - args.add(getDataSourceDirectory()); return args.toArray(new String[0]); diff --git a/application/src/ext/java/org/opentripplanner/ext/ojp/service/CallAtStopService.java b/application/src/ext/java/org/opentripplanner/ext/ojp/service/CallAtStopService.java index 6d9c0b13b8e..da2f9c2ab1c 100644 --- a/application/src/ext/java/org/opentripplanner/ext/ojp/service/CallAtStopService.java +++ b/application/src/ext/java/org/opentripplanner/ext/ojp/service/CallAtStopService.java @@ -64,7 +64,9 @@ public List findCallsAtStop(WgsCoordinate coordinate, StopEventReque .findClosestStops(coordinate.asJtsCoordinate(), params.maximumWalkDistance) .stream() .flatMap(nearbyStop -> { - List stopLocations = List.of(nearbyStop.stop); + List stopLocations = List.of( + transitService.getStopLocation(nearbyStop.stopId) + ); var calls1 = findCallsAtStop(stopLocations, params); return calls1.stream().map(tt -> tt.withWalkTime(nearbyStop.duration())); }) diff --git a/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessAdapter.java b/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessAdapter.java index 257cf0a7ca5..07ad3c368e1 100644 --- a/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessAdapter.java +++ b/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessAdapter.java @@ -14,7 +14,7 @@ public final class RideHailingAccessAdapter extends DefaultAccessEgress { private final Duration arrival; public RideHailingAccessAdapter(RoutingAccessEgress access, Duration arrival) { - super(access.stop(), access.getLastState()); + super(access.stop(), access.getFinalState()); this.arrival = arrival; } diff --git a/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifter.java b/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifter.java index 3eea7bb03f8..9739dc300d1 100644 --- a/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifter.java +++ b/application/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifter.java @@ -45,7 +45,7 @@ public static List shiftAccesses( .map(ae -> { // only time-shift access legs on a car // (there could be walk-only accesses if you're close to the stop) - if (isAccess && ae.getLastState().containsModeCar()) { + if (isAccess && ae.getFinalState().containsModeCar()) { var duration = fetchArrivalDelay(services, request, now); if (duration.isSuccess()) { return new RideHailingAccessAdapter(ae, duration.successValue()); diff --git a/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java index 7f2d112b61d..8fe949c6923 100644 --- a/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java +++ b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java @@ -40,24 +40,30 @@ public int boardingCost( } @Override - public int onTripRelativeRidingCost(int boardTime, T tripScheduledBoarded) { - return delegate.onTripRelativeRidingCost(boardTime, tripScheduledBoarded); + public int transitCost(int transitDuration, T tripScheduledBoarded) { + return delegate.transitCost(transitDuration, tripScheduledBoarded); } @Override public int transitArrivalCost( int boardCost, int alightSlack, - int transitTime, + int transitDuration, T trip, int toStopIndex ) { - int cost = delegate.transitArrivalCost(boardCost, alightSlack, transitTime, trip, toStopIndex); + int cost = delegate.transitArrivalCost( + boardCost, + alightSlack, + transitDuration, + trip, + toStopIndex + ); // This is a bit ugly, since it relies on the fact that the 'transitReluctanceFactorIndex' // returns the 'route.getMode().ordinal()' if (trip.transitReluctanceFactorIndex() == TransitMode.COACH.ordinal()) { - cost += transitTime * EXTRA_RELUCTANCE_ON_COACH; + cost += transitDuration * EXTRA_RELUCTANCE_ON_COACH; } return cost; } @@ -68,8 +74,12 @@ public int waitCost(int waitTimeInSeconds) { } @Override - public int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStopIndex) { - return delegate.calculateRemainingMinCost(minTravelTime, minNumTransfers, fromStopIndex); + public int calculateRemainingMinCost( + int minTravelDuration, + int minNumTransfers, + int fromStopIndex + ) { + return delegate.calculateRemainingMinCost(minTravelDuration, minNumTransfers, fromStopIndex); } @Override diff --git a/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/SorlandsbanenNorwayService.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/SorlandsbanenNorwayService.java index 4f5bf3a9e63..a9abf3f017a 100644 --- a/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/SorlandsbanenNorwayService.java +++ b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/SorlandsbanenNorwayService.java @@ -93,17 +93,18 @@ private static WgsCoordinate findStopCoordinate( Collection accessEgress, RaptorTransitData raptorTransitData ) { - if (location.lat != null) { - return new WgsCoordinate(location.lat, location.lng); + var coord = location.wgsCoordinate(); + if (coord != null) { + return coord; } StopLocation firstStop = null; for (RoutingAccessEgress it : accessEgress) { StopLocation stop = raptorTransitData.getStopByIndex(it.stop()); - if (stop.getId().equals(location.stopId)) { + if (stop.getId().equals(location.stopId())) { return stop.getCoordinate(); } - if (idIsParentStation(stop, location.stopId)) { + if (idIsParentStation(stop, location.stopId())) { return stop.getParentStation().getCoordinate(); } if (firstStop == null) { diff --git a/application/src/ext/java/org/opentripplanner/ext/transferanalyzer/DirectTransferAnalyzer.java b/application/src/ext/java/org/opentripplanner/ext/transferanalyzer/DirectTransferAnalyzer.java index c157560c39b..31029dba502 100644 --- a/application/src/ext/java/org/opentripplanner/ext/transferanalyzer/DirectTransferAnalyzer.java +++ b/application/src/ext/java/org/opentripplanner/ext/transferanalyzer/DirectTransferAnalyzer.java @@ -9,6 +9,7 @@ import java.util.Objects; import java.util.stream.Collectors; import org.locationtech.jts.geom.Coordinate; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.transferanalyzer.annotations.TransferCouldNotBeRouted; import org.opentripplanner.ext.transferanalyzer.annotations.TransferRoutingDistanceTooLong; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; @@ -78,10 +79,7 @@ public void buildGraph() { timetableRepository.getSiteRepository()::findRegularStops ); var linkingContextFactory = new LinkingContextFactory(graph, new VertexCreationService(linker)); - StreetGraphFinder nearbyStopFinderStreets = new StreetGraphFinder( - linkingContextFactory, - timetableRepository.getSiteRepository()::getRegularStop - ); + StreetGraphFinder nearbyStopFinderStreets = new StreetGraphFinder(linkingContextFactory); int stopsAnalyzed = 0; @@ -95,8 +93,8 @@ public void buildGraph() { Map stopsEuclidean = nearbyStopFinderEuclidian .findClosestStops(c0, radiusMeters) .stream() - .filter(t -> t.stop instanceof RegularStop) - .collect(Collectors.toMap(t -> (RegularStop) t.stop, t -> t)); + .filter(nearbyStop -> getRegularStop(nearbyStop.stopId) != null) + .collect(Collectors.toMap(nearbyStop -> getRegularStop(nearbyStop.stopId), t -> t)); Map stopsStreets = new HashMap<>(); try { @@ -104,8 +102,10 @@ public void buildGraph() { nearbyStopFinderStreets .findClosestStops(c0, radiusMeters * RADIUS_MULTIPLIER) .stream() - .filter(t -> t.stop instanceof RegularStop) - .forEach(t -> stopsStreets.putIfAbsent((RegularStop) t.stop, t)); + .filter(nearbyStop -> getRegularStop(nearbyStop.stopId) != null) + .forEach(nearbyStop -> + stopsStreets.putIfAbsent(getRegularStop(nearbyStop.stopId), nearbyStop) + ); } catch (Exception ignored) {} RegularStop originStop = Objects.requireNonNull( @@ -193,6 +193,10 @@ public void buildGraph() { ); } + private RegularStop getRegularStop(FeedScopedId id) { + return timetableRepository.getSiteRepository().getRegularStop(id); + } + private static class TransferInfo { final RegularStop origin; diff --git a/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalParameters.java b/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalParameters.java index 4fb1a06cda2..85cdae8a817 100644 --- a/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalParameters.java +++ b/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalParameters.java @@ -9,8 +9,9 @@ public class VehicleRentalParameters extends VehicleRentalUpdaterParameters { public VehicleRentalParameters( String configRef, Duration frequency, + Duration startupRetryPeriod, VehicleRentalDataSourceParameters sourceParameters ) { - super(configRef, frequency, sourceParameters); + super(configRef, frequency, startupRetryPeriod, sourceParameters); } } diff --git a/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java b/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java index 18b574d8c0e..52f7b7f3db3 100644 --- a/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java +++ b/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java @@ -39,6 +39,7 @@ public class VehicleRentalServiceDirectoryFetcher { VehicleRentalServiceDirectoryFetcher.class ); private static final Duration DEFAULT_FREQUENCY = Duration.ofSeconds(15); + private static final Duration DEFAULT_STARTUP_RETRY_PERIOD = Duration.ZERO; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() .registerModule(new JavaTimeModule()) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); @@ -170,6 +171,7 @@ private VehicleRentalUpdater fetchAndCreateUpdater( VehicleRentalParameters vehicleRentalParameters = new VehicleRentalParameters( "vehicle-rental-service-directory:" + parameters.network(), DEFAULT_FREQUENCY, + DEFAULT_STARTUP_RETRY_PERIOD, parameters ); diff --git a/application/src/main/java/org/opentripplanner/api/common/LocationStringParser.java b/application/src/main/java/org/opentripplanner/api/common/LocationStringParser.java index c70148a2b9e..54d6b5c5a86 100644 --- a/application/src/main/java/org/opentripplanner/api/common/LocationStringParser.java +++ b/application/src/main/java/org/opentripplanner/api/common/LocationStringParser.java @@ -1,5 +1,6 @@ package org.opentripplanner.api.common; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.opentripplanner.core.model.id.FeedScopedId; @@ -37,7 +38,7 @@ public class LocationStringParser { * Creates the GenericLocation by parsing a "name::place" string, where "place" is a geographic * coordinate string (latitude,longitude) or a feed scoped ID (feedId:stopId). */ - public static GenericLocation fromOldStyleString(String input) { + public static Optional fromOldStyleString(String input) { String name = null; String place = input; if (input.contains("::")) { @@ -53,18 +54,18 @@ public static GenericLocation fromOldStyleString(String input) { * for the location that can pass through to the routing response unchanged. The place contains * latitude and longitude or a stop ID. */ - public static GenericLocation getGenericLocation(String label, String place) { + public static Optional getGenericLocation(String label, String place) { if (place == null) { - return null; + return Optional.empty(); } Matcher matcher = LAT_LON_PATTERN.matcher(place); if (matcher.find()) { var lat = Double.parseDouble(matcher.group(1)); var lon = Double.parseDouble(matcher.group(4)); - return new GenericLocation(label, null, lat, lon); + return Optional.of(GenericLocation.fromCoordinate(lat, lon, label)); } else { - return new GenericLocation(label, FeedScopedId.parseOptional(place).orElse(null), null, null); + return FeedScopedId.parseOptional(place).map(id -> GenericLocation.fromStopId(id, label)); } } } diff --git a/application/src/main/java/org/opentripplanner/api/common/Message.java b/application/src/main/java/org/opentripplanner/api/common/Message.java index fd0792134ac..89bde9d2f58 100644 --- a/application/src/main/java/org/opentripplanner/api/common/Message.java +++ b/application/src/main/java/org/opentripplanner/api/common/Message.java @@ -29,6 +29,7 @@ public enum Message { GEOCODE_INTERMEDIATE_NOT_FOUND(465), TOO_CLOSE(409), LOCATION_NOT_ACCESSIBLE(470), + NO_DIRECT_MODE_CONNECTION(480), UNDERSPECIFIED_TRIANGLE(370), TRIANGLE_NOT_AFFINE(371), diff --git a/application/src/main/java/org/opentripplanner/api/model/transit/FeedScopedIdMapper.java b/application/src/main/java/org/opentripplanner/api/model/transit/FeedScopedIdMapper.java index 9a9aa8151a4..b39a01b603b 100644 --- a/application/src/main/java/org/opentripplanner/api/model/transit/FeedScopedIdMapper.java +++ b/application/src/main/java/org/opentripplanner/api/model/transit/FeedScopedIdMapper.java @@ -25,7 +25,7 @@ default FeedScopedId parseStrict(String id) throws InvalidInputException { * @param id a string representation of the id that should be parsed. May be null or * blank * @return Optional.empty() if the input id is null or blank, otherwise a - * FeedScopedId wrapped in an FeedScopedId wrapped in an Optional */ default Optional parseNullSafe(@Nullable String id) { if (id == null) { diff --git a/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java b/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java index 9393f5f0006..d1aaf9c7860 100644 --- a/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java +++ b/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java @@ -3,6 +3,7 @@ import java.io.Serializable; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.function.Predicate; import org.opentripplanner.routing.api.request.RequestModes; @@ -164,4 +165,18 @@ public String toString() { } return sb.toString(); } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + QualifiedModeSet that = (QualifiedModeSet) o; + return Objects.equals(qModes, that.qModes); + } + + @Override + public int hashCode() { + return Objects.hashCode(qModes); + } } diff --git a/application/src/main/java/org/opentripplanner/api/resource/DebugOutput.java b/application/src/main/java/org/opentripplanner/api/resource/DebugOutput.java index 818b72fc6b3..18886979785 100644 --- a/application/src/main/java/org/opentripplanner/api/resource/DebugOutput.java +++ b/application/src/main/java/org/opentripplanner/api/resource/DebugOutput.java @@ -19,7 +19,7 @@ public class DebugOutput { * Time taken in the transit router (including access/egress street router) in nanoseconds. * Detailed timing information within the transit router is sored in transitRouterTimes. * - * @see DebugOutput#transitRouterTimes; + * @see DebugOutput#transitRouterTimes */ public final long transitRouterTime; diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java index 5de98e0e2bc..975ac23303a 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/DefaultValueInjector.java @@ -315,6 +315,23 @@ private static void setTransitDefaults( .boolReq( "TimetablePreferencesInput.includeRealTimeCancellations", transit.includeRealtimeCancellations() + ) + .objectReq( + "TransitPreferencesInput.relaxTransitGroupPriority", + ObjectValue.newObjectValue() + .objectField( + ObjectField.newObjectField() + .name("constant") + .value(IntValue.of(transit.relaxTransitGroupPriority().constant().toSeconds())) + .build() + ) + .objectField( + ObjectField.newObjectField() + .name("coefficient") + .value(FloatValue.of(transit.relaxTransitGroupPriority().coefficient())) + .build() + ) + .build() ); } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java b/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java index 74a936fadea..b0669b8d4f5 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java @@ -35,6 +35,7 @@ public static GraphQLRoutingErrorCode toGraphQL(RoutingErrorCode code) { } return switch (code) { case LOCATION_NOT_FOUND -> GraphQLRoutingErrorCode.LOCATION_NOT_FOUND; + case NO_DIRECT_MODE_CONNECTION -> GraphQLRoutingErrorCode.NO_DIRECT_MODE_CONNECTION; case NO_STOPS_IN_RANGE -> GraphQLRoutingErrorCode.NO_STOPS_IN_RANGE; case NO_TRANSIT_CONNECTION -> GraphQLRoutingErrorCode.NO_TRANSIT_CONNECTION; case NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW -> GraphQLRoutingErrorCode.NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW; diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index 576089e7ad0..7416ae789b8 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -458,10 +458,10 @@ public DataFetcher node() { return null; case "stopAtDistance": { String[] parts = id.split(";"); - var stop = transitService.getRegularStop(FeedScopedId.parseStrict(parts[1])); + var stopId = FeedScopedId.parseStrict(parts[1]); // TODO: Add geometry - return new NearbyStop(stop, Integer.parseInt(parts[0]), null, null); + return new NearbyStop(stopId, Integer.parseInt(parts[0]), null, null); } case "TicketType": // TODO diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java index 1a19c05fcc7..7ef49555135 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StopImpl.java @@ -444,7 +444,12 @@ public DataFetcher> transfers() { .filter(transfer -> maxDistance == null || transfer.getDistanceMeters() < maxDistance) .filter(transfer -> transfer.to instanceof RegularStop) .map(transfer -> - new NearbyStop(transfer.to, transfer.getDistanceMeters(), transfer.getEdges(), null) + new NearbyStop( + transfer.to.getId(), + transfer.getDistanceMeters(), + transfer.getEdges(), + null + ) ) .collect(Collectors.toList()); }, diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stopAtDistanceImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stopAtDistanceImpl.java index 192c4fa17c8..2dd59452f92 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stopAtDistanceImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stopAtDistanceImpl.java @@ -3,8 +3,10 @@ import graphql.relay.Relay; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; +import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.routing.graphfinder.NearbyStop; +import org.opentripplanner.transit.service.TransitService; public class stopAtDistanceImpl implements GraphQLDataFetchers.GraphQLStopAtDistance { @@ -18,13 +20,18 @@ public DataFetcher id() { return environment -> new Relay.ResolvedGlobalId( "stopAtDistance", - getSource(environment).distance + ";" + getSource(environment).stop.getId().toString() + getSource(environment).distance + ";" + getSource(environment).stopId.toString() ); } @Override public DataFetcher stop() { - return environment -> getSource(environment).stop; + return environment -> + getTransitService(environment).getStopLocation(getSource(environment).stopId); + } + + private TransitService getTransitService(DataFetchingEnvironment environment) { + return environment.getContext().transitService(); } private NearbyStop getSource(DataFetchingEnvironment environment) { diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index a0196d4bd6f..7a7ecd8bb13 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -1623,6 +1623,35 @@ public void setGraphQLOriginModesWithParentStation( } } + public static class GraphQLLinearCostFunctionInput { + + private Double coefficient; + private org.opentripplanner.core.model.basic.Cost constant; + + public GraphQLLinearCostFunctionInput(Map args) { + if (args != null) { + this.coefficient = (Double) args.get("coefficient"); + this.constant = (org.opentripplanner.core.model.basic.Cost) args.get("constant"); + } + } + + public Double getGraphQLCoefficient() { + return this.coefficient; + } + + public org.opentripplanner.core.model.basic.Cost getGraphQLConstant() { + return this.constant; + } + + public void setGraphQLCoefficient(Double coefficient) { + this.coefficient = coefficient; + } + + public void setGraphQLConstant(org.opentripplanner.core.model.basic.Cost constant) { + this.constant = constant; + } + } + public static class GraphQLLocalDateRangeInput { private java.time.LocalDate end; @@ -4721,6 +4750,7 @@ public enum GraphQLRouteAlertType { public enum GraphQLRoutingErrorCode { LOCATION_NOT_FOUND, + NO_DIRECT_MODE_CONNECTION, NO_STOPS_IN_RANGE, NO_TRANSIT_CONNECTION, NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW, @@ -5459,6 +5489,7 @@ public static class GraphQLTransitPreferencesInput { private GraphQLAlightPreferencesInput alight; private GraphQLBoardPreferencesInput board; private List filters; + private GraphQLLinearCostFunctionInput relaxTransitGroupPriority; private GraphQLTimetablePreferencesInput timetable; private GraphQLTransferPreferencesInput transfer; @@ -5471,6 +5502,9 @@ public GraphQLTransitPreferencesInput(Map args) { .map(o -> o == null ? null : new GraphQLTransitFilterInput(o)) .collect(Collectors.toList()); } + this.relaxTransitGroupPriority = new GraphQLLinearCostFunctionInput( + (Map) args.get("relaxTransitGroupPriority") + ); this.timetable = new GraphQLTimetablePreferencesInput( (Map) args.get("timetable") ); @@ -5492,6 +5526,10 @@ public List getGraphQLFilters() { return this.filters; } + public GraphQLLinearCostFunctionInput getGraphQLRelaxTransitGroupPriority() { + return this.relaxTransitGroupPriority; + } + public GraphQLTimetablePreferencesInput getGraphQLTimetable() { return this.timetable; } @@ -5512,6 +5550,12 @@ public void setGraphQLFilters(List filters) { this.filters = filters; } + public void setGraphQLRelaxTransitGroupPriority( + GraphQLLinearCostFunctionInput relaxTransitGroupPriority + ) { + this.relaxTransitGroupPriority = relaxTransitGroupPriority; + } + public void setGraphQLTimetable(GraphQLTimetablePreferencesInput timetable) { this.timetable = timetable; } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java index 23ae84bfb3c..7c40ec26484 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java @@ -45,10 +45,10 @@ public static RouteRequest toRouteRequest( CallerWithEnvironment callWith = new CallerWithEnvironment(environment); callWith.argument("fromPlace", (String from) -> - request.withFrom(LocationStringParser.fromOldStyleString(from)) + LocationStringParser.fromOldStyleString(from).ifPresent(request::withFrom) ); callWith.argument("toPlace", (String to) -> - request.withTo(LocationStringParser.fromOldStyleString(to)) + LocationStringParser.fromOldStyleString(to).ifPresent(request::withTo) ); callWith.argument("from", (Map v) -> request.withFrom(toGenericLocation(v))); @@ -262,7 +262,7 @@ private static GenericLocation toGenericLocation(Map m) { String address = (String) m.get("address"); if (address != null) { - return new GenericLocation(address, null, lat, lng); + return GenericLocation.fromCoordinate(lat, lng, address); } return GenericLocation.fromCoordinate(lat, lng); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java index 690dc71b548..41777b61fea 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java @@ -184,7 +184,7 @@ private static GenericLocation parseGenericLocation( var stopId = stopLocation.getGraphQLStopLocationId(); return FeedScopedId.parseOptional(stopId) .map(feedScopedId -> - new GenericLocation(locationInput.getGraphQLLabel(), feedScopedId, null, null) + GenericLocation.fromStopId(feedScopedId, locationInput.getGraphQLLabel()) ) .orElseThrow(() -> new IllegalArgumentException("Stop id %s is not of valid format.".formatted(stopId)) @@ -192,11 +192,10 @@ private static GenericLocation parseGenericLocation( } var coordinate = locationInput.getGraphQLLocation().getGraphQLCoordinate(); - return new GenericLocation( - locationInput.getGraphQLLabel(), - null, + return GenericLocation.fromCoordinate( coordinate.getGraphQLLatitude(), - coordinate.getGraphQLLongitude() + coordinate.getGraphQLLongitude(), + locationInput.getGraphQLLabel() ); } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java index a6b7a4f1aa0..2bb878cbb32 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java @@ -8,6 +8,8 @@ import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.mapping.TransitModeMapper; import org.opentripplanner.apis.support.InvalidInputException; +import org.opentripplanner.core.model.basic.Cost; +import org.opentripplanner.routing.api.request.framework.CostLinearFunction; import org.opentripplanner.routing.api.request.preference.TransferPreferences; import org.opentripplanner.routing.api.request.preference.TransitPreferences; import org.opentripplanner.utils.collection.CollectionUtils; @@ -43,6 +45,21 @@ static void setTransitPreferences( return; } + var relax = transitArgs.getGraphQLRelaxTransitGroupPriority(); + if (relax != null) { + Cost constant = Cost.ZERO; + Double coefficient = 1.0; + if (relax.getGraphQLConstant() != null) { + constant = relax.getGraphQLConstant(); + } + if (relax.getGraphQLCoefficient() != null) { + coefficient = relax.getGraphQLCoefficient(); + } + transitPreferences.withRelaxTransitGroupPriority( + CostLinearFunction.of(constant, coefficient) + ); + } + var board = transitArgs.getGraphQLBoard(); if (board != null) { var slack = board.getGraphQLSlack(); diff --git a/application/src/main/java/org/opentripplanner/apis/support/mapping/PlannerErrorMapper.java b/application/src/main/java/org/opentripplanner/apis/support/mapping/PlannerErrorMapper.java index 9ec662f51e0..302992020b1 100644 --- a/application/src/main/java/org/opentripplanner/apis/support/mapping/PlannerErrorMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/support/mapping/PlannerErrorMapper.java @@ -20,6 +20,9 @@ public static PlannerError mapMessage(RoutingError domain) { case NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW: api = new PlannerError(Message.PATH_NOT_FOUND); break; + case NO_DIRECT_MODE_CONNECTION: + api = new PlannerError(Message.NO_DIRECT_MODE_CONNECTION); + break; case OUTSIDE_BOUNDS: api = new PlannerError(Message.OUTSIDE_BOUNDS); api.setMissing(List.of(domain.inputField.name())); diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaFactory.java b/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaFactory.java index 6cad89af21d..95e90b71c54 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaFactory.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaFactory.java @@ -368,7 +368,7 @@ private GraphQLSchema createDefault() { replacementForRelationType ); - GraphQLOutputType timetabledPassingTime = TimetabledPassingTimeType.create( + var timetabledPassingTime = TimetabledPassingTimeType.create( bookingArrangementType, noticeType, quayType, @@ -795,8 +795,7 @@ private GraphQLSchema createDefault() { .filter( stopAtDistance -> environment.getArgument("authority") == null || - stopAtDistance.stop - .getId() + stopAtDistance.stopId .getFeedId() .equalsIgnoreCase(environment.getArgument("authority")) ) diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/GenericLocationMapper.java b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/GenericLocationMapper.java index 9da4de2180b..efe1ab9053a 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/GenericLocationMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/GenericLocationMapper.java @@ -1,6 +1,7 @@ package org.opentripplanner.apis.transmodel.mapping; import java.util.Map; +import java.util.Optional; import org.opentripplanner.api.model.transit.FeedScopedIdMapper; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.model.GenericLocation; @@ -13,10 +14,9 @@ class GenericLocationMapper { this.idMapper = idMapper; } - /** - * Maps a GraphQL Location input type to a GenericLocation - */ - GenericLocation toGenericLocation(Map m) { + /// Maps a GraphQL Location input type to a GenericLocation. + /// Returns an empty result If the input does not contain a coordinate or an id. + Optional toGenericLocation(Map m) { Map coordinates = (Map) m.get("coordinates"); Double lat = null; Double lon = null; @@ -30,6 +30,14 @@ GenericLocation toGenericLocation(Map m) { String name = (String) m.get("name"); name = name == null ? "" : name; - return new GenericLocation(name, stopId, lat, lon); + if (stopId != null && lat != null && lon != null) { + return Optional.of(GenericLocation.fromStopIdWithFallback(stopId, lat, lon, name)); + } else if (stopId != null) { + return Optional.of(GenericLocation.fromStopId(stopId, name)); + } else if (lat != null && lon != null) { + return Optional.of(GenericLocation.fromCoordinate(lat, lon, name)); + } else { + return Optional.empty(); + } } } diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java index df8f399bab9..0f82395c74e 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java @@ -48,10 +48,10 @@ public RouteRequestBuilder createRequestBuilder(DataFetchingEnvironment environm DataFetcherDecorator callWith = new DataFetcherDecorator(environment); callWith.argument("from", (Map v) -> - requestBuilder.withFrom(genericLocationMapper.toGenericLocation(v)) + genericLocationMapper.toGenericLocation(v).ifPresent(requestBuilder::withFrom) ); callWith.argument("to", (Map v) -> - requestBuilder.withTo(genericLocationMapper.toGenericLocation(v)) + genericLocationMapper.toGenericLocation(v).ifPresent(requestBuilder::withTo) ); callWith.argument("passThroughPoints", (List> v) -> { requestBuilder.withViaLocations(tripViaLocationMapper.toLegacyPassThroughLocations(v)); diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationDeprecatedMapper.java b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationDeprecatedMapper.java index d9b45db54de..929fdf42d1f 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationDeprecatedMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaLocationDeprecatedMapper.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import org.opentripplanner.api.model.transit.FeedScopedIdMapper; +import org.opentripplanner.apis.support.InvalidInputException; import org.opentripplanner.routing.api.request.ViaLocationDeprecated; import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingErrorCode; @@ -23,7 +24,9 @@ class ViaLocationDeprecatedMapper { ViaLocationDeprecated mapViaLocation(Map viaLocation) { try { return new ViaLocationDeprecated( - genericLocationMapper.toGenericLocation(viaLocation), + genericLocationMapper + .toGenericLocation(viaLocation) + .orElseThrow(() -> new InvalidInputException("Invalid via location")), false, (Duration) viaLocation.get("minSlack"), (Duration) viaLocation.get("maxSlack") diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java index 254ef916438..8d9b52b8703 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/ViaRequestMapper.java @@ -61,8 +61,10 @@ public RouteViaRequest createRouteViaRequest(DataFetchingEnvironment environment ) ) .withSearchWindow(environment.getArgumentOrDefault("searchWindow", request.searchWindow())) - .withFrom(genericLocationMapper.toGenericLocation(environment.getArgument("from"))) - .withTo(genericLocationMapper.toGenericLocation(environment.getArgument("to"))) + .withFrom( + genericLocationMapper.toGenericLocation(environment.getArgument("from")).orElse(null) + ) + .withTo(genericLocationMapper.toGenericLocation(environment.getArgument("to")).orElse(null)) .withNumItineraries( environment.getArgumentOrDefault("numTripPatterns", request.numItineraries()) ) diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java index 2348b6a5f06..a5592713501 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java @@ -338,6 +338,11 @@ public class EnumTypes { RoutingErrorCode.WALKING_BETTER_THAN_TRANSIT, "The origin and destination are so close to each other, that walking is always better, but no direct mode was specified for the search" ) + .value( + "noDirectModeConnection", + RoutingErrorCode.NO_DIRECT_MODE_CONNECTION, + "No usable itineraries were found for the requested direct mode and no transit was included in the search" + ) .build(); public static final GraphQLEnumType SERVICE_ALTERATION = GraphQLEnumType.newEnum() diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayAtDistanceType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayAtDistanceType.java index 713173ba2e4..9dbe531fe5b 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayAtDistanceType.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/QuayAtDistanceType.java @@ -2,13 +2,16 @@ import graphql.Scalars; import graphql.relay.Relay; +import graphql.schema.DataFetchingEnvironment; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; import java.util.Optional; import org.opentripplanner.api.model.transit.FeedScopedIdMapper; +import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.routing.graphfinder.NearbyStop; +import org.opentripplanner.transit.service.TransitService; public class QuayAtDistanceType { @@ -29,10 +32,7 @@ public GraphQLObjectType createQD(GraphQLOutputType quayType, Relay relay) { relay.toGlobalId( "QAD", Optional.ofNullable((NearbyStop) environment.getSource()) - .map( - nearbyStop -> - nearbyStop.distance + ";" + idMapper.mapToApi(nearbyStop.stop.getId()) - ) + .map(nearbyStop -> nearbyStop.distance + ";" + idMapper.mapToApi(nearbyStop.stopId)) .orElse(null) ) ) @@ -42,7 +42,11 @@ public GraphQLObjectType createQD(GraphQLOutputType quayType, Relay relay) { GraphQLFieldDefinition.newFieldDefinition() .name("quay") .type(quayType) - .dataFetcher(environment -> ((NearbyStop) environment.getSource()).stop) + .dataFetcher(environment -> + getTransitService(environment).getStopLocation( + ((NearbyStop) environment.getSource()).stopId + ) + ) .build() ) .field( @@ -55,4 +59,8 @@ public GraphQLObjectType createQD(GraphQLOutputType quayType, Relay relay) { ) .build(); } + + private TransitService getTransitService(DataFetchingEnvironment environment) { + return environment.getContext().transitService(); + } } diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnUnprocessableRequestExecutionStrategy.java b/application/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnUnprocessableRequestExecutionStrategy.java index 3fa3a985cb2..009d592b5d7 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnUnprocessableRequestExecutionStrategy.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnUnprocessableRequestExecutionStrategy.java @@ -1,6 +1,7 @@ package org.opentripplanner.apis.transmodel.support; import graphql.execution.AsyncExecutionStrategy; +import graphql.execution.DataFetcherResult; import graphql.execution.ExecutionStrategyParameters; import graphql.schema.DataFetchingEnvironment; import java.io.Closeable; @@ -37,7 +38,7 @@ public AbortOnUnprocessableRequestExecutionStrategy() { } @Override - protected CompletableFuture handleFetchingException( + protected CompletableFuture> handleFetchingException( DataFetchingEnvironment environment, ExecutionStrategyParameters params, Throwable e diff --git a/application/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssue.java b/application/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssue.java index 45a61cf8a3f..b3b74179e3a 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssue.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/issue/api/DataImportIssue.java @@ -1,8 +1,6 @@ package org.opentripplanner.graph_builder.issue.api; import org.locationtech.jts.geom.Geometry; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.vertex.Vertex; /** * Represents noteworthy data import issues that occur during the graph building process. These @@ -47,22 +45,4 @@ default String getHTMLMessage() { default Geometry getGeometry() { return null; } - - /** - * @deprecated This is used in the {@link org.opentripplanner.visualizer.ShowGraph} only, which - * status is unclear. Is anyone still using it? - */ - @Deprecated - default Edge getReferencedEdge() { - return null; - } - - /** - * @deprecated This is used in the {@link org.opentripplanner.visualizer.ShowGraph} only, which - * status is unclear. Is anyone still using it? - */ - @Deprecated - default Vertex getReferencedVertex() { - return null; - } } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/issues/IsolatedStop.java b/application/src/main/java/org/opentripplanner/graph_builder/issues/IsolatedStop.java index 005b143a3fb..2533904c465 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/issues/IsolatedStop.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/issues/IsolatedStop.java @@ -7,7 +7,6 @@ import org.opentripplanner.graph_builder.issue.api.OsmUrlGenerator; import org.opentripplanner.street.geometry.GeometryUtils; import org.opentripplanner.street.model.vertex.TransitStopVertex; -import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.utils.time.DurationUtils; public record IsolatedStop(TransitStopVertex vertex, Duration maxWalk) implements DataImportIssue { @@ -28,11 +27,6 @@ public int getPriority() { return (int) maxWalk.toSeconds(); } - @Override - public Vertex getReferencedVertex() { - return vertex; - } - @Override public Geometry getGeometry() { return GeometryUtils.getGeometryFactory().createPoint(vertex.getCoordinate()); diff --git a/application/src/main/java/org/opentripplanner/graph_builder/issues/StopNotLinkedForTransfers.java b/application/src/main/java/org/opentripplanner/graph_builder/issues/StopNotLinkedForTransfers.java index ed2bdb06e61..ad3b93b2477 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/issues/StopNotLinkedForTransfers.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/issues/StopNotLinkedForTransfers.java @@ -4,7 +4,6 @@ import org.opentripplanner.graph_builder.issue.api.DataImportIssue; import org.opentripplanner.street.geometry.GeometryUtils; import org.opentripplanner.street.model.vertex.TransitStopVertex; -import org.opentripplanner.street.model.vertex.Vertex; public record StopNotLinkedForTransfers(TransitStopVertex stop) implements DataImportIssue { private static final String FMT = "Stop %s not near any other stops; no transfers are possible."; @@ -28,11 +27,6 @@ public String getHTMLMessage() { ); } - @Override - public Vertex getReferencedVertex() { - return this.stop; - } - @Override public Geometry getGeometry() { return GeometryUtils.getGeometryFactory().createPoint(stop.getCoordinate()); diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java b/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java index 3ef56cb71ac..82ba3a947cf 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java @@ -5,6 +5,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -30,7 +31,6 @@ import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.edge.StreetEdge; import org.opentripplanner.street.model.edge.StreetEdgeBuilder; -import org.opentripplanner.street.model.edge.StreetTransitStopLink; import org.opentripplanner.street.model.vertex.OsmBoardingLocationVertex; import org.opentripplanner.street.model.vertex.StreetVertex; import org.opentripplanner.street.model.vertex.TransitStopVertex; @@ -64,7 +64,7 @@ public class OsmBoardingLocationsModule implements GraphBuilderModule { private static final LocalizedString LOCALIZED_PLATFORM_NAME = new LocalizedString( "name.platform" ); - private final double searchRadiusDegrees = SphericalDistanceLibrary.metersToDegrees(250); + private static final double SEARCH_RADIUS_DEGREES = SphericalDistanceLibrary.metersToDegrees(250); private final Graph graph; @@ -73,6 +73,8 @@ public class OsmBoardingLocationsModule implements GraphBuilderModule { private final VertexFactory vertexFactory; private final VertexLinker linker; + private final Map existingBoardingLocationsAtAreas; + /** * @param timetableRepository This module requires the timetable repository because at the time * of the instantiation the site repository is empty. @@ -90,6 +92,7 @@ public OsmBoardingLocationsModule( this.osmInfoGraphBuildService = osmInfoGraphBuildService; this.vertexFactory = new VertexFactory(graph); this.linker = linker; + this.existingBoardingLocationsAtAreas = new HashMap<>(); } @Override @@ -100,18 +103,6 @@ public void buildGraph() { int successes = 0; for (TransitStopVertex ts : graph.getVerticesOfType(TransitStopVertex.class)) { - // if the street is already linked there is no need to link it again, - // could happened if using the prune isolated island - boolean alreadyLinked = false; - for (Edge e : ts.getOutgoing()) { - if (e instanceof StreetTransitStopLink) { - alreadyLinked = true; - break; - } - } - if (alreadyLinked) { - continue; - } // only connect transit stops that are not part of a pathway network if (!ts.hasPathways()) { var stop = stopResolver.getStop(ts.getId()); @@ -146,7 +137,7 @@ private Envelope getEnvelope(TransitStopVertex ts) { Envelope envelope = new Envelope(ts.getCoordinate()); double xscale = Math.cos((ts.getCoordinate().y * Math.PI) / 180); - envelope.expandBy(searchRadiusDegrees / xscale, searchRadiusDegrees); + envelope.expandBy(SEARCH_RADIUS_DEGREES / xscale, SEARCH_RADIUS_DEGREES); return envelope; } @@ -175,10 +166,9 @@ private boolean connectVertexToArea(TransitStopVertex ts, Graph graph) { if (platOpt.isPresent()) { var platform = platOpt.get(); if (matchesReference(stop, platform.references())) { - var boardingLocation = makeBoardingLocation( + var boardingLocation = getOrMakeBoardingLocationForPlatform( stop, - platform.geometry().getCentroid(), - platform.references(), + platform, area.getName() ); linker.addPermanentAreaVertex(boardingLocation, areaGroup); @@ -206,37 +196,37 @@ private boolean connectVertexToWay(TransitStopVertex ts, RegularStop stop, Graph .findPlatform(edge) .ifPresent(platform -> { if (matchesReference(stop, platform.references())) { - if (!nearbyEdges.containsKey(platform)) { - var list = new ArrayList(); - list.add(edge); - nearbyEdges.put(platform, list); - } else { - nearbyEdges.get(platform).add(edge); - } + nearbyEdges.computeIfAbsent(platform, _ -> new ArrayList<>()).add(edge); } }); } - for (var platformEdgeList : nearbyEdges.entrySet()) { - Platform platform = platformEdgeList.getKey(); - var name = platform.name(); - var boardingLocation = makeBoardingLocation( - stop, - platform.geometry().getCentroid(), - platform.references(), - name - ); - for (var vertex : linker.linkToSpecificStreetEdgesPermanently( - boardingLocation, - new TraverseModeSet(TraverseMode.WALK), - LinkingDirection.BIDIRECTIONAL, - platformEdgeList.getValue().stream().map(StreetEdge.class::cast).collect(Collectors.toSet()) - )) { - linkBoardingLocationToStop(ts, stop.getCode(), vertex); - } - return true; - } - return false; + return nearbyEdges + .entrySet() + .stream() + .findFirst() + .map(platformEdgeList -> { + Platform platform = platformEdgeList.getKey(); + var boardingLocation = getOrMakeBoardingLocationForPlatform( + stop, + platform, + platform.name() + ); + for (var vertex : linker.linkToSpecificStreetEdgesPermanently( + boardingLocation, + new TraverseModeSet(TraverseMode.WALK), + LinkingDirection.BIDIRECTIONAL, + platformEdgeList + .getValue() + .stream() + .map(StreetEdge.class::cast) + .collect(Collectors.toSet()) + )) { + linkBoardingLocationToStop(ts, stop.getCode(), vertex); + } + return true; + }) + .orElse(false); } /** @@ -272,6 +262,20 @@ private boolean connectVertexToNode(TransitStopVertex ts, RegularStop stop, Grap return false; } + /* + * when two or more stops reference the same OSM platform, only one + * OsmBoardingLocationVertex is created for that platform and both stops are linked to it. + */ + private OsmBoardingLocationVertex getOrMakeBoardingLocationForPlatform( + RegularStop stop, + Platform platform, + I18NString name + ) { + return existingBoardingLocationsAtAreas.computeIfAbsent(platform, _ -> + makeBoardingLocation(stop, platform.geometry().getCentroid(), platform.references(), name) + ); + } + private OsmBoardingLocationVertex makeBoardingLocation( RegularStop stop, Point centroid, diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/StreetLinkerModule.java b/application/src/main/java/org/opentripplanner/graph_builder/module/StreetLinkerModule.java index 03acbcb606d..f25082742b3 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/StreetLinkerModule.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/StreetLinkerModule.java @@ -181,7 +181,7 @@ private void linkStopToStreetNetwork(TransitStopVertex tStop, StopLinkType linkT *

      * This does not apply to zones as street vertices store which zones they are part of. * - * @see https://github.com/opentripplanner/OpenTripPlanner/issues/5498 + * @link https://github.com/opentripplanner/OpenTripPlanner/issues/5498 */ private void linkToDriveableEdge(TransitStopVertex tStop) { vertexLinker.linkVertexPermanently( diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 647f837194e..a1a97e6cc33 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -84,8 +84,7 @@ static OsmModule provideOsmModule( osmConfiguredDataSource.dataSource(), osmConfiguredDataSource.config().osmTagMapper(), osmConfiguredDataSource.config().timeZone(), - config.osmCacheDataInMem, - issueStore + config.osmCacheDataInMem ) ); } @@ -106,7 +105,7 @@ static OsmModule provideOsmModule( .withIncludeInclinedEdgeLevelInfo(config.includeInclinedEdgeLevelInfo) .withMaxAreaNodes(config.maxAreaNodes) .withBoardingAreaRefTags(config.boardingLocationTags) - .withIncludeOsmSubwayEntrances(config.osmDefaults.includeOsmSubwayEntrances()) + .withIncludeOsmStationEntrances(config.osmDefaults.includeOsmStationEntrances()) .withIssueStore(issueStore) .build(); } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/GraphIsland.java b/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/GraphIsland.java index e484a6fc185..8390bad3420 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/GraphIsland.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/GraphIsland.java @@ -65,11 +65,6 @@ public int getPriority() { return island.streetSize() + island.stopSize(); } - @Override - public Vertex getReferencedVertex() { - return island.getRepresentativeVertex(); - } - @Override public Geometry getGeometry() { return island.getGeometry(); diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/PrunedStopIsland.java b/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/PrunedStopIsland.java index eb7961c3865..d9bb15f958f 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/PrunedStopIsland.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/islandpruning/PrunedStopIsland.java @@ -2,7 +2,6 @@ import org.locationtech.jts.geom.Geometry; import org.opentripplanner.graph_builder.issue.api.DataImportIssue; -import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.model.vertex.VertexLabel; public record PrunedStopIsland( @@ -57,11 +56,6 @@ public int getPriority() { return island.streetSize() + island.stopSize(); } - @Override - public Vertex getReferencedVertex() { - return island.getRepresentativeVertex(); - } - @Override public Geometry getGeometry() { return island.getGeometry(); diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/NearbyStopFinderVisitor.java b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/NearbyStopFinderVisitor.java index 7514b6d5a63..811c4c2bf8b 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/NearbyStopFinderVisitor.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/NearbyStopFinderVisitor.java @@ -6,7 +6,6 @@ import com.google.common.collect.Multimap; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.Set; import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.core.model.id.FeedScopedId; @@ -19,7 +18,6 @@ import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.TraverseMode; import org.opentripplanner.street.search.state.State; -import org.opentripplanner.transit.model.site.AreaStop; /** * A {@link TraverseVisitor} that collects transit stops and flex area stops during an A* search, @@ -31,21 +29,18 @@ */ class NearbyStopFinderVisitor implements TraverseVisitor { - private final StopResolver stopResolver; private final Set originVertices; private final Set ignoreVertices; private final boolean reverseDirection; private final List transitStopsFound = new ArrayList<>(); - private final Multimap areaStopStates = ArrayListMultimap.create(); + private final Multimap statesForAreaStopIds = ArrayListMultimap.create(); NearbyStopFinderVisitor( - StopResolver stopResolver, Set originVertices, Set ignoreVertices, boolean reverseDirection ) { - this.stopResolver = requireNonNull(stopResolver); this.originVertices = requireNonNull(originVertices); this.ignoreVertices = requireNonNull(ignoreVertices); this.reverseDirection = reverseDirection; @@ -60,8 +55,7 @@ public void visitVertex(State state) { } if (vertex instanceof TransitStopVertex tsv && state.isFinal()) { - var stop = requireNonNull(stopResolver.getRegularStop(tsv.getId())); - transitStopsFound.add(NearbyStop.nearbyStopForState(state, stop)); + transitStopsFound.add(NearbyStop.nearbyStopForState(state, tsv.getId())); } if ( @@ -70,9 +64,8 @@ public void visitVertex(State state) { !streetVertex.areaStops().isEmpty() ) { for (FeedScopedId id : streetVertex.areaStops()) { - AreaStop areaStop = Objects.requireNonNull(stopResolver.getAreaStop(id)); if (canBoardFlex(state)) { - areaStopStates.put(areaStop, state); + statesForAreaStopIds.put(id, state); } } } @@ -88,8 +81,8 @@ List transitStopsFound() { return transitStopsFound; } - Multimap areaStopStates() { - return areaStopStates; + Multimap statesForAreaStopIds() { + return statesForAreaStopIds; } private boolean canBoardFlex(State state) { diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java index 2dab3b4a001..c087b2ffa45 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java @@ -31,10 +31,8 @@ public class StreetNearbyStopFinder implements NearbyStopFinder { private final Duration durationLimit; private final int maxStopCount; - private final StopResolver stopResolver; private final Collection extensionRequestContexts; private final Set ignoreVertices; - private final NearbyStopFactory nearbyStopFactory; /** * Construct a NearbyStopFinder for the given graph and search radius. @@ -44,18 +42,15 @@ public class StreetNearbyStopFinder implements NearbyStopFinder { * @param ignoreVertices A set of stop vertices to ignore and not return NearbyStops for. */ private StreetNearbyStopFinder( - StopResolver stopResolver, Duration durationLimit, int maxStopCount, Collection extensionRequestContexts, Set ignoreVertices ) { - this.stopResolver = requireNonNull(stopResolver); this.durationLimit = requireNonNull(durationLimit); this.maxStopCount = requireNonNull(maxStopCount); this.extensionRequestContexts = requireNonNull(extensionRequestContexts); this.ignoreVertices = requireNonNull(ignoreVertices); - this.nearbyStopFactory = new NearbyStopFactory(stopResolver::getRegularStop); } /** @@ -64,8 +59,8 @@ private StreetNearbyStopFinder( * @param maxStopCount The maximum stops to return. 0 means no limit. Regardless of the * maxStopCount we will always return all the directly connected stops. */ - public static Builder of(StopResolver stopResolver, Duration durationLimit, int maxStopCount) { - return new Builder(stopResolver, durationLimit, maxStopCount); + public static Builder of(Duration durationLimit, int maxStopCount) { + return new Builder(durationLimit, maxStopCount); } /** @@ -99,7 +94,7 @@ public Collection findNearbyStops( ) { OTPRequestTimeoutException.checkForTimeout(); - List stopsFound = nearbyStopFactory.nearbyStopsForTransitStopVerticesFiltered( + List stopsFound = NearbyStopFactory.nearbyStopsForTransitStopVerticesFiltered( Sets.difference(originVertices, ignoreVertices), reverseDirection, request, @@ -114,12 +109,7 @@ public Collection findNearbyStops( } stopsFound = new ArrayList<>(stopsFound); - var visitor = new NearbyStopFinderVisitor( - stopResolver, - originVertices, - ignoreVertices, - reverseDirection - ); + var visitor = new NearbyStopFinderVisitor(originVertices, ignoreVertices, reverseDirection); var streetSearch = StreetSearchBuilder.of() .withPreStartHook(OTPRequestTimeoutException::checkForTimeout) @@ -147,9 +137,9 @@ public Collection findNearbyStops( stopsFound.addAll(visitor.transitStopsFound()); if (OTPFeature.FlexRouting.isOn()) { - for (var locationStates : visitor.areaStopStates().asMap().entrySet()) { - var areaStop = locationStates.getKey(); - var states = locationStates.getValue(); + for (var statesForAreaStopIds : visitor.statesForAreaStopIds().asMap().entrySet()) { + var areaStopId = statesForAreaStopIds.getKey(); + var states = statesForAreaStopIds.getValue(); // Select the vertex from all vertices that are reachable per AreaStop by taking // the minimum walking distance State min = Collections.min(states, Comparator.comparing(State::getWeight)); @@ -161,7 +151,7 @@ public Collection findNearbyStops( min = min.getBackState(); } - stopsFound.add(NearbyStop.nearbyStopForState(min, areaStop)); + stopsFound.add(NearbyStop.nearbyStopForState(min, areaStopId)); } } @@ -193,10 +183,8 @@ public static class Builder { private final int maxStopCount; private Collection extensionRequestContexts = List.of(); private Set ignoreVertices = Set.of(); - private final StopResolver stopResolver; - public Builder(StopResolver stopResolver, Duration durationLimit, int maxStopCount) { - this.stopResolver = stopResolver; + public Builder(Duration durationLimit, int maxStopCount) { this.durationLimit = durationLimit; this.maxStopCount = maxStopCount; } @@ -224,7 +212,6 @@ public Builder withIgnoreVertices(Set ignoreVertices) { public StreetNearbyStopFinder build() { return new StreetNearbyStopFinder( - stopResolver, durationLimit, maxStopCount, extensionRequestContexts, diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/ned/MissingElevationHandler.java b/application/src/main/java/org/opentripplanner/graph_builder/module/ned/MissingElevationHandler.java index c846ea9a94f..a08ce522259 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/ned/MissingElevationHandler.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/ned/MissingElevationHandler.java @@ -41,7 +41,7 @@ *

    7. * If a vertex only had a single path, then the last known elevation is used *
    8. - * + *
    9. * Once elevations for vertices are interpolated they are used to set the elevation profile * for the incoming / outgoing StreetEdges *
    10. diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java index 067919a3c60..f8d347b456a 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java @@ -35,7 +35,6 @@ import org.opentripplanner.osm.model.OsmNode; import org.opentripplanner.osm.model.OsmRelation; import org.opentripplanner.osm.model.OsmRelationMember; -import org.opentripplanner.osm.model.OsmTag; import org.opentripplanner.osm.model.OsmWay; import org.opentripplanner.street.geometry.GeometryUtils; import org.opentripplanner.street.geometry.HashGridSpatialIndex; @@ -119,6 +118,11 @@ public class OsmDatabase { */ private final Multimap stopsInAreas = HashMultimap.create(); + /** + * Set of all entrance nodes in stop areas, which will be treated as station entrances. + */ + private final Set entrancesInStopAreas = new HashSet<>(); + /* * ID of the next virtual node we create during building phase. Negative to prevent conflicts * with existing ones. @@ -190,6 +194,10 @@ public Collection getStopsInArea(OsmEntity areaParent) { return stopsInAreas.get(areaParent); } + public boolean isEntranceInStopArea(OsmNode node) { + return entrancesInStopAreas.contains(node); + } + /** * @return If a single level is defined for an entity return that level, * otherwise the default level is returned. @@ -806,19 +814,6 @@ private void processRelations() { } } - /** - * Handle route=bicycle relations. Copies their network type to all way members. - * - * @see "https://wiki.openstreetmap.org/wiki/Tag:route%3Dbicycle" - */ - private void processBicycleRoute(OsmRelation relation) { - if (relation.isBicycleRoute()) { - // we treat networks without known network type like local networks - var network = relation.getTagOpt("network").orElse("lcn"); - setNetworkForAllMembers(relation, network); - } - } - private void setNetworkForAllMembers(OsmRelation relation, String key) { relation .getMembers() @@ -936,38 +931,11 @@ private void processRestriction(OsmRelation relation) { * Handle route=road and route=bicycle relations. */ private void processRoute(OsmRelation relation) { - for (OsmRelationMember member : relation.getMembers()) { - if (!(member.hasTypeWay() && waysById.containsKey(member.getRef()))) { - continue; - } - - OsmWay way = waysById.get(member.getRef()); - if (way == null) { - continue; - } - - if (relation.hasTag("name")) { - if (way.hasTag("otp:route_name")) { - way.addTag( - "otp:route_name", - addUniqueName(way.getTag("otp:route_name"), relation.getTag("name")) - ); - } else { - way.addTag(new OsmTag("otp:route_name", relation.getTag("name"))); - } - } - if (relation.hasTag("ref")) { - if (way.hasTag("otp:route_ref")) { - way.addTag( - "otp:route_ref", - addUniqueName(way.getTag("otp:route_ref"), relation.getTag("ref")) - ); - } else { - way.addTag(new OsmTag("otp:route_ref", relation.getTag("ref"))); - } - } + if (relation.isBicycleRoute()) { + // we treat networks without known network type like local networks + var network = relation.getTagOpt("network").orElse("lcn"); + setNetworkForAllMembers(relation, network); } - processBicycleRoute(relation); } /** @@ -988,8 +956,13 @@ private void processPublicTransportStopArea(OsmRelation relation) { switch (member.getType()) { case NODE -> { var node = nodesById.get(member.getRef()); - if (node != null && (node.isEntrance() || node.isBoardingLocation())) { - platformNodes.add(node); + if (node != null) { + if (node.isPlatformAccess()) { + platformNodes.add(node); + } + if (node.isEntrance()) { + entrancesInStopAreas.add(node); + } } } case WAY -> { diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index 78398f523a4..8a05dc96d36 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -126,7 +126,7 @@ public void buildGraph() { osmdb, graph, params.boardingAreaRefTags(), - params.includeOsmSubwayEntrances(), + params.includeOsmStationEntrances(), issueStore ); for (var provider : providers) { @@ -433,6 +433,7 @@ private void buildBasicGraph( osmEndNode.hasTag("ele") || osmEndNode.isBoardingLocation() || osmEndNode.isBarrier() || + osmEndNode.isEntrance() || vertexGenerator.nodesInBarrierWays().containsKey(osmEndNode) ) { segmentCoordinates.add(osmEndNode.getCoordinate()); diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java index 7f39456603f..bbaa3804d1d 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java @@ -34,7 +34,7 @@ public class OsmModuleBuilder { private boolean staticParkAndRide = false; private boolean staticBikeParkAndRide = false; private boolean includeInclinedEdgeLevelInfo = false; - private boolean includeOsmSubwayEntrances = false; + private boolean includeOsmStationEntrances = false; private int maxAreaNodes = StreetConstants.DEFAULT_MAX_AREA_NODES; public OsmModuleBuilder( @@ -98,8 +98,8 @@ public OsmModuleBuilder withMaxAreaNodes(int maxAreaNodes) { return this; } - public OsmModuleBuilder withIncludeOsmSubwayEntrances(boolean includeOsmSubwayEntrances) { - this.includeOsmSubwayEntrances = includeOsmSubwayEntrances; + public OsmModuleBuilder withIncludeOsmStationEntrances(boolean includeOsmStationEntrances) { + this.includeOsmStationEntrances = includeOsmStationEntrances; return this; } @@ -121,7 +121,7 @@ public OsmModule build() { staticParkAndRide, staticBikeParkAndRide, includeInclinedEdgeLevelInfo, - includeOsmSubwayEntrances + includeOsmStationEntrances ) ); } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java index 11d04a10702..24198f57586 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java @@ -63,7 +63,7 @@ class VertexGenerator { private final Map> splitVerticesOnBarriers = new HashMap<>(); private final OsmDatabase osmdb; private final Set boardingAreaRefTags; - private final Boolean includeOsmSubwayEntrances; + private final Boolean includeOsmStationEntrances; private final VertexFactory vertexFactory; private final DataImportIssueStore issueStore; @@ -71,13 +71,13 @@ public VertexGenerator( OsmDatabase osmdb, Graph graph, Set boardingAreaRefTags, - boolean includeOsmSubwayEntrances, + boolean includeOsmStationEntrances, DataImportIssueStore issueStore ) { this.osmdb = osmdb; this.vertexFactory = new VertexFactory(graph); this.boardingAreaRefTags = boardingAreaRefTags; - this.includeOsmSubwayEntrances = includeOsmSubwayEntrances; + this.includeOsmStationEntrances = includeOsmStationEntrances; this.issueStore = issueStore; } @@ -134,7 +134,9 @@ IntersectionVertex getVertexForOsmNode( } } - if (includeOsmSubwayEntrances && node.isSubwayEntrance()) { + if ( + includeOsmStationEntrances && (node.isStationEntrance() || osmdb.isEntranceInStopArea(node)) + ) { String ref = node.getTag("ref"); iv = vertexFactory.stationEntrance( nid, diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParameters.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParameters.java index 0e553b1b180..856cc87834a 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParameters.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParameters.java @@ -15,13 +15,13 @@ public record OsmExtractParameters( URI source, OsmTagMapperSource osmTagMapper, ZoneId timeZone, - boolean includeOsmSubwayEntrances + boolean includeOsmStationEntrances ) implements DataSourceConfig { public static final OsmTagMapperSource DEFAULT_OSM_TAG_MAPPER = OsmTagMapperSource.DEFAULT; public static final ZoneId DEFAULT_TIME_ZONE = null; - public static final boolean DEFAULT_INCLUDE_OSM_SUBWAY_ENTRANCES = false; + public static final boolean DEFAULT_INCLUDE_OSM_STATION_ENTRANCES = false; public static final OsmExtractParameters DEFAULT = new OsmExtractParametersBuilder().build(); @@ -30,7 +30,7 @@ public record OsmExtractParameters( builder.getSource(), builder.getOsmTagMapper(), builder.getTimeZone(), - builder.includeOsmSubwayEntrances() + builder.includeOsmStationEntrances() ); } @@ -48,8 +48,8 @@ public ZoneId timeZone() { return timeZone; } - public boolean includeOsmSubwayEntrances() { - return includeOsmSubwayEntrances; + public boolean includeOsmStationEntrances() { + return includeOsmStationEntrances; } public OsmExtractParametersBuilder copyOf() { diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParametersBuilder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParametersBuilder.java index 66c65e05d81..ccf7d2b9d0f 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParametersBuilder.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParametersBuilder.java @@ -24,18 +24,18 @@ public class OsmExtractParametersBuilder { */ private ZoneId timeZone; - private boolean includeOsmSubwayEntrances; + private boolean includeOsmStationEntrances; public OsmExtractParametersBuilder() { this.osmTagMapper = OsmExtractParameters.DEFAULT_OSM_TAG_MAPPER; this.timeZone = OsmExtractParameters.DEFAULT_TIME_ZONE; - this.includeOsmSubwayEntrances = OsmExtractParameters.DEFAULT_INCLUDE_OSM_SUBWAY_ENTRANCES; + this.includeOsmStationEntrances = OsmExtractParameters.DEFAULT_INCLUDE_OSM_STATION_ENTRANCES; } public OsmExtractParametersBuilder(OsmExtractParameters original) { this.osmTagMapper = original.osmTagMapper(); this.timeZone = original.timeZone(); - this.includeOsmSubwayEntrances = original.includeOsmSubwayEntrances(); + this.includeOsmStationEntrances = original.includeOsmStationEntrances(); } public OsmExtractParametersBuilder withSource(URI source) { @@ -53,10 +53,10 @@ public OsmExtractParametersBuilder withTimeZone(ZoneId timeZone) { return this; } - public OsmExtractParametersBuilder withIncludeOsmSubwayEntrances( - boolean includeOsmSubwayEntrances + public OsmExtractParametersBuilder withIncludeOsmStationEntrances( + boolean includeOsmStationEntrances ) { - this.includeOsmSubwayEntrances = includeOsmSubwayEntrances; + this.includeOsmStationEntrances = includeOsmStationEntrances; return this; } @@ -72,8 +72,8 @@ public ZoneId getTimeZone() { return timeZone; } - public boolean includeOsmSubwayEntrances() { - return includeOsmSubwayEntrances; + public boolean includeOsmStationEntrances() { + return includeOsmStationEntrances; } public OsmExtractParameters build() { diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java index db3fb1009af..c18982e7f4e 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java @@ -14,7 +14,7 @@ * @param staticParkAndRide Whether we should create car P+R stations from OSM data. * @param staticBikeParkAndRide Whether we should create bike P+R stations from OSM data. * @param includeInclinedEdgeLevelInfo Whether level info for inclined edges should be stored. - * @param includeOsmSubwayEntrances Whether we should create subway entrances from OSM data. + * @param includeOsmStationEntrances Whether we should create station entrances from OSM data. */ public record OsmProcessingParameters( Set boardingAreaRefTags, @@ -25,7 +25,7 @@ public record OsmProcessingParameters( boolean staticParkAndRide, boolean staticBikeParkAndRide, boolean includeInclinedEdgeLevelInfo, - boolean includeOsmSubwayEntrances + boolean includeOsmStationEntrances ) { public OsmProcessingParameters { boardingAreaRefTags = Set.copyOf(Objects.requireNonNull(boardingAreaRefTags)); diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java index 5eff2bf4d60..ae953f0a948 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java @@ -16,8 +16,6 @@ import org.opentripplanner.graph_builder.issues.StopNotLinkedForTransfers; import org.opentripplanner.graph_builder.model.GraphBuilderModule; import org.opentripplanner.graph_builder.module.nearbystops.NearbyStopFinder; -import org.opentripplanner.graph_builder.module.nearbystops.SiteRepositoryResolver; -import org.opentripplanner.graph_builder.module.nearbystops.StopResolver; import org.opentripplanner.graph_builder.module.nearbystops.StraightLineNearbyStopFinder; import org.opentripplanner.graph_builder.module.nearbystops.StreetNearbyStopFinder; import org.opentripplanner.graph_builder.module.transfer.api.RegularTransferParameters; @@ -245,10 +243,7 @@ private NearbyStopFinder createNearbyStopFinder(Duration radiusAsDuration) { finder = new StraightLineNearbyStopFinder(transitService, radiusAsDuration); } else { LOG.info("Creating direct transfer edges between stops using the street network from OSM..."); - final StopResolver stopResolver = new SiteRepositoryResolver( - timetableRepository.getSiteRepository() - ); - finder = StreetNearbyStopFinder.of(stopResolver, radiusAsDuration, 0).build(); + finder = StreetNearbyStopFinder.of(radiusAsDuration, 0).build(); } if (OTPFeature.ConsiderPatternsForDirectTransfers.isOn()) { @@ -390,12 +385,14 @@ private void calculateDefaultTransfers( .defaultNearbyStopFinderForMode() .get(mode) .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer().mode(), false); + var repository = timetableRepository.getSiteRepository(); for (NearbyStop sd : nearbyStops) { // Skip the origin stop, loop transfers are not needed. - if (sd.stop == stop) { + var nearbyStop = repository.getStopLocation(sd.stopId); + if (nearbyStop.equals(stop)) { continue; } - createPathTransfer(stop, sd.stop, sd, distinctTransfers, mode); + createPathTransfer(stop, nearbyStop, sd, distinctTransfers, mode); } } } @@ -418,16 +415,18 @@ private void calculateFlexTransfers( .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer().mode(), true); // This code is for finding transfers from AreaStops to Stops, transfers // from Stops to AreaStops and between Stops are already covered above. + var repository = timetableRepository.getSiteRepository(); for (NearbyStop sd : nearbyStops) { // Skip the origin stop, loop transfers are not needed. - if (sd.stop == stop) { + var nearbyStop = repository.getStopLocation(sd.stopId); + if (nearbyStop.equals(stop)) { continue; } - if (sd.stop instanceof RegularStop) { + if (nearbyStop instanceof RegularStop) { continue; } // The TransferKey and PathTransfer are created differently for flex routing. - createPathTransfer(sd.stop, stop, sd, distinctTransfers, mode); + createPathTransfer(nearbyStop, stop, sd, distinctTransfers, mode); } } } @@ -492,19 +491,21 @@ private void calculateTransfersForStopWithAllowedStops( var nearbyStops = nearbyStopFinder .get(mode) .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer().mode(), false); + var repository = timetableRepository.getSiteRepository(); for (NearbyStop sd : nearbyStops) { + var nearbyStop = repository.getStopLocation(sd.stopId); // Skip the origin stop, loop transfers are not needed. - if (sd.stop == stop) { + if (nearbyStop.equals(stop)) { continue; } - if (sd.stop.transfersNotAllowed()) { + if (nearbyStop.transfersNotAllowed()) { continue; } // Only calculate transfers between allowedStops. - if (!allowedStops.contains(sd.stop)) { + if (!allowedStops.contains(nearbyStop)) { continue; } - createPathTransfer(stop, sd.stop, sd, distinctTransfers, mode); + createPathTransfer(stop, nearbyStop, sd, distinctTransfers, mode); } } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/FlexTripNearbyStopFilter.java b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/FlexTripNearbyStopFilter.java index 8ca720b84b7..fa126cc68b2 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/FlexTripNearbyStopFilter.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/FlexTripNearbyStopFilter.java @@ -23,8 +23,7 @@ class FlexTripNearbyStopFilter implements NearbyStopFilter { @Override public boolean includeFromStop(FeedScopedId id, boolean reverseDirection) { - var stop = transitService.getStopLocation(id); - return !transitService.getFlexIndex().getFlexTripsByStop(stop).isEmpty(); + return !transitService.getFlexIndex().getFlexTripsByStopId(id).isEmpty(); } @Override @@ -34,11 +33,11 @@ public Collection filterToStops( ) { MinMap, NearbyStop> closestStopForFlexTrip = new MinMap<>(); for (var it : nearbyStops) { - var stop = it.stop; - var flexTrips = transitService.getFlexIndex().getFlexTripsByStop(stop); + var stopId = it.stopId; + var flexTrips = transitService.getFlexIndex().getFlexTripsByStopId(stopId); for (FlexTrip trip : flexTrips) { - if (reverseDirection ? trip.isAlightingPossible(stop) : trip.isBoardingPossible(stop)) { + if (reverseDirection ? trip.isAlightingPossible(stopId) : trip.isBoardingPossible(stopId)) { closestStopForFlexTrip.putMin(trip, it); } } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternConsideringNearbyStopFinder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternConsideringNearbyStopFinder.java index f57a886c977..6c0a0cac0d0 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternConsideringNearbyStopFinder.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternConsideringNearbyStopFinder.java @@ -32,10 +32,13 @@ public class PatternConsideringNearbyStopFinder implements NearbyStopFinder { private final NearbyStopFinder delegateNearbyStopFinder; + private final TransitService transitService; + public PatternConsideringNearbyStopFinder( TransitService transitService, NearbyStopFinder delegateNearbyStopFinder ) { + this.transitService = transitService; var builder = CompositeNearbyStopFilter.of().add(new PatternNearbyStopFilter(transitService)); if (OTPFeature.FlexRouting.isOn()) { @@ -80,12 +83,10 @@ public List findNearbyStops( return List.copyOf(result); } - private static Collection removeTransferNotAllowedStops( - Collection nearbyStops - ) { + private Collection removeTransferNotAllowedStops(Collection nearbyStops) { return nearbyStops .stream() - .filter(s -> !s.stop.transfersNotAllowed()) + .filter(s -> !transitService.getStopLocation(s.stopId).transfersNotAllowed()) .toList(); } } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternNearbyStopFilter.java b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternNearbyStopFilter.java index 4ab32e4a973..c8f16f48742 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternNearbyStopFilter.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternNearbyStopFilter.java @@ -49,7 +49,7 @@ public Collection filterToStops( Set uniqueStopsResult = new HashSet<>(); for (var it : nearbyStops) { - StopLocation stop = it.stop; + StopLocation stop = transitService.getStopLocation(it.stopId); if (stop instanceof RegularStop regularStop) { var patternsForStop = findPatternsForStop(regularStop, reverseDirection); diff --git a/application/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsModule.java b/application/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsModule.java index 312aeaca853..04918dacfe7 100644 --- a/application/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsModule.java +++ b/application/src/main/java/org/opentripplanner/gtfs/graphbuilder/GtfsModule.java @@ -19,7 +19,7 @@ import org.opentripplanner.core.framework.deduplicator.DeduplicatorService; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.core.model.time.LocalDateInterval; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.ext.flex.FlexTripsMapper; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; @@ -118,7 +118,7 @@ public static GtfsModule forTest( new Deduplicator(), DataImportIssueStore.NOOP, transitPeriodLimit, - new DefaultFareServiceFactory(), + new GtfsFareServiceFactory(), 150.0, 120 ); diff --git a/application/src/main/java/org/opentripplanner/model/GenericLocation.java b/application/src/main/java/org/opentripplanner/model/GenericLocation.java index 4d79bbc5f15..633b2d57d20 100644 --- a/application/src/main/java/org/opentripplanner/model/GenericLocation.java +++ b/application/src/main/java/org/opentripplanner/model/GenericLocation.java @@ -4,6 +4,7 @@ import javax.annotation.Nullable; import org.locationtech.jts.geom.Coordinate; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.utils.lang.StringUtils; import org.opentripplanner.utils.tostring.ValueObjectToStringBuilder; @@ -14,50 +15,49 @@ */ public class GenericLocation { - public static final GenericLocation UNKNOWN = new GenericLocation(null, null, null, null); - - /** - * A label for the place, if provided. This is pass-through information and does not affect - * routing in any way. - */ @Nullable - public final String label; + private final String label; - /** - * Refers to a specific element in the OTP model. This can currently be a regular stop, area stop, - * group stop, station, multi-modal station or group of stations. - */ @Nullable - public final FeedScopedId stopId; - - /** - * Coordinates of the location. These can be used by themselves or as a fallback if placeId is not - * found. - */ - @Nullable - public final Double lat; + private final FeedScopedId stopId; @Nullable - public final Double lng; + private final WgsCoordinate coordinate; - public GenericLocation( + private GenericLocation( @Nullable String label, @Nullable FeedScopedId stopId, - @Nullable Double lat, - @Nullable Double lng + @Nullable WgsCoordinate coordinate ) { + if (stopId == null && coordinate == null) { + throw new IllegalArgumentException( + "GenericLocation requires either a stop id or a coordinate" + ); + } this.label = label; this.stopId = stopId; - this.lat = lat; - this.lng = lng; + this.coordinate = coordinate; } public static GenericLocation fromStopId(FeedScopedId id) { - return new GenericLocation(null, id, null, null); + Objects.requireNonNull(id); + return new GenericLocation(null, id, null); } - public static GenericLocation fromStopId(String name, String feedId, String stopId) { - return new GenericLocation(name, new FeedScopedId(feedId, stopId), null, null); + public static GenericLocation fromStopId(FeedScopedId id, @Nullable String label) { + Objects.requireNonNull(id); + return new GenericLocation(label, id, null); + } + + /// Create a GenericLocation of a stop id with fallback coordinates if the id is not found. + public static GenericLocation fromStopIdWithFallback( + FeedScopedId id, + double lat, + double lng, + @Nullable String label + ) { + Objects.requireNonNull(id); + return new GenericLocation(label, id, new WgsCoordinate(lat, lng)); } /** @@ -65,22 +65,50 @@ public static GenericLocation fromStopId(String name, String feedId, String stop * inserting {@code null} values. */ public static GenericLocation fromCoordinate(double lat, double lng) { - return new GenericLocation(null, null, lat, lng); + return new GenericLocation(null, null, new WgsCoordinate(lat, lng)); + } + + public static GenericLocation fromCoordinate(double lat, double lng, @Nullable String label) { + return new GenericLocation(label, null, new WgsCoordinate(lat, lng)); } /** - * Returns this as a Coordinate object. + * Coordinates of the location. These can be used by themselves or as a fallback if placeId is not + * found. */ @Nullable public Coordinate getCoordinate() { - if (this.lat == null || this.lng == null) { + if (this.coordinate == null) { return null; } - return new Coordinate(this.lng, this.lat); + return coordinate.asJtsCoordinate(); } - public boolean isSpecified() { - return stopId != null || (lat != null && lng != null); + /** + * Coordinates of the location. These can be used by themselves or as a fallback if placeId is not + * found. + */ + @Nullable + public WgsCoordinate wgsCoordinate() { + return coordinate; + } + + /** + * Refers to a specific element in the OTP model. This can currently be a regular stop, area stop, + * group stop, station, multi-modal station or group of stations. + */ + @Nullable + public FeedScopedId stopId() { + return stopId; + } + + /** + * A label for the place, if provided. This is pass-through information and does not affect + * routing in any way. + */ + @Nullable + public String label() { + return label; } @Override @@ -92,28 +120,25 @@ public boolean equals(Object o) { return ( Objects.equals(label, that.label) && Objects.equals(stopId, that.stopId) && - Objects.equals(lat, that.lat) && - Objects.equals(lng, that.lng) + Objects.equals(coordinate, that.coordinate) ); } @Override public int hashCode() { - return Objects.hash(label, stopId, lat, lng); + return Objects.hash(label, stopId, coordinate); } @Override public String toString() { - if (UNKNOWN.equals(this)) { - return "Unknown location"; - } - ValueObjectToStringBuilder buf = ValueObjectToStringBuilder.of().skipNull(); if (StringUtils.hasValue(label)) { buf.addText(label).addText(" "); } buf.addObj(stopId); - buf.addCoordinate(lat, lng); + if (coordinate != null) { + buf.addCoordinate(coordinate.latitude(), coordinate.longitude()); + } return buf.toString(); } } diff --git a/application/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/application/src/main/java/org/opentripplanner/model/plan/Itinerary.java index ff49eae34aa..505f52775e8 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/application/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -611,9 +611,9 @@ public String toString() { .addTime("end", legs().getLast().endTime()) .addNum("nTransfers", numberOfTransfers) .addDuration("duration", totalDuration) - .addDuration("nonTransitTime", totalStreetDuration) - .addDuration("transitTime", totalTransitDuration) - .addDuration("waitingTime", totalWaitingDuration) + .addDuration("nonTransitDuration", totalStreetDuration) + .addDuration("transitDuration", totalTransitDuration) + .addDuration("waitingDuration", totalWaitingDuration) .addObj("generalizedCost", generalizedCost) .addNum("generalizedCost2", generalizedCost2) .addNum("waitTimeOptimizedCost", waitTimeOptimizedCost, UNKNOWN) diff --git a/application/src/main/java/org/opentripplanner/model/plan/Place.java b/application/src/main/java/org/opentripplanner/model/plan/Place.java index d11cd2a6d22..9fc4a1c3d96 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/Place.java +++ b/application/src/main/java/org/opentripplanner/model/plan/Place.java @@ -82,6 +82,10 @@ private Place( this.viaLocationType = viaLocationType; } + public static Place noCoords(I18NString name) { + return new Place(name, null, VertexType.NORMAL, null, null, null, null); + } + public static Place normal(Double lat, Double lon, I18NString name) { return new Place( name, @@ -94,6 +98,10 @@ public static Place normal(Double lat, Double lon, I18NString name) { ); } + public static Place normal(WgsCoordinate coord, I18NString name) { + return new Place(name, coord, VertexType.NORMAL, null, null, null, null); + } + public static Place normal( Vertex vertex, I18NString name, @@ -174,14 +182,13 @@ public static Place forGenericLocation( I18NString defaultName ) { if (location == null) { - return Place.normal(null, null, defaultName); + return Place.noCoords(defaultName); } - - return Place.normal( - location.lat, - location.lng, - NonLocalizedString.ofNullableOrElse(location.label, defaultName) - ); + var coord = location.wgsCoordinate(); + if (coord == null) { + return Place.noCoords(NonLocalizedString.ofNullableOrElse(location.label(), defaultName)); + } + return Place.normal(coord, NonLocalizedString.ofNullableOrElse(location.label(), defaultName)); } public static Place forVehicleRentalPlace(VehicleRentalPlaceVertex vertex) { diff --git a/application/src/main/java/org/opentripplanner/osm/DefaultOsmProvider.java b/application/src/main/java/org/opentripplanner/osm/DefaultOsmProvider.java index 2a514a8e400..382ac67dc6c 100644 --- a/application/src/main/java/org/opentripplanner/osm/DefaultOsmProvider.java +++ b/application/src/main/java/org/opentripplanner/osm/DefaultOsmProvider.java @@ -3,14 +3,12 @@ import crosby.binary.file.BlockInputStream; import java.io.ByteArrayInputStream; import java.io.File; -import java.io.IOException; import java.io.InputStream; import java.time.ZoneId; import org.opentripplanner.datastore.api.DataSource; import org.opentripplanner.datastore.api.FileType; import org.opentripplanner.datastore.file.FileDataSource; import org.opentripplanner.framework.application.OtpFileNames; -import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.module.osm.OsmDatabase; import org.opentripplanner.osm.tagmapping.OsmTagMapper; import org.opentripplanner.osm.tagmapping.OsmTagMapperSource; @@ -41,21 +39,14 @@ public class DefaultOsmProvider implements OsmProvider { /** For tests */ public DefaultOsmProvider(File file, boolean cacheDataInMem) { - this( - new FileDataSource(file, FileType.OSM), - OsmTagMapperSource.DEFAULT, - null, - cacheDataInMem, - DataImportIssueStore.NOOP - ); + this(new FileDataSource(file, FileType.OSM), OsmTagMapperSource.DEFAULT, null, cacheDataInMem); } public DefaultOsmProvider( DataSource dataSource, OsmTagMapperSource tagMapperSource, ZoneId zoneId, - boolean cacheDataInMem, - DataImportIssueStore issueStore + boolean cacheDataInMem ) { this.source = dataSource; this.zoneId = zoneId; @@ -102,21 +93,12 @@ private static InputStream track(OsmParserPhase phase, long size, InputStream in return ProgressTracker.track("Parse OSM " + phase, 1000, size, inputStream, m -> LOG.info(m)); } - private void parsePhase(OsmParser parser, OsmParserPhase phase) throws IOException { + private void parsePhase(OsmParser parser, OsmParserPhase phase) { parser.setPhase(phase); - BlockInputStream in = null; - try { - in = new BlockInputStream(createInputStream(phase), parser); + try (BlockInputStream in = new BlockInputStream(createInputStream(phase), parser)) { in.process(); - } finally { - // Close - try { - if (in != null) { - in.close(); - } - } catch (Exception e) { - LOG.error(e.getMessage(), e); - } + } catch (Exception e) { + LOG.error(e.getMessage(), e); } } diff --git a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java index a5af1711504..05b4e3f18e2 100644 --- a/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java +++ b/application/src/main/java/org/opentripplanner/osm/model/OsmEntity.java @@ -144,8 +144,9 @@ public abstract class OsmEntity { "bicycle", "vehicle" ); - public static final Set NO_ACCESS_TAGS = Set.of("no", "license", "dismount"); - public static final Map OSM_TAGS_FOR_TRAVERSAL_PERMISSION = + private static final Set WALK_ONLY_HIGHWAYS = Set.of("footway", "step", "corridor"); + private static final Set NO_ACCESS_TAGS = Set.of("no", "license", "dismount"); + private static final Map OSM_TAGS_FOR_TRAVERSAL_PERMISSION = Map.of( StreetTraversalPermission.CAR, "motorcar", @@ -224,15 +225,13 @@ public Map getTags() { * Is the tag defined? */ public boolean hasTag(String tag) { - tag = tag.toLowerCase(); - return tags != null && tags.containsKey(tag); + return getTag(tag) != null; } /** * Determines if a tag contains a false value. 'no', 'false', and '0' are considered false. */ public boolean isTagFalse(String tag) { - tag = tag.toLowerCase(); if (tags == null) { return false; } @@ -257,7 +256,6 @@ public Accessibility explicitWheelchairAccessibility() { * Determines if a tag contains a true value. 'yes', 'true', and '1' are considered true. */ public boolean isTagTrue(String tag) { - tag = tag.toLowerCase(); if (tags == null) { return false; } @@ -324,7 +322,6 @@ protected boolean isExplicitlyAllowed(String key) { if (isTagTrue(key)) { return true; } - key = key.toLowerCase(); String value = getTag(key); return ( "designated".equals(value) || @@ -340,9 +337,8 @@ protected boolean isExplicitlyAllowed(String key) { */ @Nullable public String getTag(String tag) { - tag = tag.toLowerCase(); - if (tags != null && tags.containsKey(tag)) { - return tags.get(tag); + if (tags != null) { + return tags.get(tag.toLowerCase()); } return null; } @@ -504,19 +500,19 @@ public OptionalInt parseIntOrBoolean(String tag, Consumer errorHandler) * Checks if a tag contains the specified value. */ public boolean isTag(String tag, String value) { - tag = tag.toLowerCase(); - if (tags != null && tags.containsKey(tag) && value != null) { - return value.equals(tags.get(tag)); - } - - return false; + return tags != null && value != null && value.equals(tags.get(tag.toLowerCase())); } /** * Takes a tag key and checks if the value is any of those in {@code oneOfTags}. */ public boolean isOneOfTags(String key, Set oneOfTags) { - return oneOfTags.stream().anyMatch(value -> isTag(key, value)); + var value = getTag(key); + if (value == null) { + return false; + } else { + return oneOfTags.contains(value); + } } /** @@ -534,15 +530,9 @@ public I18NString getAssumedName() { false ); } - if (tags.containsKey("otp:route_name")) { - return new NonLocalizedString(tags.get("otp:route_name")); - } if (this.creativeName != null) { return this.creativeName; } - if (tags.containsKey("otp:route_ref")) { - return new NonLocalizedString(tags.get("otp:route_ref")); - } if (tags.containsKey("ref")) { return new NonLocalizedString(tags.get("ref")); } @@ -658,7 +648,7 @@ public Optional isOneWay(@Nullable String mode) { return Optional.empty(); } - if ("foot".equals(mode) && !isOneOfTags("highway", Set.of("footway", "step", "corridor"))) { + if ("foot".equals(mode) && !isOneOfTags("highway", WALK_ONLY_HIGHWAYS)) { return Optional.empty(); } @@ -769,20 +759,6 @@ public boolean isPlatform() { return isPlatform && !isTag("usage", "tourism"); } - /** - * @return True if this entity provides an entrance to a platform or similar entity - */ - public boolean isEntrance() { - return ( - (isTag("railway", "subway_entrance") || - isTag("highway", "elevator") || - isTag("entrance", "yes") || - isTag("entrance", "main")) && - !isTag("access", "private") && - !isTag("access", "no") - ); - } - /** * @return True if this node / area is a bike parking. */ @@ -940,7 +916,7 @@ public boolean hasNoName() { * set on the entity in OSM. * * @see OsmEntity#isNamed() - * @see https://wiki.openstreetmap.org/wiki/Tag:noname%3Dyes + * @link https://wiki.openstreetmap.org/wiki/Tag:noname%3Dyes */ public boolean isExplicitlyUnnamed() { return isTagTrue("noname"); diff --git a/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java b/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java index d21cf8d316b..c429cebb2ba 100644 --- a/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java +++ b/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java @@ -4,10 +4,16 @@ import static org.opentripplanner.street.model.StreetTraversalPermission.NONE; import java.util.OptionalDouble; +import java.util.Set; import org.locationtech.jts.geom.Coordinate; public class OsmNode extends OsmEntity { + private static final Set RAILWAY_STATION_ENTRANCE_TAGS = Set.of( + "subway_entrance", + "train_station_entrance" + ); + public double lat; public double lon; @@ -44,12 +50,25 @@ public boolean isBarrier() { } /** - * Checks if this node is a subway station entrance. + * Checks if this node is a station entrance. * * @return true if it is */ - public boolean isSubwayEntrance() { - return hasTag("railway") && "subway_entrance".equals(getTag("railway")); + public boolean isStationEntrance() { + return ( + isOneOfTags("railway", RAILWAY_STATION_ENTRANCE_TAGS) || isTag("public_transport", "entrance") + ); + } + + /** + * @return True if this entity provides an entrance to a platform or similar entity + */ + public boolean isEntrance() { + return ( + (isStationEntrance() || isTag("entrance", "yes") || isTag("entrance", "main")) && + !isTag("access", "private") && + !isTag("access", "no") + ); } /** checks for units (m/ft) in an OSM ele tag value, and returns the value in meters */ @@ -79,8 +98,8 @@ public String url() { } /** - * Check if this node represents a tagged barrier crossing if placed on an intersection - * of a highway and a barrier way. + * Check if this node represents a tagged barrier crossing if placed on an intersection of a + * highway and a barrier way. * * @return true if it has a barrier tag, or if it explicitly overrides permissions. */ @@ -93,4 +112,16 @@ public boolean isTaggedBarrierCrossing() { overridePermissions(NONE) != NONE ); } + + /** + * Check if this node represents access to a platform. + *

      + * If this node appears inside a platform area and belongs to the same public transport relation, + * the platform will be kept even if it isn't physically linked to this node so that + * {@link org.opentripplanner.graph_builder.module.OsmBoardingLocationsModule} can associate the + * transit stop with the physical platform. + */ + public boolean isPlatformAccess() { + return isEntrance() || isBoardingLocation() || isTag("highway", "elevator"); + } } diff --git a/application/src/main/java/org/opentripplanner/osm/model/TraverseDirection.java b/application/src/main/java/org/opentripplanner/osm/model/TraverseDirection.java index c062e599375..6fbd16b7256 100644 --- a/application/src/main/java/org/opentripplanner/osm/model/TraverseDirection.java +++ b/application/src/main/java/org/opentripplanner/osm/model/TraverseDirection.java @@ -10,22 +10,24 @@ public enum TraverseDirection { /** * Traverse in the direction the way is defined, from the beginning node to the end node. */ - FORWARD, + FORWARD(":forward"), /** * Traverse against the direction the way is defined, from the end node to the beginning node. */ - BACKWARD, + BACKWARD(":backward"), /** * Traverse not in a specific direction of the way, for example, across an area or through a node. */ - DIRECTIONLESS; + DIRECTIONLESS(""); - public String tagSuffix() { - if (this == DIRECTIONLESS) { - return ""; - } + private final String tagSuffix; + + TraverseDirection(String tagSuffix) { + this.tagSuffix = tagSuffix; + } - return ":" + name().toLowerCase(); + public String tagSuffix() { + return tagSuffix; } public TraverseDirection reverse() { diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java index 793c345b501..dedc01f2b4a 100644 --- a/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java +++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java @@ -404,7 +404,6 @@ static void populateNotesAndNames(WayPropertySetBuilder props) { props.createNames("indoor=area", "name.indoor_area"); // Platforms - props.createNames("otp:route_ref=*", "name.otp_route_ref"); props.createNames("highway=platform;ref=*", "name.platform_ref"); props.createNames("railway=platform;ref=*", "name.platform_ref"); props.createNames("railway=platform;highway=footway;footway=sidewalk", "name.platform"); diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/RequestPreProcessor.java b/application/src/main/java/org/opentripplanner/routing/algorithm/RequestPreProcessor.java new file mode 100644 index 00000000000..10d782a4d00 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/RequestPreProcessor.java @@ -0,0 +1,54 @@ +package org.opentripplanner.routing.algorithm; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Objects; +import org.opentripplanner.raptor.api.request.RaptorTuningParameters; +import org.opentripplanner.routing.algorithm.raptoradapter.router.AdditionalSearchDays; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.utils.time.ServiceDateUtils; + +/** + * Pre-processes a {@link RouteRequest} into a {@link RoutingWorkerRequest} for usage in + * {@link RoutingWorker}. This includes extending the request with a page cursor and amending + * its data with additional resolved data, such as additional search days. + */ +public final class RequestPreProcessor { + + private final RaptorTuningParameters tuningParameters; + private final ZoneId zoneId; + + public RequestPreProcessor(RaptorTuningParameters tuningParameters, ZoneId zoneId) { + this.tuningParameters = tuningParameters; + this.zoneId = zoneId; + } + + /** + * Pre-process {@code originalRequest} and return a {@link RoutingWorkerRequest} with all values + * needed to initialize a {@link RoutingWorker}. + */ + public RoutingWorkerRequest computeRequest(RouteRequest originalRequest) { + var request = originalRequest.withPageCursor(); + var transitSearchTimeZero = ServiceDateUtils.asStartOfService(request.dateTime(), zoneId); + var additionalSearchDays = createAdditionalSearchDays(tuningParameters, zoneId, request); + return new RoutingWorkerRequest(request, transitSearchTimeZero, additionalSearchDays); + } + + private static AdditionalSearchDays createAdditionalSearchDays( + RaptorTuningParameters raptorTuningParameters, + ZoneId zoneId, + RouteRequest request + ) { + var dateTime = Objects.requireNonNull(request.dateTime()); + var searchDateTime = ZonedDateTime.ofInstant(dateTime, zoneId); + var maxWindow = raptorTuningParameters.dynamicSearchWindowCoefficients().maxWindow(); + + return new AdditionalSearchDays( + request.arriveBy(), + searchDateTime, + request.searchWindow(), + maxWindow, + request.preferences().system().maxJourneyDuration() + ); + } +} diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java b/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java index e92b337fe2c..37b801749cd 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorker.java @@ -2,8 +2,8 @@ import java.time.Duration; import java.time.Instant; -import java.time.ZoneId; import java.time.ZonedDateTime; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -14,7 +14,6 @@ import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.grouppriority.TransitGroupPriorityItineraryDecorator; import org.opentripplanner.model.plan.paging.cursor.PageCursorInput; -import org.opentripplanner.raptor.api.request.RaptorTuningParameters; import org.opentripplanner.raptor.api.request.SearchParams; import org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain; import org.opentripplanner.routing.algorithm.mapping.PagingServiceFactory; @@ -40,7 +39,6 @@ import org.opentripplanner.street.linking.TemporaryVerticesContainer; import org.opentripplanner.street.model.StreetMode; import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService; -import org.opentripplanner.utils.time.ServiceDateUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,15 +56,6 @@ public class RoutingWorker { private final RouteRequest request; private final OtpServerRequestContext serverContext; - /** - * The transit service time-zero normalized for the current search. All transit times are relative - * to a "time-zero". This enables us to use an integer(small memory footprint). The times are - * number for seconds past the {@code transitSearchTimeZero}. In the internal model all times are - * stored relative to the {@link java.time.LocalDate}, but to be able - * to compare trip times for different service days we normalize all times by calculating an - * offset. Now all times for the selected trip patterns become relative to the {@code - * transitSearchTimeZero}. - */ private final ZonedDateTime transitSearchTimeZero; private final AdditionalSearchDays additionalSearchDays; private final TransitGroupPriorityService transitGroupPriorityService; @@ -77,23 +66,15 @@ public class RoutingWorker { @Nullable private LinkingContext currentLinkingContext = null; - public RoutingWorker( - OtpServerRequestContext serverContext, - RouteRequest orginalRequest, - ZoneId zoneId - ) { - this.request = orginalRequest.withPageCursor(); + public RoutingWorker(OtpServerRequestContext serverContext, RoutingWorkerRequest workerRequest) { + this.request = workerRequest.request(); + this.transitSearchTimeZero = workerRequest.transitSearchTimeZero(); + this.additionalSearchDays = workerRequest.additionalSearchDays(); this.serverContext = serverContext; this.debugTimingAggregator = new DebugTimingAggregator( serverContext.meterRegistry(), request.preferences().system().tags() ); - this.transitSearchTimeZero = ServiceDateUtils.asStartOfService(request.dateTime(), zoneId); - this.additionalSearchDays = createAdditionalSearchDays( - serverContext.raptorTuningParameters(), - zoneId, - request - ); this.transitGroupPriorityService = TransitGroupPriorityService.of( request.preferences().transit().relaxTransitGroupPriority(), request.journey().transit().priorityGroupsByAgency(), @@ -159,6 +140,8 @@ public RoutingResponse route() { result.addErrors(filterChain.getRoutingErrors()); } + result.addErrors(checkForEmptyDirectModeResult(result)); + if (LOG.isDebugEnabled()) { LOG.debug( "Return TripPlan with {} filtered itineraries out of {} total.", @@ -188,23 +171,6 @@ public RoutingResponse route() { ); } - private static AdditionalSearchDays createAdditionalSearchDays( - RaptorTuningParameters raptorTuningParameters, - ZoneId zoneId, - RouteRequest request - ) { - var searchDateTime = ZonedDateTime.ofInstant(request.dateTime(), zoneId); - var maxWindow = raptorTuningParameters.dynamicSearchWindowCoefficients().maxWindow(); - - return new AdditionalSearchDays( - request.arriveBy(), - searchDateTime, - request.searchWindow(), - maxWindow, - request.preferences().system().maxJourneyDuration() - ); - } - /** * Calculate the earliest-departure-time used in the transit search. * This method returns {@code null} if no transit search is performed. @@ -361,6 +327,21 @@ private void checkIfTransitConnectionExistsInSearchWindow(List itiner } } + /** + * If this is a direct-only search (no transit) and no itineraries were found, return an error + * so the client knows why no results were returned. + */ + private Collection checkForEmptyDirectModeResult(RoutingResult result) { + if ( + !request.journey().transit().enabled() && + result.errors().isEmpty() && + result.itineraries().stream().allMatch(Itinerary::isFlaggedForDeletion) + ) { + return List.of(new RoutingError(RoutingErrorCode.NO_DIRECT_MODE_CONNECTION, null)); + } + return List.of(); + } + private LinkingContext linkingContext() { return Objects.requireNonNull(currentLinkingContext); } diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorkerRequest.java b/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorkerRequest.java new file mode 100644 index 00000000000..12e96789f03 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/RoutingWorkerRequest.java @@ -0,0 +1,52 @@ +package org.opentripplanner.routing.algorithm; + +import java.time.ZonedDateTime; +import org.opentripplanner.routing.algorithm.raptoradapter.router.AdditionalSearchDays; +import org.opentripplanner.routing.api.request.RouteRequest; + +/** + * A trip planning request for handling in {@link RoutingWorker} + */ +public final class RoutingWorkerRequest { + + private final RouteRequest request; + private final ZonedDateTime transitSearchTimeZero; + private final AdditionalSearchDays additionalSearchDays; + + /** + * @param request the route request ready for routing. It should already be pre-processed to have + * e.g. page cursor applied + */ + public RoutingWorkerRequest( + RouteRequest request, + ZonedDateTime transitSearchTimeZero, + AdditionalSearchDays additionalSearchDays + ) { + this.request = request; + this.transitSearchTimeZero = transitSearchTimeZero; + this.additionalSearchDays = additionalSearchDays; + } + + /** The route request with the page cursor applied. */ + public RouteRequest request() { + return request; + } + + /** + * The transit service time-zero normalized for the current search. All transit times are relative + * to a "time-zero". This enables us to use an integer(small memory footprint). The times are + * number for seconds past the {@code transitSearchTimeZero}. In the internal model all times are + * stored relative to the {@link java.time.LocalDate}, but to be able + * to compare trip times for different service days we normalize all times by calculating an + * offset. Now all times for the selected trip patterns become relative to the {@code + * transitSearchTimeZero}. + */ + public ZonedDateTime transitSearchTimeZero() { + return transitSearchTimeZero; + } + + /** The number of extra calendar days to search before and after the requested date. */ + public AdditionalSearchDays additionalSearchDays() { + return additionalSearchDays; + } +} diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java index 2d283726cd4..9da42a95f1b 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java @@ -324,6 +324,8 @@ private Leg generateFlexLeg(StreetPath path) { return FlexibleTransitLeg.of() .withFlexTripEdge(flexEdge) + .withFromStop(siteResolver.getStopLocation(flexEdge.fromStopId())) + .withToStop(siteResolver.getStopLocation(flexEdge.toStopId())) .withStartTime(startTime) .withEndTime(endTime) .withGeneralizedCost(generalizedCost) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java index b98e80168f7..5ab803ec01f 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java @@ -102,10 +102,7 @@ public RaptorPathToItineraryMapper( graph.ellipsoidToGeoidDifference ); this.transitService = transitService; - this.carpoolItineraryMapper = new CarpoolItineraryMapper( - transitService.getTimeZone(), - transitSearchTimeZero - ); + this.carpoolItineraryMapper = new CarpoolItineraryMapper(transitSearchTimeZero); } public Itinerary createItinerary(RaptorPath path) { @@ -474,7 +471,7 @@ private Itinerary mapUnknownRaptorPath(RaptorPath path) { } private Place mapPlace(GenericLocation location) { - return Place.normal(location.lat, location.lng, new NonLocalizedString(location.label)); + return Place.normal(location.wgsCoordinate(), new NonLocalizedString(location.label())); } private ZonedDateTime createZonedDateTime(int timeInSeconds) { @@ -499,7 +496,7 @@ private boolean includeTransferInItinerary(Leg transitLegBeforeTransfer) { private Itinerary mapAccessEgressPathLeg(RaptorAccessEgress accessEgress) { return accessEgress .findOriginal(RoutingAccessEgress.class) - .map(RoutingAccessEgress::getLastState) + .map(RoutingAccessEgress::getFinalState) .map(StreetPath::new) .map(path -> graphPathToItineraryMapper.generateItinerary(path, request)) .orElseThrow(); diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java new file mode 100644 index 00000000000..41f391ac453 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/AccessEgressFetcher.java @@ -0,0 +1,170 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.router; + +import static org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType.ACCESS; +import static org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType.EGRESS; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.opentripplanner.ext.carpooling.CarpoolingService; +import org.opentripplanner.ext.ridehailing.RideHailingAccessShifter; +import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressRouter; +import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType; +import org.opentripplanner.routing.algorithm.raptoradapter.router.street.FlexAccessEgressRouter; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.AccessEgressMapper; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.routing.api.request.request.StreetRequest; +import org.opentripplanner.routing.graphfinder.TransitServiceResolver; +import org.opentripplanner.routing.linking.LinkingContext; +import org.opentripplanner.standalone.api.OtpServerRequestContext; +import org.opentripplanner.street.model.StreetMode; + +/** + * This class exposes methods for fetching access and egress legs for a request. + * An access or egress may be e.g. a walking path to the first transit stop on a route, + * but could also include other modes such as bicycle, shared mobility, flex or carpooling. + */ +class AccessEgressFetcher { + + private final RouteRequest request; + private final OtpServerRequestContext serverContext; + private final ZonedDateTime transitSearchTimeZero; + private final AdditionalSearchDays additionalSearchDays; + private final LinkingContext linkingContext; + private final TransitServiceResolver transitServiceResolver; + private final AccessEgressMapper accessEgressMapper; + private final CarpoolingService carpoolingService; + + /** + * Creates an {@code AccessEgressFetcher} for a single route request. + * + * @param transitSearchTimeZero the point in time all times in seconds are counted from + * @param additionalSearchDays extra search days beyond the departure day, required for flex + * routing + * @param linkingContext contains temporary vertices for request locations. + */ + public AccessEgressFetcher( + RouteRequest request, + OtpServerRequestContext serverContext, + ZonedDateTime transitSearchTimeZero, + AdditionalSearchDays additionalSearchDays, + LinkingContext linkingContext, + CarpoolingService carpoolingService + ) { + this.request = request; + this.serverContext = serverContext; + this.transitSearchTimeZero = transitSearchTimeZero; + this.additionalSearchDays = additionalSearchDays; + this.linkingContext = linkingContext; + this.carpoolingService = carpoolingService; + this.transitServiceResolver = new TransitServiceResolver(serverContext.transitService()); + this.accessEgressMapper = new AccessEgressMapper(transitServiceResolver); + } + + Collection fetchAccess() { + return fetchAccessEgresses(ACCESS); + } + + Collection fetchEgress() { + return fetchAccessEgresses(EGRESS); + } + + private Collection fetchAccessEgresses(AccessEgressType type) { + var streetRequest = type.isAccess() ? request.journey().access() : request.journey().egress(); + StreetMode mode = streetRequest.mode(); + + // Prepare access/egress lists + var accessBuilder = request.copyOf(); + + if (type.isAccess()) { + accessBuilder.withPreferences(p -> { + p.withBike(b -> b.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false))); + p.withCar(c -> c.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false))); + p.withScooter(s -> + s.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false)) + ); + }); + } + + var accessRequest = accessBuilder.buildRequest(); + + var accessEgressPreferences = accessRequest.preferences().street().accessEgress(); + + Duration durationLimit = accessEgressPreferences.maxDuration().valueOf(mode); + int stopCountLimit = accessEgressPreferences.maxStopCountLimit().limitForMode(mode); + + var nearbyStops = AccessEgressRouter.findAccessEgresses( + accessRequest, + mode, + serverContext.listExtensionRequestContexts(accessRequest), + type, + durationLimit, + stopCountLimit, + linkingContext + ); + var accessEgresses = accessEgressMapper.mapNearbyStops(nearbyStops); + accessEgresses = timeshiftRideHailing(streetRequest, type, accessEgresses); + + var results = new ArrayList<>(accessEgresses); + + // Special handling of flex accesses + if (OTPFeature.FlexRouting.isOn() && mode == StreetMode.FLEXIBLE) { + var flexAccessList = FlexAccessEgressRouter.routeAccessEgress( + accessRequest, + serverContext, + additionalSearchDays, + serverContext.flexParameters(), + serverContext.listExtensionRequestContexts(accessRequest), + type, + linkingContext + ); + + results.addAll(AccessEgressMapper.mapFlexAccessEgresses(flexAccessList)); + } + + if (OTPFeature.CarPooling.isOn() && mode == StreetMode.CARPOOL) { + var carpoolAccessEgressList = carpoolingService.routeAccessEgress( + accessRequest, + streetRequest, + type, + transitServiceResolver, + linkingContext, + transitSearchTimeZero + ); + results.addAll(carpoolAccessEgressList); + } + + return results; + } + + /** + * Given a list of {@code results} shift the access ones that contain driving so that they only + * start at the time when the ride hailing vehicle can actually be there to pick up passengers. + *

      + * If there are accesses/egresses with only walking, then they remain unchanged. + *

      + * This method is a good candidate to be moved to the access/egress filter chain when that has + * been added. + */ + private List timeshiftRideHailing( + StreetRequest streetRequest, + AccessEgressType type, + List accessEgressList + ) { + if (streetRequest.mode() != StreetMode.CAR_HAILING) { + return accessEgressList; + } + return RideHailingAccessShifter.shiftAccesses( + type.isAccess(), + accessEgressList, + serverContext.rideHailingServices(), + request, + Instant.now() + ); + } +} diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index 771bcf2c278..454516121c3 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -1,10 +1,5 @@ package org.opentripplanner.routing.algorithm.raptoradapter.router; -import static org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType.ACCESS; -import static org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType.EGRESS; - -import java.time.Duration; -import java.time.Instant; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collection; @@ -15,9 +10,7 @@ import javax.annotation.Nullable; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.ext.carpooling.CarpoolingService; -import org.opentripplanner.ext.ridehailing.RideHailingAccessShifter; import org.opentripplanner.framework.application.OTPFeature; -import org.opentripplanner.graph_builder.module.nearbystops.TransitServiceResolver; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.raptor.RaptorService; import org.opentripplanner.raptor.api.path.RaptorPath; @@ -25,21 +18,16 @@ import org.opentripplanner.raptor.extensions.extrasearch.ExtraMcRouterSearch; import org.opentripplanner.routing.algorithm.mapping.RaptorPathToItineraryMapper; import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressPenaltyDecorator; -import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressRouter; -import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType; import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgresses; -import org.opentripplanner.routing.algorithm.raptoradapter.router.street.FlexAccessEgressRouter; import org.opentripplanner.routing.algorithm.raptoradapter.transit.RaptorTransitData; import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; -import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.AccessEgressMapper; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.DirectTransitRequestMapper; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorRequestMapper; import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.DefaultTransitDataProviderFilter; import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.RaptorRoutingRequestTransitData; import org.opentripplanner.routing.algorithm.transferoptimization.configure.TransferOptimizationServiceConfigurator; import org.opentripplanner.routing.api.request.RouteRequest; -import org.opentripplanner.routing.api.request.request.StreetRequest; import org.opentripplanner.routing.api.response.InputField; import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingErrorCode; @@ -48,7 +36,6 @@ import org.opentripplanner.routing.linking.LinkingContext; import org.opentripplanner.routing.via.ViaCoordinateTransferFactory; import org.opentripplanner.standalone.api.OtpServerRequestContext; -import org.opentripplanner.street.model.StreetMode; import org.opentripplanner.transit.model.framework.EntityNotFoundException; import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService; import org.opentripplanner.transit.model.site.StopLocation; @@ -65,9 +52,7 @@ public class TransitRouter { private final AdditionalSearchDays additionalSearchDays; private final ViaCoordinateTransferFactory viaTransferResolver; private final LinkingContext linkingContext; - private final AccessEgressRouter accessEgressRouter; - private final TransitServiceResolver transitServiceResolver; - private final CarpoolingService carpoolingService; + private final AccessEgressFetcher fetchAccessEgress; private TransitRouter( RouteRequest request, @@ -87,9 +72,15 @@ private TransitRouter( this.debugTimingAggregator = debugTimingAggregator; this.viaTransferResolver = serverContext.viaTransferResolver(); this.linkingContext = linkingContext; - this.transitServiceResolver = new TransitServiceResolver(serverContext.transitService()); - this.accessEgressRouter = new AccessEgressRouter(this.transitServiceResolver); - this.carpoolingService = carpoolingService; + + this.fetchAccessEgress = new AccessEgressFetcher( + request, + serverContext, + transitSearchTimeZero, + additionalSearchDays, + linkingContext, + carpoolingService + ); } public static TransitRouterResult route( @@ -117,7 +108,8 @@ public static TransitRouterResult route( } private TransitRouterResult route() { - if (!request.journey().transit().enabled()) { + // Skip the creation of raptor transit data when the request cannot use transit + if (!request.journey().transit().enabled() || request.cannotReachTransit()) { return new TransitRouterResult(List.of(), null); } @@ -263,113 +255,18 @@ private AccessEgresses fetchAccessEgresses() { private Collection fetchAccess() { debugTimingAggregator.startedAccessCalculating(); - var list = fetchAccessEgresses(ACCESS); + var list = fetchAccessEgress.fetchAccess(); debugTimingAggregator.finishedAccessCalculating(); return list; } private Collection fetchEgress() { debugTimingAggregator.startedEgressCalculating(); - var list = fetchAccessEgresses(EGRESS); + var list = fetchAccessEgress.fetchEgress(); debugTimingAggregator.finishedEgressCalculating(); return list; } - private Collection fetchAccessEgresses(AccessEgressType type) { - var streetRequest = type.isAccess() ? request.journey().access() : request.journey().egress(); - StreetMode mode = streetRequest.mode(); - - // Prepare access/egress lists - var accessBuilder = request.copyOf(); - - if (type.isAccess()) { - accessBuilder.withPreferences(p -> { - p.withBike(b -> b.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false))); - p.withCar(c -> c.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false))); - p.withScooter(s -> - s.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false)) - ); - }); - } - - var accessRequest = accessBuilder.buildRequest(); - - var accessEgressPreferences = accessRequest.preferences().street().accessEgress(); - - Duration durationLimit = accessEgressPreferences.maxDuration().valueOf(mode); - int stopCountLimit = accessEgressPreferences.maxStopCountLimit().limitForMode(mode); - - var nearbyStops = accessEgressRouter.findAccessEgresses( - accessRequest, - streetRequest.mode(), - serverContext.listExtensionRequestContexts(accessRequest), - type, - durationLimit, - stopCountLimit, - linkingContext - ); - var accessEgresses = AccessEgressMapper.mapNearbyStops(nearbyStops, type); - accessEgresses = timeshiftRideHailing(streetRequest, type, accessEgresses); - - var results = new ArrayList<>(accessEgresses); - - // Special handling of flex accesses - if (OTPFeature.FlexRouting.isOn() && mode == StreetMode.FLEXIBLE) { - var flexAccessList = FlexAccessEgressRouter.routeAccessEgress( - accessRequest, - accessEgressRouter, - serverContext, - additionalSearchDays, - serverContext.flexParameters(), - serverContext.listExtensionRequestContexts(accessRequest), - type, - linkingContext - ); - - results.addAll(AccessEgressMapper.mapFlexAccessEgresses(flexAccessList, type)); - } - - if (OTPFeature.CarPooling.isOn() && mode == StreetMode.CARPOOL) { - var carpoolAccessEgressList = carpoolingService.routeAccessEgress( - accessRequest, - streetRequest, - type, - transitServiceResolver, - linkingContext, - transitSearchTimeZero - ); - results.addAll(carpoolAccessEgressList); - } - - return results; - } - - /** - * Given a list of {@code results} shift the access ones that contain driving so that they only - * start at the time when the ride hailing vehicle can actually be there to pick up passengers. - *

      - * If there are accesses/egresses with only walking, then they remain unchanged. - *

      - * This method is a good candidate to be moved to the access/egress filter chain when that has - * been added. - */ - private List timeshiftRideHailing( - StreetRequest streetRequest, - AccessEgressType type, - List accessEgressList - ) { - if (streetRequest.mode() != StreetMode.CAR_HAILING) { - return accessEgressList; - } - return RideHailingAccessShifter.shiftAccesses( - type.isAccess(), - accessEgressList, - serverContext.rideHailingServices(), - request, - Instant.now() - ); - } - private RaptorRoutingRequestTransitData createRequestTransitDataProvider( RaptorTransitData raptorTransitData ) { diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouter.java index 34ff3d8789c..f6629fabbe6 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouter.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.stream.Collectors; import org.opentripplanner.framework.application.OTPRequestTimeoutException; -import org.opentripplanner.graph_builder.module.nearbystops.StopResolver; import org.opentripplanner.graph_builder.module.nearbystops.StreetNearbyStopFinder; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.graphfinder.NearbyStop; @@ -23,18 +22,11 @@ public class AccessEgressRouter { private static final Logger LOG = LoggerFactory.getLogger(AccessEgressRouter.class); - private final StopResolver stopResolver; - private final NearbyStopFactory nearbyStopFactory; - - public AccessEgressRouter(StopResolver stopResolver) { - this.stopResolver = stopResolver; - this.nearbyStopFactory = new NearbyStopFactory(stopResolver::getRegularStop); - } /** * Find accesses or egresses. */ - public Collection findAccessEgresses( + public static Collection findAccessEgresses( RouteRequest request, StreetMode streetMode, Collection extensionRequestContexts, @@ -64,7 +56,7 @@ public Collection findAccessEgresses( var originVertices = accessOrEgress.isAccess() ? linkingContext.findVertices(request.from()) : linkingContext.findVertices(request.to()); - var streetAccessEgress = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount) + var streetAccessEgress = StreetNearbyStopFinder.of(durationLimit, maxStopCount) .withIgnoreVertices(ignoreVertices) .withExtensionRequestContexts(extensionRequestContexts) .build() @@ -79,7 +71,7 @@ public Collection findAccessEgresses( * Return a list of direct accesses/egresses that do not require any street search. This will * return an empty list if the source/destination is not a stopId. */ - private List findAccessEgressWithZeroDistance( + private static List findAccessEgressWithZeroDistance( RouteRequest routeRequest, StreetMode streetMode, AccessEgressType accessOrEgress, @@ -89,7 +81,7 @@ private List findAccessEgressWithZeroDistance( ? linkingContext.fromStopVertices() : linkingContext.toStopVertices(); - return nearbyStopFactory.nearbyStopsForTransitStopVerticesFiltered( + return NearbyStopFactory.nearbyStopsForTransitStopVerticesFiltered( transitStopVertices, accessOrEgress.isEgress(), routeRequest, diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectFlexRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectFlexRouter.java index 325c5ab0b5b..80025ca78a1 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectFlexRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectFlexRouter.java @@ -7,7 +7,6 @@ import org.opentripplanner.ext.flex.FlexRouter; import org.opentripplanner.ext.flex.filter.FilterMapper; import org.opentripplanner.framework.application.OTPRequestTimeoutException; -import org.opentripplanner.graph_builder.module.nearbystops.TransitServiceResolver; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.routing.algorithm.raptoradapter.router.AdditionalSearchDays; import org.opentripplanner.routing.api.request.RouteRequest; @@ -24,15 +23,12 @@ public static List route( AdditionalSearchDays additionalSearchDays, LinkingContext linkingContext ) { - var accessEgressRouter = new AccessEgressRouter( - new TransitServiceResolver(serverContext.transitService()) - ); if (!StreetMode.FLEXIBLE.equals(request.journey().direct().mode())) { return Collections.emptyList(); } OTPRequestTimeoutException.checkForTimeout(); // Prepare access/egress transfers - Collection accessStops = accessEgressRouter.findAccessEgresses( + Collection accessStops = AccessEgressRouter.findAccessEgresses( request, request.journey().direct().mode(), serverContext.listExtensionRequestContexts(request), @@ -41,7 +37,7 @@ public static List route( 0, linkingContext ); - Collection egressStops = accessEgressRouter.findAccessEgresses( + Collection egressStops = AccessEgressRouter.findAccessEgresses( request, request.journey().direct().mode(), serverContext.listExtensionRequestContexts(request), diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java index 71912b676c1..3abd84dbec6 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java @@ -40,7 +40,6 @@ public static List route( // we could also get a persistent router-scoped GraphPathFinder but there's no setup cost here GraphPathFinder gpFinder = new GraphPathFinder( - serverContext.traverseVisitor(), serverContext.listExtensionRequestContexts(request), maxCarSpeed ); diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/FlexAccessEgressRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/FlexAccessEgressRouter.java index 7336e562538..60e8cea194a 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/FlexAccessEgressRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/FlexAccessEgressRouter.java @@ -22,7 +22,6 @@ private FlexAccessEgressRouter() {} public static Collection routeAccessEgress( RouteRequest request, - AccessEgressRouter accessEgressRouter, OtpServerRequestContext serverContext, AdditionalSearchDays searchDays, FlexParameters config, @@ -35,7 +34,7 @@ public static Collection routeAccessEgress( TransitService transitService = serverContext.transitService(); Collection accessStops = accessOrEgress.isAccess() - ? accessEgressRouter.findAccessEgresses( + ? AccessEgressRouter.findAccessEgresses( request, StreetMode.WALK, extensionRequestContexts, @@ -47,7 +46,7 @@ public static Collection routeAccessEgress( : List.of(); Collection egressStops = accessOrEgress.isEgress() - ? accessEgressRouter.findAccessEgresses( + ? AccessEgressRouter.findAccessEgresses( request, StreetMode.WALK, extensionRequestContexts, diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java index 376732dc4f5..114f14be03c 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java @@ -8,6 +8,17 @@ /** * Default implementation of the RaptorAccessEgress interface. + *

      + * Implementation note: As stated in the RoutingAccessEgress interface contract {@link RoutingAccessEgress#getFinalState()}, + * this class exposes the final A* state in search order, not in chronological order. For egress searches this State is + * unreversed ({@code request.arriveBy() == true}) — reversal is deferred to + * {@link org.opentripplanner.astar.model.GraphPath} construction, which only happens for + * winning paths during itinerary mapping. This avoids the cost of cloning and reversing the entire State + * chain for every egress candidate. + *

      + * The scalar values extracted below ({@code getElapsedTimeSeconds}, {@code getWeight}, + * {@code containsOnlyWalkMode}) are direction-independent and produce identical results on + * both reversed and unreversed State chains. */ public class DefaultAccessEgress implements RoutingAccessEgress { @@ -19,10 +30,7 @@ public class DefaultAccessEgress implements RoutingAccessEgress { /** Keep this to be able to map back to itinerary */ private final TimeAndCost penalty; - /** - * This should be the last state both in the case of access and egress. - */ - private final State lastState; + private final State finalState; /** * This is public to allow unit-tests full control over the field values. @@ -32,23 +40,23 @@ public DefaultAccessEgress( int durationInSeconds, int generalizedCost, TimeAndCost penalty, - State lastState + State finalState ) { this.stop = stop; this.durationInSeconds = durationInSeconds; this.generalizedCost = generalizedCost; this.timePenalty = penalty.isZero() ? RaptorConstants.TIME_NOT_SET : penalty.timeInSeconds(); this.penalty = penalty; - this.lastState = Objects.requireNonNull(lastState); + this.finalState = Objects.requireNonNull(finalState); } - public DefaultAccessEgress(int stop, State lastState) { + public DefaultAccessEgress(int stop, State finalState) { this( stop, - (int) lastState.getElapsedTimeSeconds(), - RaptorCostConverter.toRaptorCost(lastState.getWeight()), + (int) finalState.getElapsedTimeSeconds(), + RaptorCostConverter.toRaptorCost(finalState.getWeight()), TimeAndCost.ZERO, - lastState + finalState ); } @@ -61,7 +69,7 @@ protected DefaultAccessEgress(RoutingAccessEgress other, TimeAndCost penalty) { other.durationInSeconds(), other.c1() + penalty.cost().toCentiSeconds(), penalty, - other.getLastState() + other.getFinalState() ); if (other.penalty() != TimeAndCost.ZERO) { throw new IllegalStateException("Can not add penalty twice..."); @@ -93,14 +101,25 @@ public boolean hasOpeningHours() { return false; } + /** + * The final state from the access/egress street search. For egress searches this State is + * unreversed ({@code request.arriveBy() == true}) — reversal is deferred to + * {@link org.opentripplanner.astar.model.GraphPath} construction, which only happens for + * winning paths during itinerary mapping. This avoids the cost of cloning and reversing the entire State + * chain for every egress candidate. + *

      + * The scalar values extracted below ({@code getElapsedTimeSeconds}, {@code getWeight}, + * {@code containsOnlyWalkMode}) are direction-independent and produce identical results on + * both reversed and unreversed State chains. + */ @Override - public State getLastState() { - return lastState; + public State getFinalState() { + return finalState; } @Override public boolean isWalkOnly() { - return lastState.containsOnlyWalkMode(); + return finalState.containsOnlyWalkMode(); } @Override diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java index 901053c4bd2..1fc45cfcdad 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java @@ -4,7 +4,6 @@ import org.opentripplanner.framework.model.TimeAndCost; import org.opentripplanner.model.StopTime; import org.opentripplanner.raptor.spi.RaptorConstants; -import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType; /** * This class is used to adapt the FlexAccessEgress into a time-dependent multi-leg DefaultAccessEgress. @@ -13,16 +12,8 @@ public class FlexAccessEgressAdapter extends DefaultAccessEgress { private final FlexAccessEgress flexAccessEgress; - public FlexAccessEgressAdapter( - FlexAccessEgress flexAccessEgress, - AccessEgressType accessOrEgress - ) { - super( - flexAccessEgress.stop().getIndex(), - accessOrEgress.isEgress() - ? flexAccessEgress.lastState().reverse() - : flexAccessEgress.lastState() - ); + public FlexAccessEgressAdapter(FlexAccessEgress flexAccessEgress) { + super(flexAccessEgress.stop().getIndex(), flexAccessEgress.lastState()); this.flexAccessEgress = flexAccessEgress; } diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java index dbc260d3d90..a8b964c1891 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java @@ -18,9 +18,13 @@ public interface RoutingAccessEgress extends RaptorAccessEgress { RoutingAccessEgress withPenalty(TimeAndCost penalty); /** - * Return the last state both in the case of access and egress. + * Return the final state of the A* street search that reached the transit stop. "Final" + * refers to the search order, not chronological order — for egress searches + * ({@code request.arriveBy() == true}) the state chain runs backward in time and is not + * reversed. Callers that need a chronological state chain must wrap this in a + * {@link org.opentripplanner.astar.model.GraphPath}. */ - State getLastState(); + State getFinalState(); /** * Return true if all edges are traversed on foot. diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java index 9185f8da585..0921cfc4c64 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java @@ -91,26 +91,21 @@ public int boardingCost( } @Override - public int onTripRelativeRidingCost(int boardTime, DefaultTripSchedule tripScheduledBoarded) { - // The relative-transit-time is time spent on transit. We do not know the alight-stop, so - // it is impossible to calculate the "correct" time. But the only thing that maters is that - // the relative difference between to boardings are correct, assuming riding the same trip. - // So, we can use the negative board time as relative-transit-time. - return -boardTime * transitFactors.factor(tripScheduledBoarded.transitReluctanceFactorIndex()); + public int transitCost(int transitDuration, T tripScheduledBoarded) { + return ( + transitDuration * transitFactors.factor(tripScheduledBoarded.transitReluctanceFactorIndex()) + ); } @Override public int transitArrivalCost( int boardCost, int alightSlack, - int transitTime, + int transitDuration, T trip, int toStopIndex ) { - int cost = - boardCost + - transitFactors.factor(trip.transitReluctanceFactorIndex()) * transitTime + - waitFactor * alightSlack; + int cost = boardCost + transitCost(transitDuration, trip) + waitFactor * alightSlack; // Add transfer cost on all alighting events. // If it turns out to be the last one this cost will be removed during costEgress phase. @@ -127,16 +122,20 @@ public int waitCost(int waitTimeInSeconds) { } @Override - public int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStopIndex) { + public int calculateRemainingMinCost( + int minTravelDuration, + int minNumTransfers, + int fromStopIndex + ) { if (minNumTransfers > -1) { return ( boardCostOnly + boardAndTransferCost * minNumTransfers + - transitFactors.minFactor() * minTravelTime + transitFactors.minFactor() * minTravelDuration ); } else { // Remove cost that was added during alighting similar as we do in the costEgress() method - int fixedCost = transitFactors.minFactor() * minTravelTime; + int fixedCost = transitFactors.minFactor() * minTravelDuration; return stopBoardAlightTransferCosts == null ? fixedCost diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculator.java index 25b98f92d29..e236a6f1f02 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculator.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculator.java @@ -40,29 +40,29 @@ public int boardingCost( } @Override - public int onTripRelativeRidingCost(int boardTime, T tripScheduledBoarded) { - return delegate.onTripRelativeRidingCost(boardTime, tripScheduledBoarded); + public int transitCost(int transitDuration, T tripScheduledBoarded) { + return delegate.transitCost(transitDuration, tripScheduledBoarded); } @Override public int transitArrivalCost( int boardCost, int alightSlack, - int transitTime, + int transitDuration, T trip, int toStopIndex ) { int defaultCost = delegate.transitArrivalCost( boardCost, alightSlack, - transitTime, + transitDuration, trip, toStopIndex ); boolean includeUnpreferredCost = unpreferredPatterns.get(trip.pattern().patternIndex()); if (includeUnpreferredCost) { - int unpreferredCostValue = unpreferredCost.calculateRaptorCost(transitTime); + int unpreferredCostValue = unpreferredCost.calculateRaptorCost(transitDuration); return defaultCost + unpreferredCostValue; } else { return defaultCost; @@ -75,8 +75,12 @@ public int waitCost(int waitTimeInSeconds) { } @Override - public int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStopIndex) { - return delegate.calculateRemainingMinCost(minTravelTime, minNumTransfers, fromStopIndex); + public int calculateRemainingMinCost( + int minTravelDuration, + int minNumTransfers, + int fromStopIndex + ) { + return delegate.calculateRemainingMinCost(minTravelDuration, minNumTransfers, fromStopIndex); } @Override diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/SingleValueFactorStrategy.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/SingleValueFactorStrategy.java index 018f4dfe10d..3537d4af665 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/SingleValueFactorStrategy.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/SingleValueFactorStrategy.java @@ -12,7 +12,7 @@ final class SingleValueFactorStrategy implements FactorStrategy { private final int factor; - SingleValueFactorStrategy(int factor) { + private SingleValueFactorStrategy(int factor) { this.factor = factor; } diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java index a5b103ebe18..05715182e99 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java @@ -44,19 +44,19 @@ public int boardingCost( } @Override - public int onTripRelativeRidingCost(int boardTime, T tripScheduledBoarded) { - return delegate.onTripRelativeRidingCost(boardTime, tripScheduledBoarded); + public int transitCost(int transitDuration, T tripScheduledBoarded) { + return delegate.transitCost(transitDuration, tripScheduledBoarded); } @Override public int transitArrivalCost( int boardCost, int alightSlack, - int transitTime, + int transitDuration, T trip, int toStopIndex ) { - return delegate.transitArrivalCost(boardCost, alightSlack, transitTime, trip, toStopIndex); + return delegate.transitArrivalCost(boardCost, alightSlack, transitDuration, trip, toStopIndex); } @Override @@ -65,8 +65,12 @@ public int waitCost(int waitTimeInSeconds) { } @Override - public int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStopIndex) { - return delegate.calculateRemainingMinCost(minTravelTime, minNumTransfers, fromStopIndex); + public int calculateRemainingMinCost( + int minTravelDuration, + int minNumTransfers, + int fromStopIndex + ) { + return delegate.calculateRemainingMinCost(minTravelDuration, minNumTransfers, fromStopIndex); } @Override diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/frequency/FrequencyBoardOrAlightEvent.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/frequency/FrequencyBoardOrAlightEvent.java index a438ac2b73f..8650e7a5034 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/frequency/FrequencyBoardOrAlightEvent.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/frequency/FrequencyBoardOrAlightEvent.java @@ -111,6 +111,11 @@ public int tripSortIndex() { @Override public abstract int departure(int stopPosInPattern); + @Override + public int relativeTravelDuration(int boardTime) { + return offset - boardTime; + } + @Override public RaptorTripPattern pattern() { return raptorTripPattern; diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java index 2205a2feb7b..43a377f5e9e 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java @@ -5,47 +5,44 @@ import java.util.Objects; import java.util.stream.Collectors; import org.opentripplanner.ext.flex.FlexAccessEgress; -import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType; import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress; import org.opentripplanner.routing.algorithm.raptoradapter.transit.FlexAccessEgressAdapter; import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress; import org.opentripplanner.routing.graphfinder.NearbyStop; +import org.opentripplanner.routing.graphfinder.TransitServiceResolver; import org.opentripplanner.transit.model.site.RegularStop; public class AccessEgressMapper { - public static List mapNearbyStops( - Collection accessStops, - AccessEgressType accessOrEgress - ) { + private final TransitServiceResolver resolver; + + public AccessEgressMapper(TransitServiceResolver resolver) { + this.resolver = resolver; + } + + public List mapNearbyStops(Collection accessStops) { return accessStops .stream() - .map(nearbyStop -> mapNearbyStop(nearbyStop, accessOrEgress)) + .map(this::mapNearbyStop) .filter(Objects::nonNull) .collect(Collectors.toList()); } public static Collection mapFlexAccessEgresses( - Collection flexAccessEgresses, - AccessEgressType accessOrEgress + Collection flexAccessEgresses ) { return flexAccessEgresses .stream() - .map(flexAccessEgress -> new FlexAccessEgressAdapter(flexAccessEgress, accessOrEgress)) + .map(FlexAccessEgressAdapter::new) .collect(Collectors.toList()); } - private static RoutingAccessEgress mapNearbyStop( - NearbyStop nearbyStop, - AccessEgressType accessOrEgress - ) { - if (!(nearbyStop.stop instanceof RegularStop)) { + private RoutingAccessEgress mapNearbyStop(NearbyStop nearbyStop) { + var stop = resolver.getStopLocation(nearbyStop.stopId); + if (!(stop instanceof RegularStop)) { return null; } - return new DefaultAccessEgress( - nearbyStop.stop.getIndex(), - accessOrEgress.isEgress() ? nearbyStop.state.reverse() : nearbyStop.state - ); + return new DefaultAccessEgress(stop.getIndex(), nearbyStop.state); } } diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java index 96f22cdb3d9..52449a25ea9 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripScheduleWithOffset.java @@ -50,6 +50,14 @@ public int departure(int stopPosInPattern) { return pattern.departureTime(stopPosInPattern, tripIndexForDates); } + @Override + public int relativeTravelDuration(int boardTime) { + // sortIndex is the arrival time at stop 0. This allow us to use it to compute the relative + // travel duration. It satisfies both invariants of relativeTravelDuration (see JavaDoc on this + // method). + return sortIndex - boardTime; + } + @Override public RaptorTripPattern pattern() { return pattern; diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripTimesForDaysIndex.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripTimesForDaysIndex.java index d16d17bc572..902ae7296d3 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripTimesForDaysIndex.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripTimesForDaysIndex.java @@ -11,13 +11,13 @@ * at 04:05+1d, while the first bus on Sundays may leave at 03:00. Be aware, these two trips may * not have the same service calendar, normally they are different. For example "Sundays" might be * all Sundays and public holidays. - *

      + *

      * The trip index is a sorted list of trips based on the first stop departure time. The index * contains two pointers, the first is the day and the second is the trip index on that day: * ``` * tripIndexForTripPatternPerDates -> (day, tripIndexForTripPatternPerDate) * ``` - *

      + *

      * This class might at first look a bit complicated. A much easier approach would be to just sort * on departure times using a Java built in sort, but sorting is expensive. This code will merge * the timetables instead - which is faster. In 99% of the cases we will just do one extra diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinSafeTransferTimeCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinSafeTransferTimeCalculator.java index 9c229698586..a1c496509b2 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinSafeTransferTimeCalculator.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/MinSafeTransferTimeCalculator.java @@ -81,12 +81,12 @@ public MinSafeTransferTimeCalculator(RaptorSlackProvider slackProvider) { */ public static int minSafeTransferTimeOp( Collection list, - ToIntFunction transitTimeSeconds + ToIntFunction transitDurationSeconds ) { if (list.isEmpty()) { return MIN_SAFE_TRANSFER_TIME_LIMIT_UPPER_BOUND; } - int minTransitTime = list.stream().mapToInt(transitTimeSeconds).min().getAsInt(); + int minTransitTime = list.stream().mapToInt(transitDurationSeconds).min().getAsInt(); int minSafeTransitTime = IntUtils.round((minTransitTime * P) / 100.0); return bound( minSafeTransitTime, diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index b2401d3f191..6b0cc864ddb 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -22,6 +22,7 @@ import org.opentripplanner.routing.api.response.RoutingErrorCode; import org.opentripplanner.routing.error.RoutingValidationException; import org.opentripplanner.standalone.config.routerconfig.TransitRoutingConfig; +import org.opentripplanner.street.model.StreetMode; import org.opentripplanner.utils.collection.ListSection; import org.opentripplanner.utils.time.TimeUtils; import org.opentripplanner.utils.tostring.ToStringBuilder; @@ -234,13 +235,13 @@ public ListSection cropItinerariesAt() { public void validateOriginAndDestination() { List routingErrors = new ArrayList<>(2); - if (from == null || !from.isSpecified()) { + if (from == null) { routingErrors.add( new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.FROM_PLACE) ); } - if (to == null || !to.isSpecified()) { + if (to == null) { routingErrors.add(new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.TO_PLACE)); } @@ -277,6 +278,20 @@ public boolean allowTransferOptimization() { return !isViaSearch() || via.stream().allMatch(ViaLocation::isPassThroughLocation); } + /** + * Returns {@code true} when the request has no way to reach transit on at least one of the + * access and egress sides. A side is unreachable when its street mode is + * {@link StreetMode#NOT_SET} and the corresponding endpoint is not a stop (a stop endpoint + * provides a zero-distance access/egress, so it does not need a street mode). + */ + public boolean cannotReachTransit() { + boolean accessUnreachable = + journey.access().mode() == StreetMode.NOT_SET && (from == null || from.stopId() == null); + boolean egressUnreachable = + journey.egress().mode() == StreetMode.NOT_SET && (to == null || to.stopId() == null); + return accessUnreachable || egressUnreachable; + } + /** * Return {@code true} if at least one via location is set! */ @@ -521,13 +536,13 @@ public List validateFromAndToLocation() { List routingErrors = new ArrayList<>(2); - if (from == null || !from.isSpecified()) { + if (from == null) { routingErrors.add( new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.FROM_PLACE) ); } - if (to == null || !to.isSpecified()) { + if (to == null) { routingErrors.add(new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.TO_PLACE)); } return routingErrors; diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/ViaLocationDeprecated.java b/application/src/main/java/org/opentripplanner/routing/api/request/ViaLocationDeprecated.java index 7d864dddb16..3145462d0fc 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/ViaLocationDeprecated.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/ViaLocationDeprecated.java @@ -29,8 +29,5 @@ public record ViaLocationDeprecated( Objects.requireNonNull(minSlack); Objects.requireNonNull(maxSlack); Objects.requireNonNull(point); - if (!point.isSpecified()) { - throw new IllegalArgumentException("The Via location is not specified"); - } } } diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransferPreferences.java b/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransferPreferences.java index 92ef21972bf..08ed0ec99f2 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransferPreferences.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransferPreferences.java @@ -115,7 +115,7 @@ public double waitReluctance() { /** * Ideally maxTransfers should be set in the router config, not from the client. Instead, the - * client should use {@link #maxAdditionalTransfers)} instead to pass in the max number of + * client should use {@link #maxAdditionalTransfers} instead to pass in the max number of * additional/extra transfers relative to the best trip (with the fewest possible transfers) * within constraint of the other search parameters. This might be too complicated to explain to * the customer, so you might stick to the old limit, but that has side-effects where you might diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java b/application/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java index 4437d2f94f3..f82bc183d36 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/via/VisitViaLocation.java @@ -58,7 +58,7 @@ public GenericLocation coordinateLocation() { if (coordinate == null) { return null; } - return new GenericLocation(label(), null, coordinate.latitude(), coordinate.longitude()); + return GenericLocation.fromCoordinate(coordinate.latitude(), coordinate.longitude(), label()); } /** diff --git a/application/src/main/java/org/opentripplanner/routing/api/response/RoutingErrorCode.java b/application/src/main/java/org/opentripplanner/routing/api/response/RoutingErrorCode.java index 363849319bc..ad85b63e713 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/response/RoutingErrorCode.java +++ b/application/src/main/java/org/opentripplanner/routing/api/response/RoutingErrorCode.java @@ -36,4 +36,11 @@ public enum RoutingErrorCode { * The location was found, but no stops could be found within the search radius. */ NO_STOPS_IN_RANGE, + + /** + * No usable itineraries were found for a direct-only search (no transit modes requested). + * This includes both the case where no route exists and the case where routes were found + * but all were filtered out (e.g. by quality or distance filters). + */ + NO_DIRECT_MODE_CONNECTION, } diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java index cdf5e9a1c4f..b16cb462e03 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/DirectGraphFinder.java @@ -41,7 +41,7 @@ public List findClosestStops(Coordinate coordinate, double radiusMet SphericalDistanceLibrary.distance(coordinate, it.getCoordinate().asJtsCoordinate()) ); if (distance < radiusMeters) { - NearbyStop sd = new NearbyStop(it, distance, null, null); + NearbyStop sd = new NearbyStop(it.getId(), distance, null, null); stopsFound.add(sd); } } diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java index 1f4a09de08b..1fc372d0a4b 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/GraphFinder.java @@ -22,12 +22,11 @@ public interface GraphFinder { */ static GraphFinder getInstance( boolean graphHasStreets, - StopResolver stopResolver, Function> queryNearbyStops, LinkingContextFactory linkingContextFactory ) { return graphHasStreets - ? new StreetGraphFinder(linkingContextFactory, stopResolver) + ? new StreetGraphFinder(linkingContextFactory) : new DirectGraphFinder(queryNearbyStops); } diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStop.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStop.java index f9e2fc48b72..35c65ead7b0 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStop.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStop.java @@ -5,9 +5,9 @@ import java.util.List; import java.util.Locale; import java.util.Objects; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.search.state.State; -import org.opentripplanner.transit.model.site.StopLocation; /** * A specific stop at a distance. Also includes a geometry and potentially a list of edges and a @@ -15,33 +15,33 @@ */ public class NearbyStop implements Comparable { - public final StopLocation stop; + public final FeedScopedId stopId; public final double distance; public final List edges; public final State state; - public NearbyStop(StopLocation stop, double distance, List edges, State state) { - this.stop = Objects.requireNonNull(stop); + public NearbyStop(FeedScopedId stopId, double distance, List edges, State state) { + this.stopId = Objects.requireNonNull(stopId); this.distance = distance; this.edges = edges; this.state = state; } /** - * Given a State at a StopVertex, bundle the StopVertex together with information about how far + * Given a State at a StopVertex, bundle the stop's id together with information about how far * away it is and the geometry of the path leading up to the given State. */ - public static NearbyStop nearbyStopForState(State state, StopLocation stop) { + public static NearbyStop nearbyStopForState(State state, FeedScopedId stopId) { var result = ChronologicalGraphPath.of(state); - return new NearbyStop(stop, result.effectiveWalkDistance(), result.edges(), state); + return new NearbyStop(stopId, result.effectiveWalkDistance(), result.edges(), state); } /** * Create a NearbyStop with zero distance and no edges. */ - public static NearbyStop ofZeroDistance(StopLocation stop, State state) { - return new NearbyStop(stop, 0d, Collections.emptyList(), state); + public static NearbyStop ofZeroDistance(FeedScopedId stopId, State state) { + return new NearbyStop(stopId, 0d, Collections.emptyList(), state); } /** @@ -76,7 +76,7 @@ public Duration duration() { @Override public int hashCode() { - return Objects.hash(stop, distance, edges, state); + return Objects.hash(stopId, distance, edges, state); } @Override @@ -90,7 +90,7 @@ public boolean equals(Object o) { final NearbyStop that = (NearbyStop) o; return ( Double.compare(that.distance, distance) == 0 && - stop.equals(that.stop) && + stopId.equals(that.stopId) && Objects.equals(edges, that.edges) && Objects.equals(state, that.state) ); @@ -100,7 +100,7 @@ public String toString() { return String.format( Locale.ROOT, "stop %s at %.1f meters%s%s", - stop, + stopId, distance, edges != null ? " (" + edges.size() + " edges)" : "", state != null ? " w/state" : "" diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStopFactory.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStopFactory.java index 415bb67e5db..d91ea5cb7f7 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStopFactory.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStopFactory.java @@ -4,30 +4,21 @@ import java.util.Collection; import java.util.List; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; -import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.street.model.StreetMode; import org.opentripplanner.street.model.vertex.TransitStopVertex; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.state.State; import org.opentripplanner.streetadapter.StreetSearchRequestMapper; -import org.opentripplanner.transit.model.site.RegularStop; public class NearbyStopFactory { - private final StopResolver stopResolver; - - public NearbyStopFactory(StopResolver stopResolver) { - this.stopResolver = Objects.requireNonNull(stopResolver); - } - /** * Create zero distance NearbyStops given a list of TransitStopVertices */ - public List nearbyStopsForTransitStopVertices( + public static List nearbyStopsForTransitStopVertices( Set stopVertices, boolean reverseDirection, RouteRequest routeRequest, @@ -44,7 +35,7 @@ public List nearbyStopsForTransitStopVertices( return stopVertices .stream() - .map(s -> ofZeroDistance(stop(s.getId()), new State(s, streetSearchRequest))) + .map(s -> ofZeroDistance(s.getId(), new State(s, streetSearchRequest))) .toList(); } @@ -52,7 +43,7 @@ public List nearbyStopsForTransitStopVertices( * Given a list of Vertices, find the TransitStopVertices and create zero distance NearbyStops * for them. */ - public List nearbyStopsForTransitStopVerticesFiltered( + public static List nearbyStopsForTransitStopVerticesFiltered( Collection vertices, boolean reverseDirection, RouteRequest routeRequest, @@ -66,8 +57,4 @@ public List nearbyStopsForTransitStopVerticesFiltered( return nearbyStopsForTransitStopVertices(transitStops, reverseDirection, routeRequest, mode); } - - private RegularStop stop(FeedScopedId id) { - return Objects.requireNonNull(stopResolver.getStop(id)); - } } diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/SiteResolver.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/SiteResolver.java index fb09f82f5db..aa25b8b36c5 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/SiteResolver.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/SiteResolver.java @@ -3,6 +3,7 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.transit.model.site.Entrance; import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.site.StopLocation; /** * Interface for resolving stops and entrances by ID. @@ -12,5 +13,6 @@ */ public interface SiteResolver extends EntranceResolver { RegularStop getStop(FeedScopedId id); + StopLocation getStopLocation(FeedScopedId id); Entrance getEntrance(FeedScopedId id); } diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitor.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitor.java index 4a7c4a94354..f0a0da18c7f 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitor.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitor.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Objects; import org.opentripplanner.astar.spi.SkipEdgeStrategy; import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.street.model.edge.Edge; @@ -16,14 +15,12 @@ */ public class StopFinderTraverseVisitor implements TraverseVisitor { - private final StopResolver stopResolver; private final double radiusMeters; /** A list of closest stops found while walking the graph */ private final List stopsFound = new ArrayList<>(); - public StopFinderTraverseVisitor(StopResolver stopResolver, double radiusMeters) { - this.stopResolver = stopResolver; + public StopFinderTraverseVisitor(double radiusMeters) { this.radiusMeters = radiusMeters; } @@ -34,8 +31,7 @@ public void visitEdge(Edge edge) {} public void visitVertex(State state) { Vertex vertex = state.getVertex(); if (vertex instanceof TransitStopVertex tsv) { - var stop = Objects.requireNonNull(stopResolver.getStop(tsv.getId())); - stopsFound.add(NearbyStop.nearbyStopForState(state, stop)); + stopsFound.add(NearbyStop.nearbyStopForState(state, tsv.getId())); } } @@ -46,7 +42,7 @@ public void visitEnqueue() {} * @return A de-duplicated list of nearby stops found by this visitor. */ public List stopsFound() { - return ListUtils.distinctByKey(stopsFound, ns -> ns.stop); + return ListUtils.distinctByKey(stopsFound, ns -> ns.stopId); } /** diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java index 90f0164c10d..ec6c283174a 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/StreetGraphFinder.java @@ -29,16 +29,14 @@ public class StreetGraphFinder implements GraphFinder { private final LinkingContextFactory linkingContextFactory; - private final StopResolver stopResolver; - public StreetGraphFinder(LinkingContextFactory linkingContextFactory, StopResolver stopResolver) { + public StreetGraphFinder(LinkingContextFactory linkingContextFactory) { this.linkingContextFactory = linkingContextFactory; - this.stopResolver = stopResolver; } @Override public List findClosestStops(Coordinate coordinate, double radiusMeters) { - StopFinderTraverseVisitor visitor = new StopFinderTraverseVisitor(stopResolver, radiusMeters); + StopFinderTraverseVisitor visitor = new StopFinderTraverseVisitor(radiusMeters); findClosestUsingStreets( coordinate.getY(), coordinate.getX(), diff --git a/application/src/main/java/org/opentripplanner/routing/graphfinder/TransitServiceResolver.java b/application/src/main/java/org/opentripplanner/routing/graphfinder/TransitServiceResolver.java index dd235409e24..827b8d20848 100644 --- a/application/src/main/java/org/opentripplanner/routing/graphfinder/TransitServiceResolver.java +++ b/application/src/main/java/org/opentripplanner/routing/graphfinder/TransitServiceResolver.java @@ -4,6 +4,7 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.transit.model.site.Entrance; import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.service.TransitService; /** @@ -22,6 +23,11 @@ public RegularStop getStop(FeedScopedId id) { return Objects.requireNonNull(transitService.getRegularStop(id)); } + @Override + public StopLocation getStopLocation(FeedScopedId id) { + return Objects.requireNonNull(transitService.getStopLocation(id)); + } + @Override public Entrance getEntrance(FeedScopedId id) { return transitService.getEntrance(id); diff --git a/application/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java b/application/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java index 7959869bd4d..3bf8a27b1ba 100644 --- a/application/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java +++ b/application/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java @@ -4,8 +4,6 @@ import java.util.List; import java.util.Objects; import java.util.Set; -import javax.annotation.Nullable; -import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.astar.strategy.DurationSkipEdgeStrategy; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.routing.api.request.RouteRequest; @@ -13,13 +11,11 @@ import org.opentripplanner.routing.error.PathNotFoundException; import org.opentripplanner.routing.linking.LinkingContext; import org.opentripplanner.street.model.StreetConstants; -import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.edge.ExtensionRequestContext; import org.opentripplanner.street.model.path.StreetPath; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.EuclideanRemainingWeightHeuristic; import org.opentripplanner.street.search.StreetSearchBuilder; -import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.strategy.DominanceFunctions; import org.opentripplanner.streetadapter.StreetSearchRequestMapper; import org.slf4j.Logger; @@ -51,23 +47,18 @@ public class GraphPathFinder { private static final Logger LOG = LoggerFactory.getLogger(GraphPathFinder.class); - @Nullable - private final TraverseVisitor traverseVisitor; - private final Collection extensionRequestContexts; private final float maxCarSpeed; - public GraphPathFinder(@Nullable TraverseVisitor traverseVisitor) { - this(traverseVisitor, List.of(), StreetConstants.DEFAULT_MAX_CAR_SPEED); + public GraphPathFinder() { + this(List.of(), StreetConstants.DEFAULT_MAX_CAR_SPEED); } public GraphPathFinder( - @Nullable TraverseVisitor traverseVisitor, Collection extensionRequestContexts, float maxCarSpeed ) { - this.traverseVisitor = traverseVisitor; this.extensionRequestContexts = Objects.requireNonNull(extensionRequestContexts); this.maxCarSpeed = maxCarSpeed; } @@ -98,12 +89,6 @@ public List getPaths(RouteRequest request, Set from, Set getFromVertices( LinkingContextRequest request ) { var from = request.from(); - if (from == null || !from.isSpecified()) { + if (from == null) { return Set.of(); } var modes = request.accessMode() != StreetMode.NOT_SET @@ -143,7 +143,7 @@ private Set getToVertices( LinkingContextRequest request ) { var to = request.to(); - if (to == null || !to.isSpecified()) { + if (to == null) { return Set.of(); } var modes = request.egressMode() != StreetMode.NOT_SET @@ -193,8 +193,8 @@ private Map> getVerticesForViaLocationsWithCoordina } private Set getStopVertices(GenericLocation location) { - if (location != null && location.stopId != null) { - return findStopOrChildStopVertices(location.stopId); + if (location != null && location.stopId() != null) { + return findStopOrChildStopVertices(location.stopId()); } return Set.of(); } @@ -269,10 +269,6 @@ private Set getStreetVerticesForLocation( EnumSet streetModes, LocationType type ) { - if (!location.isSpecified()) { - return Set.of(); - } - // Differentiate between driving and non-driving, as driving is not available from transit stops List modes = streetModes .stream() @@ -281,7 +277,7 @@ private Set getStreetVerticesForLocation( .toList(); var results = new HashSet(); - if (location.stopId != null) { + if (location.stopId() != null) { if (!modes.stream().allMatch(TraverseMode::isInCar)) { results.addAll(getStreetVerticesForStop(location)); } @@ -299,7 +295,7 @@ private Set getStreetVerticesForLocation( vertexCreationService.createVertexFromCoordinate( container, location.getCoordinate(), - location.label, + location.label(), modes, type ) @@ -311,19 +307,19 @@ private Set getStreetVerticesForLocation( private Set getStreetVerticesForStop(GenericLocation location) { // check if there is a stop by the given id - var stopVertex = graph.findStopVertex(location.stopId); + var stopVertex = graph.findStopVertex(location.stopId()); if (stopVertex.isPresent()) { return Set.of(stopVertex.get()); } // station centroids may be used instead of child stop vertices for stations - var centroidVertex = graph.findStationCentroidVertex(location.stopId); + var centroidVertex = graph.findStationCentroidVertex(location.stopId()); if (centroidVertex.isPresent()) { return Set.of(centroidVertex.get()); } // in the regular case you want to resolve a (multi-modal) station into its child stops - var childVertices = findStopOrChildStopVertices(location.stopId); + var childVertices = findStopOrChildStopVertices(location.stopId()); if (!childVertices.isEmpty()) { return childVertices.stream().map(Vertex.class::cast).collect(Collectors.toUnmodifiableSet()); } @@ -339,43 +335,33 @@ private Optional getCarRoutableStreetVertex( GenericLocation location, LocationType type ) { + Coordinate coordinate = location.getCoordinate(); // Fetch coordinate from stop, if not given in request - if (location.getCoordinate() == null) { - var stopVertex = graph.getStopVertex(location.stopId); + if (coordinate == null) { + var stopVertex = graph.getStopVertex(location.stopId()); if (stopVertex != null) { - var c = stopVertex.toWgsCoordinate(); - location = new GenericLocation( - location.label, - location.stopId, - c.latitude(), - c.longitude() - ); + coordinate = stopVertex.getCoordinate(); } else { // For car routing, we use station's coordinate instead of child stops' if stop location is // a station. - var coordinate = findStopLocationsGroupCentroid.apply(location.stopId); - if (coordinate.isPresent()) { - var c = coordinate.get(); - location = new GenericLocation( - location.label, - location.stopId, - c.latitude(), - c.longitude() - ); - } + coordinate = findStopLocationsGroupCentroid + .apply(location.stopId()) + .map(WgsCoordinate::asJtsCoordinate) + .orElse(null); } } - return location.getCoordinate() != null - ? Optional.of( - vertexCreationService.createVertexFromCoordinate( - container, - location.getCoordinate(), - location.label, - List.of(TraverseMode.CAR), - type - ) - ) - : Optional.empty(); + if (coordinate == null) { + return Optional.empty(); + } + return Optional.of( + vertexCreationService.createVertexFromCoordinate( + container, + coordinate, + location.label(), + List.of(TraverseMode.CAR), + type + ) + ); } private void checkIfVerticesFound( @@ -398,12 +384,7 @@ private void checkIfVerticesFound( } // check that vertices where found if to-location was specified - if ( - to != null && - to.isSpecified() && - toStopVertices.isEmpty() && - isDisconnected(toVertices, LocationType.TO) - ) { + if (to != null && toStopVertices.isEmpty() && isDisconnected(toVertices, LocationType.TO)) { routingErrors.add( new RoutingError(getRoutingErrorCodeForDisconnected(to), InputField.TO_PLACE) ); diff --git a/application/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java b/application/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java index 7972e3acf03..af0b4b36b18 100644 --- a/application/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java +++ b/application/src/main/java/org/opentripplanner/routing/service/DefaultRoutingService.java @@ -1,10 +1,11 @@ package org.opentripplanner.routing.service; -import java.time.ZoneId; import org.opentripplanner.framework.application.OTPRequestTimeoutException; import org.opentripplanner.framework.time.ZoneIdFallback; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.RequestPreProcessor; import org.opentripplanner.routing.algorithm.RoutingWorker; +import org.opentripplanner.routing.algorithm.RoutingWorkerRequest; import org.opentripplanner.routing.algorithm.via.ViaRoutingWorker; import org.opentripplanner.routing.api.RoutingService; import org.opentripplanner.routing.api.request.RouteRequest; @@ -27,11 +28,17 @@ public class DefaultRoutingService implements RoutingService { private final OtpServerRequestContext serverContext; - private final ZoneId timeZone; + private final RequestPreProcessor requestPreProcessor; public DefaultRoutingService(OtpServerRequestContext serverContext) { this.serverContext = serverContext; - this.timeZone = ZoneIdFallback.zoneId(serverContext.transitService().getTimeZone()); + + var timeZone = ZoneIdFallback.zoneId(serverContext.transitService().getTimeZone()); + + this.requestPreProcessor = new RequestPreProcessor( + serverContext.raptorTuningParameters(), + timeZone + ); } @Override @@ -39,7 +46,7 @@ public RoutingResponse route(RouteRequest request) { LOG.debug("Request: {}", request); OTPRequestTimeoutException.checkForTimeout(); request.validateOriginAndDestination(); - var worker = new RoutingWorker(serverContext, request, timeZone); + var worker = new RoutingWorker(serverContext, mapRequest(request)); var response = worker.route(); logResponse(response); return response; @@ -50,12 +57,16 @@ public ViaRoutingResponse route(RouteViaRequest request) { LOG.debug("Request: {}", request); OTPRequestTimeoutException.checkForTimeout(); var viaRoutingWorker = new ViaRoutingWorker(request, req -> - new RoutingWorker(serverContext, req, serverContext.transitService().getTimeZone()).route() + new RoutingWorker(serverContext, mapRequest(req)).route() ); // TODO: Add output logging here, see route(..) method return viaRoutingWorker.route(); } + private RoutingWorkerRequest mapRequest(RouteRequest request) { + return requestPreProcessor.computeRequest(request); + } + private void logResponse(RoutingResponse response) { if (response.getTripPlan().itineraries.isEmpty() && response.getRoutingErrors().isEmpty()) { // We should provide an error if there is no results, this is important for the client so diff --git a/application/src/main/java/org/opentripplanner/routing/via/service/DefaultViaCoordinateTransferFactory.java b/application/src/main/java/org/opentripplanner/routing/via/service/DefaultViaCoordinateTransferFactory.java index b911d4854c4..f444af63342 100644 --- a/application/src/main/java/org/opentripplanner/routing/via/service/DefaultViaCoordinateTransferFactory.java +++ b/application/src/main/java/org/opentripplanner/routing/via/service/DefaultViaCoordinateTransferFactory.java @@ -7,7 +7,6 @@ import org.opentripplanner.graph_builder.module.nearbystops.NearbyStopFinder; import org.opentripplanner.graph_builder.module.nearbystops.StraightLineNearbyStopFinder; import org.opentripplanner.graph_builder.module.nearbystops.StreetNearbyStopFinder; -import org.opentripplanner.graph_builder.module.nearbystops.TransitServiceResolver; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.graphfinder.NearbyStop; import org.opentripplanner.routing.via.ViaCoordinateTransferFactory; @@ -57,8 +56,8 @@ public List createViaTransfers( transfers.add( new ViaCoordinateTransfer( coordinate, - from.stop.getIndex(), - to.stop.getIndex(), + transitService.getStopLocation(from.stopId).getIndex(), + transitService.getStopLocation(to.stopId).getIndex(), from.edges, to.edges, (int) (from.state.getElapsedTimeSeconds() + to.state.getElapsedTimeSeconds()), @@ -78,11 +77,7 @@ private NearbyStopFinder createNearbyStopFinder(Duration radiusAsDuration) { if (!graph.hasStreets) { return new StraightLineNearbyStopFinder(transitService, radiusAsDuration); } else { - return StreetNearbyStopFinder.of( - new TransitServiceResolver(transitService), - radiusAsDuration, - 0 - ).build(); + return StreetNearbyStopFinder.of(radiusAsDuration, 0).build(); } } @@ -99,7 +94,7 @@ private List findNearbyStops( var r = finder.findNearbyStops(viaVertex, request, transferMode, reverseDirection); return r .stream() - .filter(it -> !it.stop.transfersNotAllowed()) + .filter(it -> !transitService.getStopLocation(it.stopId).transfersNotAllowed()) .toList(); } } diff --git a/application/src/main/java/org/opentripplanner/standalone/OTPMain.java b/application/src/main/java/org/opentripplanner/standalone/OTPMain.java index ae92788bba0..4fd349144af 100644 --- a/application/src/main/java/org/opentripplanner/standalone/OTPMain.java +++ b/application/src/main/java/org/opentripplanner/standalone/OTPMain.java @@ -193,11 +193,6 @@ private static void startOtpWebServer(CommandLineParameters params, ConstructApp // publishing the config version info make it available to the APIs setOtpConfigVersionsOnServerInfo(app); - /* Start visualizer if requested. */ - if (params.visualize) { - app.graphVisualizer().run(); - } - /* Start web server if requested. */ // We could start the server first so it can report build/load progress to a load balancer. // This would also avoid the awkward call to set the router on the appConstruction after it's constructed. diff --git a/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index 91fc66b00bf..aadab3aad66 100644 --- a/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -7,7 +7,6 @@ import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.GtfsApiParameters; import org.opentripplanner.apis.transmodel.TransmodelAPIParameters; -import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.carpooling.CarpoolingService; import org.opentripplanner.ext.dataoverlay.configuration.DataOverlayParameterBindings; import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext; @@ -40,9 +39,7 @@ import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; import org.opentripplanner.street.graph.Graph; import org.opentripplanner.street.linking.VertexLinker; -import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.edge.ExtensionRequestContext; -import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.service.StreetLimitationParametersService; import org.opentripplanner.transfer.regular.RegularTransferService; import org.opentripplanner.transit.service.TransitService; @@ -124,17 +121,9 @@ public interface OtpServerRequestContext { MeterRegistry meterRegistry(); - /** - * Callback which is injected into the {@code DirectStreetRouter}, used to visualize the - * search. - */ - @HttpRequestScoped - TraverseVisitor traverseVisitor(); - default GraphFinder graphFinder() { return GraphFinder.getInstance( graph().hasStreets, - transitService()::getRegularStop, transitService()::findRegularStopsByBoundingBox, linkingContextFactory() ); diff --git a/application/src/main/java/org/opentripplanner/standalone/config/CommandLineParameters.java b/application/src/main/java/org/opentripplanner/standalone/config/CommandLineParameters.java index 8b84a3e9f5a..101bbeaee7d 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/CommandLineParameters.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/CommandLineParameters.java @@ -127,12 +127,6 @@ public class CommandLineParameters { ) public Integer port = DEFAULT_PORT; - @Parameter( - names = { "--visualize" }, - description = "Open a graph visualizer window for debugging." - ) - public boolean visualize; - @Parameter( names = { "--abortOnUnknownConfig" }, description = "Abort the startup if configuration files are found to contain unknown parameters." diff --git a/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java index 5f88433c771..6866d371ba9 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.node.MissingNode; import java.io.Serializable; import java.util.List; +import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.GtfsApiParameters; import org.opentripplanner.ext.flex.FlexParameters; import org.opentripplanner.ext.ojp.config.OjpApiConfig; @@ -22,10 +23,12 @@ import org.opentripplanner.standalone.config.routerconfig.TransitRoutingConfig; import org.opentripplanner.standalone.config.routerconfig.UpdatersConfig; import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig; +import org.opentripplanner.standalone.config.routerconfig.WarmupConfig; import org.opentripplanner.standalone.config.sandbox.FlexConfig; import org.opentripplanner.standalone.config.sandbox.GtfsApiConfig; import org.opentripplanner.standalone.config.sandbox.TransmodelAPIConfig; import org.opentripplanner.updater.UpdatersParameters; +import org.opentripplanner.warmup.api.WarmupParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,6 +61,7 @@ public class RouterConfig implements Serializable { private final VectorTileConfig vectorTileConfig; private final TriasApiParameters triasApiParameters; private final OjpApiParameters ojpApiParameters; + private final WarmupParameters warmupParameters; public RouterConfig(JsonNode node, String source, boolean logUnusedParams) { this(new NodeAdapter(node, source), logUnusedParams); @@ -88,6 +92,7 @@ public RouterConfig(JsonNode node, String source, boolean logUnusedParams) { this.triasApiParameters = TriasApiConfig.mapParameters("triasApi", root); this.ojpApiParameters = OjpApiConfig.mapParameters("ojpApi", root); this.flexConfig = new FlexConfig(root, "flex"); + this.warmupParameters = WarmupConfig.mapWarmupConfig("warmup", root); if (logUnusedParams && LOG.isWarnEnabled()) { root.logAllWarnings(LOG::warn); @@ -158,6 +163,11 @@ public GtfsApiParameters gtfsApiParameters() { return gtfsApi; } + @Nullable + public WarmupParameters warmupParameters() { + return warmupParameters; + } + public NodeAdapter asNodeAdapter() { return root; } diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java index fadd998b8cc..fa5f1f72ce2 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java @@ -1,7 +1,7 @@ package org.opentripplanner.standalone.config.buildconfig; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_10; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2; -import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7; import org.opentripplanner.graph_builder.module.osm.parameters.OsmExtractParameters; import org.opentripplanner.graph_builder.module.osm.parameters.OsmExtractParametersBuilder; @@ -86,13 +86,15 @@ public static OsmExtractParametersBuilder mapOsmGenericParameters( .docDefaultValue(docDefaults.timeZone()) .asZoneId(defaults.timeZone()) ) - .withIncludeOsmSubwayEntrances( + .withIncludeOsmStationEntrances( node - .of("includeOsmSubwayEntrances") - .since(V2_7) - .summary("Whether to include subway entrances from the OSM data." + documentationAddition) - .docDefaultValue(docDefaults.includeOsmSubwayEntrances()) - .asBoolean(defaults.includeOsmSubwayEntrances()) + .of("includeOsmStationEntrances") + .since(V2_10) + .summary( + "Whether to include station entrances from the OSM data." + documentationAddition + ) + .docDefaultValue(docDefaults.includeOsmStationEntrances()) + .asBoolean(defaults.includeOsmStationEntrances()) ); } } diff --git a/application/src/main/java/org/opentripplanner/standalone/config/configure/LoadConfigModule.java b/application/src/main/java/org/opentripplanner/standalone/config/configure/LoadConfigModule.java index ad62c4c7126..ff81e863339 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/configure/LoadConfigModule.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/configure/LoadConfigModule.java @@ -21,7 +21,7 @@ * types. The {@link ConfigModule} is a separate module to be able to use it without this module; * If the {@link ConfigModel} is already instantiated. *

      - * The binding to {@link OtpDataStoreConfig} and {@link @TransitServicePeriod} is done + * The binding to {@link OtpDataStoreConfig} and {@link TransitServicePeriod} is done * here, not in the {@link ConfigModel}, because they are only needed at load time - if this change, * then move the binding to the {@link ConfigModule}. */ diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java new file mode 100644 index 00000000000..e63f8477afb --- /dev/null +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfig.java @@ -0,0 +1,122 @@ +package org.opentripplanner.standalone.config.routerconfig; + +import static org.opentripplanner.standalone.config.framework.json.EnumMapper.docEnumValueList; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_10; + +import java.util.List; +import javax.annotation.Nullable; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; +import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.street.model.StreetMode; +import org.opentripplanner.warmup.api.WarmupApi; +import org.opentripplanner.warmup.api.WarmupParameters; + +/** + * Maps the {@code warmup} section of {@code router-config.json} into a {@link WarmupParameters} + * record consumed by the warmup module. + */ +public final class WarmupConfig { + + private static final List DEFAULT_ACCESS_MODES = List.of( + StreetMode.WALK, + StreetMode.CAR_TO_PARK + ); + private static final List DEFAULT_EGRESS_MODES = List.of( + StreetMode.WALK, + StreetMode.WALK + ); + + private WarmupConfig() {} + + @Nullable + public static WarmupParameters mapWarmupConfig(String parameterName, NodeAdapter root) { + if (!root.exist(parameterName)) { + return null; + } + + var c = root + .of(parameterName) + .since(V2_10) + .summary("Configure application warmup by running transit searches during startup.") + .description( + """ + When configured, OTP runs transit routing queries between the given locations + during startup. This warms up the application (JIT compilation, GraphQL schema + caches, routing data structures, etc.) before production traffic arrives. + Queries start after the Raptor transit data is created and stop when all updaters + are primed (the health endpoint would return "UP"). + If no updaters are configured, no warmup queries are run. + """ + ) + .asObject(); + + WarmupApi api = c + .of("api") + .since(V2_10) + .summary("Which GraphQL API to use for warmup queries.") + .description(docEnumValueList(WarmupApi.values())) + .asEnum(WarmupApi.TRANSMODEL); + + WgsCoordinate from = mapCoordinate(c, "from", "Origin location for warmup searches.", "origin"); + WgsCoordinate to = mapCoordinate( + c, + "to", + "Destination location for warmup searches.", + "destination" + ); + + var accessModeStrings = c + .of("accessModes") + .since(V2_10) + .summary("Access modes to cycle through in warmup queries.") + .description( + "Ordered list of `StreetMode` values used as access modes. " + + "Each entry is paired with the egress mode at the same index. " + + docEnumValueList(StreetMode.values()) + ) + .asStringList(DEFAULT_ACCESS_MODES.stream().map(Enum::name).toList()); + List accessModes = accessModeStrings + .stream() + .map(s -> StreetMode.valueOf(s)) + .toList(); + + var egressModeStrings = c + .of("egressModes") + .since(V2_10) + .summary("Egress modes to cycle through in warmup queries.") + .description( + "Ordered list of `StreetMode` values used as egress modes. " + + "Each entry is paired with the access mode at the same index. " + + docEnumValueList(StreetMode.values()) + ) + .asStringList(DEFAULT_EGRESS_MODES.stream().map(Enum::name).toList()); + List egressModes = egressModeStrings + .stream() + .map(s -> StreetMode.valueOf(s)) + .toList(); + + if (accessModes.size() != egressModes.size()) { + throw new IllegalArgumentException( + "warmup.accessModes and warmup.egressModes must have the same number of entries, " + + "got %d access modes and %d egress modes.".formatted( + accessModes.size(), + egressModes.size() + ) + ); + } + + return new WarmupParameters(api, from, to, accessModes, egressModes); + } + + private static WgsCoordinate mapCoordinate( + NodeAdapter parent, + String name, + String summary, + String noun + ) { + var node = parent.of(name).since(V2_10).summary(summary).asObject(); + double lat = node.of("lat").since(V2_10).summary("Latitude of the " + noun + ".").asDouble(); + double lon = node.of("lon").since(V2_10).summary("Longitude of the " + noun + ".").asDouble(); + return new WgsCoordinate(lat, lon); + } +} diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleRentalUpdaterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleRentalUpdaterConfig.java index 81416ac7d78..b852fd7e534 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleRentalUpdaterConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleRentalUpdaterConfig.java @@ -1,6 +1,7 @@ package org.opentripplanner.standalone.config.routerconfig.updaters; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V1_5; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_10; import java.time.Duration; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; @@ -23,6 +24,20 @@ public static VehicleRentalUpdaterParameters create(String configRef, NodeAdapte .since(V1_5) .summary("How often the data should be updated.") .asDuration(Duration.ofMinutes(1)), + c + .of("startupRetryPeriod") + .since(V2_10) + .summary( + "How long to retry loading the vehicle rental data source on startup if it initially fails." + ) + .description( + """ + The first time the data source is loaded, OTP will retry for this duration every + 5 seconds before giving up. This is useful to handle temporary network failures during + OTP startup. Set to `PT0S` to disable retries. + """ + ) + .asDuration(Duration.ZERO), VehicleRentalSourceFactory.create(sourceType, c) ); } diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index 73cc75f1a1c..0789b4e890e 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -44,7 +44,6 @@ import org.opentripplanner.updater.configure.UpdaterConfigurator; import org.opentripplanner.updater.trip.TimetableSnapshotManager; import org.opentripplanner.utils.logging.ProgressTracker; -import org.opentripplanner.visualizer.GraphVisualizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -102,10 +101,6 @@ public class ConstructApplication { this.graphBuilderDataSources = graphBuilderDataSources; this.osmInfoGraphBuildRepository = osmInfoGraphBuildRepository; - // We create the optional GraphVisualizer here, because it would be significant more complex to - // use Dagger DI to do it - passing in a parameter to enable it or not. - var graphVisualizer = cli.visualize ? new GraphVisualizer(graph) : null; - ConstructApplicationFactory.Builder builder = DaggerConstructApplicationFactory.builder(); this.factory = builder .configModel(config) @@ -113,7 +108,6 @@ public class ConstructApplication { .streetDetailsRepository(streetDetailsRepository) .timetableRepository(timetableRepository) .transferRepository(transferRepository) - .graphVisualizer(graphVisualizer) .worldEnvelopeRepository(worldEnvelopeRepository) .vehicleParkingRepository(vehicleParkingRepository) .emissionRepository(emissionRepository) @@ -212,6 +206,9 @@ private void setupTransitRoutingServer() { routerConfig().updaterConfig() ); + // Start application warmup — runs routing queries to warm up the application + factory.warmupLauncher().start(); + initEllipsoidToGeoidDifference(); initializeTransferCache(routerConfig().transitTuningConfig(), timetableRepository()); @@ -364,10 +361,6 @@ public RaptorConfig raptorConfig() { return factory.raptorConfig(); } - public GraphVisualizer graphVisualizer() { - return factory.graphVisualizer(); - } - private OtpServerRequestContext createServerContext() { return factory.createServerContext(); } diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index 26f082ccced..88e492516e9 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -66,7 +66,8 @@ import org.opentripplanner.transit.service.TimetableRepository; import org.opentripplanner.transit.service.TransitService; import org.opentripplanner.updater.trip.TimetableSnapshotManager; -import org.opentripplanner.visualizer.GraphVisualizer; +import org.opentripplanner.warmup.WarmupLauncher; +import org.opentripplanner.warmup.configure.WarmupModule; /** * A Factory used by the Dagger dependency injection system to create the components of OTP, which @@ -100,6 +101,7 @@ VehicleRentalRepositoryModule.class, VehicleRentalServiceModule.class, ViaModule.class, + WarmupModule.class, WorldEnvelopeServiceModule.class, } ) @@ -136,9 +138,6 @@ public interface ConstructApplicationFactory { @Nullable EmpiricalDelayRepository empiricalDelayRepository(); - @Nullable - GraphVisualizer graphVisualizer(); - TransitService transitService(); OtpServerRequestContext createServerContext(); @@ -170,6 +169,8 @@ public interface ConstructApplicationFactory { DeduplicatorService deduplicatorService(); + WarmupLauncher warmupLauncher(); + @Component.Builder interface Builder { @BindsInstance @@ -184,9 +185,6 @@ interface Builder { @BindsInstance Builder transferRepository(TransferRepository transferRepository); - @BindsInstance - Builder graphVisualizer(@Nullable GraphVisualizer graphVisualizer); - @BindsInstance Builder worldEnvelopeRepository(WorldEnvelopeRepository worldEnvelopeRepository); diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java index c27861705fa..54316995b58 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java @@ -9,7 +9,6 @@ import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.configure.GtfsSchema; import org.opentripplanner.apis.transmodel.configure.TransmodelSchema; -import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.carpooling.CarpoolingService; import org.opentripplanner.ext.dataoverlay.configuration.DataOverlayParameterBindings; import org.opentripplanner.ext.empiricaldelay.EmpiricalDelayService; @@ -40,7 +39,6 @@ import org.opentripplanner.street.service.StreetLimitationParametersService; import org.opentripplanner.transfer.regular.RegularTransferService; import org.opentripplanner.transit.service.TransitService; -import org.opentripplanner.visualizer.GraphVisualizer; @Module public class ConstructApplicationModule { @@ -65,7 +63,6 @@ OtpServerRequestContext providesServerContext( @Nullable DataOverlayParameterBindings dataOverlayParameterBindings, @Nullable StopConsolidationService stopConsolidationService, StreetLimitationParametersService streetLimitationParametersService, - @Nullable TraverseVisitor traverseVisitor, @Nullable @EmissionDecorator ItineraryDecorator emissionItineraryDecorator, StreetDetailsService streetDetailsService, @Nullable @GtfsSchema GraphQLSchema gtfsSchema, @@ -121,7 +118,6 @@ OtpServerRequestContext providesServerContext( transmodelSchema, sorlandsbanenService, stopConsolidationService, - traverseVisitor, transmodelAPIParameters ); } @@ -131,10 +127,4 @@ OtpServerRequestContext providesServerContext( public FareService fareService(FareServiceFactory fareServiceFactory) { return fareServiceFactory.makeFareService(); } - - @Provides - @Nullable - TraverseVisitor traverseVisitor(@Nullable GraphVisualizer graphVisualizer) { - return graphVisualizer == null ? null : graphVisualizer.traverseVisitor; - } } diff --git a/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java index 99cb7d852da..a3d2bb8839b 100644 --- a/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java +++ b/application/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java @@ -8,7 +8,6 @@ import org.opentripplanner.apis.gtfs.configure.GtfsSchema; import org.opentripplanner.apis.transmodel.TransmodelAPIParameters; import org.opentripplanner.apis.transmodel.configure.TransmodelSchema; -import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.carpooling.CarpoolingService; import org.opentripplanner.ext.dataoverlay.configuration.DataOverlayParameterBindings; import org.opentripplanner.ext.empiricaldelay.EmpiricalDelayService; @@ -103,9 +102,6 @@ public class DefaultServerRequestContext implements OtpServerRequestContext { @Nullable private final StopConsolidationService stopConsolidationService; - @Nullable - private final TraverseVisitor traverseVisitor; - private final TriasApiParameters triasApiParameters; private final OjpApiParameters ojpApiParameters; @@ -159,7 +155,6 @@ public DefaultServerRequestContext( @Nullable @TransmodelSchema GraphQLSchema transmodelSchema, @Nullable SorlandsbanenNorwayService sorlandsbanenService, @Nullable StopConsolidationService stopConsolidationService, - @Nullable TraverseVisitor traverseVisitor, TransmodelAPIParameters transmodelAPIParameters ) { this.debugUiConfig = debugUiConfig; @@ -197,7 +192,6 @@ public DefaultServerRequestContext( this.gtfsSchema = gtfsSchema; this.sorlandsbanenService = sorlandsbanenService; this.stopConsolidationService = stopConsolidationService; - this.traverseVisitor = traverseVisitor; this.transmodelAPIParameters = transmodelAPIParameters; } @@ -298,11 +292,6 @@ public MeterRegistry meterRegistry() { return meterRegistry; } - @Override - public TraverseVisitor traverseVisitor() { - return traverseVisitor; - } - @Override public FlexParameters flexParameters() { return flexParameters; diff --git a/application/src/main/java/org/opentripplanner/transit/api/model/FilterValues.java b/application/src/main/java/org/opentripplanner/transit/api/model/FilterValues.java index 7d2ef3f09d9..a2fba3b95ab 100644 --- a/application/src/main/java/org/opentripplanner/transit/api/model/FilterValues.java +++ b/application/src/main/java/org/opentripplanner/transit/api/model/FilterValues.java @@ -10,11 +10,11 @@ /** * {@link FilterValues} is meant to be used when filtering results from {@link TransitService}. - *

      + *

      * This abstraction over the Collection type lets us keep filter specific functionality separate * from interpretation of various states of a collection. For instance in which case the filter values * should match all entities they are meant to filter. - *

      + *

      * @param - The type of the filter values. Typically, String or {@link FeedScopedId}. */ public abstract class FilterValues { @@ -31,7 +31,7 @@ public abstract class FilterValues { /** * Returns a {@link FilterValues} that matches everything if there are no filter values. - *

      + *

      * @param name - The name of the filter. * @param - The type of the filter values. Typically, String or {@link FeedScopedId}. * @param values - The {@link Collection} of filter values. @@ -46,7 +46,7 @@ public static FilterValues ofEmptyIsEverything( /** * Returns a {@link FilterValues} that matches everything if the filter values are null. - *

      + *

      * @param name - The name of the filter. * @param - The type of the filter values. Typically, String or {@link FeedScopedId}. * @param values - The {@link Collection} of filter values. @@ -62,7 +62,7 @@ public static FilterValues ofNullIsEverything( /** * Returns a {@link RequiredFilterValues} that throws an exception at creation time if the filter * values is null or empty. - *

      + *

      * @param name - The name of the filter. * @param - The type of the filter values. Typically, String or {@link FeedScopedId}. * @param values - The {@link Collection} of filter values. @@ -79,7 +79,7 @@ public static RequiredFilterValues ofRequired( * Returns True if the collection of filter values matches everything that it could filter. If this * is the case, then the filter values should not be used to filter anything and filtering logic can * safely ignore it. - *

      + *

      * @return boolean */ public abstract boolean includeEverything(); @@ -87,7 +87,7 @@ public static RequiredFilterValues ofRequired( /** * Returns the collection of filter values. If the filter values effectively don't filter anything, * an exception is thrown. - *

      + *

      * @return Collection - The values of the filter. */ public Collection get() { diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequest.java b/application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequest.java index d0f2153921d..f2efc2b2273 100644 --- a/application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequest.java +++ b/application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequest.java @@ -6,7 +6,7 @@ /** * A request for finding {@link Route}s. - *

      + *

      * This request is used to retrieve Routes that match the provided filter values. * At least one filter value must be provided. */ diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/FindStopLocationsRequest.java b/application/src/main/java/org/opentripplanner/transit/api/request/FindStopLocationsRequest.java index 55c36bfce40..20a65e7183b 100644 --- a/application/src/main/java/org/opentripplanner/transit/api/request/FindStopLocationsRequest.java +++ b/application/src/main/java/org/opentripplanner/transit/api/request/FindStopLocationsRequest.java @@ -4,7 +4,7 @@ /** * A request for finding {@link StopLocation}. - *

      + *

      * This request is used to retrieve StopLocations that match the provided filter values. */ public class FindStopLocationsRequest { diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequest.java b/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequest.java index 231366d8433..9dcd312080f 100644 --- a/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequest.java +++ b/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequest.java @@ -9,7 +9,7 @@ /** * A request for trips on certain service dates. - *

      + *

      * This request is used to retrieve {@link TripOnServiceDate}s that match the provided filter * values. */ diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/TripRequest.java b/application/src/main/java/org/opentripplanner/transit/api/request/TripRequest.java index f63af90fdd9..5cc3ec2ed8a 100644 --- a/application/src/main/java/org/opentripplanner/transit/api/request/TripRequest.java +++ b/application/src/main/java/org/opentripplanner/transit/api/request/TripRequest.java @@ -9,7 +9,7 @@ /** * A request for {@link Trip}s. - *

      + *

      * This request is used to retrieve {@link Trip}s that match the provided filter values. */ public class TripRequest { diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/NarrowedTransitMode.java b/application/src/main/java/org/opentripplanner/transit/model/basic/NarrowedTransitMode.java index 362c1e2c16d..f32576b5b13 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/NarrowedTransitMode.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/NarrowedTransitMode.java @@ -85,7 +85,7 @@ public String toString() { * Make sure the String serialization is deterministic by sorting the elements in * alphabetic order. * - * @see MainAndSubMode::toString(Collection) + * @see MainAndSubMode#toString(Collection) */ public static String toString(Collection modes) { return modes != null diff --git a/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java b/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java index c7ba45c9882..a8323e993e3 100644 --- a/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java +++ b/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java @@ -67,6 +67,7 @@ import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.updater.GraphUpdaterStatus; import org.opentripplanner.utils.collection.CollectionsView; +import org.opentripplanner.utils.collection.SetUtils; import org.opentripplanner.utils.time.ServiceDateUtils; /** @@ -254,7 +255,14 @@ public Collection findRoutes(FindRoutesRequest request) { @Override public Set findRoutes(StopLocation stop) { OTPRequestTimeoutException.checkForTimeout(); - return this.timetableRepositoryIndex.getRoutesForStop(stop); + Collection flexRoutes = List.of(); + var flexIndex = timetableRepositoryIndex.getFlexIndex(); + if (flexIndex != null) { + flexRoutes = flexIndex.findRoutes(stop); + } + var fixedRoutes = timetableRepositoryIndex.getRoutesForStop(stop); + + return SetUtils.combine(flexRoutes, fixedRoutes); } @Override diff --git a/application/src/main/java/org/opentripplanner/transit/service/TransitService.java b/application/src/main/java/org/opentripplanner/transit/service/TransitService.java index f1ccb36728d..fdbabf8aa3b 100644 --- a/application/src/main/java/org/opentripplanner/transit/service/TransitService.java +++ b/application/src/main/java/org/opentripplanner/transit/service/TransitService.java @@ -122,6 +122,8 @@ public interface TransitService { /** * Return the routes using the given stop, not including real-time updates. + * This includes area stops and resolution of members of group stops - if a trip visits a group + * stop, all member stops are considered visited, too. */ Set findRoutes(StopLocation stop); diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java index 59325c67636..ae13a84622f 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdater.java @@ -12,6 +12,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.framework.retry.OtpRetry; +import org.opentripplanner.framework.retry.OtpRetryBuilder; +import org.opentripplanner.framework.retry.OtpRetryException; import org.opentripplanner.service.vehiclerental.VehicleRentalRepository; import org.opentripplanner.service.vehiclerental.model.GeofencingZone; import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; @@ -46,6 +49,8 @@ public class VehicleRentalUpdater extends PollingGraphUpdater { private static final Logger LOG = LoggerFactory.getLogger(VehicleRentalUpdater.class); + private static final Duration RETRY_INTERVAL = Duration.ofSeconds(5); + private static final int RETRY_BACKOFF_MULTIPLIER = 1; private final Throttle unlinkedPlaceThrottle; @@ -83,10 +88,21 @@ public VehicleRentalUpdater( // Adding a vehicle rental station service needs a graph writer runnable this.service = repository; + OtpRetry retry = new OtpRetryBuilder() + .withName("%s updater setup".formatted(nameForLogging)) + .withMaxAttempts((int) parameters.startupRetryPeriod().dividedBy(RETRY_INTERVAL)) + .withInitialRetryInterval(RETRY_INTERVAL) + .withBackoffMultiplier(RETRY_BACKOFF_MULTIPLIER) + .withRetryableException(UpdaterConstructionException.class::isInstance) + .build(); + try { // Do any setup if needed - source.setup(); - } catch (UpdaterConstructionException e) { + retry.execute(source::setup); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOG.warn("Updater setup interrupted: {}", nameForLogging, e); + } catch (OtpRetryException e) { LOG.warn("Unable to setup updater: {}", nameForLogging, e); } diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterParameters.java index d21f3d2dcef..6e0d5f55647 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterParameters.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterParameters.java @@ -8,15 +8,18 @@ public class VehicleRentalUpdaterParameters implements PollingGraphUpdaterParame private final String configRef; private final Duration frequency; + private final Duration startupRetryPeriod; private final VehicleRentalDataSourceParameters source; public VehicleRentalUpdaterParameters( String configRef, Duration frequency, + Duration startupRetryPeriod, VehicleRentalDataSourceParameters source ) { this.configRef = configRef; this.frequency = frequency; + this.startupRetryPeriod = startupRetryPeriod; this.source = source; } @@ -33,6 +36,13 @@ public String configRef() { return configRef; } + /** + * How long to retry loading the vehicle rental data source on startup if it initially fails. + */ + public Duration startupRetryPeriod() { + return startupRetryPeriod; + } + public VehicleRentalDataSourceParameters sourceParameters() { return source; } diff --git a/application/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java b/application/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java deleted file mode 100644 index 6e7aba5082c..00000000000 --- a/application/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java +++ /dev/null @@ -1,1568 +0,0 @@ -package org.opentripplanner.visualizer; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.GridLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.time.Instant; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import javax.swing.AbstractListModel; -import javax.swing.BoxLayout; -import javax.swing.ButtonGroup; -import javax.swing.DefaultListCellRenderer; -import javax.swing.DefaultListModel; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JRadioButton; -import javax.swing.JScrollPane; -import javax.swing.JTabbedPane; -import javax.swing.JTextField; -import javax.swing.ListModel; -import javax.swing.ScrollPaneConstants; -import javax.swing.SwingUtilities; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import org.locationtech.jts.geom.Coordinate; -import org.opentripplanner.api.common.LocationStringParser; -import org.opentripplanner.api.parameter.ApiRequestMode; -import org.opentripplanner.api.parameter.QualifiedModeSet; -import org.opentripplanner.astar.model.ShortestPathTree; -import org.opentripplanner.astar.spi.DominanceFunction; -import org.opentripplanner.astar.spi.TraverseVisitor; -import org.opentripplanner.graph_builder.issue.api.DataImportIssue; -import org.opentripplanner.routing.api.request.RouteRequest; -import org.opentripplanner.routing.api.request.RouteRequestBuilder; -import org.opentripplanner.routing.impl.GraphPathFinder; -import org.opentripplanner.routing.linking.LinkingContextFactory; -import org.opentripplanner.routing.linking.internal.VertexCreationService; -import org.opentripplanner.routing.linking.mapping.LinkingContextRequestMapper; -import org.opentripplanner.street.graph.Graph; -import org.opentripplanner.street.linking.TemporaryVerticesContainer; -import org.opentripplanner.street.linking.VertexLinker; -import org.opentripplanner.street.linking.VisibilityMode; -import org.opentripplanner.street.model.StreetConstants; -import org.opentripplanner.street.model.VehicleRoutingOptimizeType; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.edge.StreetEdge; -import org.opentripplanner.street.model.path.StreetPath; -import org.opentripplanner.street.model.vertex.IntersectionVertex; -import org.opentripplanner.street.model.vertex.Vertex; -import org.opentripplanner.street.model.vertex.VertexLabel; -import org.opentripplanner.street.search.state.State; -import org.opentripplanner.street.search.strategy.DominanceFunctions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Exit on window close. - */ -class ExitListener extends WindowAdapter { - - public void windowClosing(WindowEvent event) { - System.exit(0); - } -} - -/** - * DisplayVertex holds a vertex, but has a toString value that's a little more useful. - */ -class DisplayVertex { - - public Vertex vertex; - - public DisplayVertex(Vertex v) { - vertex = v; - } - - public String toString() { - String label = vertex.getLabelString(); - if (label.contains("osm node")) { - label = vertex.getDefaultName(); - } - return label; - } -} - -/** - * This is a ListModel that holds Edges. It gets its edges from a PatternBoard/PatternAlight, hence - * the iterable. - */ -class EdgeListModel extends AbstractListModel { - - private final ArrayList edges; - - EdgeListModel(Iterable edges) { - this.edges = new ArrayList<>(); - for (Edge e : edges) { - this.edges.add(e); - } - } - - public int getSize() { - return edges.size(); - } - - public Edge getElementAt(int index) { - return edges.get(index); - } -} - -/** - * A list of vertices where the internal container is exposed. - */ -class VertexList extends AbstractListModel { - - public List selected; - - VertexList(List selected) { - this.selected = selected; - } - - public int getSize() { - return selected.size(); - } - - public DisplayVertex getElementAt(int index) { - return new DisplayVertex(selected.get(index)); - } -} - -/** - * A simple visualizer for graphs. It shows (using ShowGraph) a map of the graph, intersections and - * TransitStops only, and allows a user to select stops, examine incoming and outgoing edges, and - * examine trip patterns. It's meant mainly for debugging, so it's totally OK if it develops (say) a - * bunch of weird buttons designed to debug specific cases. - *

      - * 2024-01-26: We talked about the visualizer in the developer meeting and while the code is a bit - * dusty, we decided that we want to keep the option open to build make the visualization of routing - * steps work again in the future and won't delete it. - */ -public class GraphVisualizer extends JFrame implements VertexSelectionListener { - - private static final Logger LOG = LoggerFactory.getLogger(GraphVisualizer.class); - private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern( - "yyyy-MM-dd HH:mm:ss z" - ); - public static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss z"); - - /* The graph from the router we are visualizing, note that it will not be updated if the router reloads. */ - private final Graph graph; - private JPanel leftPanel; - - /* The Processing applet that actually displays the graph. */ - private ShowGraph showGraph; - - /* The set of callbacks that display search progress on the showGraph Processing applet. */ - public TraverseVisitor traverseVisitor; - - public JList nearbyVertices; - - private JList outgoingEdges; - - private JList incomingEdges; - - private JTextField sourceVertex; - - private JTextField sinkVertex; - - private JCheckBox walkCheckBox; - - private JCheckBox bikeCheckBox; - - private JCheckBox trainCheckBox; - - private JCheckBox busCheckBox; - - private JCheckBox ferryCheckBox; - - private JCheckBox transitCheckBox; - - private JCheckBox carCheckBox; - - private JTextField searchDate; - private JTextField boardingPenaltyField; - private DefaultListModel issueMatchesModel; - private JList issueMatches; - private DefaultListModel metadataModel; - private HashSet closed; - private Vertex tracingVertex; - private HashSet open; - private HashSet seen; - private JList metadataList; - private JRadioButton opQuick; - private JRadioButton opSafe; - private JRadioButton opFlat; - private JRadioButton opGreenways; - private ButtonGroup optimizeTypeGrp; - private JTextField maxWalkField; - private JTextField walkSpeed; - private JTextField bikeSpeed; - private JTextField heuristicWeight; - private JCheckBox softWalkLimiting; - private JTextField softWalkPenalty; - private JTextField softWalkOverageRate; - private JCheckBox arriveByCheckBox; - private JLabel searchTimeElapsedLabel; - private JCheckBox dontUseGraphicalCallbackCheckBox; - private JTextField nPaths; - private JList pathsList; - private JList pathStates; - private JCheckBox showTransitCheckbox; - private JCheckBox showStreetsCheckbox; - private JCheckBox showMultistateVerticesCheckbox; - private JCheckBox showHighlightedCheckbox; - private JCheckBox showSPTCheckbox; - private ShortestPathTree spt; - private JTextField sptFlattening; - private JTextField sptThickness; - private JPopupMenu popup; - private StreetPath firstComparePath; - private StreetPath secondComparePath; - private JList firstComparePathStates; - private JList secondComparePathStates; - private JList secondStateData; - private JList firstStateData; - protected State lastStateClicked = null; - private JCheckBox longDistanceModeCheckbox; - - public GraphVisualizer(Graph graph) { - super(); - setTitle("GraphVisualizer"); - setExtendedState(JFrame.MAXIMIZED_BOTH); - this.graph = graph; - } - - public void run() { - LOG.info("Starting up graph visualizer..."); - this.init(); - this.setVisible(true); - } - - public void init() { - final JTabbedPane tabbedPane = new JTabbedPane(); - - final Container mainTab = makeMainTab(); - Container prefsPanel = makePrefsPanel(); - Container diffTab = makeDiffTab(); - - tabbedPane.addTab("Main", null, mainTab, "Pretty much everything"); - - tabbedPane.addTab("Prefs", null, prefsPanel, "Routing preferences"); - - tabbedPane.addTab("Diff", null, diffTab, "multistate path diffs"); - - //Add the tabbed pane to this panel. - add(tabbedPane); - - //The following line enables to use scrolling tabs. - tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); - - // startup the graphical pane; ensure closing works; draw the window - showGraph.init(); - addWindowListener(new ExitListener()); - pack(); - - // make sure the showGraph quits drawing when we switch tabs - tabbedPane.addChangeListener( - new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - if (tabbedPane.getSelectedComponent().equals(mainTab)) { - showGraph.loop(); - } else { - showGraph.noLoop(); - } - } - } - ); - } - - public void verticesSelected(final List selected) { - // sort vertices by name - Collections.sort( - selected, - new Comparator<>() { - @Override - public int compare(Vertex arg0, Vertex arg1) { - return arg0.getLabelString().compareTo(arg1.getLabelString()); - } - } - ); - ListModel data = new VertexList(selected); - nearbyVertices.setModel(data); - - // pick out an intersection vertex and find the path - // if the spt is already available - Vertex target = null; - for (Vertex vv : selected) { - if (vv instanceof IntersectionVertex) { - target = vv; - break; - } - } - if (target != null && spt != null) { - var paths = spt.getPaths(target).stream().map(StreetPath::new).toList(); - showPathsInPanel(paths); - } - } - - public Graph getGraph() { - return graph; - } - - protected JComponent makeTextPanel(String text) { - JPanel panel = new JPanel(false); - JLabel filler = new JLabel(text); - filler.setHorizontalAlignment(JLabel.CENTER); - panel.setLayout(new GridLayout(1, 1)); - panel.add(filler); - return panel; - } - - protected void trace() { - DisplayVertex selected = (DisplayVertex) nearbyVertices.getSelectedValue(); - if (selected == null) { - return; - } - Vertex v = selected.vertex; - - if (tracingVertex != v) { - tracingVertex = v; - closed = new HashSet<>(); - open = new HashSet<>(); - open.add(v); - seen = new HashSet<>(); - } - HashSet newOpen = new HashSet<>(); - for (Vertex v2 : open) { - closed.add(v2); - for (Edge e : v2.getOutgoing()) { - Vertex target = e.getToVertex(); - if (closed.contains(target)) { - continue; - } - newOpen.add(target); - } - } - seen.addAll(newOpen); - open = newOpen; - showGraph.setHighlightedVertices(seen); - } - - protected void traceOld() { - HashSet seenVertices = new HashSet<>(); - DisplayVertex selected = (DisplayVertex) nearbyVertices.getSelectedValue(); - if (selected == null) { - System.out.println("no vertex selected"); - return; - } - Vertex v = selected.vertex; - System.out.println("initial vertex: " + v); - Queue toExplore = new LinkedList<>(); - toExplore.add(v); - seenVertices.add(v); - while (!toExplore.isEmpty()) { - Vertex src = toExplore.poll(); - for (Edge e : src.getOutgoing()) { - Vertex tov = e.getToVertex(); - if (!seenVertices.contains(tov)) { - seenVertices.add(tov); - toExplore.add(tov); - } - } - } - showGraph.setHighlightedVertices(seenVertices); - } - - protected void checkGraph() { - HashSet seenVertices = new HashSet<>(); - Collection allVertices = getGraph().getVertices(); - Vertex v = allVertices.iterator().next(); - System.out.println("initial vertex: " + v); - Queue toExplore = new LinkedList<>(); - toExplore.add(v); - seenVertices.add(v); - while (!toExplore.isEmpty()) { - Vertex src = toExplore.poll(); - for (Edge e : src.getOutgoing()) { - Vertex tov = e.getToVertex(); - if (!seenVertices.contains(tov)) { - seenVertices.add(tov); - toExplore.add(tov); - } - } - } - - System.out.println( - "After investigation, visited " + seenVertices.size() + " of " + allVertices.size() - ); - - /* now, let's find an unvisited vertex */ - for (Vertex u : allVertices) { - if (!seenVertices.contains(u)) { - System.out.println("unvisited vertex" + u); - break; - } - } - } - - protected void route(String from, String to) { - Instant when; - // Year + 1900 - try { - when = ZonedDateTime.parse(searchDate.getText(), DATE_FORMAT).toInstant(); - } catch (DateTimeParseException e) { - searchDate.setText("Format: " + DATE_FORMAT.toString()); - return; - } - List modes = new ArrayList<>(); - if (walkCheckBox.isSelected()) { - modes.add(ApiRequestMode.WALK.name()); - } - if (bikeCheckBox.isSelected()) { - modes.add(ApiRequestMode.BICYCLE.name()); - } - if (carCheckBox.isSelected()) { - modes.add(ApiRequestMode.CAR.name()); - } - if (ferryCheckBox.isSelected()) { - modes.add(ApiRequestMode.FERRY.name()); - } - if (trainCheckBox.isSelected()) { - modes.add(ApiRequestMode.RAIL.name()); - modes.add(ApiRequestMode.TRAM.name()); - modes.add(ApiRequestMode.SUBWAY.name()); - modes.add(ApiRequestMode.FUNICULAR.name()); - modes.add(ApiRequestMode.GONDOLA.name()); - } - if (busCheckBox.isSelected()) { - modes.add(ApiRequestMode.BUS.name()); - modes.add(ApiRequestMode.CABLE_CAR.name()); - } - if (transitCheckBox.isSelected()) { - modes.add(ApiRequestMode.TRANSIT.name()); - } - - // TODO: This should use the configured defaults, not the code defaults - RouteRequestBuilder builder = RouteRequest.of(); - QualifiedModeSet qualifiedModeSet = new QualifiedModeSet(modes.toArray(String[]::new)); - builder.withJourney(b -> b.withModes(qualifiedModeSet.getRequestModes())); - - builder.withArriveBy(arriveByCheckBox.isSelected()); - builder.withDateTime(when); - builder.withFrom(LocationStringParser.fromOldStyleString(from)); - builder.withTo(LocationStringParser.fromOldStyleString(to)); - builder.withNumItineraries(Integer.parseInt(this.nPaths.getText())); - - builder.withPreferences(preferences -> { - preferences.withWalk(walk -> { - // override low 2-4 minute values - walk.withBoardCost(Integer.parseInt(boardingPenaltyField.getText()) * 60); - walk.withSpeed(Float.parseFloat(walkSpeed.getText())); - }); - preferences.withBike(bike -> - bike - .withSpeed(Float.parseFloat(bikeSpeed.getText())) - // TODO LG Add ui element for bike board cost (for now bike = 2 * walk) - .withBoardCost(Integer.parseInt(boardingPenaltyField.getText()) * 60 * 2) - // there should be a ui element for walk distance and optimize type - .withOptimizeType(getSelectedOptimizeType()) - ); - preferences.withScooter(scooter -> - scooter - .withSpeed(Float.parseFloat(bikeSpeed.getText())) - // there should be a ui element for walk distance and optimize type - .withOptimizeType(getSelectedOptimizeType()) - ); - }); - - // apply callback if the options call for it - // if( dontUseGraphicalCallbackCheckBox.isSelected() ){ - // TODO perhaps avoid using a GraphPathFinder and go one level down the call chain directly to a GenericAStar - // TODO perhaps instead of giving the pathservice a callback, we can just put the visitor in the routing request - GraphPathFinder finder = new GraphPathFinder(traverseVisitor); - - var request = builder.buildRequest(); - long t0 = System.currentTimeMillis(); - // TODO: check options properly intialized (AMB) - try (var temporaryVerticesContainer = new TemporaryVerticesContainer()) { - var linkingContextFactory = new LinkingContextFactory( - graph, - new VertexCreationService( - new VertexLinker( - graph, - VisibilityMode.TRAVERSE_AREA_EDGES, - StreetConstants.DEFAULT_MAX_AREA_NODES, - true - ) - ) - ); - var linkingRequest = LinkingContextRequestMapper.map(request); - var linkingContext = linkingContextFactory.create(temporaryVerticesContainer, linkingRequest); - List paths = finder.graphPathFinderEntryPoint(request, linkingContext); - long dt = System.currentTimeMillis() - t0; - searchTimeElapsedLabel.setText("search time elapsed: " + dt + "ms"); - - // grab the spt from the visitor - // TODO somehow yank the SPT out of the depths of the call stack... but there multiple SPTs here. - // This is why we should probably just use AStar directly. - /* - spt = vis.spt; - showGraph.setSPT(spt); - System.out.println( "got spt:"+spt ); - */ - - if (paths == null) { - System.out.println("no path"); - showGraph.highlightStreetPath(null); - return; - } - - // now's a convenient time to set graphical SPT weights - showGraph.simpleSPT.setWeights(); - - showPathsInPanel(paths); - - // now's a good time to set showGraph's SPT drawing weights - showGraph.setSPTFlattening(Float.parseFloat(sptFlattening.getText())); - showGraph.setSPTThickness(Float.parseFloat(sptThickness.getText())); - showGraph.redraw(); - } - } - - VehicleRoutingOptimizeType getSelectedOptimizeType() { - if (opQuick.isSelected()) { - return VehicleRoutingOptimizeType.SHORTEST_DURATION; - } - if (opSafe.isSelected()) { - return VehicleRoutingOptimizeType.SAFE_STREETS; - } - if (opFlat.isSelected()) { - return VehicleRoutingOptimizeType.FLAT_STREETS; - } - if (opGreenways.isSelected()) { - return VehicleRoutingOptimizeType.SAFEST_STREETS; - } - return VehicleRoutingOptimizeType.SHORTEST_DURATION; - } - - private Container makeDiffTab() { - JPanel pane = new JPanel(); - pane.setLayout(new GridLayout(0, 2)); - - firstStateData = new JList<>(); - secondStateData = new JList<>(); - - // a place to list the states of the first path - firstComparePathStates = new JList<>(); - JScrollPane stScrollPane = new JScrollPane(firstComparePathStates); - stScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - pane.add(stScrollPane); - firstComparePathStates.addListSelectionListener( - new ComparePathStatesClickListener(firstStateData) - ); - - // a place to list the states of the second path - secondComparePathStates = new JList<>(); - stScrollPane = new JScrollPane(secondComparePathStates); - stScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - pane.add(stScrollPane); - secondComparePathStates.addListSelectionListener( - new ComparePathStatesClickListener(secondStateData) - ); - - // a place to list details of a state selected from the first path - stScrollPane = new JScrollPane(firstStateData); - stScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - pane.add(stScrollPane); - - // a place to list details of a state selected from the second path - stScrollPane = new JScrollPane(secondStateData); - stScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - pane.add(stScrollPane); - - // A button that executes the 'dominates' function between the two states - // this is useful only if you have a breakpoint set up - JButton dominateButton = new JButton(); - dominateButton.setText("dominates"); - dominateButton.addActionListener( - new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - State s1 = firstComparePathStates.getSelectedValue(); - State s2 = secondComparePathStates.getSelectedValue(); - DominanceFunction pareto = new DominanceFunctions.Pareto(); - System.out.println("s1 dominates s2:" + pareto.betterOrEqualAndComparable(s1, s2)); - System.out.println("s2 dominates s1:" + pareto.betterOrEqualAndComparable(s2, s1)); - } - } - ); - pane.add(dominateButton); - - // A button that executes the 'traverse' function leading to the last clicked state - // in either window. Also only useful if you set a breakpoint. - JButton traverseButton = new JButton(); - traverseButton.setText("traverse"); - traverseButton.addActionListener( - new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (lastStateClicked == null) { - return; - } - - Edge backEdge = lastStateClicked.getBackEdge(); - State backState = lastStateClicked.getBackState(); - - backEdge.traverse(backState); - } - } - ); - pane.add(traverseButton); - - return pane; - } - - private Container makeMainTab() { - Container pane = new JPanel(); - pane.setLayout(new BorderLayout()); - - // init center graphical panel - showGraph = new ShowGraph(this, getGraph()); - pane.add(showGraph, BorderLayout.CENTER); - traverseVisitor = new VisualTraverseVisitor(showGraph); - - // init left panel - leftPanel = new JPanel(); - leftPanel.setLayout(new BorderLayout()); - - pane.add(leftPanel, BorderLayout.LINE_START); - - initRoutingSubpanel(); - initVertexInfoSubpanel(); - initControlButtons(); - - // init right panel - initRightPanel(pane); - return pane; - } - - private JComponent makePrefsPanel() { - JPanel pane = new JPanel(); - pane.setLayout(new GridLayout(0, 2)); - - // 4 rows (7 elements): transport mode options - walkCheckBox = new JCheckBox("walk"); - walkCheckBox.setSelected(true); - pane.add(walkCheckBox); - bikeCheckBox = new JCheckBox("bike"); - pane.add(bikeCheckBox); - trainCheckBox = new JCheckBox("trainish"); - pane.add(trainCheckBox); - busCheckBox = new JCheckBox("busish"); - pane.add(busCheckBox); - ferryCheckBox = new JCheckBox("ferry"); - pane.add(ferryCheckBox); - transitCheckBox = new JCheckBox("transit"); - transitCheckBox.setSelected(true); - pane.add(transitCheckBox); - carCheckBox = new JCheckBox("car"); - pane.add(carCheckBox); - - // GridLayout does not support empty cells, so a dummy label is used to fix the layout. - JLabel dummyLabel = new JLabel(""); - pane.add(dummyLabel); - - // row: arrive by? - JLabel arriveByLabel = new JLabel("Arrive by?:"); - pane.add(arriveByLabel); - arriveByCheckBox = new JCheckBox("arrive by"); - pane.add(arriveByCheckBox); - - // row: boarding penalty - JLabel boardPenaltyLabel = new JLabel("Boarding penalty (min):"); - pane.add(boardPenaltyLabel); - boardingPenaltyField = new JTextField("5"); - pane.add(boardingPenaltyField); - - // row: max walk - JLabel maxWalkLabel = new JLabel("Maximum walk (meters):"); - pane.add(maxWalkLabel); - maxWalkField = new JTextField("5000"); - pane.add(maxWalkField); - - // row: walk speed - JLabel walkSpeedLabel = new JLabel("Walk speed (m/s):"); - pane.add(walkSpeedLabel); - walkSpeed = new JTextField("1.33"); - pane.add(walkSpeed); - - // row: bike speed - JLabel bikeSpeedLabel = new JLabel("Bike speed (m/s):"); - pane.add(bikeSpeedLabel); - bikeSpeed = new JTextField("5.0"); - pane.add(bikeSpeed); - - // row: heuristic weight - JLabel heuristicWeightLabel = new JLabel("Heuristic weight:"); - pane.add(heuristicWeightLabel); - heuristicWeight = new JTextField("1.0"); - pane.add(heuristicWeight); - - // row: soft walk? - JLabel softWalkLimitLabel = new JLabel("Soft walk-limit?:"); - pane.add(softWalkLimitLabel); - softWalkLimiting = new JCheckBox("soft walk-limiting"); - pane.add(softWalkLimiting); - - // row: soft walk-limit penalty - JLabel softWalkLimitPenaltyLabel = new JLabel("Soft walk-limiting penalty:"); - pane.add(softWalkLimitPenaltyLabel); - softWalkPenalty = new JTextField("60.0"); - pane.add(softWalkPenalty); - - // row: soft walk-limit overage - JLabel softWalkLimitOverageLabel = new JLabel("Soft walk-limiting overage:"); - pane.add(softWalkLimitOverageLabel); - softWalkOverageRate = new JTextField("5.0"); - pane.add(softWalkOverageRate); - - // row: nPaths - JLabel nPathsLabel = new JLabel("nPaths:"); - pane.add(nPathsLabel); - nPaths = new JTextField("1"); - pane.add(nPaths); - - // viz preferences - ItemListener onChangeVizPrefs = new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - showGraph.setShowTransit(showTransitCheckbox.isSelected()); - showGraph.setShowStreets(showStreetsCheckbox.isSelected()); - showGraph.setShowMultistateVertices(showMultistateVerticesCheckbox.isSelected()); - showGraph.setShowHightlights(showHighlightedCheckbox.isSelected()); - showGraph.setShowSPT(showSPTCheckbox.isSelected()); - showGraph.redraw(); - } - }; - showTransitCheckbox = new JCheckBox("show transit"); - showTransitCheckbox.setSelected(true); - showTransitCheckbox.addItemListener(onChangeVizPrefs); - pane.add(showTransitCheckbox); - showStreetsCheckbox = new JCheckBox("show streets"); - showStreetsCheckbox.setSelected(true); - showStreetsCheckbox.addItemListener(onChangeVizPrefs); - pane.add(showStreetsCheckbox); - showHighlightedCheckbox = new JCheckBox("show highlighted"); - showHighlightedCheckbox.setSelected(true); - showHighlightedCheckbox.addItemListener(onChangeVizPrefs); - pane.add(showHighlightedCheckbox); - showSPTCheckbox = new JCheckBox("show SPT"); - showSPTCheckbox.setSelected(true); - showSPTCheckbox.addItemListener(onChangeVizPrefs); - pane.add(showSPTCheckbox); - showMultistateVerticesCheckbox = new JCheckBox("show multistate vertices"); - showMultistateVerticesCheckbox.setSelected(true); - showMultistateVerticesCheckbox.addItemListener(onChangeVizPrefs); - pane.add(showMultistateVerticesCheckbox); - - // GridLayout does not support empty cells, so a dummy label is used to fix the layout. - JLabel dummyLabel2 = new JLabel(""); - pane.add(dummyLabel2); - - // row: SPT flattening - JLabel sptFlatteningLabel = new JLabel("SPT flattening:"); - pane.add(sptFlatteningLabel); - sptFlattening = new JTextField("0.3"); - pane.add(sptFlattening); - - // row: SPT thickness - JLabel sptThicknessLabel = new JLabel("SPT thickness:"); - pane.add(sptThicknessLabel); - sptThickness = new JTextField("0.1"); - pane.add(sptThickness); - - // radio buttons: optimize type - JLabel optimizeTypeLabel = new JLabel("Optimize type:"); - pane.add(optimizeTypeLabel); - - opQuick = new JRadioButton("Quick"); - opQuick.setSelected(true); - opSafe = new JRadioButton("Safe"); - opFlat = new JRadioButton("Flat"); - opGreenways = new JRadioButton("Greenways"); - - optimizeTypeGrp = new ButtonGroup(); - optimizeTypeGrp.add(opQuick); - optimizeTypeGrp.add(opSafe); - optimizeTypeGrp.add(opFlat); - optimizeTypeGrp.add(opGreenways); - - JPanel optimizeTypePane = new JPanel(); - optimizeTypePane.add(opQuick); - optimizeTypePane.add(opSafe); - optimizeTypePane.add(opFlat); - optimizeTypePane.add(opGreenways); - - pane.add(optimizeTypePane); - - return pane; - } - - private void initRightPanel(Container pane) { - /* right panel holds trip pattern and stop metadata */ - JPanel rightPanel = new JPanel(); - rightPanel.setLayout(new BorderLayout()); - pane.add(rightPanel, BorderLayout.LINE_END); - - JTabbedPane rightPanelTabs = new JTabbedPane(); - - rightPanel.add(rightPanelTabs, BorderLayout.LINE_END); - - // a place to print out the details of a path - pathStates = new JList<>(); - JScrollPane stScrollPane = new JScrollPane(pathStates); - stScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - rightPanelTabs.addTab("path states", stScrollPane); - - // when you select a path component state, it prints the backedge's metadata - pathStates.addListSelectionListener( - new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent e) { - outgoingEdges.clearSelection(); - incomingEdges.clearSelection(); - - @SuppressWarnings("unchecked") - JList theList = (JList) e.getSource(); - State st = (State) theList.getSelectedValue(); - Edge edge = st.getBackEdge(); - reactToEdgeSelection(edge, false); - } - } - ); - - metadataList = new JList<>(); - metadataModel = new DefaultListModel<>(); - metadataList.setModel(metadataModel); - JScrollPane mdScrollPane = new JScrollPane(metadataList); - mdScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - rightPanelTabs.addTab("metadata", mdScrollPane); - - // This is where matched issues from an issue search go - issueMatches = new JList<>(); - issueMatches.addListSelectionListener(e -> { - @SuppressWarnings("unchecked") - JList theList = (JList) e.getSource(); - - DataImportIssue issue = theList.getSelectedValue(); - if (issue == null) { - return; - } - showGraph.drawIssue(issue); - }); - - issueMatchesModel = new DefaultListModel<>(); - issueMatches.setModel(issueMatchesModel); - JScrollPane imScrollPane = new JScrollPane(issueMatches); - imScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - rightPanelTabs.addTab("issues", imScrollPane); - - Dimension size = new Dimension(200, 1600); - - imScrollPane.setMaximumSize(size); - imScrollPane.setPreferredSize(size); - stScrollPane.setMaximumSize(size); - stScrollPane.setPreferredSize(size); - mdScrollPane.setMaximumSize(size); - mdScrollPane.setPreferredSize(size); - rightPanelTabs.setMaximumSize(size); - rightPanel.setMaximumSize(size); - } - - private void initControlButtons() { - /* buttons at bottom */ - JPanel buttonPanel = new JPanel(); - buttonPanel.setLayout(new GridLayout(0, 3)); - leftPanel.add(buttonPanel, BorderLayout.PAGE_END); - - JButton zoomDefaultButton = new JButton("Zoom to default"); - zoomDefaultButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - showGraph.zoomToDefault(); - } - } - ); - buttonPanel.add(zoomDefaultButton); - - final JFrame frame = this; - - JButton zoomToNodeButton = new JButton("Zoom to node"); - zoomToNodeButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - String nodeName = JOptionPane.showInputDialog( - frame, - "Node id", - JOptionPane.PLAIN_MESSAGE - ); - Vertex v = getGraph().getVertex(VertexLabel.string(nodeName)); - if (v == null) { - System.out.println("no such node " + nodeName); - } else { - showGraph.zoomToVertex(v); - } - } - } - ); - buttonPanel.add(zoomToNodeButton); - - JButton zoomToLocationButton = new JButton("Zoom to location"); - zoomToLocationButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - String result = JOptionPane.showInputDialog("Enter the location (lat lon)"); - if (result == null || result.length() == 0) { - return; - } - String[] tokens = result.split("[\\s,]+"); - double lat = Double.parseDouble(tokens[0]); - double lon = Double.parseDouble(tokens[1]); - Coordinate c = new Coordinate(lon, lat); - showGraph.zoomToLocation(c); - } - } - ); - buttonPanel.add(zoomToLocationButton); - - JButton zoomOutButton = new JButton("Zoom out"); - zoomOutButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - showGraph.zoomOut(); - } - } - ); - buttonPanel.add(zoomOutButton); - - JButton routeButton2 = new JButton("Route"); - routeButton2.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - // String initialFrom = ""; - // Object selected = nearbyVertices.getSelectedValue(); - // if (selected != null) { - // initialFrom = selected.toString(); - // } - // RouteDialog dlg = new RouteDialog(frame, initialFrom); // modal - String from = sourceVertex.getText(); - String to = sinkVertex.getText(); - route(from, to); - } - } - ); - buttonPanel.add(routeButton2); - - JButton findButton = new JButton("Find node"); - findButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - String nodeName = (String) JOptionPane.showInputDialog( - frame, - "Node id", - JOptionPane.PLAIN_MESSAGE - ); - Vertex v = getGraph().getVertex(VertexLabel.string(nodeName)); - if (v == null) { - System.out.println("no such node " + nodeName); - } else { - showGraph.highlightVertex(v); - ArrayList l = new ArrayList<>(); - l.add(v); - verticesSelected(l); - } - } - } - ); - buttonPanel.add(findButton); - - JButton findEdgeButton = new JButton("Find edge"); - findEdgeButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - String edgeName = (String) JOptionPane.showInputDialog( - frame, - "Edge name like", - JOptionPane.PLAIN_MESSAGE - ); - for (Vertex gv : getGraph().getVertices()) { - for (Edge edge : gv.getOutgoing()) { - if (edge.getDefaultName() != null && edge.getDefaultName().contains(edgeName)) { - showGraph.highlightVertex(gv); - ArrayList l = new ArrayList<>(); - l.add(gv); - verticesSelected(l); - } - } - } - } - } - ); - buttonPanel.add(findEdgeButton); - - JButton checkButton = new JButton("Check graph"); - checkButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - checkGraph(); - } - } - ); - buttonPanel.add(checkButton); - - JButton traceButton = new JButton("Trace"); - traceButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - trace(); - } - } - ); - buttonPanel.add(traceButton); - - JButton findEdgeByIdButton = new JButton("Find edge ID"); - findEdgeByIdButton.addActionListener(e -> { - throw new UnsupportedOperationException("Edges no longer have integer IDs."); - }); - buttonPanel.add(findEdgeByIdButton); - - JButton snapButton = new JButton("Snap location"); - snapButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - LOG.error("StreetIndex.getClosestPointOnStreet no longer exists."); - } - } - ); - buttonPanel.add(snapButton); - } - - private void getMetadata(Object selected) { - Class c = selected.getClass(); - Field[] fields; - while (c != null && c != Object.class) { - metadataModel.addElement("Class:" + c); - fields = c.getDeclaredFields(); - for (Field field : fields) { - int modifiers = field.getModifiers(); - if ((modifiers & Modifier.STATIC) != 0) { - continue; - } - field.setAccessible(true); - String name = field.getName(); - - String value = "(unknown -- see console for stack trace)"; - try { - value = "" + field.get(selected); - } catch (IllegalArgumentException e1) { - LOG.error("IllegalArgumentException", e1); - } catch (IllegalAccessException e1) { - LOG.error("IllegalAccessException", e1); - } - metadataModel.addElement(name + ": " + value); - } - c = c.getSuperclass(); - } - } - - private void reactToEdgeSelection(Edge selected, boolean outgoing) { - if (selected == null) { - return; - } - showGraph.highlightEdge(selected); - - /* for turns, highlight the outgoing street's ends */ - if (selected instanceof StreetEdge) { - List vertices = new ArrayList<>(); - List edges = new ArrayList<>(); - Vertex tov = selected.getToVertex(); - for (Edge og : tov.getOutgoing()) { - if (og instanceof StreetEdge) { - edges.add(og); - vertices.add(og.getToVertex()); - break; - } - } - Vertex fromv = selected.getFromVertex(); - for (Edge ic : fromv.getIncoming()) { - if (ic instanceof StreetEdge) { - edges.add(ic); - vertices.add(ic.getFromVertex()); - break; - } - } - // showGraph.setHighlightedVertices(vertices); - showGraph.setHighlightedEdges(edges); - } - - /* add the connected vertices to the list of vertices */ - VertexList nearbyModel = (VertexList) nearbyVertices.getModel(); - List vertices = nearbyModel.selected; - - Vertex v; - if (outgoing) { - v = selected.getToVertex(); - } else { - v = selected.getFromVertex(); - } - if (!vertices.contains(v)) { - vertices.add(v); - nearbyModel = new VertexList(vertices); - nearbyVertices.setModel(nearbyModel); - // this should just be an event, but for some reason, JList doesn't implement the right event. - } - - /* set up metadata tab */ - metadataModel.clear(); - getMetadata(selected); - // fromv - Vertex fromv = selected.getFromVertex(); - getMetadata(fromv); - if (selected instanceof StreetEdge) { - //TODO ElevationProfileSegment do not exist anymore - //getMetadata(((StreetEdge) selected).getElevationProfileSegment()); - } - metadataList.revalidate(); - } - - private void initVertexInfoSubpanel() { - JPanel vertexDataPanel = new JPanel(); - vertexDataPanel.setLayout(new BoxLayout(vertexDataPanel, BoxLayout.PAGE_AXIS)); - vertexDataPanel.setPreferredSize(new Dimension(300, 600)); - leftPanel.add(vertexDataPanel, BorderLayout.CENTER); - - // nearby vertices - JLabel nvLabel = new JLabel("Vertices"); - vertexDataPanel.add(nvLabel); - nearbyVertices = new JList<>(); - nearbyVertices.setVisibleRowCount(4); - JScrollPane nvScrollPane = new JScrollPane(nearbyVertices); - vertexDataPanel.add(nvScrollPane); - nearbyVertices.addListSelectionListener( - new ListSelectionListener() { - public void valueChanged(ListSelectionEvent e) { - outgoingEdges.removeAll(); - incomingEdges.removeAll(); - DisplayVertex selected = (DisplayVertex) nearbyVertices.getSelectedValue(); - if (selected != null) { - Vertex nowSelected = selected.vertex; - showGraph.highlightVertex(nowSelected); - outgoingEdges.setModel(new EdgeListModel(nowSelected.getOutgoing())); - incomingEdges.setModel(new EdgeListModel(nowSelected.getIncoming())); - } - } - } - ); - - // listener useful for both incoming and outgoing edge list panes - // when a different edge is selected, change up the pattern pane and list of nearby nodes - ListSelectionListener edgeChanged = new ListSelectionListener() { - public void valueChanged(ListSelectionEvent e) { - @SuppressWarnings("unchecked") - JList edgeList = (JList) e.getSource(); - - Edge selected = (Edge) edgeList.getSelectedValue(); - - boolean outgoing = (edgeList == outgoingEdges); - reactToEdgeSelection(selected, outgoing); - } - }; - - // outgoing edges - JLabel ogeLabel = new JLabel("Outgoing edges"); - vertexDataPanel.add(ogeLabel); - outgoingEdges = new JList<>(); - outgoingEdges.setVisibleRowCount(4); - JScrollPane ogeScrollPane = new JScrollPane(outgoingEdges); - vertexDataPanel.add(ogeScrollPane); - outgoingEdges.addListSelectionListener(edgeChanged); - - // incoming edges - JLabel iceLabel = new JLabel("Incoming edges"); - vertexDataPanel.add(iceLabel); - incomingEdges = new JList<>(); - JScrollPane iceScrollPane = new JScrollPane(incomingEdges); - vertexDataPanel.add(iceScrollPane); - incomingEdges.addListSelectionListener(edgeChanged); - - // paths list - JLabel pathsLabel = new JLabel("Paths"); - vertexDataPanel.add(pathsLabel); - pathsList = new JList<>(); - - popup = new JPopupMenu(); - JMenuItem compareMenuItem = new JMenuItem("compare"); - compareMenuItem.addActionListener(new OnPopupMenuClickListener()); - popup.add(compareMenuItem); - - // make paths list right-clickable - pathsList.addMouseListener( - new MouseListener() { - @Override - public void mouseClicked(MouseEvent e) { - if (SwingUtilities.isRightMouseButton(e)) { - @SuppressWarnings("unchecked") - JList list = (JList) e.getSource(); - int row = list.locationToIndex(e.getPoint()); - list.setSelectedIndex(row); - - popup.show(list, e.getX(), e.getY()); - } - } - - @Override - public void mousePressed(MouseEvent e) {} - - @Override - public void mouseReleased(MouseEvent e) {} - - @Override - public void mouseEntered(MouseEvent e) {} - - @Override - public void mouseExited(MouseEvent e) {} - } - ); - pathsList.addListSelectionListener( - new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent ev) { - PathPrinter pp = ((PathPrinter) pathsList.getSelectedValue()); - if (pp == null) { - return; - } - var path = pp.sp; - - DefaultListModel pathModel = new DefaultListModel<>(); - for (State st : path.states()) { - pathModel.addElement(st); - } - pathStates.setModel(pathModel); - - showGraph.highlightStreetPath(path); - } - } - ); - JScrollPane pathsScrollPane = new JScrollPane(pathsList); - vertexDataPanel.add(pathsScrollPane); - } - - private void initRoutingSubpanel() { - /* ROUTING SUBPANEL */ - JPanel routingPanel = new JPanel(); - routingPanel.setLayout(new GridLayout(0, 2)); - leftPanel.add(routingPanel, BorderLayout.NORTH); - - // row: source vertex - JButton setSourceVertexButton = new JButton("set source"); - setSourceVertexButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - Object selected = nearbyVertices.getSelectedValue(); - if (selected != null) { - sourceVertex.setText(selected.toString()); - } - } - } - ); - routingPanel.add(setSourceVertexButton); - sourceVertex = new JTextField(); - routingPanel.add(sourceVertex); - - // row: sink vertex - JButton setSinkVertexButton = new JButton("set sink"); - setSinkVertexButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - Object selected = nearbyVertices.getSelectedValue(); - if (selected != null) { - sinkVertex.setText(selected.toString()); - } - } - } - ); - routingPanel.add(setSinkVertexButton); - sinkVertex = new JTextField(); - routingPanel.add(sinkVertex); - - // row: set date - JButton resetSearchDateButton = new JButton("now ->"); - resetSearchDateButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - searchDate.setText(DATE_FORMAT.format(ZonedDateTime.now())); - } - } - ); - routingPanel.add(resetSearchDateButton); - searchDate = new JTextField(); - searchDate.setText(DATE_FORMAT.format(ZonedDateTime.now())); - routingPanel.add(searchDate); - - // row: launch, continue, and clear path search - JButton routeButton = new JButton("path search"); - routeButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - String from = sourceVertex.getText(); - String to = sinkVertex.getText(); - route(from, to); - } - } - ); - routingPanel.add(routeButton); - JButton continueButton = new JButton("continue"); - continueButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - //TODO continue search - } - } - ); - routingPanel.add(continueButton); - JButton clearRouteButton = new JButton("clear path"); - clearRouteButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - showGraph.highlightStreetPath(null); - showGraph.clearHighlights(); - showGraph.resetSPT(); - } - } - ); - routingPanel.add(clearRouteButton); - - //label: search time elapsed - searchTimeElapsedLabel = new JLabel("search time elapsed:"); - routingPanel.add(searchTimeElapsedLabel); - - //option: don't use graphical callback. useful for doing a quick profile - dontUseGraphicalCallbackCheckBox = new JCheckBox("no graphics"); - routingPanel.add(dontUseGraphicalCallbackCheckBox); - } - - private void showPathsInPanel(List paths) { - // show paths in a list panel - DefaultListModel data = new DefaultListModel<>(); - for (var gp : paths) { - data.addElement(new PathPrinter(gp)); - } - pathsList.setModel(data); - } - - static class PathPrinter { - - StreetPath sp; - - PathPrinter(StreetPath sp) { - this.sp = sp; - } - - public String toString() { - String startTime = TIME_FORMAT.format(sp.startTime()); - String endTime = TIME_FORMAT.format(sp.endTime()); - return ( - "Path (" + - startTime + - "-" + - endTime + - ") weight:" + - sp.weight() + - " dur:" + - (sp.duration().toSeconds() / 60.0) - // " walk:" + - // gp.getWalkDistance() - ); - } - } - - private final class ComparePathStatesClickListener implements ListSelectionListener { - - private final JList outputList; - - ComparePathStatesClickListener(JList outputList) { - this.outputList = outputList; - } - - @Override - public void valueChanged(ListSelectionEvent e) { - @SuppressWarnings("unchecked") - JList theList = (JList) e.getSource(); - State st = (State) theList.getSelectedValue(); - if (st == null) { - return; - } - - DefaultListModel stateListModel = new DefaultListModel<>(); - stateListModel.addElement("weight:" + st.getWeight()); - stateListModel.addElement("weightdelta:" + st.getWeightDelta()); - stateListModel.addElement("rentingVehicle:" + st.isRentingVehicle()); - stateListModel.addElement("vehicleParked:" + st.isVehicleParked()); - stateListModel.addElement("traversalDistance:" + st.getTraversalDistanceMeters()); - stateListModel.addElement("elapsedTime:" + st.getElapsedTimeSeconds()); - outputList.setModel(stateListModel); - - lastStateClicked = st; - } - } - - private final class OnPopupMenuClickListener implements ActionListener { - - @Override - public void actionPerformed(ActionEvent e) { - PathPrinter pp = ((PathPrinter) pathsList.getSelectedValue()); - if (pp == null) { - return; - } - var path = pp.sp; - - firstComparePath = secondComparePath; - secondComparePath = path; - - if (firstComparePath != null) { - DefaultListModel pathModel = new DefaultListModel<>(); - for (State st : firstComparePath.states()) { - pathModel.addElement(st); - } - firstComparePathStates.setModel(pathModel); - } - if (secondComparePath != null) { - DefaultListModel pathModel = new DefaultListModel<>(); - for (State st : secondComparePath.states()) { - pathModel.addElement(st); - } - secondComparePathStates.setModel(pathModel); - } - - int[] diff = diffPaths(); - final int diverge = diff[0]; - final int converge = diff[1]; - if (diff[0] >= 0) { - firstComparePathStates.setCellRenderer( - new DiffListCellRenderer(diverge, firstComparePath.states().size() - converge - 1) - ); - secondComparePathStates.setCellRenderer( - new DiffListCellRenderer(diverge, secondComparePath.states().size() - converge - 1) - ); - } - } - - private int[] diffPaths() { - if (firstComparePath == null || secondComparePath == null) { - return new int[] { -2, -2 }; - } - - int l1 = firstComparePath.states().size(); - int l2 = secondComparePath.states().size(); - int minlen = l1 < l2 ? l1 : l2; - - int divergence = -1; - int convergence = -1; - - // find divergence - for (int i = 0; i < minlen; i++) { - Vertex v1 = firstComparePath.states().get(i).getVertex(); - Vertex v2 = secondComparePath.states().get(i).getVertex(); - if (!v1.equals(v2)) { - divergence = i - 1; - break; - } - } - - // find convergence - for (int i = 0; i < minlen; i++) { - Vertex v1 = firstComparePath.states().get(l1 - i - 1).getVertex(); - Vertex v2 = secondComparePath.states().get(l2 - i - 1).getVertex(); - if (!v1.equals(v2)) { - convergence = i - 1; - break; - } - } - - return new int[] { divergence, convergence }; - } - - private final class DiffListCellRenderer extends DefaultListCellRenderer { - - private final int diverge; - private final int converge; - - private DiffListCellRenderer(int diverge, int converge) { - this.diverge = diverge; - this.converge = converge; - } - - @Override - public Component getListCellRendererComponent( - JList list, - Object value, - int index, - boolean isSelected, - boolean cellHasFocus - ) { - Component c = super.getListCellRendererComponent( - list, - value, - index, - isSelected, - cellHasFocus - ); - if (isSelected) { - return c; - } - - if (index <= diverge) { - c.setBackground(new Color(196, 201, 255)); - } - if (index >= converge) { - c.setBackground(new Color(255, 196, 196)); - } - - return c; - } - } - } -} diff --git a/application/src/main/java/org/opentripplanner/visualizer/RouteDialog.java b/application/src/main/java/org/opentripplanner/visualizer/RouteDialog.java deleted file mode 100644 index 04c343d2b30..00000000000 --- a/application/src/main/java/org/opentripplanner/visualizer/RouteDialog.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.opentripplanner.visualizer; - -import java.awt.Container; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JTextField; - -/** - * A dialog box to plan a route. - */ -public class RouteDialog extends JDialog { - - private final JTextField fromField; - private final JTextField toField; - private final JButton goButton; - - public String from; - public String to; - - public RouteDialog(JFrame owner, String initialFrom) { - super(owner, true); - fromField = new JTextField(initialFrom, 30); - toField = new JTextField(30); - goButton = new JButton("Go"); - - Container pane = getContentPane(); - - pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS)); - pane.add(new JLabel("From")); - pane.add(fromField); - pane.add(new JLabel("To")); - pane.add(toField); - pane.add(goButton); - pack(); - final RouteDialog outer = this; - goButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - from = fromField.getText().trim(); - to = toField.getText().trim(); - outer.setVisible(false); - } - } - ); - setVisible(true); - } -} diff --git a/application/src/main/java/org/opentripplanner/visualizer/ShowGraph.java b/application/src/main/java/org/opentripplanner/visualizer/ShowGraph.java deleted file mode 100644 index 26d567b6903..00000000000 --- a/application/src/main/java/org/opentripplanner/visualizer/ShowGraph.java +++ /dev/null @@ -1,1222 +0,0 @@ -package org.opentripplanner.visualizer; - -import java.awt.Point; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionAdapter; -import java.awt.event.MouseWheelEvent; -import java.awt.event.MouseWheelListener; -import java.text.DecimalFormat; -import java.time.Instant; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.ConcurrentModificationException; -import java.util.HashMap; -import java.util.List; -import java.util.Queue; -import java.util.Set; -import java.util.concurrent.LinkedBlockingQueue; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Envelope; -import org.locationtech.jts.geom.LineString; -import org.locationtech.jts.index.strtree.STRtree; -import org.opentripplanner.astar.model.ShortestPathTree; -import org.opentripplanner.graph_builder.issue.api.DataImportIssue; -import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; -import org.opentripplanner.service.vehiclerental.street.StreetVehicleRentalLink; -import org.opentripplanner.street.geometry.GeometryUtils; -import org.opentripplanner.street.graph.Graph; -import org.opentripplanner.street.model.StreetTraversalPermission; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.model.edge.ElevatorAlightEdge; -import org.opentripplanner.street.model.edge.ElevatorBoardEdge; -import org.opentripplanner.street.model.edge.FreeEdge; -import org.opentripplanner.street.model.edge.PathwayEdge; -import org.opentripplanner.street.model.edge.StreetEdge; -import org.opentripplanner.street.model.edge.StreetTransitEntityLink; -import org.opentripplanner.street.model.edge.StreetVehicleParkingLink; -import org.opentripplanner.street.model.edge.VehicleParkingEdge; -import org.opentripplanner.street.model.path.StreetPath; -import org.opentripplanner.street.model.vertex.ElevatorHopVertex; -import org.opentripplanner.street.model.vertex.ExitVertex; -import org.opentripplanner.street.model.vertex.IntersectionVertex; -import org.opentripplanner.street.model.vertex.SplitterVertex; -import org.opentripplanner.street.model.vertex.StreetLocation; -import org.opentripplanner.street.model.vertex.TemporaryVertex; -import org.opentripplanner.street.model.vertex.TransitBoardingAreaVertex; -import org.opentripplanner.street.model.vertex.TransitEntranceVertex; -import org.opentripplanner.street.model.vertex.TransitPathwayNodeVertex; -import org.opentripplanner.street.model.vertex.TransitStopVertex; -import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex; -import org.opentripplanner.street.model.vertex.Vertex; -import org.opentripplanner.street.search.state.State; -import processing.core.PApplet; -import processing.core.PFont; - -/** - * Processing applet to show a map of the graph. The user can: - Use mouse wheel to zoom (or right - * drag, or ctrl-drag) - Left drag to pan around the map - Left click to send a list of nearby - * vertices to the associated VertexSelectionListener. - */ -public class ShowGraph extends PApplet implements MouseWheelListener { - - private static final int FRAME_RATE = 30; - private static final boolean VIDEO = false; - private static final String VIDEO_PATH = "/home/syncopate/pathimage/"; - private static final DecimalFormat LAT_FORMATTER = new DecimalFormat("00.0000°N ; 00.0000°S"); - private static final DecimalFormat LON_FORMATTER = new DecimalFormat("000.0000°E ; 000.0000°W"); - /* Layer constants */ - // XY coordinates - static final int DRAW_MINIMAL = 0; - static final int DRAW_HIGHLIGHTED = 1; - static final int DRAW_SPT = 2; - static final int DRAW_VERTICES = 3; - static final int DRAW_TRANSIT = 4; - static final int DRAW_LINKS = 5; - static final int DRAW_STREETS = 6; - static final int DRAW_ALL = 7; - static final int DRAW_PARTIAL = 8; - private static double lastLabelY; - // how many edges to draw before checking whether we need to move on to the next frame - private final int BLOCK_SIZE = 1000; - // how many edges to skip over (to ensure a sampling of edges throughout the visible area) - private final long DECIMATE = 40; - // 800 instead of 1000 msec, leaving 20% of the time for work other than drawing. - private final int FRAME_TIME = 800 / FRAME_RATE; - private final ArrayList selectors; - private final List visibleStreetEdges = new ArrayList<>(1000); - private final List visibleLinkEdges = new ArrayList<>(1000); - private final List visibleTransitEdges = new ArrayList<>(1000); - // these queues are filled by a search in another thread, so must be threadsafe - private final Queue newHighlightedVertices = new LinkedBlockingQueue<>(); - private final Queue newHighlightedEdges = new LinkedBlockingQueue<>(); - private static final DateTimeFormatter SHORT_DATE_FORMAT = DateTimeFormatter.ofPattern( - "HH:mm:ss z" - ); - private final LinkedBlockingQueue newSPTEdges = new LinkedBlockingQueue<>(); - private final boolean drawEdges = true; - private int videoFrameNumber = 0; - Graph graph; - STRtree vertexIndex; - STRtree edgeIndex; - Envelope modelOuterBounds; - Envelope modelBounds = new Envelope(); - VertexSelectionListener selector; - private List visibleVertices; - private List highlightedVertices = new ArrayList<>(1000); - private List highlightedEdges = new ArrayList<>(1000); - private Coordinate highlightedCoordinate; - private Edge highlightedEdge; - private StreetPath highlightedStreetPath; - protected double mouseModelX; - protected double mouseModelY; - private Point startDrag = null; - private int dragX; - private int dragY; - private boolean ctrlPressed = false; - boolean drawFast = false; - boolean drawStreetEdges = true; - boolean drawTransitEdges = true; - boolean drawLinkEdges = true; - boolean drawStreetVertices = true; - boolean drawTransitStopVertices = true; - boolean drawExtraVertices = true; - private int drawLevel = DRAW_ALL; - private int drawOffset = 0; - private boolean drawHighlighted = true; - public SimpleSPT simpleSPT = new SimpleSPT(); - private LinkedBlockingQueue sptEdgeQueue; - private boolean sptVisible = true; - private float sptFlattening = 0.3f; - private float sptThickness = 0.1f; - private boolean drawMultistateVertices = true; - private ShortestPathTree spt; - - /* - * Constructor. Call processing constructor, and register the listener to notify when the user selects vertices. - */ - public ShowGraph(VertexSelectionListener selector, Graph graph) { - super(); - this.graph = graph; - this.spt = null; - this.selector = selector; - this.selectors = new ArrayList<>(); - } - - /* - * Setup Processing applet - */ - public void setup() { - size(getSize().width, getSize().height, JAVA2D); - - /* Build spatial index of vertices and edges */ - buildSpatialIndex(); - - /* Set model bounds to encompass all vertices in the index, and then some */ - modelBounds = (Envelope) (vertexIndex.getRoot().getBounds()); - modelBounds.expandBy(0.02); - matchAspect(); - /* save this zoom level to allow returning to default later */ - modelOuterBounds = new Envelope(modelBounds); - - /* find and set up the appropriate font */ - String[] fonts = PFont.list(); - String[] preferredFonts = { "Mono", "Courier" }; - PFont font = null; - for (String preferredFontName : preferredFonts) { - for (String fontName : fonts) { - if (fontName.contains(preferredFontName)) { - font = createFont(fontName, 16); - break; - } - } - if (font != null) { - break; - } - } - textFont(font); - textMode(SCREEN); - addMouseWheelListener(this); - addMouseMotionListener( - new MouseMotionAdapter() { - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - Point p = e.getPoint(); - mouseModelX = toModelX(p.x); - mouseModelY = toModelY(p.y); - } - } - ); - addComponentListener( - new ComponentAdapter() { - public void componentResized(ComponentEvent e) { - matchAspect(); - drawLevel = DRAW_PARTIAL; - } - } - ); - frameRate(FRAME_RATE); - } - - public synchronized void draw() { - smooth(); - int startMillis = millis(); - if (drawLevel == DRAW_PARTIAL) { - drawPartial(startMillis); - } else if (drawLevel == DRAW_ALL) { - boolean finished = drawAll(startMillis); - if (!finished) { - return; - } - } else if (drawLevel == DRAW_LINKS) { - boolean finished = drawLinks(startMillis); - if (!finished) { - return; - } - } else if (drawLevel == DRAW_TRANSIT) { - boolean finished = drawTransit(startMillis); - if (!finished) { - return; - } - } else if (drawLevel == DRAW_VERTICES) { - drawVertices(); - } else if (drawLevel == DRAW_SPT) { - boolean finished = drawSPT(); - if (!finished) { - return; - } - } else if (drawLevel == DRAW_HIGHLIGHTED) { - drawHighlighted(); - } else if (drawLevel == DRAW_MINIMAL) { - if (!newHighlightedEdges.isEmpty()) { - handleNewHighlights(); - } - drawNewEdges(); - drawCoords(); - } - drawOffset = 0; - if (drawLevel > DRAW_MINIMAL) { - // move to next layer - drawLevel -= 1; - } - } - - public void redraw() { - drawLevel = DRAW_ALL; - } - - public void mouseReleased(MouseEvent e) { - startDrag = null; - } - - public void mouseDragged(MouseEvent e) { - Point c = e.getPoint(); - if (startDrag == null) { - startDrag = c; - dragX = c.x; - dragY = c.y; - } - double dx = dragX - c.x; - double dy = c.y - dragY; - if (ctrlPressed || mouseButton == RIGHT) { - zoom(dy * 0.01, startDrag); - } else { - double tx = (modelBounds.getWidth() * dx) / getWidth(); - double ty = (modelBounds.getHeight() * dy) / getHeight(); - modelBounds.translate(tx, ty); - } - dragX = c.x; - dragY = c.y; - drawLevel = DRAW_PARTIAL; - } - - /* - * Zoom in/out proportional to the number of clicks of the mouse wheel. - */ - public void mouseWheelMoved(MouseWheelEvent e) { - double f = e.getWheelRotation() * 0.2; - zoom(f, e.getPoint()); - } - - @SuppressWarnings("unchecked") - public void mouseClicked() { - Envelope screenEnv = new Envelope(new Coordinate(mouseX, mouseY)); - screenEnv.expandBy(4, 4); - Envelope env = new Envelope( - toModelX(screenEnv.getMinX()), - toModelX(screenEnv.getMaxX()), - toModelY(screenEnv.getMinY()), - toModelY(screenEnv.getMaxY()) - ); - - List nearby = (List) vertexIndex.query(env); - selector.verticesSelected(nearby); - drawLevel = DRAW_ALL; - } - - public void keyPressed() { - if (key == CODED && keyCode == CONTROL) { - ctrlPressed = true; - } - } - - public void keyReleased() { - if (key == CODED && keyCode == CONTROL) { - ctrlPressed = false; - } - } - - public void zoomToDefault() { - modelBounds = new Envelope(modelOuterBounds); - drawLevel = DRAW_ALL; - } - - public void zoomOut() { - modelBounds.expandBy(modelBounds.getWidth(), modelBounds.getHeight()); - drawLevel = DRAW_ALL; - } - - public void zoomToLocation(Coordinate c) { - Envelope e = new Envelope(); - e.expandToInclude(c); - e.expandBy(0.002); - modelBounds = e; - matchAspect(); - drawLevel = DRAW_ALL; - } - - public void zoomToVertex(Vertex v) { - Envelope e = new Envelope(); - e.expandToInclude(v.getCoordinate()); - e.expandBy(0.002); - modelBounds = e; - drawLevel = DRAW_ALL; - } - - /** - * Zoom to an envelope. Used for issue zoom. - * - * @author mattwigway - */ - public void zoomToEnvelope(Envelope e) { - modelBounds = e; - matchAspect(); - drawLevel = DRAW_ALL; - } - - /* - * Iterate through all vertices and their (outgoing) edges. If they are of 'interesting' types, - * add them to the corresponding spatial index. - */ - public synchronized void buildSpatialIndex() { - vertexIndex = new STRtree(); - edgeIndex = new STRtree(); - Envelope env; - - // int xminx, xmax, ymin, ymax; - for (Vertex v : graph.getVertices()) { - Coordinate c = v.getCoordinate(); - env = new Envelope(c); - vertexIndex.insert(env, v); - for (Edge e : v.getOutgoing()) { - var edgeGeometry = e.getGeometry(); - if (edgeGeometry == null) { - edgeIndex.insert( - new Envelope(e.getFromVertex().getCoordinate(), e.getToVertex().getCoordinate()), - e - ); - } else { - edgeIndex.insert(edgeGeometry.getEnvelopeInternal(), e); - } - } - } - vertexIndex.build(); - edgeIndex.build(); - } - - /** - * Set the Vertex selector to newSelector, and store the old selector on the stack of selectors - */ - public void pushSelector(VertexSelectionListener newSelector) { - selectors.add(selector); - selector = newSelector; - } - - /** - * Restore the previous vertexSelector - */ - public void popSelector() { - selector = selectors.get(selectors.size() - 1); - selectors.remove(selectors.size() - 1); - } - - public void highlightCoordinate(Coordinate c) { - double xd = 0; - double yd = 0; - while (!modelBounds.contains(c)) { - xd = modelBounds.getWidth() / 100; - yd = modelBounds.getHeight() / 100; - modelBounds.expandBy(xd, yd); - } - modelBounds.expandBy(xd, yd); - highlightedCoordinate = c; - drawLevel = DRAW_ALL; - } - - public void highlightVertex(Vertex v) { - highlightCoordinate(v.getCoordinate()); - } - - public void enqueueHighlightedEdge(Edge de) { - newHighlightedEdges.add(de); - } - - public void clearHighlights() { - highlightedEdges.clear(); - highlightedVertices.clear(); - drawLevel = DRAW_ALL; - } - - public void highlightEdge(Edge selected) { - highlightedEdge = selected; - drawLevel = DRAW_ALL; - } - - public void highlightStreetPath(StreetPath sp) { - highlightedStreetPath = sp; - // drawLevel = DRAW_ALL; - // leave streets in grey - drawLevel = DRAW_TRANSIT; - } - - public void setHighlightedVertices(Set vertices) { - highlightedVertices = new ArrayList<>(vertices); - drawLevel = DRAW_ALL; - } - - public void setHighlightedVertices(List vertices) { - highlightedVertices = vertices; - drawLevel = DRAW_ALL; - } - - public void setHighlightedEdges(List edges) { - highlightedEdges = edges; - drawLevel = DRAW_ALL; - } - - public void drawIssue(DataImportIssue anno) { - Envelope env = new Envelope(); - - Edge e = anno.getReferencedEdge(); - if (e != null) { - this.enqueueHighlightedEdge(e); - env.expandToInclude(e.getFromVertex().getCoordinate()); - env.expandToInclude(e.getToVertex().getCoordinate()); - } - - ArrayList vertices = new ArrayList<>(); - Vertex v = anno.getReferencedVertex(); - if (v != null) { - env.expandToInclude(v.getCoordinate()); - vertices.add(v); - } - - if (e == null && v == null) { - return; - } - - // make it a little bigger, especially needed for STOP_UNLINKED - env.expandBy(0.02); - - // highlight relevant things - this.clearHighlights(); - this.setHighlightedVertices(vertices); - - // zoom the graph display - this.zoomToEnvelope(env); - - // and draw - this.draw(); - } - - public void setShowTransit(boolean selected) { - drawTransitEdges = selected; - drawTransitStopVertices = selected; - } - - public void setShowStreets(boolean selected) { - drawStreetEdges = selected; - drawStreetVertices = selected; - } - - public void setShowHightlights(boolean selected) { - drawHighlighted = selected; - } - - public void addNewSPTEdge(State state) { - this.newSPTEdges.add(state); - this.simpleSPT.add(state); - } - - public void resetSPT() { - this.simpleSPT = new SimpleSPT(); - } - - public void setShowSPT(boolean selected) { - sptVisible = selected; - } - - public void setSPTFlattening(float sptFlattening) { - this.sptFlattening = sptFlattening; - } - - public void setSPTThickness(float sptThickness) { - this.sptThickness = sptThickness; - } - - public void setShowMultistateVertices(boolean selected) { - this.drawMultistateVertices = selected; - } - - public void setSPT(ShortestPathTree spt) { - this.spt = spt; - } - - /* - * Zoom in/out. Translate the viewing window such that the place under the mouse pointer is a fixed point. If p is null, zoom around the center of - * the viewport. - */ - void zoom(double f, Point p) { - double ex = modelBounds.getWidth() * f; - double ey = modelBounds.getHeight() * f; - modelBounds.expandBy(ex / 2, ey / 2); - if (p != null) { - // Note: Graphics Y coordinates increase down the screen, hence the opposite signs. - double tx = ex * -((p.getX() / this.width) - 0.5); - double ty = ey * +((p.getY() / this.height) - 0.5); - modelBounds.translate(tx, ty); - } - // update the display - drawLevel = DRAW_PARTIAL; - } - - void matchAspect() { - /* Basic sinusoidal projection of lat/lon data to square pixels */ - double yCenter = modelBounds.centre().y; - float xScale = cos(radians((float) yCenter)); - double newX = - modelBounds.getHeight() * (1 / xScale) * ((float) this.getWidth() / this.getHeight()); - modelBounds.expandBy((newX - modelBounds.getWidth()) / 2f, 0); - } - - private static LineString getOrCreateGeometry(Edge edge) { - var edgeGeometry = edge.getGeometry(); - if (edgeGeometry != null) { - return edgeGeometry; - } - - Coordinate[] coordinates = new Coordinate[] { - edge.getFromVertex().getCoordinate(), - edge.getToVertex().getCoordinate(), - }; - return GeometryUtils.getGeometryFactory().createLineString(coordinates); - } - - @SuppressWarnings("unchecked") - private synchronized void findVisibleElements() { - visibleVertices = (List) vertexIndex.query(modelBounds); - visibleStreetEdges.clear(); - visibleLinkEdges.clear(); - visibleTransitEdges.clear(); - for (Edge de : (Iterable) edgeIndex.query(modelBounds)) { - if ( - de instanceof PathwayEdge || - de instanceof VehicleParkingEdge || - de instanceof StreetTransitEntityLink || - de instanceof FreeEdge || - de instanceof StreetVehicleParkingLink || - de instanceof StreetVehicleRentalLink - ) { - visibleLinkEdges.add(de); - } else if ( - de instanceof StreetEdge || - de instanceof ElevatorAlightEdge || - de instanceof ElevatorBoardEdge - ) { - visibleStreetEdges.add(de); - } - } - } - - private int drawEdge(Edge e) { - var geometry = getOrCreateGeometry(e); - Coordinate[] coords = geometry.getCoordinates(); - beginShape(); - for (Coordinate coord : coords) { - vertex((float) toScreenX(coord.x), (float) toScreenY(coord.y)); - } - endShape(); - // should be used to count segments, not edges drawn - return coords.length; - } - - /* use endpoints instead of geometry for quick updating */ - private void drawEdgeFast(Edge e) { - Coordinate[] coords = getOrCreateGeometry(e).getCoordinates(); - Coordinate c0 = coords[0]; - Coordinate c1 = coords[coords.length - 1]; - line( - (float) toScreenX(c0.x), - (float) toScreenY(c0.y), - (float) toScreenX(c1.x), - (float) toScreenY(c1.y) - ); - } - - private void drawStreetPath(StreetPath sp) { - // draw edges in different colors according to mode - for (State s : sp.states()) { - Edge e = s.getBackEdge(); - if (e == null) { - continue; - } - - // TODO Add support for crating transit edges on the fly - // if (mode != null && mode.isTransit()) { - // stroke(200, 050, 000); - // strokeWeight(6); - // drawEdge(e); - // } - if (e instanceof StreetEdge) { - StreetTraversalPermission stp = ((StreetEdge) e).getPermission(); - if (stp == StreetTraversalPermission.PEDESTRIAN) { - stroke(000, 200, 000); - strokeWeight(6); - drawEdge(e); - } else if (stp == StreetTraversalPermission.BICYCLE) { - stroke(000, 000, 200); - strokeWeight(6); - drawEdge(e); - } else if (stp == StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE) { - stroke(000, 200, 200); - strokeWeight(6); - drawEdge(e); - } else if (stp == StreetTraversalPermission.ALL) { - stroke(200, 200, 200); - strokeWeight(6); - drawEdge(e); - } else { - stroke(64, 64, 64); - strokeWeight(6); - drawEdge(e); - } - } - } - // mark key vertices - lastLabelY = -999; - labelState(sp.states().getFirst(), "begin"); - labelState(sp.states().getLast(), "end"); - - if (VIDEO) { - // freeze on final path for a few frames - for (int i = 0; i < 10; i++) { - saveVideoFrame(); - } - resetVideoFrameNumber(); - } - } - - private void labelState(State s, String str) { - fill(240, 240, 240); - Vertex v = s.getVertex(); - drawVertex(v, 8); - str += " " + SHORT_DATE_FORMAT.format(Instant.ofEpochSecond(s.getTimeSeconds())); - str += " [" + (int) s.getWeight() + "]"; - double x = toScreenX(v.getX()) + 10; - double y = toScreenY(v.getY()); - double dy = y - lastLabelY; - if (dy == 0) { - y = lastLabelY + 20; - } else if (Math.abs(dy) < 20) { - y = lastLabelY + Math.signum(dy) * 20; - } - text(str, (float) x, (float) y); - lastLabelY = y; - } - - private void drawCoordinate(Coordinate c, double r) { - noStroke(); - ellipse(toScreenX(c.x), toScreenY(c.y), r, r); - } - - private void drawVertex(Vertex v, double r) { - drawCoordinate(v.getCoordinate(), r); - } - - private boolean drawSPT() { - if (!sptVisible) { - return true; - } - - noFill(); - // if(sptEdgeQueue==null){ - // sptEdgeQueue = simpleSPT.getEdgeQueue(); - // } - - // colorOverlappingBranches(sptEdgeQueue); - // - // int i=0; - // while(!sptEdgeQueue.isEmpty()){ - // SPTNode node = sptEdgeQueue.poll(); - // i++; - // node.draw(sptBranchColors); - // if ((i%BLOCK_SIZE==0) && (millis() - startMillis > FRAME_TIME)) - // return false; - // } - // sptEdgeQueue=null; - - simpleSPT.draw(); - - return true; - } - - private void colorOverlappingBranches(LinkedBlockingQueue queue) { - HashMap stateHeight = new HashMap<>(); - - for (SPTNode node : queue) { - Integer height = stateHeight.get(node.state.getVertex()); - if (height == null) { - height = 0; - } else { - height += 1; - } - stateHeight.put(node.state.getVertex(), height); - - node.setHeight(height); - } - } - - private void drawNewEdges() { - if (drawEdges) { - strokeWeight(1); - // white - stroke(255, 255, 255); - noFill(); - while (!newSPTEdges.isEmpty()) { - State leaf = newSPTEdges.poll(); - - if (leaf != null) { - if (leaf.getBackEdge() != null) { - drawEdge(leaf.getBackEdge()); - } - } - } - } - } - - private void drawCoords() { - // Black background box - fill(0, 0, 0); - stroke(30, 128, 30); - // noStroke(); - strokeWeight(1); - rect(3, 3, 303, textAscent() + textDescent() + 6); - // Print lat & lon coordinates - fill(128, 128, 256); - // noStroke(); - String output = LON_FORMATTER.format(mouseModelX) + " " + LAT_FORMATTER.format(mouseModelY); - textAlign(LEFT, TOP); - text(output, 6, 6); - } - - private void drawVertices() { - /* turn off vertex display when zoomed out */ - final double METERS_PER_DEGREE_LAT = 111111.111111; - boolean closeEnough = ((modelBounds.getHeight() * METERS_PER_DEGREE_LAT) / this.width < 5); - /* Draw selected visible vertices */ - for (Vertex v : visibleVertices) { - if ( - drawTransitStopVertices && - closeEnough && - (v instanceof TransitStopVertex || - v instanceof TransitPathwayNodeVertex || - v instanceof TransitEntranceVertex || - v instanceof TransitBoardingAreaVertex) - ) { - // Make transit stops blue dots - fill(60, 60, 200); - drawVertex(v, 7); - } - if ( - drawExtraVertices && - closeEnough && - (v instanceof VehicleParkingEntranceVertex || v instanceof VehicleRentalPlace) - ) { - // Make B+R/P+R pink - fill(255, 70, 255); - drawVertex(v, 7); - } - if ( - drawStreetVertices && - ((v instanceof IntersectionVertex && ((IntersectionVertex) v).hasCyclingTrafficLight()) || - (v instanceof ElevatorHopVertex || - v instanceof ExitVertex || - v instanceof TemporaryVertex || - v instanceof SplitterVertex || - v instanceof StreetLocation)) - ) { - if (v instanceof IntersectionVertex && ((IntersectionVertex) v).hasCyclingTrafficLight()) { - // Make traffic lights red dots - fill(120, 60, 60); - drawVertex(v, 5); - } - } - if (drawMultistateVertices && spt != null) { - List states = spt.getStates(v); - if (states != null) { - fill(100, 60, 100); - drawVertex(v, states.size() * 2); - } - } - } - } - - private void drawHighlighted() { - /* Draw highlighted edges in another color */ - noFill(); - // yellow transparent edge highlight - stroke(200, 200, 000, 16); - strokeWeight(8); - if (drawHighlighted && highlightedEdges != null) { - try { - for (Edge e : highlightedEdges) { - drawEdge(e); - } - } catch (ConcurrentModificationException cme) { - // The edge list was cleared or added to while it was being drawn, no harm done. - } - } - /* Draw highlighted street path in another color */ - if (highlightedStreetPath != null) { - drawStreetPath(highlightedStreetPath); - } - /* Draw (single) highlighted edge in highlight color */ - if (highlightedEdge != null) { - stroke(10, 200, 10, 128); - strokeWeight(12); - drawEdge(highlightedEdge); - } - /* Draw highlighted vertices */ - // orange fill - fill(255, 127, 0); - noStroke(); - if (highlightedVertices != null) { - for (Vertex v : highlightedVertices) { - drawVertex(v, 8); - } - } - /* Draw (single) highlighed coordinate in a different color */ - if (highlightedCoordinate != null) { - fill(255, 255, 30); - drawCoordinate(highlightedCoordinate, 7); - } - noFill(); - } - - private boolean drawTransit(int startMillis) { - if (drawTransitEdges) { - // transparent blue - stroke(40, 40, 128, 30); - strokeWeight(4); - noFill(); - // for (Edge e : visibleTransitEdges) { - while (drawOffset < visibleTransitEdges.size()) { - Edge e = visibleTransitEdges.get(drawOffset); - drawEdge(e); - drawOffset += 1; - if (drawOffset % BLOCK_SIZE == 0) { - if (millis() - startMillis > FRAME_TIME) { - return false; - } - } - } - } - return true; - } - - private boolean drawLinks(int startMillis) { - if (drawLinkEdges) { - // transparent orange - stroke(256, 165, 0, 30); - strokeWeight(3); - noFill(); - // for (Edge e : visibleTransitEdges) { - while (drawOffset < visibleLinkEdges.size()) { - Edge e = visibleLinkEdges.get(drawOffset); - drawEdge(e); - drawOffset += 1; - if (drawOffset % BLOCK_SIZE == 0) { - if (millis() - startMillis > FRAME_TIME) { - return false; - } - } - } - } - return true; - } - - private boolean drawAll(int startMillis) { - if (drawOffset == 0) { - findVisibleElements(); - background(15); - } - if (drawStreetEdges) { - // dark green - stroke(30, 128, 30); - strokeWeight(1); - noFill(); - while (drawOffset < visibleStreetEdges.size()) { - drawEdge(visibleStreetEdges.get(drawOffset)); - drawOffset += 1; - if (drawOffset % BLOCK_SIZE == 0) { - if (millis() - startMillis > FRAME_TIME) { - return false; - } - } - } - } - return true; - } - - private void drawPartial(int startMillis) { - background(15); - stroke(30, 128, 30); - strokeWeight(1); - noFill(); - // noSmooth(); - int drawIndex = 0; - int drawStart = 0; - int drawCount = 0; - while (drawStart < DECIMATE && drawStart < visibleStreetEdges.size()) { - if (drawFast) { - drawEdgeFast(visibleStreetEdges.get(drawIndex)); - } else { - drawEdge(visibleStreetEdges.get(drawIndex)); - } - drawIndex += DECIMATE; - drawCount += 1; - if (drawCount % BLOCK_SIZE == 0 && millis() - startMillis > FRAME_TIME) { - // ran out of time to draw this frame. - // enable fast-drawing when too few edges were drawn: - // drawFast = drawCount < visibleStreetEdges.size() / 10; - // leave edge drawing loop to let other work happen. - break; - } - if (drawIndex >= visibleStreetEdges.size()) { - // start over drawing every DECIMATEth edge, offset by 1 - drawStart += 1; - drawIndex = drawStart; - } - } - } - - private void handleNewHighlights() { - // fill(0, 0, 0, 1); - // rect(0,0,this.width, this.height); - desaturate(); - noFill(); - stroke(256, 0, 0, 128); - strokeWeight(6); - while (!newHighlightedEdges.isEmpty()) { - Edge de = newHighlightedEdges.poll(); - if (de != null) { - drawEdge(de); - highlightedEdges.add(de); - } - } - if (VIDEO) { - saveVideoFrame(); - } - } - - private void saveVideoFrame() { - save(VIDEO_PATH + "/" + videoFrameNumber++ + ".bmp"); - } - - private void resetVideoFrameNumber() { - videoFrameNumber = 0; - } - - private void desaturate() { - final float f = 8; - loadPixels(); - for (int i = 0; i < width * height; i++) { - int c = pixels[i]; - float r = red(c); - float g = green(c); - float b = blue(c); - float avg = (r + g + b) / 3; - r += (avg - r) / f; - g += (avg - g) / f; - b += (avg - b) / f; - pixels[i] = color(r, g, b); - } - updatePixels(); - } - - private double toScreenY(double y) { - return map( - (float) y, - (float) modelBounds.getMinY(), - (float) modelBounds.getMaxY(), - getSize().height, - 0 - ); - } - - private double toScreenX(double x) { - return map( - (float) x, - (float) modelBounds.getMinX(), - (float) modelBounds.getMaxX(), - 0, - getSize().width - ); - } - - private double toModelY(double y) { - return map( - (float) y, - 0, - getSize().height, - (float) modelBounds.getMaxY(), - (float) modelBounds.getMinY() - ); - } - - private double toModelX(double x) { - return map( - (float) x, - 0, - getSize().width, - (float) modelBounds.getMinX(), - (float) modelBounds.getMaxX() - ); - } - - /** - * A version of ellipse that takes double args, because apparently Java is too stupid to downgrade - * automatically. - */ - private void ellipse(double d, double e, double f, double g) { - ellipse((float) d, (float) e, (float) f, (float) g); - } - - static class Trunk { - - public Edge edge; - public Double trunkiness; - - Trunk(Edge edge, Double trunkiness) { - this.edge = edge; - this.trunkiness = trunkiness; - } - } - - class SimpleSPT { - - private final HashMap nodes; - SPTNode root; - - SimpleSPT() { - nodes = new HashMap<>(); - } - - public void add(State state) { - // create simpleSPT entry - SPTNode curNode = new SPTNode(state); - SPTNode parentNode = this.nodes.get(state.getBackState()); - if (parentNode != null) { - parentNode.children.add(curNode); - } else { - root = curNode; - } - curNode.parent = parentNode; - this.nodes.put(state, curNode); - } - - public void draw() { - if (root == null) { - return; - } - - HashMap vertexHeight = new HashMap<>(); - - root.drawRecursive(0, vertexHeight); - } - - public LinkedBlockingQueue getEdgeQueue() { - LinkedBlockingQueue ret = new LinkedBlockingQueue<>(); - if (root != null) { - root.addToEdgeQueue(ret); - } - return ret; - } - - void setWeights() { - if (root == null) { - return; - } - root.setWeight(); - } - } - - class SPTNode { - - // this is a tool for the traverse visitor to build a very simple - // shortest path tree, which we can use to come up with the trunkiness - // of every SPT edge. - - State state; - SPTNode parent; - List children; - double weight = 0.0; - public Integer height; - - SPTNode(State state) { - this.state = state; - this.height = null; - this.children = new ArrayList<>(); - } - - public void addToEdgeQueue(LinkedBlockingQueue ret) { - ret.add(this); - for (SPTNode child : children) { - child.addToEdgeQueue(ret); - } - } - - public void drawRecursive(int height, HashMap vertexStatesEncountered) { - colorMode(HSB); - - // get the number of states we've already drawn from this vertex - Integer vertexHeight = vertexStatesEncountered.get(this.state.getVertex()); - if (vertexHeight == null) { - vertexHeight = 0; - } - - // if it's larger than the 'height' of the state we're about to draw, bump the state's visual height - // up to the number of states it has to climb over - if (vertexHeight > height) { - height = vertexHeight; - } - - // increment the counter of the number of times we've encountered this vertex - vertexStatesEncountered.put(this.state.getVertex(), vertexHeight + 1); - - if (state.getBackEdge() != null) { - //stroke( colorRamp( (int)(state.getWeight()/10.0) ) ); - stroke(color((height * 10) % 255, 255, 255)); - - strokeWeight((float) (sptThickness * Math.pow(weight, sptFlattening))); - drawEdge(state.getBackEdge()); - } - - for (SPTNode child : children) { - child.drawRecursive(height, vertexStatesEncountered); - } - - colorMode(RGB); - } - - public void draw(List colors) { - colorMode(HSB); - - if (state.getBackEdge() != null) { - //stroke( colorRamp( (int)(state.getWeight()/10.0) ) ); - strokeWeight((float) (sptThickness * Math.pow(weight, sptFlattening))); - - stroke(colors.get(this.height)); - - drawEdge(state.getBackEdge()); - } - - colorMode(RGB); - } - - public void setWeight() { - weight = state.getWeight(); - for (SPTNode child : children) { - child.setWeight(); - weight += child.weight; - } - } - - public void setHeight(Integer height) { - this.height = height; - } - - void addChild(SPTNode child) { - this.children.add(child); - } - - private int colorRamp(int aa) { - int NHUES = 6; - int HUELEN = 256; - int RAMPLEN = NHUES * HUELEN; - int BRIGHTNESS = 220; - - // make sure aa fits within the color ramp - aa = aa % RAMPLEN; - // establish the hue - int hueIndex = aa / HUELEN; - // convert that to a hue value - int hue = hueIndex * (HUELEN / NHUES); - int saturation = HUELEN - (aa % HUELEN); - - return color(hue, saturation, BRIGHTNESS); - } - } -} diff --git a/application/src/main/java/org/opentripplanner/visualizer/VertexSelectionListener.java b/application/src/main/java/org/opentripplanner/visualizer/VertexSelectionListener.java deleted file mode 100644 index 6e91494b4d6..00000000000 --- a/application/src/main/java/org/opentripplanner/visualizer/VertexSelectionListener.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.opentripplanner.visualizer; - -import java.util.List; -import org.opentripplanner.street.model.vertex.Vertex; - -/** - * An interface allowing a map UI element to report that the user has selected vertices. - */ -public interface VertexSelectionListener { - void verticesSelected(List selected); -} diff --git a/application/src/main/java/org/opentripplanner/visualizer/VisualTraverseVisitor.java b/application/src/main/java/org/opentripplanner/visualizer/VisualTraverseVisitor.java deleted file mode 100644 index 04add0fe46f..00000000000 --- a/application/src/main/java/org/opentripplanner/visualizer/VisualTraverseVisitor.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.opentripplanner.visualizer; - -import org.opentripplanner.astar.spi.TraverseVisitor; -import org.opentripplanner.street.model.edge.Edge; -import org.opentripplanner.street.search.state.State; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class VisualTraverseVisitor implements TraverseVisitor { - - private static final Logger LOG = LoggerFactory.getLogger(VisualTraverseVisitor.class); - - private final ShowGraph gui; - - private final int SLEEP_AFTER = 50; - private final int SLEEP_LEN = 2; - - private int sleepAfter = SLEEP_AFTER; - - public VisualTraverseVisitor(ShowGraph gui) { - this.gui = gui; - } - - @Override - public void visitEdge(Edge edge) { - gui.enqueueHighlightedEdge(edge); - //gui.highlightVertex(state.getVertex()); - } - - @Override - public void visitVertex(State state) { - // every SLEEP_AFTER visits of a vertex, sleep for SLEEP_LEN - // this slows down the search so it animates prettily - if (--sleepAfter <= 0) { - sleepAfter = SLEEP_AFTER; - try { - Thread.sleep(SLEEP_LEN); - } catch (InterruptedException e) { - LOG.warn("interrupted", e); - } - } - gui.addNewSPTEdge(state); - } - - @Override - public void visitEnqueue() { - // Edge e = state.getBackEdge(); - // if (e instanceof Edge) { - // gui.enqueueHighlightedEdge((Edge) e); - // } - } -} diff --git a/application/src/main/java/org/opentripplanner/visualizer/package-info.md b/application/src/main/java/org/opentripplanner/visualizer/package-info.md deleted file mode 100644 index f1b60dde32f..00000000000 --- a/application/src/main/java/org/opentripplanner/visualizer/package-info.md +++ /dev/null @@ -1,5 +0,0 @@ -# Graph visualizer - -This package contains classes used for visualizing OpenTripPlanner graphs. This graph visualizer -is intended for debugging purposes and may therefore have arcane developer-oriented features and -grow new UI components as needed. diff --git a/application/src/main/java/org/opentripplanner/warmup/GtfsWarmupQueryExecutor.java b/application/src/main/java/org/opentripplanner/warmup/GtfsWarmupQueryExecutor.java new file mode 100644 index 00000000000..33a8ee0bd7c --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/GtfsWarmupQueryExecutor.java @@ -0,0 +1,110 @@ +package org.opentripplanner.warmup; + +import graphql.ExecutionInput; +import graphql.GraphQL; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import org.opentripplanner.apis.gtfs.GraphQLRequestContext; +import org.opentripplanner.apis.gtfs.mapping.routerequest.AccessModeMapper; +import org.opentripplanner.apis.gtfs.mapping.routerequest.EgressModeMapper; +import org.opentripplanner.apis.support.graphql.OtpDataFetcherExceptionHandler; +import org.opentripplanner.standalone.api.OtpServerRequestContext; +import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.street.model.StreetMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class GtfsWarmupQueryExecutor implements WarmupQueryStrategy { + + private static final Logger LOG = LoggerFactory.getLogger(GtfsWarmupQueryExecutor.class); + + static final String QUERY = """ + query( + $fromLat: CoordinateValue!, $fromLon: CoordinateValue!, + $toLat: CoordinateValue!, $toLon: CoordinateValue!, + $dateTime: PlanDateTimeInput!, + $accessMode: PlanAccessMode!, $egressMode: PlanEgressMode! + ) { + planConnection( + origin: { location: { coordinate: { latitude: $fromLat, longitude: $fromLon } } } + destination: { location: { coordinate: { latitude: $toLat, longitude: $toLon } } } + dateTime: $dateTime + modes: { transit: { access: [$accessMode], egress: [$egressMode] } } + ) { + edges { + node { + start + end + legs { + mode + duration + from { name lat lon } + to { name lat lon } + route { shortName } + legGeometry { points } + } + } + } + } + } + """; + + private final GraphQL graphQL; + private final GraphQLRequestContext requestContext; + private final ModeCombinations modeCombinations; + + GtfsWarmupQueryExecutor( + OtpServerRequestContext context, + List accessModes, + List egressModes + ) { + this.requestContext = GraphQLRequestContext.ofServerContext(context); + this.graphQL = GraphQL.newGraphQL(context.gtfsSchema()) + .defaultDataFetcherExceptionHandler(new OtpDataFetcherExceptionHandler()) + .build(); + this.modeCombinations = new ModeCombinations(accessModes, egressModes); + } + + @Override + public boolean execute(WgsCoordinate from, WgsCoordinate to, boolean arriveBy, int queryCount) { + var variables = buildVariables(from, to, arriveBy, queryCount); + + var input = ExecutionInput.newExecutionInput() + .query(QUERY) + .context(requestContext) + .variables(variables) + .locale(Locale.US) + .build(); + + var result = graphQL.execute(input); + if (!result.getErrors().isEmpty()) { + LOG.warn("Warmup query had GraphQL errors: {}", result.getErrors()); + return false; + } + return true; + } + + Map buildVariables( + WgsCoordinate from, + WgsCoordinate to, + boolean arriveBy, + int queryCount + ) { + var now = Instant.now().atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + var dateTime = arriveBy ? Map.of("latestArrival", now) : Map.of("earliestDeparture", now); + + return Map.ofEntries( + Map.entry("fromLat", from.latitude()), + Map.entry("fromLon", from.longitude()), + Map.entry("toLat", to.latitude()), + Map.entry("toLon", to.longitude()), + Map.entry("dateTime", dateTime), + Map.entry("accessMode", AccessModeMapper.map(modeCombinations.access(queryCount)).name()), + Map.entry("egressMode", EgressModeMapper.map(modeCombinations.egress(queryCount)).name()) + ); + } +} diff --git a/application/src/main/java/org/opentripplanner/warmup/ModeCombinations.java b/application/src/main/java/org/opentripplanner/warmup/ModeCombinations.java new file mode 100644 index 00000000000..6b83fa49733 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/ModeCombinations.java @@ -0,0 +1,44 @@ +package org.opentripplanner.warmup; + +import java.util.List; +import org.opentripplanner.street.model.StreetMode; + +/** + * Access/egress mode pairs to cycle through during warmup. Entries {@code i} of the access and + * egress lists form one pair. The cycle is driven by a caller-supplied counter; modulo arithmetic + * maps the counter to a pair so warmup queries iterate through every combination before repeating. + */ +class ModeCombinations { + + private final List accessModes; + private final List egressModes; + + ModeCombinations(List accessModes, List egressModes) { + if (accessModes.size() != egressModes.size()) { + throw new IllegalArgumentException( + "accessModes and egressModes must have the same size, got %d and %d.".formatted( + accessModes.size(), + egressModes.size() + ) + ); + } + this.accessModes = List.copyOf(accessModes); + this.egressModes = List.copyOf(egressModes); + } + + int size() { + return accessModes.size(); + } + + StreetMode access(int counter) { + return accessModes.get(indexFor(counter)); + } + + StreetMode egress(int counter) { + return egressModes.get(indexFor(counter)); + } + + private int indexFor(int counter) { + return Math.floorMod(counter - 1, size()); + } +} diff --git a/application/src/main/java/org/opentripplanner/warmup/TransmodelWarmupQueryExecutor.java b/application/src/main/java/org/opentripplanner/warmup/TransmodelWarmupQueryExecutor.java new file mode 100644 index 00000000000..4cfb6f85d85 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/TransmodelWarmupQueryExecutor.java @@ -0,0 +1,126 @@ +package org.opentripplanner.warmup; + +import graphql.ExecutionInput; +import graphql.GraphQL; +import graphql.schema.GraphQLSchema; +import java.util.List; +import java.util.Map; +import org.opentripplanner.apis.transmodel.TransmodelRequestContext; +import org.opentripplanner.apis.transmodel.model.EnumTypes; +import org.opentripplanner.apis.transmodel.support.AbortOnUnprocessableRequestExecutionStrategy; +import org.opentripplanner.standalone.api.OtpServerRequestContext; +import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.street.model.StreetMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class TransmodelWarmupQueryExecutor implements WarmupQueryStrategy { + + private static final Logger LOG = LoggerFactory.getLogger(TransmodelWarmupQueryExecutor.class); + + static final String QUERY = """ + query( + $fromLat: Float!, $fromLon: Float!, + $toLat: Float!, $toLon: Float!, + $arriveBy: Boolean!, + $accessMode: StreetMode!, $egressMode: StreetMode! + ) { + trip( + from: { coordinates: { latitude: $fromLat, longitude: $fromLon } } + to: { coordinates: { latitude: $toLat, longitude: $toLon } } + arriveBy: $arriveBy + modes: { accessMode: $accessMode, egressMode: $egressMode } + ) { + tripPatterns { + duration + legs { + mode + duration + fromPlace { name } + toPlace { name } + line { publicCode } + pointsOnLink { points } + } + } + } + } + """; + + private final OtpServerRequestContext serverContext; + private final TransmodelRequestContext requestContext; + private final GraphQLSchema schema; + private final ModeCombinations modeCombinations; + + TransmodelWarmupQueryExecutor( + OtpServerRequestContext context, + List accessModes, + List egressModes + ) { + this.serverContext = context; + this.schema = context.transmodelSchema(); + this.requestContext = new TransmodelRequestContext( + context, + context.routingService(), + context.transitService(), + context.empiricalDelayService() + ); + this.modeCombinations = new ModeCombinations(accessModes, egressModes); + } + + @Override + public boolean execute(WgsCoordinate from, WgsCoordinate to, boolean arriveBy, int queryCount) { + var variables = buildVariables(from, to, arriveBy, queryCount); + + var input = ExecutionInput.newExecutionInput() + .query(QUERY) + .context(requestContext) + .root(serverContext) + .variables(variables) + .build(); + + // The AbortOnUnprocessableRequestExecutionStrategy has per-query state + // (ProgressTracker) and must be created fresh for each execution. + try (var strategy = new AbortOnUnprocessableRequestExecutionStrategy()) { + var graphQL = GraphQL.newGraphQL(schema).queryExecutionStrategy(strategy).build(); + var result = graphQL.execute(input); + if (!result.getErrors().isEmpty()) { + LOG.warn("Warmup query had GraphQL errors: {}", result.getErrors()); + return false; + } + return true; + } + } + + private static String toGraphQLName(StreetMode mode) { + return EnumTypes.STREET_MODE.getValues() + .stream() + .filter(v -> v.getValue() == mode) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No TransModel mapping for " + mode)) + .getName(); + } + + Map buildVariables( + WgsCoordinate from, + WgsCoordinate to, + boolean arriveBy, + int queryCount + ) { + return Map.of( + "fromLat", + from.latitude(), + "fromLon", + from.longitude(), + "toLat", + to.latitude(), + "toLon", + to.longitude(), + "arriveBy", + arriveBy, + "accessMode", + toGraphQLName(modeCombinations.access(queryCount)), + "egressMode", + toGraphQLName(modeCombinations.egress(queryCount)) + ); + } +} diff --git a/application/src/main/java/org/opentripplanner/warmup/WarmupLauncher.java b/application/src/main/java/org/opentripplanner/warmup/WarmupLauncher.java new file mode 100644 index 00000000000..9c8f7e69bd2 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/WarmupLauncher.java @@ -0,0 +1,73 @@ +package org.opentripplanner.warmup; + +import javax.annotation.Nullable; +import org.opentripplanner.standalone.api.OtpServerRequestContext; +import org.opentripplanner.transit.service.TimetableRepository; +import org.opentripplanner.updater.GraphUpdaterManager; +import org.opentripplanner.warmup.api.WarmupParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Launches the application warmup background thread. + *

      + * Injected dependencies come from the Dagger {@link + * org.opentripplanner.warmup.configure.WarmupModule}. The launcher decides whether a warmup run + * is applicable (parameters present, updaters present, selected API enabled) and, when it is, + * starts a daemon thread running a {@link WarmupWorker}. + */ +public class WarmupLauncher { + + private static final Logger LOG = LoggerFactory.getLogger(WarmupLauncher.class); + + @Nullable + private final WarmupParameters parameters; + + private final OtpServerRequestContext serverContext; + private final TimetableRepository timetableRepository; + + public WarmupLauncher( + @Nullable WarmupParameters parameters, + OtpServerRequestContext serverContext, + TimetableRepository timetableRepository + ) { + this.parameters = parameters; + this.serverContext = serverContext; + this.timetableRepository = timetableRepository; + } + + /** + * Start the application warmup thread if configured and applicable. + *

      + * No warmup is started if parameters are null (warmup section absent in router-config.json), + * if no updaters are configured (health probe would immediately return "UP"), or if the + * selected API schema is not available. + */ + public void start() { + if (parameters == null) { + return; + } + GraphUpdaterManager updaterManager = timetableRepository.getUpdaterManager(); + if (updaterManager == null) { + LOG.info("Application warmup configured but no updaters found. Skipping warmup."); + return; + } + var schema = switch (parameters.api()) { + case TRANSMODEL -> serverContext.transmodelSchema(); + case GTFS -> serverContext.gtfsSchema(); + }; + if (schema == null) { + LOG.warn( + "Application warmup configured for {} API, but the schema is not available. " + + "Is the corresponding API feature enabled?", + parameters.api() + ); + return; + } + var worker = new WarmupWorker(parameters, serverContext, () -> updaterManager); + var thread = new Thread(worker, "app-warmup"); + thread.setDaemon(true); + thread.start(); + LOG.info("Application warmup thread started."); + } +} diff --git a/application/src/main/java/org/opentripplanner/warmup/WarmupQueryStrategy.java b/application/src/main/java/org/opentripplanner/warmup/WarmupQueryStrategy.java new file mode 100644 index 00000000000..3a3fe2e4652 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/WarmupQueryStrategy.java @@ -0,0 +1,14 @@ +package org.opentripplanner.warmup; + +import org.opentripplanner.street.geometry.WgsCoordinate; + +/** Strategy for executing warmup queries against a specific GraphQL API. */ +interface WarmupQueryStrategy { + /** + * Execute one warmup query. The strategy picks access/egress modes and any other per-query + * parameters from the given {@code queryCount}, so the caller only needs a running counter. + * + * @return true if the query executed without GraphQL errors. + */ + boolean execute(WgsCoordinate from, WgsCoordinate to, boolean arriveBy, int queryCount); +} diff --git a/application/src/main/java/org/opentripplanner/warmup/WarmupWorker.java b/application/src/main/java/org/opentripplanner/warmup/WarmupWorker.java new file mode 100644 index 00000000000..da90f05dca7 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/WarmupWorker.java @@ -0,0 +1,134 @@ +package org.opentripplanner.warmup; + +import java.time.Duration; +import java.time.Instant; +import java.util.function.Supplier; +import org.opentripplanner.standalone.api.OtpServerRequestContext; +import org.opentripplanner.updater.GraphUpdaterStatus; +import org.opentripplanner.utils.time.DurationUtils; +import org.opentripplanner.warmup.api.WarmupParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Runs GraphQL trip queries in a background thread during OTP startup to warm up the + * application before production traffic arrives. + *

      + * The worker sends sequential queries through the configured GraphQL API (TransModel or GTFS), + * exercising the full stack: GraphQL parsing, data fetchers, routing (Raptor + A*), itinerary + * filtering, and response serialization. This warms up JIT compilation, GraphQL schema caches, + * routing data structures, and other lazily initialized components. It alternates between + * depart-at / arrive-by and cycles through access/egress modes (walk, bike, car-to-park). + *

      + * It starts after Raptor transit data is created and stops when the health probe + * reports "UP" (all updaters primed). + */ +class WarmupWorker implements Runnable { + + private static final Logger LOG = LoggerFactory.getLogger(WarmupWorker.class); + private static final int MAX_QUERIES = 20; + + private final WarmupParameters parameters; + private final WarmupQueryStrategy queryStrategy; + private final Supplier updaterStatusProvider; + + WarmupWorker( + WarmupParameters parameters, + OtpServerRequestContext serverContext, + Supplier updaterStatusProvider + ) { + this.parameters = parameters; + this.updaterStatusProvider = updaterStatusProvider; + this.queryStrategy = switch (parameters.api()) { + case TRANSMODEL -> new TransmodelWarmupQueryExecutor( + serverContext, + parameters.accessModes(), + parameters.egressModes() + ); + case GTFS -> new GtfsWarmupQueryExecutor( + serverContext, + parameters.accessModes(), + parameters.egressModes() + ); + }; + } + + @Override + public void run() { + LOG.info( + "Application warmup started. Sending {} GraphQL trip queries from {} to {}.", + parameters.api(), + parameters.from(), + parameters.to() + ); + + var startTime = Instant.now(); + int queryCount = 0; + int failureCount = 0; + + try { + while (queryCount < MAX_QUERIES) { + if (isHealthy()) { + LOG.info( + "Application warmup complete: {} queries ({} failures) in {}. All updaters primed.", + queryCount, + failureCount, + DurationUtils.durationToStr(Duration.between(startTime, Instant.now())) + ); + return; + } + + queryCount++; + boolean arriveBy = queryCount % 2 == 0; + if (!executeQuery(queryCount, arriveBy)) { + failureCount++; + } + } + + LOG.info( + "Application warmup reached maximum of {} queries ({} failures) in {}" + + " before all updaters were primed.", + MAX_QUERIES, + failureCount, + DurationUtils.durationToStr(Duration.between(startTime, Instant.now())) + ); + } catch (Throwable e) { + LOG.error("Application warmup terminated by error after {} queries.", queryCount, e); + } + } + + /** @return true if the query succeeded without errors, false otherwise. */ + private boolean executeQuery(int queryCount, boolean arriveBy) { + var queryStart = Instant.now(); + try { + boolean success = queryStrategy.execute( + parameters.from(), + parameters.to(), + arriveBy, + queryCount + ); + var elapsed = Duration.between(queryStart, Instant.now()); + LOG.info( + "Warmup query #{} completed in {}.", + queryCount, + DurationUtils.durationToStr(elapsed) + ); + return success; + } catch (Exception e) { + var elapsed = Duration.between(queryStart, Instant.now()); + LOG.info( + "Warmup query #{} failed in {}: {}", + queryCount, + DurationUtils.durationToStr(elapsed), + e.getMessage() + ); + LOG.debug("Warmup query #{} exception detail", queryCount, e); + return false; + } + } + + private boolean isHealthy() { + var status = updaterStatusProvider.get(); + return status == null || status.listUnprimedUpdaters().isEmpty(); + } +} diff --git a/application/src/main/java/org/opentripplanner/warmup/api/WarmupApi.java b/application/src/main/java/org/opentripplanner/warmup/api/WarmupApi.java new file mode 100644 index 00000000000..96cf590c2d9 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/api/WarmupApi.java @@ -0,0 +1,25 @@ +package org.opentripplanner.warmup.api; + +import org.opentripplanner.core.model.doc.DocumentedEnum; + +/** Which GraphQL API to use for warmup queries. */ +public enum WarmupApi implements DocumentedEnum { + TRANSMODEL("Use the TransModel GraphQL API for warmup queries."), + GTFS("Use the GTFS GraphQL API for warmup queries."); + + private final String description; + + WarmupApi(String description) { + this.description = description; + } + + @Override + public String typeDescription() { + return "Which GraphQL API to use for warmup queries."; + } + + @Override + public String enumValueDescription() { + return description; + } +} diff --git a/application/src/main/java/org/opentripplanner/warmup/api/WarmupParameters.java b/application/src/main/java/org/opentripplanner/warmup/api/WarmupParameters.java new file mode 100644 index 00000000000..e0aec217f97 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/api/WarmupParameters.java @@ -0,0 +1,23 @@ +package org.opentripplanner.warmup.api; + +import java.util.List; +import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.street.model.StreetMode; + +/** + * Parameters for the application warmup feature. + *

      + * See the configuration for documentation on the parameter fields. + */ +public record WarmupParameters( + WarmupApi api, + WgsCoordinate from, + WgsCoordinate to, + List accessModes, + List egressModes +) { + public WarmupParameters { + accessModes = List.copyOf(accessModes); + egressModes = List.copyOf(egressModes); + } +} diff --git a/application/src/main/java/org/opentripplanner/warmup/configure/WarmupModule.java b/application/src/main/java/org/opentripplanner/warmup/configure/WarmupModule.java new file mode 100644 index 00000000000..ac553f9d2aa --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/configure/WarmupModule.java @@ -0,0 +1,39 @@ +package org.opentripplanner.warmup.configure; + +import dagger.Module; +import dagger.Provides; +import jakarta.inject.Singleton; +import javax.annotation.Nullable; +import org.opentripplanner.standalone.api.OtpServerRequestContext; +import org.opentripplanner.standalone.config.RouterConfig; +import org.opentripplanner.transit.service.TimetableRepository; +import org.opentripplanner.warmup.WarmupLauncher; +import org.opentripplanner.warmup.api.WarmupParameters; + +/** + * Dagger wiring for the application warmup feature. + *

      + * Provides the {@link WarmupParameters} binding (mapped from the JSON config section by {@code + * WarmupConfig}) and the {@link WarmupLauncher} that {@link + * org.opentripplanner.standalone.configure.ConstructApplication} uses to start the warmup thread + * after Raptor transit data and updaters have been set up. + */ +@Module +public class WarmupModule { + + @Provides + @Nullable + static WarmupParameters provideWarmupParameters(RouterConfig routerConfig) { + return routerConfig.warmupParameters(); + } + + @Provides + @Singleton + static WarmupLauncher provideWarmupLauncher( + @Nullable WarmupParameters parameters, + OtpServerRequestContext serverContext, + TimetableRepository timetableRepository + ) { + return new WarmupLauncher(parameters, serverContext, timetableRepository); + } +} diff --git a/application/src/main/java/org/opentripplanner/warmup/package.md b/application/src/main/java/org/opentripplanner/warmup/package.md new file mode 100644 index 00000000000..a750913b469 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/warmup/package.md @@ -0,0 +1,34 @@ +# Application Warmup + +Runs GraphQL trip queries in a background thread during OTP startup to warm up the application +(JIT compilation, GraphQL schema caches, routing data structures, etc.) before production traffic +arrives. + +## Lifecycle + +1. `ConstructApplication` obtains a `WarmupLauncher` from the Dagger factory + (`configure.WarmupModule` wires it) and calls `start()` after Raptor transit data is created + and updaters are configured. +2. A daemon thread sends sequential queries through the configured GraphQL API (TransModel or GTFS), + exercising the full stack: GraphQL parsing, data fetchers, routing (Raptor + A*), itinerary + filtering, and response serialization. +3. It alternates between depart-at / arrive-by and cycles through access/egress modes. +4. The thread stops when the health probe reports "UP" (all updaters primed). + +## Design + +The public API of the module lives in the `api` subpackage and exposes only value objects: +`WarmupParameters` (the configured parameter values) and `WarmupApi` (which GraphQL API to exercise). +`WarmupConfig` (in `standalone.config.routerconfig`) reads the JSON config section and produces a +`WarmupParameters` instance. + +`WarmupQueryStrategy` is the strategy interface with two implementations: +- `TransmodelWarmupQueryExecutor` -- builds and executes TransModel `trip` queries. +- `GtfsWarmupQueryExecutor` -- builds and executes GTFS `planConnection` queries. + +Each executor owns a `ModeCombinations` helper that holds the configured access/egress mode lists +and maps a running query counter to the next access/egress pair via modulo arithmetic. + +## Configuration + +Configured via the `warmup` section in `router-config.json`. See `WarmupConfig` for details. diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 0d11667d47a..e8853cb7e0d 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3906,6 +3906,12 @@ enum RoutingErrorCode { """ LOCATION_NOT_FOUND """ + No usable itineraries were found for the requested direct mode (e.g. walking, cycling, car, flex) + and no transit modes were included in the search. This is returned both when no route exists and + when routes were found but didn't pass quality filters. + """ + NO_DIRECT_MODE_CONNECTION + """ No stops are reachable from the start or end locations specified. You can try searching using a different access or egress mode, for example cycling instead of walking, @@ -4553,6 +4559,18 @@ input InputUnpreferred { useUnpreferredRoutesPenalty: Int @deprecated(reason : "Use unpreferredCost instead") } +""" +A generalized linear cost function that can be applied to a cost value. +The coefficient is used to scale the input Cost linearly and the constant is used +to add a fixed offset. If we assume a cost c, then f(c) = coefficient * c + constant. +""" +input LinearCostFunctionInput { + "The coefficient that scales the Cost input linearly." + coefficient: Float! + "The 0th degree constant of the function as a Cost, must be a non-negative integer" + constant: Cost! +} + "Filters an entity by a date range." input LocalDateRangeInput { """ @@ -5077,6 +5095,23 @@ input TransitPreferencesInput { an AND-condition. """ filters: [TransitFilterInput!] + """ + Relax generalized-cost when comparing itineraries with a different set of + transit-group-priorities. The groups are set server side for routes and + can not be configured in the API. + + This relaxes the comparison inside the routing engine for each stop-arrival. If two + paths have a different set of transit-group-priorities, then the generalized-cost + comparison is relaxed. The final set of paths are filtered through the normal + itinerary-filters. + + A relax-cost is used to increase the limit when comparing one cost to another cost + using a linear function applied to the generalized cost. + This is used to include more results into the result. A `coefficient=2.0` means a path (itinerary) + with twice as high cost as another one, is accepted. A `constant=300` means a "fixed" + constant is added to the limit. + """ + relaxTransitGroupPriority: LinearCostFunctionInput "Preferences related to cancellations and real-time." timetable: TimetablePreferencesInput "Preferences related to transfers between transit vehicles (typically between stops)." diff --git a/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index f671547e610..197515d5a4f 100644 --- a/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -1916,6 +1916,8 @@ enum ReportType { enum RoutingErrorCode { "The specified location is not close to any streets or transit stops" locationNotFound + "No usable itineraries were found for the requested direct mode and no transit was included in the search" + noDirectModeConnection "No stops are reachable from the location specified. You can try searching using a different access or egress mode" noStopsInRange "No transit connection was found between the origin and destination withing the operating day or the next day" diff --git a/application/src/test/java/org/opentripplanner/transit/model/_data/FeedScopedIdForTestFactory.java b/application/src/test-fixtures/java/org/opentripplanner/core/model/id/FeedScopedIdForTestFactory.java similarity index 71% rename from application/src/test/java/org/opentripplanner/transit/model/_data/FeedScopedIdForTestFactory.java rename to application/src/test-fixtures/java/org/opentripplanner/core/model/id/FeedScopedIdForTestFactory.java index 5f416471331..1703c80dc8a 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/_data/FeedScopedIdForTestFactory.java +++ b/application/src/test-fixtures/java/org/opentripplanner/core/model/id/FeedScopedIdForTestFactory.java @@ -1,6 +1,4 @@ -package org.opentripplanner.transit.model._data; - -import org.opentripplanner.core.model.id.FeedScopedId; +package org.opentripplanner.core.model.id; public class FeedScopedIdForTestFactory { diff --git a/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareModelForTest.java b/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareModelForTest.java index f7f13cead65..33b41b9fa7b 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareModelForTest.java +++ b/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareModelForTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.fares.model; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.OTHER_FEED_AGENCY; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.time.ZonedDateTime; import org.opentripplanner.core.model.i18n.I18NString; diff --git a/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareTestConstants.java b/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareTestConstants.java index ce487a23af0..a160f0568ca 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareTestConstants.java +++ b/application/src/test-fixtures/java/org/opentripplanner/ext/fares/model/FareTestConstants.java @@ -1,13 +1,13 @@ package org.opentripplanner.ext.fares.model; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.ext.fares.model.FareModelForTest.fareProduct; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.groupOfRoutes; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.time.LocalTime; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.fare.FareProduct; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model.basic.Money; import org.opentripplanner.transit.model.network.GroupOfRoutes; diff --git a/application/src/test-fixtures/java/org/opentripplanner/gtfs/graphbuilder/GtfsModuleTestFactory.java b/application/src/test-fixtures/java/org/opentripplanner/gtfs/graphbuilder/GtfsModuleTestFactory.java index 1fab2524543..b45dbab6ee1 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/gtfs/graphbuilder/GtfsModuleTestFactory.java +++ b/application/src/test-fixtures/java/org/opentripplanner/gtfs/graphbuilder/GtfsModuleTestFactory.java @@ -2,7 +2,7 @@ import java.util.List; import org.opentripplanner.core.model.time.LocalDateInterval; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.service.streetdetails.internal.DefaultStreetDetailsRepository; import org.opentripplanner.street.graph.Graph; @@ -25,7 +25,7 @@ public static GtfsModule forTest( new Deduplicator(), DataImportIssueStore.NOOP, transitPeriodLimit, - new DefaultFareServiceFactory(), + new GtfsFareServiceFactory(), 150.0, 120 ); diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexStopTimesForTest.java b/application/src/test-fixtures/java/org/opentripplanner/model/FlexStopTimesFactory.java similarity index 78% rename from application/src/ext-test/java/org/opentripplanner/ext/flex/FlexStopTimesForTest.java rename to application/src/test-fixtures/java/org/opentripplanner/model/FlexStopTimesFactory.java index 89e38edda54..1d17817bc53 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexStopTimesForTest.java +++ b/application/src/test-fixtures/java/org/opentripplanner/model/FlexStopTimesFactory.java @@ -1,21 +1,27 @@ -package org.opentripplanner.ext.flex; +package org.opentripplanner.model; import org.opentripplanner._support.geometry.Polygons; -import org.opentripplanner.model.PickDrop; -import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model.site.GroupStop; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.utils.time.TimeUtils; -public class FlexStopTimesForTest { +public class FlexStopTimesFactory { private static final TimetableRepositoryForTest TEST_MODEL = TimetableRepositoryForTest.of(); private static final StopLocation AREA_STOP = TEST_MODEL.areaStop("area") .withGeometry(Polygons.BERLIN) .build(); - private static final RegularStop REGULAR_STOP = TEST_MODEL.stop("stop").build(); + private static final RegularStop REGULAR_STOP_1 = TEST_MODEL.stop("stop-1").build(); + private static final RegularStop REGULAR_STOP_2 = TEST_MODEL.stop("stop-2").build(); + private static final RegularStop REGULAR_STOP_3 = TEST_MODEL.stop("stop-3").build(); + private static final GroupStop GROUP_STOP = TEST_MODEL.groupStop( + "group-stop1", + REGULAR_STOP_2, + REGULAR_STOP_3 + ); private static final Trip TRIP = TimetableRepositoryForTest.trip("flex").build(); @@ -89,10 +95,19 @@ public static StopTime regularStopWithContinuousDropOff(String time) { public static StopTime regularStop(int arrivalTime, int departureTime) { var stopTime = new StopTime(); - stopTime.setStop(REGULAR_STOP); + stopTime.setStop(REGULAR_STOP_1); stopTime.setArrivalTime(arrivalTime); stopTime.setDepartureTime(departureTime); stopTime.setTrip(TRIP); return stopTime; } + + public static StopTime groupStop(String windowStart, String windowEnd) { + var stopTime = new StopTime(); + stopTime.setStop(GROUP_STOP); + stopTime.setFlexWindowStart(TimeUtils.time(windowStart)); + stopTime.setFlexWindowEnd(TimeUtils.time(windowEnd)); + stopTime.setTrip(TRIP); + return stopTime; + } } diff --git a/application/src/test-fixtures/java/org/opentripplanner/model/plan/TestTransitLegBuilder.java b/application/src/test-fixtures/java/org/opentripplanner/model/plan/TestTransitLegBuilder.java index eacd09ec8c3..530c38a849a 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/model/plan/TestTransitLegBuilder.java +++ b/application/src/test-fixtures/java/org/opentripplanner/model/plan/TestTransitLegBuilder.java @@ -1,6 +1,6 @@ package org.opentripplanner.model.plan; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.time.LocalTime; diff --git a/application/src/test-fixtures/java/org/opentripplanner/osm/model/NodeBuilder.java b/application/src/test-fixtures/java/org/opentripplanner/osm/model/NodeBuilder.java index cd1284fded6..90b26726657 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/osm/model/NodeBuilder.java +++ b/application/src/test-fixtures/java/org/opentripplanner/osm/model/NodeBuilder.java @@ -4,9 +4,32 @@ public class NodeBuilder { + private final OsmNode node; + + private NodeBuilder(OsmNode node) { + this.node = node; + } + public static OsmNode node(long id, WgsCoordinate wgsCoordinate) { - var node = new OsmNode(wgsCoordinate.latitude(), wgsCoordinate.longitude()); - node.setId(id); + return of(id, wgsCoordinate).build(); + } + + public static NodeBuilder of() { + return new NodeBuilder(new OsmNode()); + } + + public static NodeBuilder of(long id, WgsCoordinate wgsCoordinate) { + var builder = new NodeBuilder(new OsmNode(wgsCoordinate.latitude(), wgsCoordinate.longitude())); + builder.node.setId(id); + return builder; + } + + public NodeBuilder withTag(String key, String value) { + node.addTag(key, value); + return this; + } + + public OsmNode build() { return node; } } diff --git a/application/src/test-fixtures/java/org/opentripplanner/osm/model/RelationBuilder.java b/application/src/test-fixtures/java/org/opentripplanner/osm/model/RelationBuilder.java index 784de04d1d0..7505db0a436 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/osm/model/RelationBuilder.java +++ b/application/src/test-fixtures/java/org/opentripplanner/osm/model/RelationBuilder.java @@ -5,23 +5,36 @@ public class RelationBuilder { private final OsmRelation relation = new OsmRelation(); public static RelationBuilder ofMultiPolygon() { - var builder = new RelationBuilder(); - builder.relation.addTag("type", "multipolygon"); - builder.relation.addTag("highway", "pedestrian"); - return builder; + return ofType("multipolygon").withTag("highway", "pedestrian"); } public static RelationBuilder ofTurnRestriction(String restrictionType) { + return ofType("restriction").withTag("restriction", restrictionType); + } + + public static RelationBuilder ofStopArea() { + return ofType("public_transport").withTag("public_transport", "stop_area"); + } + + public static RelationBuilder ofType(String type) { var builder = new RelationBuilder(); - builder.relation.addTag("type", "restriction"); - builder.relation.addTag("restriction", restrictionType); + builder.relation.addTag("type", type); return builder; } + public RelationBuilder withTag(String key, String value) { + relation.addTag(key, value); + return this; + } + public RelationBuilder withWayMember(long id, String role) { return withMember(id, role, OsmMemberType.WAY); } + public RelationBuilder withNodeMember(long id) { + return withMember(id, "", OsmMemberType.NODE); + } + public RelationBuilder withNodeMember(long id, String role) { return withMember(id, role, OsmMemberType.NODE); } diff --git a/application/src/test-fixtures/java/org/opentripplanner/routing/graphfinder/NoopSiteResolver.java b/application/src/test-fixtures/java/org/opentripplanner/routing/graphfinder/NoopSiteResolver.java index 70119e782bd..f3d21760f5d 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/routing/graphfinder/NoopSiteResolver.java +++ b/application/src/test-fixtures/java/org/opentripplanner/routing/graphfinder/NoopSiteResolver.java @@ -4,6 +4,7 @@ import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.transit.model.site.Entrance; import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.site.StopLocation; public class NoopSiteResolver implements SiteResolver { @@ -12,6 +13,11 @@ public RegularStop getStop(FeedScopedId id) { throw new NotImplementedException(); } + @Override + public StopLocation getStopLocation(FeedScopedId id) { + throw new NotImplementedException(); + } + @Override public Entrance getEntrance(FeedScopedId id) { throw new NotImplementedException(); diff --git a/application/src/test-fixtures/java/org/opentripplanner/routing/linking/TransitStopVertexBuilderFactory.java b/application/src/test-fixtures/java/org/opentripplanner/routing/linking/TransitStopVertexBuilderFactory.java index c1e90ad6a4e..5109d129aef 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/routing/linking/TransitStopVertexBuilderFactory.java +++ b/application/src/test-fixtures/java/org/opentripplanner/routing/linking/TransitStopVertexBuilderFactory.java @@ -1,6 +1,6 @@ package org.opentripplanner.routing.linking; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import org.locationtech.jts.geom.Coordinate; import org.opentripplanner.street.geometry.GeometryUtils; diff --git a/application/src/test-fixtures/java/org/opentripplanner/street/graph/GraphFetcher.java b/application/src/test-fixtures/java/org/opentripplanner/street/graph/GraphFetcher.java new file mode 100644 index 00000000000..92a7088eb15 --- /dev/null +++ b/application/src/test-fixtures/java/org/opentripplanner/street/graph/GraphFetcher.java @@ -0,0 +1,26 @@ +package org.opentripplanner.street.graph; + +import org.opentripplanner.osm.model.OsmNode; +import org.opentripplanner.street.model.vertex.OsmVertex; + +public class GraphFetcher { + + private final Graph graph; + + public GraphFetcher(Graph graph) { + this.graph = graph; + } + + public OsmVertex getVertexForOsmNode(OsmNode node) { + return graph + .getVerticesOfType(OsmVertex.class) + .stream() + .filter(v -> v.nodeId() == node.getId()) + .findFirst() + .orElseThrow(); + } + + public Graph graph() { + return graph; + } +} diff --git a/application/src/test-fixtures/java/org/opentripplanner/street/model/StreetModelForTest.java b/application/src/test-fixtures/java/org/opentripplanner/street/model/StreetModelForTest.java index 54b9c81d64d..28de61e5595 100644 --- a/application/src/test-fixtures/java/org/opentripplanner/street/model/StreetModelForTest.java +++ b/application/src/test-fixtures/java/org/opentripplanner/street/model/StreetModelForTest.java @@ -1,6 +1,6 @@ package org.opentripplanner.street.model; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.HashSet; import java.util.Set; diff --git a/application/src/test/java/org/opentripplanner/ConstantsForTests.java b/application/src/test/java/org/opentripplanner/ConstantsForTests.java index c0d70bd2de1..308086df3de 100644 --- a/application/src/test/java/org/opentripplanner/ConstantsForTests.java +++ b/application/src/test/java/org/opentripplanner/ConstantsForTests.java @@ -14,7 +14,7 @@ import org.opentripplanner.datastore.api.CompositeDataSource; import org.opentripplanner.datastore.api.FileType; import org.opentripplanner.datastore.file.DirectoryDataSource; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.model.ConfiguredCompositeDataSource; @@ -147,7 +147,7 @@ public static TestOtpModel buildNewPortlandGraph(boolean withElevation) { try { var graph = new Graph(); var timetableRepository = new TimetableRepository(new SiteRepository()); - var fareFactory = new DefaultFareServiceFactory(); + var fareFactory = new GtfsFareServiceFactory(); // Add street data from OSM { var osmModule = OsmModuleTestFactory.of(new DefaultOsmProvider(PORTLAND_CENTRAL_OSM, false)) @@ -239,7 +239,7 @@ public static TestOtpModel buildOsmAndGtfsGraph(File osmPath, File gtfsPath) { otpModel.graph(), otpModel.timetableRepository(), gtfsPath, - new DefaultFareServiceFactory(), + new GtfsFareServiceFactory(), null ); @@ -250,7 +250,7 @@ public static TestOtpModel buildOsmAndGtfsGraph(File osmPath, File gtfsPath) { } public static TestOtpModel buildGtfsGraph(File gtfsPath) { - return buildGtfsGraph(gtfsPath, new DefaultFareServiceFactory()); + return buildGtfsGraph(gtfsPath, new GtfsFareServiceFactory()); } public static TestOtpModel buildGtfsGraph(File gtfsFile, FareServiceFactory fareServiceFactory) { diff --git a/application/src/test/java/org/opentripplanner/GtfsTest.java b/application/src/test/java/org/opentripplanner/GtfsTest.java index 5b4874afb63..0fcdd35830d 100644 --- a/application/src/test/java/org/opentripplanner/GtfsTest.java +++ b/application/src/test/java/org/opentripplanner/GtfsTest.java @@ -21,13 +21,13 @@ import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; -import org.opentripplanner.api.common.LocationStringParser; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.core.model.time.LocalDateInterval; import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareService; import org.opentripplanner.gtfs.graphbuilder.GtfsBundle; import org.opentripplanner.gtfs.graphbuilder.GtfsBundleTestFactory; import org.opentripplanner.gtfs.graphbuilder.GtfsModule; +import org.opentripplanner.model.GenericLocation; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Leg; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RealTimeRaptorTransitDataUpdater; @@ -97,10 +97,10 @@ public Itinerary plan( .withDateTime(Instant.ofEpochSecond(Math.abs(dateTime))); if (fromVertex != null && !fromVertex.isEmpty()) { - builder.withFrom(LocationStringParser.getGenericLocation(null, FEED_ID + ":" + fromVertex)); + builder.withFrom(GenericLocation.fromStopId(FeedScopedId.of(FEED_ID, fromVertex))); } if (toVertex != null && !toVertex.isEmpty()) { - builder.withTo(LocationStringParser.getGenericLocation(null, FEED_ID + ":" + toVertex)); + builder.withTo(GenericLocation.fromStopId(FeedScopedId.of(FEED_ID, toVertex))); } if (onTripId != null && !onTripId.isEmpty()) { // TODO VIA - set different on-board request diff --git a/application/src/test/java/org/opentripplanner/TestOtpModel.java b/application/src/test/java/org/opentripplanner/TestOtpModel.java index 320be49eadc..8c8e56d2c19 100644 --- a/application/src/test/java/org/opentripplanner/TestOtpModel.java +++ b/application/src/test/java/org/opentripplanner/TestOtpModel.java @@ -1,6 +1,6 @@ package org.opentripplanner; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.routing.fares.FareServiceFactory; import org.opentripplanner.street.graph.Graph; import org.opentripplanner.transfer.regular.TransferRepository; @@ -17,7 +17,7 @@ public TestOtpModel( TimetableRepository timetableRepository, TransferRepository transferRepository ) { - this(graph, timetableRepository, transferRepository, new DefaultFareServiceFactory()); + this(graph, timetableRepository, transferRepository, new GtfsFareServiceFactory()); } public TestOtpModel index() { diff --git a/application/src/test/java/org/opentripplanner/TestServerContext.java b/application/src/test/java/org/opentripplanner/TestServerContext.java index 97cf1cbc881..1b16288bdcf 100644 --- a/application/src/test/java/org/opentripplanner/TestServerContext.java +++ b/application/src/test/java/org/opentripplanner/TestServerContext.java @@ -151,7 +151,6 @@ public static OtpServerRequestContext createServerContext( null, null, null, - null, null ); } diff --git a/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java b/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java index cab62f54b77..abf4461538e 100644 --- a/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java +++ b/application/src/test/java/org/opentripplanner/api/common/LocationStringParserTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import java.util.Optional; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Coordinate; import org.opentripplanner.core.model.id.FeedScopedId; @@ -12,94 +13,63 @@ public class LocationStringParserTest { @Test public void testFromOldStyleString() { - GenericLocation loc = LocationStringParser.fromOldStyleString("name::12345"); - assertEquals("name", loc.label); - assertNull(loc.stopId); - assertNull(loc.lat); - assertNull(loc.lng); - assertNull(loc.getCoordinate()); + var loc = LocationStringParser.fromOldStyleString("name::12345"); + assertEquals(Optional.empty(), loc); } @Test public void testWithLabelAndCoord() { - GenericLocation loc = LocationStringParser.fromOldStyleString("name::1.0,2.5"); - assertEquals("name", loc.label); - assertNull(loc.stopId); - assertEquals(Double.valueOf(1.0), loc.lat); - assertEquals(Double.valueOf(2.5), loc.lng); + GenericLocation loc = LocationStringParser.fromOldStyleString("name::1.0,2.5").get(); + assertEquals("name", loc.label()); + assertNull(loc.stopId()); assertEquals(new Coordinate(2.5, 1.0), loc.getCoordinate()); - loc = LocationStringParser.fromOldStyleString("Label Label::-15.0, 200"); - assertEquals("Label Label", loc.label); - assertNull(loc.stopId); - assertEquals(Double.valueOf(-15.0), loc.lat); - assertEquals(Double.valueOf(200), loc.lng); - assertEquals(new Coordinate(200, -15), loc.getCoordinate()); + loc = LocationStringParser.fromOldStyleString("Label Label::-15.0, 170").get(); + assertEquals("Label Label", loc.label()); + assertNull(loc.stopId()); + assertEquals(new Coordinate(170, -15), loc.getCoordinate()); - loc = LocationStringParser.fromOldStyleString("A Label::122,-22.3"); - assertEquals("A Label", loc.label); - assertNull(loc.stopId); - assertEquals(Double.valueOf(122), loc.lat); - assertEquals(Double.valueOf(-22.3), loc.lng); - assertEquals(new Coordinate(-22.3, 122), loc.getCoordinate()); + loc = LocationStringParser.fromOldStyleString("A Label::89,-22.3").get(); + assertEquals("A Label", loc.label()); + assertNull(loc.stopId()); + assertEquals(new Coordinate(-22.3, 89), loc.getCoordinate()); } @Test public void testWithId() { - GenericLocation loc = LocationStringParser.fromOldStyleString("name::aFeed:A1B2C3"); - assertEquals("name", loc.label); - assertEquals(loc.stopId, new FeedScopedId("aFeed", "A1B2C3")); - assertNull(loc.lat); - assertNull(loc.lng); + GenericLocation loc = LocationStringParser.fromOldStyleString("name::aFeed:A1B2C3").get(); + assertEquals("name", loc.label()); + assertEquals(loc.stopId(), new FeedScopedId("aFeed", "A1B2C3")); assertNull(loc.getCoordinate()); - loc = LocationStringParser.fromOldStyleString("feed:4321"); - assertNull(loc.label); - assertEquals(loc.stopId, new FeedScopedId("feed", "4321")); - assertNull(loc.lat); - assertNull(loc.lng); + loc = LocationStringParser.fromOldStyleString("feed:4321").get(); + assertNull(loc.label()); + assertEquals(loc.stopId(), new FeedScopedId("feed", "4321")); assertNull(loc.getCoordinate()); } @Test public void testWithCoordOnly() { - GenericLocation loc = LocationStringParser.fromOldStyleString("1.0,2.5"); - assertNull(loc.label); - assertNull(loc.stopId); - assertEquals(Double.valueOf(1.0), loc.lat); - assertEquals(Double.valueOf(2.5), loc.lng); + GenericLocation loc = LocationStringParser.fromOldStyleString("1.0,2.5").get(); + assertNull(loc.label()); + assertNull(loc.stopId()); assertEquals(new Coordinate(2.5, 1.0), loc.getCoordinate()); - loc = LocationStringParser.fromOldStyleString(" -15.0, 200"); - assertNull(loc.label); - assertNull(loc.stopId); - assertEquals(Double.valueOf(-15.0), loc.lat); - assertEquals(Double.valueOf(200), loc.lng); - assertEquals(new Coordinate(200, -15), loc.getCoordinate()); + loc = LocationStringParser.fromOldStyleString(" -15.0, 170").get(); + assertNull(loc.label()); + assertNull(loc.stopId()); + assertEquals(new Coordinate(170, -15), loc.getCoordinate()); - loc = LocationStringParser.fromOldStyleString("122,-22.3 "); - assertNull(loc.label); - assertNull(loc.stopId); - assertEquals(Double.valueOf(122), loc.lat); - assertEquals(Double.valueOf(-22.3), loc.lng); - assertEquals(new Coordinate(-22.3, 122), loc.getCoordinate()); + loc = LocationStringParser.fromOldStyleString("89,-22.3 ").get(); + assertNull(loc.label()); + assertNull(loc.stopId()); + assertEquals(new Coordinate(-22.3, 89), loc.getCoordinate()); } @Test public void testFromOldStyleStringIncomplete() { - String input = "0::"; - GenericLocation loc = LocationStringParser.fromOldStyleString(input); - assertEquals("0", loc.label); - assertNull(loc.stopId); - - input = "::1"; - loc = LocationStringParser.fromOldStyleString(input); - assertEquals("", loc.label); - assertNull(loc.stopId); - - input = "::"; - loc = LocationStringParser.fromOldStyleString(input); - assertEquals("", loc.label); - assertNull(loc.stopId); + assertEquals(Optional.empty(), LocationStringParser.fromOldStyleString("0::")); + assertEquals(Optional.empty(), LocationStringParser.fromOldStyleString("::1")); + assertEquals(Optional.empty(), LocationStringParser.fromOldStyleString("::")); } } diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java index 91762d72db3..534505e4a4c 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import static org.opentripplanner._support.time.ZoneIds.BERLIN; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.PlanTestConstants.D10_m; import static org.opentripplanner.model.plan.PlanTestConstants.T11_00; import static org.opentripplanner.model.plan.PlanTestConstants.T11_01; @@ -12,7 +13,6 @@ 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.TimetableRepositoryForTest.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; @@ -48,6 +48,7 @@ import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.ext.fares.ItineraryFaresDecorator; import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareService; import org.opentripplanner.model.FeedInfoTestFactory; @@ -208,7 +209,7 @@ static void setup() { var siteRepository = siteRepositoryBuilder.build(); var timetableRepository = new TimetableRepository(siteRepository); - var cal_id = TimetableRepositoryForTest.id("CAL_1"); + var cal_id = FeedScopedIdForTestFactory.id("CAL_1"); var trip = TimetableRepositoryForTest.trip("123") .withHeadsign(I18NString.of("Trip Headsign")) .withServiceId(cal_id) diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java index d569710ccb8..7d88bc07ac7 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/SchemaFactoryTest.java @@ -7,6 +7,7 @@ import graphql.language.FloatValue; import graphql.language.IntValue; +import graphql.language.ObjectValue; import graphql.language.Value; import graphql.schema.AsyncDataFetcher; import graphql.schema.DataFetcher; @@ -34,6 +35,8 @@ void testDefaultValueInjection() { double walkSpeed = 15; var maxTransfers = 2; var numItineraries = 63; + var relaxCoefficient = 1; + var relaxConstant = 0; var routeRequest = RouteRequest.of() .withPreferences(preferences -> { @@ -60,6 +63,28 @@ void testDefaultValueInjection() { ); assertEquals(maxTransfers, defaultMaxTransfers.getValue().intValue()); + var defaultRelax = (ObjectValue) getDefaultValueForField( + schema, + "TransitPreferencesInput", + "relaxTransitGroupPriority" + ); + var defaultRelaxCoefficient = (FloatValue) defaultRelax + .getObjectFields() + .stream() + .filter(f -> f.getName().equals("coefficient")) + .findFirst() + .orElseThrow() + .getValue(); + assertEquals(relaxCoefficient, defaultRelaxCoefficient.getValue().doubleValue()); + var defaultRelaxConstant = (IntValue) defaultRelax + .getObjectFields() + .stream() + .filter(f -> f.getName().equals("constant")) + .findFirst() + .orElseThrow() + .getValue(); + assertEquals(relaxConstant, defaultRelaxConstant.getValue().intValue()); + var defaultNumberOfItineraries = (IntValue) getDefaultValueForArgument( schema, "planConnection", diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index c0cb7629c30..72e43ce3d35 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -6,9 +6,9 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.params.provider.Arguments.of; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.street.model.VehicleRoutingOptimizeType.SAFE_STREETS; import static org.opentripplanner.street.model.VehicleRoutingOptimizeType.TRIANGLE; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import graphql.schema.DataFetchingEnvironment; import graphql.schema.DataFetchingEnvironmentImpl; @@ -90,7 +90,6 @@ class LegacyRouteRequestMapperTest implements PlanTestConstants { SchemaFactory.createSchemaWithDefaultInjection(routeRequest), GraphFinder.getInstance( graph.hasStreets, - transitService::getRegularStop, transitService::findRegularStopsByBoundingBox, linkingContextFactory ), diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java index 18ab6017d71..407ea13be95 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java @@ -32,10 +32,8 @@ void testMinimalArgs() { var defaultRequest = RouteRequest.defaultValue(); var routeRequest = RouteRequestMapper.toRouteRequest(env, testCtx.context()); - assertEquals(_RouteRequestTestContext.ORIGIN.x, routeRequest.from().lat); - assertEquals(_RouteRequestTestContext.ORIGIN.y, routeRequest.from().lng); - assertEquals(_RouteRequestTestContext.DESTINATION.x, routeRequest.to().lat); - assertEquals(_RouteRequestTestContext.DESTINATION.y, routeRequest.to().lng); + assertEquals(_RouteRequestTestContext.ORIGIN, routeRequest.from().getCoordinate()); + assertEquals(_RouteRequestTestContext.DESTINATION, routeRequest.to().getCoordinate()); assertEquals(testCtx.locale(), routeRequest.preferences().locale()); assertEquals(defaultRequest.journey().wheelchair(), routeRequest.journey().wheelchair()); assertEquals(defaultRequest.arriveBy(), routeRequest.arriveBy()); @@ -140,10 +138,10 @@ void testStopLocationAndLabel() { ); var env = testCtx.executionContext(stopLocationArgs); var routeRequest = RouteRequestMapper.toRouteRequest(env, testCtx.context()); - assertEquals(FeedScopedId.parseStrict(stopA), routeRequest.from().stopId); - assertEquals(originLabel, routeRequest.from().label); - assertEquals(FeedScopedId.parseStrict(stopB), routeRequest.to().stopId); - assertEquals(destinationLabel, routeRequest.to().label); + assertEquals(FeedScopedId.parseStrict(stopA), routeRequest.from().stopId()); + assertEquals(originLabel, routeRequest.from().label()); + assertEquals(FeedScopedId.parseStrict(stopB), routeRequest.to().stopId()); + assertEquals(destinationLabel, routeRequest.to().label()); } @Test diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java index 778e201e6ec..39e9867bc11 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java @@ -132,4 +132,29 @@ void testTimetablePreferences() { assertEquals(includePlannedCancellations, transitPreferences.includePlannedCancellations()); assertEquals(includeRealTimeCancellations, transitPreferences.includeRealtimeCancellations()); } + + @Test + void testRelaxTransitGroupPriority() { + var args = testCtx.basicRequest(); + var constant = Cost.costOfSeconds(120); + var coefficient = 1.2; + var relaxTransitGroupPriority = Map.ofEntries( + entry("constant", constant), + entry("coefficient", coefficient) + ); + args.put( + "preferences", + Map.ofEntries( + entry( + "transit", + Map.ofEntries(entry("relaxTransitGroupPriority", relaxTransitGroupPriority)) + ) + ) + ); + var env = testCtx.executionContext(args); + var routeRequest = RouteRequestMapper.toRouteRequest(env, testCtx.context()); + var transitPreferences = routeRequest.preferences().transit(); + assertEquals(constant, transitPreferences.relaxTransitGroupPriority().constant()); + assertEquals(coefficient, transitPreferences.relaxTransitGroupPriority().coefficient()); + } } diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/_RouteRequestTestContext.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/_RouteRequestTestContext.java index 7ed6f883f01..58fbb76215f 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/_RouteRequestTestContext.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/_RouteRequestTestContext.java @@ -39,12 +39,12 @@ class _RouteRequestTestContext { static final Map ARGS = Map.ofEntries( entry( "origin", - Map.ofEntries(entry("location", Map.of("coordinate", mapCoordinate(ORIGIN.x, ORIGIN.y)))) + Map.ofEntries(entry("location", Map.of("coordinate", mapCoordinate(ORIGIN.y, ORIGIN.x)))) ), entry( "destination", Map.ofEntries( - entry("location", Map.of("coordinate", mapCoordinate(DESTINATION.x, DESTINATION.y))) + entry("location", Map.of("coordinate", mapCoordinate(DESTINATION.y, DESTINATION.x))) ) ) ); @@ -83,7 +83,6 @@ public _RouteRequestTestContext(Locale locale) { SchemaFactory.createSchemaWithDefaultInjection(routeRequest), GraphFinder.getInstance( graph.hasStreets, - transitService::getRegularStop, transitService::findRegularStopsByBoundingBox, linkingContextFactory ), diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java index c6d159402b7..a85aea7cb91 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/service/ApiTransitServiceTest.java @@ -2,7 +2,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; import static org.opentripplanner.updater.trip.UpdateIncrementality.FULL_DATASET; @@ -88,7 +88,7 @@ void justScheduledTrips() { * Tests that you get a single {@link TripTimeOnDate} for a stop in a pattern even if several * trips in the pattern have the same stop skipped. * - * @see https://github.com/opentripplanner/OpenTripPlanner/issues/6654 + * @link https://github.com/opentripplanner/OpenTripPlanner/issues/6654 */ @Test void skipStopInMultipleTripsInPattern() { diff --git a/application/src/test/java/org/opentripplanner/ext/flex/FlexAccessEgressBookingTest.java b/application/src/test/java/org/opentripplanner/ext/flex/FlexAccessEgressBookingTest.java index bc34dbeadc1..7b232c23330 100644 --- a/application/src/test/java/org/opentripplanner/ext/flex/FlexAccessEgressBookingTest.java +++ b/application/src/test/java/org/opentripplanner/ext/flex/FlexAccessEgressBookingTest.java @@ -7,6 +7,7 @@ import java.time.LocalTime; import java.util.List; import org.junit.jupiter.api.Test; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.ext.flex.trip.UnscheduledTrip; import org.opentripplanner.model.StopTime; import org.opentripplanner.street.search.state.State; @@ -42,7 +43,7 @@ private static FlexAccessEgress buildFlexAccessEgress( int alightPos, int requestedBookingTime ) { - var trip = UnscheduledTrip.of(TimetableRepositoryForTest.id("flex")) + var trip = UnscheduledTrip.of(FeedScopedIdForTestFactory.id("flex")) .withTrip(TimetableRepositoryForTest.trip("t1").build()) .withStopTimes(stopTimes) .build(); diff --git a/application/src/test/java/org/opentripplanner/gbfs/v2/GbfsStationStatusMapperTest.java b/application/src/test/java/org/opentripplanner/gbfs/v2/GbfsStationStatusMapperTest.java index 285f7e77147..7e3ff7e6dfb 100644 --- a/application/src/test/java/org/opentripplanner/gbfs/v2/GbfsStationStatusMapperTest.java +++ b/application/src/test/java/org/opentripplanner/gbfs/v2/GbfsStationStatusMapperTest.java @@ -5,12 +5,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.service.vehiclerental.model.ReturnPolicy.ANY_TYPE; import static org.opentripplanner.service.vehiclerental.model.ReturnPolicy.SPECIFIC_TYPES; import static org.opentripplanner.street.model.RentalFormFactor.BICYCLE; import static org.opentripplanner.street.model.RentalFormFactor.CAR; import static org.opentripplanner.street.model.RentalFormFactor.SCOOTER; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.util.List; import java.util.Map; diff --git a/application/src/test/java/org/opentripplanner/gbfs/v3/GbfsStationStatusMapperTest.java b/application/src/test/java/org/opentripplanner/gbfs/v3/GbfsStationStatusMapperTest.java index 8cf3db9cd10..ba402be379e 100644 --- a/application/src/test/java/org/opentripplanner/gbfs/v3/GbfsStationStatusMapperTest.java +++ b/application/src/test/java/org/opentripplanner/gbfs/v3/GbfsStationStatusMapperTest.java @@ -4,12 +4,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.service.vehiclerental.model.ReturnPolicy.ANY_TYPE; import static org.opentripplanner.service.vehiclerental.model.ReturnPolicy.SPECIFIC_TYPES; import static org.opentripplanner.street.model.RentalFormFactor.BICYCLE; import static org.opentripplanner.street.model.RentalFormFactor.CAR; import static org.opentripplanner.street.model.RentalFormFactor.SCOOTER; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import static shadow.org.assertj.core.api.Assertions.assertThat; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java index 36b5f0e6ce6..b746d66b303 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java @@ -64,8 +64,9 @@ static Stream herrenbergTestCases() { } /** - * We test that the platform area at Herrenberg station (https://www.openstreetmap.org/way/27558650) - * is correctly linked to the stop even though it is not the closest edge to the stop. + * We test that the platform area at Herrenberg station + * (https://www.openstreetmap.org/way/27558650) is correctly linked to the stop even though it is + * not the closest edge to the stop. */ @ParameterizedTest( name = "add boarding locations and link them to platform edges when skipVisibility={0}" @@ -201,8 +202,9 @@ void addAndLinkBoardingLocations(boolean areaVisibility, Set linkedVerti } /** - * We test that the underground platforms at Moorgate station (https://www.openstreetmap.org/way/1328222021) - * is correctly linked to the stop even though it is not the closest edge to the stop. + * We test that the underground platforms at Moorgate station + * (https://www.openstreetmap.org/way/1328222021) is correctly linked to the stop even though it + * is not the closest edge to the stop. */ @Test void testLinearPlatforms() { @@ -340,6 +342,113 @@ TransitStopVertex getPlatformVertex() { } } + /** + * Test that when two stops reference the same OSM platform area (via ref:IFOPT), only one + * OsmBoardingLocationVertex centroid is created for that area and both stops are linked to it. + *

      + * The Herrenberg platform area (way 27558650) has + * ref:IFOPT=de:08115:4512:4:101;de:08115:4512:4:102, so both stop IDs match the same area. + */ + @Test + void testDeduplicationOfAreaBoardinglocations() { + File file = ResourceLoader.of(OsmBoardingLocationsModuleTest.class).file( + "herrenberg-minimal.osm.pbf" + ); + + // Two stops that both match the same platform area via ref:IFOPT + RegularStop platform1 = testModel + .stop("de:08115:4512:4:101") + .withCoordinate(48.59328, 8.86128) + .build(); + RegularStop platform2 = testModel + .stop("de:08115:4512:4:102") + .withCoordinate(48.59328, 8.86128) + .build(); + + var siteRepo = testModel + .siteRepositoryBuilder() + .withRegularStops(List.of(platform1, platform2)) + .build(); + + var graph = new Graph(); + var timetableRepository = new TimetableRepository(siteRepo); + var factory = new VertexFactory(graph); + + var osmInfoRepository = new DefaultOsmInfoGraphBuildRepository(); + var osmModule = OsmModuleTestFactory.of(new DefaultOsmProvider(file, false)) + .withGraph(graph) + .withOsmInfoGraphBuildRepository(osmInfoRepository) + .builder() + .withBoardingAreaRefTags(Set.of("ref", "ref:IFOPT")) + .withAreaVisibility(true) + .build(); + + osmModule.buildGraph(); + + var platformVertex1 = factory.transitStop(ofStop(platform1)); + var platformVertex2 = factory.transitStop(ofStop(platform2)); + + timetableRepository.index(); + graph.index(); + + // Both vertices should start unlinked + assertEquals(0, platformVertex1.getIncoming().size()); + assertEquals(0, platformVertex1.getOutgoing().size()); + assertEquals(0, platformVertex2.getIncoming().size()); + assertEquals(0, platformVertex2.getOutgoing().size()); + + var osmService = new DefaultOsmInfoGraphBuildService(osmInfoRepository); + new OsmBoardingLocationsModule( + graph, + timetableRepository, + VertexLinkerTestFactory.of(graph), + osmService + ).buildGraph(); + + // Both vertices should now be linked + assertEquals(1, platformVertex1.getIncoming().size()); + assertEquals(1, platformVertex1.getOutgoing().size()); + assertEquals(1, platformVertex2.getIncoming().size()); + assertEquals(1, platformVertex2.getOutgoing().size()); + + var boardingLocations = graph.getVerticesOfType(OsmBoardingLocationVertex.class); + + // Only one centroid should exist for the shared platform area + var areaCentroids = boardingLocations + .stream() + .filter( + bl -> + bl.references.contains(platform1.getId().getId()) || + bl.references.contains(platform2.getId().getId()) + ) + .toList(); + assertEquals( + 1, + areaCentroids.size(), + "Expected exactly one OsmBoardingLocationVertex for the shared platform area, but found " + + areaCentroids.size() + ); + + // Both transit stop vertices should be connected to the same boarding location vertex + var linkedVertex1 = platformVertex1 + .getOutgoing() + .stream() + .findFirst() + .orElseThrow() + .getToVertex(); + var linkedVertex2 = platformVertex2 + .getOutgoing() + .stream() + .findFirst() + .orElseThrow() + .getToVertex(); + assertEquals( + linkedVertex1, + linkedVertex2, + "Both stops should be linked to the same deduplicated boarding location vertex" + ); + } + /** * Assert that a split vertex is near to the given centroid, and it is possible to travel between * the original vertices through the split vertex in a straight line @@ -379,8 +488,8 @@ private static void assertSplitVertex( } /** - * Assert that there is a one-way path from the beginning through the given vertex to the end - * or vice versa. + * Assert that there is a one-way path from the beginning through the given vertex to the end or + * vice versa. */ private static void assertConnections(Vertex vertex, Vertex beginning, Vertex end) { if (vertex == beginning || vertex == end) { diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java index 4a1e13c25ac..ed59bddfad3 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java @@ -5,10 +5,10 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner._support.geometry.Coordinates.KONGSBERG_PLATFORM_1; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.street.linking.VisibilityMode.TRAVERSE_AREA_EDGES; import static org.opentripplanner.street.model.StreetTraversalPermission.CAR; import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.util.Arrays; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/VehicleParkingLinkingTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/VehicleParkingLinkingTest.java index 1bbc5178d80..bf7a2a16854 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/VehicleParkingLinkingTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/VehicleParkingLinkingTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.List; import org.junit.jupiter.api.BeforeEach; diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessorTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessorTest.java index 1c34e86ba66..65339ee59dc 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessorTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/geometry/GeometryProcessorTest.java @@ -2,11 +2,11 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.graph_builder.issue.api.DataImportIssueStore.NOOP; import static org.opentripplanner.street.geometry.GeometryUtils.makeLineString; import static org.opentripplanner.street.geometry.SphericalDistanceLibrary.distance; import static org.opentripplanner.street.geometry.SphericalDistanceLibrary.moveMeters; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.util.List; import java.util.stream.IntStream; diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/NearbyStopFinderVisitorTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/NearbyStopFinderVisitorTest.java index cee2898ec46..0b62bc10fd6 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/NearbyStopFinderVisitorTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/NearbyStopFinderVisitorTest.java @@ -17,35 +17,22 @@ import org.opentripplanner.street.search.state.TestStateBuilder; import org.opentripplanner.transit.model._data.TransitTestEnvironment; import org.opentripplanner.transit.model._data.TransitTestEnvironmentBuilder; -import org.opentripplanner.transit.model.site.AreaStop; import org.opentripplanner.transit.model.site.RegularStop; class NearbyStopFinderVisitorTest { private static final TransitTestEnvironmentBuilder ENV = TransitTestEnvironment.of(); static final RegularStop STOP = ENV.stop("stop-1"); - static final AreaStop AREA_STOP = ENV.areaStop("area-1"); - - private static final StopResolver STOP_RESOLVER = new StopResolver() { - @Override - public RegularStop getRegularStop(FeedScopedId id) { - return STOP; - } - - @Override - public AreaStop getAreaStop(FeedScopedId id) { - return AREA_STOP; - } - }; + static final FeedScopedId AREA_STOP_ID = new FeedScopedId("A", "area-1"); @Test void collectsTransitStops() { - var visitor = new NearbyStopFinderVisitor(STOP_RESOLVER, Set.of(), Set.of(), false); + var visitor = new NearbyStopFinderVisitor(Set.of(), Set.of(), false); var state = TestStateBuilder.ofWalking().streetEdge().stop(STOP).build(); visitor.visitVertex(state); - var expected = NearbyStop.nearbyStopForState(state, STOP); + var expected = NearbyStop.nearbyStopForState(state, STOP.getId()); assertEquals(List.of(expected), visitor.transitStopsFound()); } @@ -53,7 +40,7 @@ void collectsTransitStops() { void skipsOriginVertices() { var state = TestStateBuilder.ofWalking().streetEdge().stop(STOP).build(); var originVertices = Set.of(state.getVertex()); - var visitor = new NearbyStopFinderVisitor(STOP_RESOLVER, originVertices, Set.of(), false); + var visitor = new NearbyStopFinderVisitor(originVertices, Set.of(), false); visitor.visitVertex(state); @@ -64,7 +51,7 @@ void skipsOriginVertices() { void skipsIgnoreVertices() { var state = TestStateBuilder.ofWalking().streetEdge().stop(STOP).build(); var ignoreVertices = Set.of(state.getVertex()); - var visitor = new NearbyStopFinderVisitor(STOP_RESOLVER, Set.of(), ignoreVertices, false); + var visitor = new NearbyStopFinderVisitor(Set.of(), ignoreVertices, false); visitor.visitVertex(state); @@ -73,7 +60,7 @@ void skipsIgnoreVertices() { @Test void skipsNonTransitVertices() { - var visitor = new NearbyStopFinderVisitor(STOP_RESOLVER, Set.of(), Set.of(), false); + var visitor = new NearbyStopFinderVisitor(Set.of(), Set.of(), false); // State at a regular intersection, not a transit stop var state = TestStateBuilder.ofWalking().streetEdge().build(); @@ -88,15 +75,15 @@ void collectsAreaStopsWhenFlexEnabled() { var vertex = StreetModelForTest.intersectionVertex(10, 10); var other = StreetModelForTest.intersectionVertex(10.1, 10.1); StreetModelForTest.streetEdge(vertex, other, StreetTraversalPermission.CAR); - vertex.addAreaStops(Set.of(AREA_STOP.getId())); + vertex.addAreaStops(Set.of(AREA_STOP_ID)); var state = new State(vertex, StreetSearchRequest.of().withStartTime(Instant.EPOCH).build()); - var visitor = new NearbyStopFinderVisitor(STOP_RESOLVER, Set.of(), Set.of(), false); + var visitor = new NearbyStopFinderVisitor(Set.of(), Set.of(), false); visitor.visitVertex(state); - assertEquals(1, visitor.areaStopStates().size()); - assertTrue(visitor.areaStopStates().containsKey(AREA_STOP)); - assertEquals(state, visitor.areaStopStates().get(AREA_STOP).iterator().next()); + assertEquals(1, visitor.statesForAreaStopIds().size()); + assertTrue(visitor.statesForAreaStopIds().containsKey(AREA_STOP_ID)); + assertEquals(state, visitor.statesForAreaStopIds().get(AREA_STOP_ID).iterator().next()); }); } @@ -106,13 +93,13 @@ void doesNotCollectAreaStopsWithoutCarPermission() { var vertex = StreetModelForTest.intersectionVertex(20, 20); var other = StreetModelForTest.intersectionVertex(20.1, 20.1); StreetModelForTest.streetEdge(vertex, other, StreetTraversalPermission.PEDESTRIAN); - vertex.addAreaStops(Set.of(AREA_STOP.getId())); + vertex.addAreaStops(Set.of(AREA_STOP_ID)); var state = new State(vertex, StreetSearchRequest.of().withStartTime(Instant.EPOCH).build()); - var visitor = new NearbyStopFinderVisitor(STOP_RESOLVER, Set.of(), Set.of(), false); + var visitor = new NearbyStopFinderVisitor(Set.of(), Set.of(), false); visitor.visitVertex(state); - assertTrue(visitor.areaStopStates().isEmpty()); + assertTrue(visitor.statesForAreaStopIds().isEmpty()); }); } @@ -123,14 +110,14 @@ void collectsAreaStopsInReverseDirection() { var other = StreetModelForTest.intersectionVertex(30.1, 30.1); // Create incoming CAR edge: other -> vertex StreetModelForTest.streetEdge(other, vertex, StreetTraversalPermission.CAR); - vertex.addAreaStops(Set.of(AREA_STOP.getId())); + vertex.addAreaStops(Set.of(AREA_STOP_ID)); var state = new State(vertex, StreetSearchRequest.of().withStartTime(Instant.EPOCH).build()); - var visitor = new NearbyStopFinderVisitor(STOP_RESOLVER, Set.of(), Set.of(), true); + var visitor = new NearbyStopFinderVisitor(Set.of(), Set.of(), true); visitor.visitVertex(state); - assertEquals(1, visitor.areaStopStates().size()); - assertTrue(visitor.areaStopStates().containsKey(AREA_STOP)); + assertEquals(1, visitor.statesForAreaStopIds().size()); + assertTrue(visitor.statesForAreaStopIds().containsKey(AREA_STOP_ID)); }); } } diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderMultipleLinksTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderMultipleLinksTest.java index daa04feb01a..cab190bbcfb 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderMultipleLinksTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderMultipleLinksTest.java @@ -23,11 +23,11 @@ class StreetNearbyStopFinderMultipleLinksTest extends GraphRoutingTest { private TransitStopVertex stopA; private TransitStopVertex stopB; private TransitStopVertex stopC; - private StopResolver stopResolver; @BeforeEach protected void setUp() throws Exception { - var model = modelOf( + // TODO this could be reimplemented to use StreetModelForTest + modelOf( new Builder() { @Override public void build() { @@ -54,7 +54,6 @@ public void build() { } } ); - this.stopResolver = new SiteRepositoryResolver(model.timetableRepository().getSiteRepository()); } @Test @@ -62,7 +61,7 @@ void testMaxStopCountRegression() { // Max-stop-count should work correctly even though there are multiple links B <-> stopB var durationLimit = Duration.ofMinutes(10); var maxStopCount = 3; - var finder = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount).build(); + var finder = StreetNearbyStopFinder.of(durationLimit, maxStopCount).build(); var sortedNearbyStops = sort( finder.findNearbyStops(stopA, RouteRequest.defaultValue(), StreetMode.WALK, false) @@ -78,7 +77,7 @@ void testMaxStopCountRegression() { * Verify that the nearby stop is zero distance and corresponds to the expected vertex */ void assertZeroDistanceStop(TransitStopVertex expected, NearbyStop nearbyStop) { - assertEquals(stopResolver.getRegularStop(expected.getId()), nearbyStop.stop); + assertEquals(expected.getId(), nearbyStop.stopId); assertEquals(0, nearbyStop.distance); assertEquals(0, nearbyStop.edges.size()); assertEquals(expected, nearbyStop.state.getVertex()); @@ -93,7 +92,7 @@ void assertStopAtDistance( double expectedDistance, NearbyStop nearbyStop ) { - assertEquals(stopResolver.getRegularStop(expected.getId()), nearbyStop.stop); + assertEquals(expected.getId(), nearbyStop.stopId); assertEquals(expectedDistance, nearbyStop.distance); assertEquals(expected, nearbyStop.state.getVertex()); assertFalse(nearbyStop.edges.isEmpty()); diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java index d32e447eb70..8d67017350e 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java @@ -29,11 +29,11 @@ class StreetNearbyStopFinderTest extends GraphRoutingTest { private TransitStopVertex stopB; private TransitStopVertex stopC; private TransitStopVertex stopD; - private StopResolver stopResolver; @BeforeEach protected void setUp() throws Exception { - var model = modelOf( + // TODO this could be reimplemented to use StreetModelForTest + modelOf( new GraphRoutingTest.Builder() { @Override public void build() { @@ -61,14 +61,13 @@ public void build() { } } ); - this.stopResolver = new SiteRepositoryResolver(model.timetableRepository().getSiteRepository()); } @Test void testIsolatedStop() { var durationLimit = Duration.ofMinutes(10); var maxStopCount = 0; - var finder = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount).build(); + var finder = StreetNearbyStopFinder.of(durationLimit, maxStopCount).build(); var nearbyStops = finder.findNearbyStops( isolatedStop, @@ -86,7 +85,7 @@ void testIsolatedStop() { void testMultipleStops() { var durationLimit = Duration.ofMinutes(10); var maxStopCount = 0; - var finder = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount).build(); + var finder = StreetNearbyStopFinder.of(durationLimit, maxStopCount).build(); var sortedNearbyStops = sort( finder.findNearbyStops(stopA, RouteRequest.defaultValue(), StreetMode.WALK, false) @@ -103,7 +102,7 @@ void testMultipleStops() { void testMaxStopCount() { var durationLimit = Duration.ofMinutes(10); var maxStopCount = 2; - var finder = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount).build(); + var finder = StreetNearbyStopFinder.of(durationLimit, maxStopCount).build(); var sortedNearbyStops = sort( finder.findNearbyStops(stopA, RouteRequest.defaultValue(), StreetMode.WALK, false) @@ -124,7 +123,7 @@ void testDurationLimit() { .withPreferences(b -> b.withWalk(w -> w.withSpeed(1.0))) .buildDefault(); - var finder = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount).build(); + var finder = StreetNearbyStopFinder.of(durationLimit, maxStopCount).build(); var sortedNearbyStops = sort( finder.findNearbyStops(stopA, routeRequest, StreetMode.WALK, false) ); @@ -139,7 +138,7 @@ void testIgnoreStops() { var durationLimit = Duration.ofMinutes(10); var maxStopCount = 0; Set ignore = Set.of(stopA, stopB); - var finder = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount) + var finder = StreetNearbyStopFinder.of(durationLimit, maxStopCount) .withIgnoreVertices(ignore) .build(); @@ -157,7 +156,7 @@ void testIgnoreStopsWithMaxStops() { var durationLimit = Duration.ofMinutes(10); var maxStopCount = 1; Set ignore = Set.of(stopA, stopB); - var finder = StreetNearbyStopFinder.of(stopResolver, durationLimit, maxStopCount) + var finder = StreetNearbyStopFinder.of(durationLimit, maxStopCount) .withIgnoreVertices(ignore) .build(); @@ -177,7 +176,7 @@ static List sort(Collection stops) { * Verify that the nearby stop is zero distance and corresponds to the expected vertex */ void assertZeroDistanceStop(TransitStopVertex expected, NearbyStop nearbyStop) { - assertEquals(stopResolver.getRegularStop(expected.getId()), nearbyStop.stop); + assertEquals(expected.getId(), nearbyStop.stopId); assertEquals(0, nearbyStop.distance); assertEquals(0, nearbyStop.edges.size()); assertEquals(expected, nearbyStop.state.getVertex()); @@ -192,7 +191,7 @@ void assertStopAtDistance( double expectedDistance, NearbyStop nearbyStop ) { - assertEquals(stopResolver.getRegularStop(expected.getId()), nearbyStop.stop); + assertEquals(expected.getId(), nearbyStop.stopId); assertEquals(expectedDistance, nearbyStop.distance); assertEquals(expected, nearbyStop.state.getVertex()); assertFalse(nearbyStop.edges.isEmpty()); diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java index cd55843943a..ee60230ff47 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java @@ -277,7 +277,7 @@ private void testBuildingAreas(boolean skipVisibility) { Vertex bottomV = graph.getVertex(VertexLabel.osm(580290955)); Vertex topV = graph.getVertex(VertexLabel.osm(559271124)); - GraphPathFinder graphPathFinder = new GraphPathFinder(null); + GraphPathFinder graphPathFinder = new GraphPathFinder(); var pathList = graphPathFinder.graphPathFinderEntryPoint( request, Set.of(bottomV), diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java new file mode 100644 index 00000000000..807c7b347bf --- /dev/null +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/moduletests/StationEntrancesTest.java @@ -0,0 +1,85 @@ +package org.opentripplanner.graph_builder.module.osm.moduletests; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; +import org.opentripplanner.graph_builder.module.osm.OsmModuleTestFactory; +import org.opentripplanner.osm.TestOsmProvider; +import org.opentripplanner.osm.model.NodeBuilder; +import org.opentripplanner.osm.model.OsmNode; +import org.opentripplanner.osm.model.OsmRelation; +import org.opentripplanner.osm.model.RelationBuilder; +import org.opentripplanner.street.geometry.WgsCoordinate; +import org.opentripplanner.street.graph.Graph; +import org.opentripplanner.street.graph.GraphFetcher; +import org.opentripplanner.street.model.vertex.StationEntranceVertex; + +public class StationEntrancesTest { + + private static final GraphFetcher GRAPH = new GraphFetcher(new Graph()); + private static final OsmNode ENTRANCE_IN_STOP_AREA = NodeBuilder.of(1, new WgsCoordinate(0, 0)) + .withTag("entrance", "yes") + .build(); + private static final OsmNode ENTRANCE_OUTSIDE_STOP_AREA = NodeBuilder.of( + 2, + new WgsCoordinate(1, 1) + ) + .withTag("entrance", "yes") + .build(); + + private static final OsmNode SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA = NodeBuilder.of( + 3, + new WgsCoordinate(2, 2) + ) + .withTag("railway", "subway_entrance") + .withTag("entrance", "yes") + .build(); + + private static final OsmRelation STOP_AREA = RelationBuilder.ofStopArea() + .withNodeMember(ENTRANCE_IN_STOP_AREA.getId()) + .build(); + + @BeforeAll + public static void setUp() { + var osmProvider = TestOsmProvider.of() + .addWayFromNodes( + way -> way.addTag("highway", "footway"), + ENTRANCE_IN_STOP_AREA, + ENTRANCE_OUTSIDE_STOP_AREA, + SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA + ) + .addRelation(STOP_AREA) + .build(); + + OsmModuleTestFactory.of(osmProvider) + .withGraph(GRAPH.graph()) + .builder() + .withIncludeOsmStationEntrances(true) + .withIssueStore(DataImportIssueStore.NOOP) + .build() + .buildGraph(); + } + + @Test + void entranceInStopArea() { + assertInstanceOf(StationEntranceVertex.class, GRAPH.getVertexForOsmNode(ENTRANCE_IN_STOP_AREA)); + } + + @Test + void entranceOutsideStopArea() { + assertFalse( + GRAPH.getVertexForOsmNode(ENTRANCE_OUTSIDE_STOP_AREA) instanceof StationEntranceVertex + ); + } + + @Test + void stationEntranceOutsideStopArea() { + assertInstanceOf( + StationEntranceVertex.class, + GRAPH.getVertexForOsmNode(SUBWAY_ENTRANCE_OUTSIDE_STOP_AREA) + ); + } +} diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/stopconnectivity/StopConnectivityModuleTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/stopconnectivity/StopConnectivityModuleTest.java index b1e00b192f1..91d72f3a52a 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/stopconnectivity/StopConnectivityModuleTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/stopconnectivity/StopConnectivityModuleTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.graph_builder.module.stopconnectivity; import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.street.model.StreetModelForTest.intersectionVertex; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; import java.time.Duration; import org.junit.jupiter.api.Test; diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGeneratorTestData.java b/application/src/test/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGeneratorTestData.java index 608d515b4c1..cdca41ffe19 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGeneratorTestData.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGeneratorTestData.java @@ -2,6 +2,7 @@ import java.time.Duration; import java.util.List; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.module.transfer.api.RegularTransferParameters; import org.opentripplanner.graph_builder.module.transfer.api.TransferParametersForMode; @@ -153,13 +154,13 @@ public void build() { var agency = TimetableRepositoryForTest.agency("Agency"); tripPattern( - TripPattern.of(TimetableRepositoryForTest.id("TP0")) + TripPattern.of(FeedScopedIdForTestFactory.id("TP0")) .withRoute(route("R0", TransitMode.RAIL, agency)) .withStopPattern(new StopPattern(List.of(st(S_FAR_AWAY), st(S0)))) .build() ); tripPattern( - TripPattern.of(TimetableRepositoryForTest.id("TP1")) + TripPattern.of(FeedScopedIdForTestFactory.id("TP1")) .withRoute(route("R1", TransitMode.BUS, agency)) .withStopPattern( new StopPattern(List.of(st(S11, !withBoardingConstraint, true), st(S12))) @@ -167,7 +168,7 @@ public void build() { .build() ); tripPattern( - TripPattern.of(TimetableRepositoryForTest.id("TP2")) + TripPattern.of(FeedScopedIdForTestFactory.id("TP2")) .withRoute(route("R2", TransitMode.BUS, agency)) .withStopPattern(new StopPattern(List.of(st(S21), st(S22), st(S_FAR_AWAY)))) .withScheduledTimeTableBuilder(builder -> @@ -187,7 +188,7 @@ public void build() { if (includeCarFerryTrips) { tripPattern( - TripPattern.of(TimetableRepositoryForTest.id("TP4")) + TripPattern.of(FeedScopedIdForTestFactory.id("TP4")) .withRoute(route("R4", TransitMode.FERRY, agency)) .withStopPattern(new StopPattern(List.of(st(S_FAR_AWAY), st(S0), st(S12)))) .withScheduledTimeTableBuilder(b -> @@ -196,7 +197,7 @@ public void build() { .build() ); tripPattern( - TripPattern.of(TimetableRepositoryForTest.id("TP5")) + TripPattern.of(FeedScopedIdForTestFactory.id("TP5")) .withRoute(route("R5", TransitMode.FERRY, agency)) .withStopPattern(new StopPattern(List.of(st(S22), st(S23)))) .withScheduledTimeTableBuilder(b -> diff --git a/application/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java b/application/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java index 1ed2c270615..ff6f430d360 100644 --- a/application/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java +++ b/application/src/test/java/org/opentripplanner/gtfs/GenerateTripPatternsOperationTest.java @@ -13,6 +13,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.issue.service.DefaultDataImportIssueStore; import org.opentripplanner.graph_builder.issues.TripDegenerate; @@ -68,7 +69,7 @@ static void setupClass() { stopTimeC = new StopTime(); stopTimeC.setStop(stopC); - FeedScopedId serviceId1 = TimetableRepositoryForTest.id("SERVICE_ID_1"); + FeedScopedId serviceId1 = FeedScopedIdForTestFactory.id("SERVICE_ID_1"); trip1 = trip("TRIP_ID_1") .withServiceId(serviceId1) .withMode(TransitMode.RAIL) @@ -77,7 +78,7 @@ static void setupClass() { .build(); // same route, mode, submode and direction as trip1 - FeedScopedId serviceId2 = TimetableRepositoryForTest.id("SERVICE_ID_2"); + FeedScopedId serviceId2 = FeedScopedIdForTestFactory.id("SERVICE_ID_2"); trip2 = trip("TRIP_ID_2") .withServiceId(serviceId2) .withRoute(trip1.getRoute()) @@ -87,7 +88,7 @@ static void setupClass() { .build(); // same route, direction as trip1, different mode - FeedScopedId serviceId3 = TimetableRepositoryForTest.id("SERVICE_ID_3"); + FeedScopedId serviceId3 = FeedScopedIdForTestFactory.id("SERVICE_ID_3"); trip3 = trip("TRIP_ID_3") .withServiceId(serviceId3) .withRoute(trip1.getRoute()) @@ -96,7 +97,7 @@ static void setupClass() { .build(); // same route, mode, direction as trip1, different submode - FeedScopedId serviceId4 = TimetableRepositoryForTest.id("SERVICE_ID_4"); + FeedScopedId serviceId4 = FeedScopedIdForTestFactory.id("SERVICE_ID_4"); trip4 = trip("TRIP_ID_4") .withServiceId(serviceId4) .withRoute(trip1.getRoute()) @@ -106,7 +107,7 @@ static void setupClass() { .build(); // same route, mode as trip1, different direction - FeedScopedId serviceId5 = TimetableRepositoryForTest.id("SERVICE_ID_5"); + FeedScopedId serviceId5 = FeedScopedIdForTestFactory.id("SERVICE_ID_5"); trip5 = trip("TRIP_ID_5") .withServiceId(serviceId5) .withRoute(trip1.getRoute()) diff --git a/application/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java b/application/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java index 81ce4881097..369152059f5 100644 --- a/application/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java +++ b/application/src/test/java/org/opentripplanner/gtfs/interlining/InterlineProcessorTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.gtfs.mapping.StaySeatedNotAllowed; import org.opentripplanner.model.calendar.CalendarServiceData; @@ -163,7 +164,7 @@ private static TripPattern tripPattern(String tripId, String blockId, String ser var stopPattern = new StopPattern(stopTimes); var tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, new Deduplicator()); - return TripPattern.of(TimetableRepositoryForTest.id(tripId)) + return TripPattern.of(FeedScopedIdForTestFactory.id(tripId)) .withRoute(trip.getRoute()) .withStopPattern(stopPattern) .withScheduledTimeTableBuilder(builder -> builder.addTripTimes(tripTimes)) diff --git a/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java b/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java index 950e6e32642..3f7dfee3e5e 100644 --- a/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java +++ b/application/src/test/java/org/opentripplanner/model/GenericLocationTest.java @@ -1,13 +1,13 @@ package org.opentripplanner.model; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.model.GenericLocation.UNKNOWN; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; import org.opentripplanner._support.asserts.AssertEqualsAndHashCode; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.street.geometry.WgsCoordinate; class GenericLocationTest { @@ -15,33 +15,55 @@ class GenericLocationTest { private static final FeedScopedId STOP_ID = new FeedScopedId("F", "Stop:1"); private static final double LATITUDE = 20.0; private static final double LONGITUDE = 30.0; - private final GenericLocation subject = new GenericLocation(LABEL, STOP_ID, LATITUDE, LONGITUDE); + private final GenericLocation subject = GenericLocation.fromStopIdWithFallback( + STOP_ID, + LATITUDE, + LONGITUDE, + LABEL + ); + + private final GenericLocation other = GenericLocation.fromCoordinate(LATITUDE, LONGITUDE, LABEL); + + @Test + void fromStopIdWithFallback() { + var location = GenericLocation.fromStopIdWithFallback(STOP_ID, LATITUDE, LONGITUDE, LABEL); + assertEquals(STOP_ID, location.stopId()); + assertEquals(new WgsCoordinate(LATITUDE, LONGITUDE), location.wgsCoordinate()); + assertEquals(LABEL, location.label()); + } @Test void fromStopId() { - assertEquals(STOP_ID, subject.stopId); + var location = GenericLocation.fromStopId(STOP_ID, LABEL); + assertEquals(STOP_ID, location.stopId()); + assertNull(location.wgsCoordinate()); + assertEquals(LABEL, location.label()); } @Test - void getCoordinate() { - assertEquals(STOP_ID, subject.stopId); + void fromCoordinate() { + var location = GenericLocation.fromCoordinate(LATITUDE, LONGITUDE, LABEL); + assertNull(location.stopId()); + assertEquals(new WgsCoordinate(LATITUDE, LONGITUDE), location.wgsCoordinate()); + assertEquals(LABEL, location.label()); } @Test - void isSpecified() { - assertTrue(subject.isSpecified()); - assertFalse(UNKNOWN.isSpecified()); + void testInvalid() { + assertThrows(NullPointerException.class, () -> GenericLocation.fromStopId(null)); + assertThrows(NullPointerException.class, () -> + GenericLocation.fromStopIdWithFallback(null, 0.0, 0.0, "label") + ); } @Test void testEquals() { - var copy = new GenericLocation(LABEL, STOP_ID, LATITUDE, LONGITUDE); - AssertEqualsAndHashCode.verify(subject).sameAs(copy).differentFrom(UNKNOWN); + var copy = GenericLocation.fromStopIdWithFallback(STOP_ID, LATITUDE, LONGITUDE, LABEL); + AssertEqualsAndHashCode.verify(subject).sameAs(copy).differentFrom(other); } @Test void testToString() { - assertEquals("Unknown location", UNKNOWN.toString()); assertEquals("A place F:Stop:1 (20.0, 30.0)", subject.toString()); } } diff --git a/application/src/test/java/org/opentripplanner/model/calendar/impl/CalendarServiceDataFactoryImplTest.java b/application/src/test/java/org/opentripplanner/model/calendar/impl/CalendarServiceDataFactoryImplTest.java index 84c3c9473f3..bc29d49f12b 100644 --- a/application/src/test/java/org/opentripplanner/model/calendar/impl/CalendarServiceDataFactoryImplTest.java +++ b/application/src/test/java/org/opentripplanner/model/calendar/impl/CalendarServiceDataFactoryImplTest.java @@ -5,10 +5,10 @@ import static java.util.stream.Collectors.toList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.gtfs.GtfsContextBuilder.contextBuilder; import static org.opentripplanner.model.calendar.ServiceCalendarDate.EXCEPTION_TYPE_REMOVE; import static org.opentripplanner.model.calendar.impl.CalendarServiceDataFactoryImpl.merge; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.io.IOException; import java.time.LocalDate; diff --git a/application/src/test/java/org/opentripplanner/model/impl/DefaultTransitDataImportTest.java b/application/src/test/java/org/opentripplanner/model/impl/DefaultTransitDataImportTest.java index 2ac1f02db0f..9a2b3f6d5b3 100644 --- a/application/src/test/java/org/opentripplanner/model/impl/DefaultTransitDataImportTest.java +++ b/application/src/test/java/org/opentripplanner/model/impl/DefaultTransitDataImportTest.java @@ -4,9 +4,9 @@ import static java.util.stream.Collectors.joining; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.gtfs.GtfsContextBuilder.contextBuilder; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.FEED_ID; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.io.IOException; import java.util.ArrayList; diff --git a/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderLimitPeriodTest.java b/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderLimitPeriodTest.java index ee860fea58e..cdda8e2fc02 100644 --- a/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderLimitPeriodTest.java +++ b/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderLimitPeriodTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.core.model.time.LocalDateInterval; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.PickDrop; @@ -43,10 +44,10 @@ public class TransitDataImportBuilderLimitPeriodTest { private static final LocalDate D1 = LocalDate.of(2020, 1, 8); private static final LocalDate D2 = LocalDate.of(2020, 1, 15); private static final LocalDate D3 = LocalDate.of(2020, 1, 31); - private static final FeedScopedId SERVICE_C_IN = TimetableRepositoryForTest.id("CalSrvIn"); - private static final FeedScopedId SERVICE_D_IN = TimetableRepositoryForTest.id("CalSrvDIn"); - private static final FeedScopedId SERVICE_C_OUT = TimetableRepositoryForTest.id("CalSrvOut"); - private static final FeedScopedId SERVICE_D_OUT = TimetableRepositoryForTest.id("CalSrvDOut"); + private static final FeedScopedId SERVICE_C_IN = FeedScopedIdForTestFactory.id("CalSrvIn"); + private static final FeedScopedId SERVICE_D_IN = FeedScopedIdForTestFactory.id("CalSrvDIn"); + private static final FeedScopedId SERVICE_C_OUT = FeedScopedIdForTestFactory.id("CalSrvOut"); + private static final FeedScopedId SERVICE_D_OUT = FeedScopedIdForTestFactory.id("CalSrvDOut"); private static final Deduplicator DEDUPLICATOR = new Deduplicator(); private static final TimetableRepositoryForTest TEST_MODEL = TimetableRepositoryForTest.of(); private static final RegularStop STOP_1 = TEST_MODEL.stop("Stop-1").build(); @@ -190,11 +191,11 @@ private static StopTime createStopTime(RegularStop stop, int time) { } private static FeedScopedId newId() { - return TimetableRepositoryForTest.id(Integer.toString(++SEQ_NR)); + return FeedScopedIdForTestFactory.id(Integer.toString(++SEQ_NR)); } private TripPattern createTripPattern(Collection trips) { - FeedScopedId patternId = TimetableRepositoryForTest.id( + FeedScopedId patternId = FeedScopedIdForTestFactory.id( trips .stream() .map(t -> t.getId().getId()) diff --git a/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderTest.java b/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderTest.java index 66d051dfc90..9a64b4cb0f8 100644 --- a/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/model/impl/TransitDataImportBuilderTest.java @@ -14,6 +14,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.ConstantsForTests; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.FeedInfoTestFactory; import org.opentripplanner.model.Frequency; import org.opentripplanner.model.calendar.ServiceCalendar; @@ -24,7 +25,7 @@ public class TransitDataImportBuilderTest { private static final String FEED_ID = TimetableRepositoryForTest.FEED_ID; - private static final FeedScopedId SERVICE_WEEKDAYS_ID = TimetableRepositoryForTest.id("weekdays"); + private static final FeedScopedId SERVICE_WEEKDAYS_ID = FeedScopedIdForTestFactory.id("weekdays"); private static TransitDataImportBuilder subject; diff --git a/application/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java b/application/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java index 25196df35aa..bbee09d96d4 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java +++ b/application/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java @@ -15,6 +15,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.basic.Cost; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.framework.model.TimeAndCost; import org.opentripplanner.model.SystemNotice; import org.opentripplanner.model.plan.leg.ScheduledTransitLeg; @@ -74,7 +75,7 @@ void testDerivedFieldsWithBusAllTheWay() { assertEquals(newTime(T11_00), firstLeg.startTime()); assertEquals(newTime(T11_10), firstLeg.endTime()); assertEquals(TransitMode.BUS, result.transitLeg(0).mode()); - assertEquals(TimetableRepositoryForTest.id("55"), firstLeg.trip().getId()); + assertEquals(FeedScopedIdForTestFactory.id("55"), firstLeg.trip().getId()); assertEquals(A_TO_B_DISTANCE, firstLeg.distanceMeters(), DISTANCE_DELTA); assertEquals("A ~ BUS 55 11:00 11:10 ~ B [C₁720]", result.toStr()); @@ -99,7 +100,7 @@ void testDerivedFieldsWithTrainAllTheWay() { assertEquals(newTime(T11_05), firstLeg.startTime()); assertEquals(newTime(T11_15), firstLeg.endTime()); assertEquals(TransitMode.RAIL, result.transitLeg(0).mode()); - assertEquals(TimetableRepositoryForTest.id("20"), firstLeg.trip().getId()); + assertEquals(FeedScopedIdForTestFactory.id("20"), firstLeg.trip().getId()); assertEquals(A_TO_B_DISTANCE, firstLeg.distanceMeters(), DISTANCE_DELTA); assertEquals("A ~ RAIL R2 11:05 11:15 ~ B [C₁720]", result.toStr()); diff --git a/application/src/test/java/org/opentripplanner/model/plan/PlaceTest.java b/application/src/test/java/org/opentripplanner/model/plan/PlaceTest.java index 4b08b4ffa94..a98ffc10570 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/PlaceTest.java +++ b/application/src/test/java/org/opentripplanner/model/plan/PlaceTest.java @@ -173,7 +173,10 @@ public void forStopWithViaLocationType() { @Test void forGenericLocation() { - GenericLocation location = GenericLocation.fromStopId(PLACE_NAME, "id", "stopId"); + GenericLocation location = GenericLocation.fromStopId( + new FeedScopedId("id", "stopId"), + PLACE_NAME + ); Place place = Place.forGenericLocation(location, DEFAULT_PLACE_NAME); assertNotNull(place); assertEquals(PLACE_NAME, place.name.toString()); diff --git a/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java b/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java index 05d75e29eb5..435f7d52a8b 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java +++ b/application/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java @@ -1,10 +1,10 @@ package org.opentripplanner.model.plan; import static java.time.ZoneOffset.UTC; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.street.search.TraverseMode.BICYCLE; import static org.opentripplanner.street.search.TraverseMode.CAR; import static org.opentripplanner.street.search.TraverseMode.WALK; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.route; import java.time.Duration; @@ -245,8 +245,8 @@ public TestItineraryBuilder flex(int start, int end, Place to) { var edge = new FlexTripEdge( fromv, tov, - lastPlace.stop, - to.stop, + lastPlace.stop.getId(), + to.stop.getId(), flexTrip, fromStopPos, toStopPos, @@ -256,6 +256,8 @@ public TestItineraryBuilder flex(int start, int end, Place to) { FlexibleTransitLeg leg = FlexibleTransitLeg.of() .withFlexTripEdge(edge) + .withFromStop(lastPlace.stop) + .withToStop(to.stop) .withStartTime(newTime(start)) .withEndTime(newTime(end)) .withGeneralizedCost(legCost) diff --git a/application/src/test/java/org/opentripplanner/model/plan/leg/ScheduledTransitLegTest.java b/application/src/test/java/org/opentripplanner/model/plan/leg/ScheduledTransitLegTest.java index f85f867a8db..902e698a040 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/leg/ScheduledTransitLegTest.java +++ b/application/src/test/java/org/opentripplanner/model/plan/leg/ScheduledTransitLegTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.time.OffsetDateTime; diff --git a/application/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java b/application/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java index 42d3b304847..a3c781403cc 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java +++ b/application/src/test/java/org/opentripplanner/model/plan/legreference/LegReferenceSerializerTest.java @@ -7,17 +7,17 @@ import java.time.LocalDate; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; class LegReferenceSerializerTest { - private static final FeedScopedId TRIP_ID = TimetableRepositoryForTest.id("Trip"); + private static final FeedScopedId TRIP_ID = FeedScopedIdForTestFactory.id("Trip"); private static final LocalDate SERVICE_DATE = LocalDate.of(2022, 1, 31); private static final int FROM_STOP_POS = 1; - private static final FeedScopedId FROM_STOP_ID = TimetableRepositoryForTest.id("Boarding Stop"); + private static final FeedScopedId FROM_STOP_ID = FeedScopedIdForTestFactory.id("Boarding Stop"); private static final int TO_STOP_POS = 3; - private static final FeedScopedId TO_STOP_ID = TimetableRepositoryForTest.id("Alighting Stop"); + private static final FeedScopedId TO_STOP_ID = FeedScopedIdForTestFactory.id("Alighting Stop"); /** * Token based on the latest format, including stop ids and TripOnServiceDate id. diff --git a/application/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java b/application/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java index edfe07ca60f..12c90233468 100644 --- a/application/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java +++ b/application/src/test/java/org/opentripplanner/model/plan/legreference/ScheduledTransitLegReferenceTest.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.util.List; @@ -12,6 +12,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.calendar.CalendarServiceData; import org.opentripplanner.model.plan.leg.ScheduledTransitLeg; import org.opentripplanner.street.graph.Graph; @@ -178,7 +179,7 @@ void getLegFromReferenceMismatchOnBoardingStop() { SERVICE_DATE, 0, 1, - TimetableRepositoryForTest.id("invalid stop id"), + FeedScopedIdForTestFactory.id("invalid stop id"), STOP_2_ID, null ); @@ -345,7 +346,7 @@ void legReferenceCannotReferToBothTripAndTripOnServiceDate() { NUMBER_OF_STOPS, STOP_1_ID, STOP_2_ID, - TimetableRepositoryForTest.id("trip on date id") + FeedScopedIdForTestFactory.id("trip on date id") ) ); } @@ -388,7 +389,7 @@ void getLegFromReferenceWithUnknownTripOnDate() { NUMBER_OF_STOPS, STOP_1_ID, STOP_2_ID, - TimetableRepositoryForTest.id("unknown trip on date id") + FeedScopedIdForTestFactory.id("unknown trip on date id") ); assertNull(scheduledTransitLegReference.getLeg(transitService)); } diff --git a/application/src/test/java/org/opentripplanner/netex/mapping/TripMapperTest.java b/application/src/test/java/org/opentripplanner/netex/mapping/TripMapperTest.java index 196f65362a3..ec851d14e16 100644 --- a/application/src/test/java/org/opentripplanner/netex/mapping/TripMapperTest.java +++ b/application/src/test/java/org/opentripplanner/netex/mapping/TripMapperTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.accessibility.Accessibility; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.impl.TransitDataImportBuilder; import org.opentripplanner.netex.index.hierarchy.HierarchicalMap; @@ -33,7 +34,7 @@ public class TripMapperTest { private static final String ROUTE_ID = "RUT:Route:1"; private static final String SERVICE_JOURNEY_ID = NetexTestDataSample.SERVICE_JOURNEY_ID; private static final String JOURNEY_PATTERN_ID = "RUT:JourneyPattern:1"; - private static final FeedScopedId SERVICE_ID = TimetableRepositoryForTest.id("S001"); + private static final FeedScopedId SERVICE_ID = FeedScopedIdForTestFactory.id("S001"); private static final DataImportIssueStore ISSUE_STORE = DataImportIssueStore.NOOP; private static final JAXBElement LINE_REF = MappingSupport.createWrappedRef( diff --git a/application/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java b/application/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java index eca7e842234..2c84b88e7f2 100644 --- a/application/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java +++ b/application/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java @@ -11,10 +11,10 @@ import java.util.Optional; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.netex.index.hierarchy.HierarchicalMap; import org.opentripplanner.netex.index.hierarchy.HierarchicalMapById; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.DefaultEntityById; import org.opentripplanner.transit.model.timetable.Trip; @@ -28,7 +28,7 @@ class TripPatternMapperTest { - private static final FeedScopedId SERVICE_ID = TimetableRepositoryForTest.id("S01"); + private static final FeedScopedId SERVICE_ID = FeedScopedIdForTestFactory.id("S01"); @Test void testMapTripPattern() { diff --git a/application/src/test/java/org/opentripplanner/netex/mapping/calendar/CalendarServiceBuilderTest.java b/application/src/test/java/org/opentripplanner/netex/mapping/calendar/CalendarServiceBuilderTest.java index bbd9c791570..c226b2cc78d 100644 --- a/application/src/test/java/org/opentripplanner/netex/mapping/calendar/CalendarServiceBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/netex/mapping/calendar/CalendarServiceBuilderTest.java @@ -9,18 +9,18 @@ import java.util.Set; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.calendar.ServiceCalendarDate; import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; public class CalendarServiceBuilderTest { private static final LocalDate D1 = LocalDate.of(2020, 11, 1); private static final LocalDate D2 = LocalDate.of(2020, 11, 2); - private static final FeedScopedId EXP_SID_1 = TimetableRepositoryForTest.id("S000001"); - private static final FeedScopedId EXP_SID_2 = TimetableRepositoryForTest.id("S000002"); - private static final FeedScopedId EXP_SID_3 = TimetableRepositoryForTest.id("S000003"); + private static final FeedScopedId EXP_SID_1 = FeedScopedIdForTestFactory.id("S000001"); + private static final FeedScopedId EXP_SID_2 = FeedScopedIdForTestFactory.id("S000002"); + private static final FeedScopedId EXP_SID_3 = FeedScopedIdForTestFactory.id("S000003"); @Test public void addDatesForAGivenService() { @@ -64,8 +64,8 @@ public void createServiceCalendar() { @Test public void createServiceId() { CalendarServiceBuilder subject = new CalendarServiceBuilder(new FeedScopedIdFactory(FEED_ID)); - assertEquals(TimetableRepositoryForTest.id("S000001"), subject.createServiceId()); - assertEquals(TimetableRepositoryForTest.id("S000002"), subject.createServiceId()); + assertEquals(FeedScopedIdForTestFactory.id("S000001"), subject.createServiceId()); + assertEquals(FeedScopedIdForTestFactory.id("S000002"), subject.createServiceId()); } private void assertServiceDateExistInList( diff --git a/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java b/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java index 895907efc95..3484c3dac1e 100644 --- a/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java +++ b/application/src/test/java/org/opentripplanner/osm/model/OsmNodeTest.java @@ -1,12 +1,47 @@ package org.opentripplanner.osm.model; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class OsmNodeTest { + public static Stream entranceTestCases() { + var mainEntrance = NodeBuilder.of().withTag("entrance", "main").build(); + var trainStationEntrance = NodeBuilder.of() + .withTag("railway", "train_station_entrance") + .build(); + var subwayEntrance = NodeBuilder.of().withTag("railway", "subway_entrance").build(); + var publicTransportEntrance = NodeBuilder.of().withTag("public_transport", "entrance").build(); + var trainStationEntranceWithEntranceTag = NodeBuilder.of() + .withTag("railway", "train_station_entrance") + .withTag("entrance", "main") + .build(); + var notAnEntrance = NodeBuilder.of().withTag("entrance", "no").build(); + var emergencyEntrance = NodeBuilder.of().withTag("entrance", "emergency").build(); + + return Stream.of( + Arguments.argumentSet("main entrance", mainEntrance, true, false), + Arguments.argumentSet("train station entrance", trainStationEntrance, true, true), + Arguments.argumentSet("subway entrance", subwayEntrance, true, true), + Arguments.argumentSet("public transport entrance", publicTransportEntrance, true, true), + Arguments.argumentSet( + "train station entrance with entrance tag", + trainStationEntranceWithEntranceTag, + true, + true + ), + Arguments.argumentSet("not an entrance", notAnEntrance, false, false), + Arguments.argumentSet("emergency entrance", emergencyEntrance, false, false) + ); + } + @Test public void isBarrier() { OsmNode node = new OsmNode(); @@ -54,4 +89,11 @@ public void isTaggedBarrierCrossing() { node.addTag("entrance", "main"); assertTrue(node.isTaggedBarrierCrossing()); } + + @ParameterizedTest + @MethodSource("entranceTestCases") + public void isEntrance(OsmNode node, boolean entrance, boolean stationEntrance) { + assertEquals(entrance, node.isEntrance()); + assertEquals(stationEntrance, node.isStationEntrance()); + } } diff --git a/application/src/test/java/org/opentripplanner/raptorlegacy/_data/transit/TestTripSchedule.java b/application/src/test/java/org/opentripplanner/raptorlegacy/_data/transit/TestTripSchedule.java index b061208af7a..0a67e3be719 100644 --- a/application/src/test/java/org/opentripplanner/raptorlegacy/_data/transit/TestTripSchedule.java +++ b/application/src/test/java/org/opentripplanner/raptorlegacy/_data/transit/TestTripSchedule.java @@ -16,7 +16,6 @@ /** * An implementation of the {@link RaptorTripSchedule} for unit-testing. *

      - * The {@link RaptorTripPattern} for this schedule return {@code stopIndex == stopPosInPattern + 1 } * * @deprecated This was earlier part of Raptor and should not be used outside the Raptor * module. Use the OTP model entities instead. @@ -84,6 +83,11 @@ public int departure(int stopPosInPattern) { return departureTimes[stopPosInPattern]; } + @Override + public int relativeTravelDuration(int boardTime) { + return arrivalTimes[arrivalTimes.length - 1] - boardTime; + } + public TestTripPattern pattern() { return pattern; } diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/FilterTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/FilterTest.java index 510ca5c8463..82359653d85 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/FilterTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/FilterTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.Collection; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java index 8be85ef6ffa..b0b91442e2e 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.routing.algorithm; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.routing.linking.TransitStopVertexBuilderFactory.ofStop; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.util.ArrayList; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/StreetModeLinkingTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/StreetModeLinkingTest.java index 616c4f30d18..a2e32c291f8 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/StreetModeLinkingTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/StreetModeLinkingTest.java @@ -84,11 +84,10 @@ public class StreetModeLinkingTest extends GraphRoutingTest { * A place used as dummy to/from, when testing from/to. It can be anywhere, except * the same location as the place under test. */ - private static final GenericLocation ANY_PLACE = new GenericLocation( - "Any place - not used", - null, + private static final GenericLocation ANY_PLACE = GenericLocation.fromCoordinate( LATITUDE_START, - LONGITUDE_0 + LONGITUDE_0, + "Any place - not used" ); private Graph graph; @@ -138,7 +137,8 @@ public void build() { graph.hasStreets = true; TestStreetLinkerModule.link(graph, otpModel.timetableRepository()); - this.stopLocation = new GenericLocation(stop.getLabelString(), stop.getId(), null, null); + String label = stop.getLabelString(); + this.stopLocation = GenericLocation.fromStopId(stop.getId(), label); } private static List testPedestrianLinkingTestCases() { @@ -327,7 +327,7 @@ static LinkingTestCase of(StreetTraversalPermission permission) { * {@code N, N+1, N-1, N+2, N-2 ... } */ GenericLocation placeCloseToStreet() { - return new GenericLocation("On " + name, null, LATITUDE_MIDDLE, longitude + OFFSET); + return GenericLocation.fromCoordinate(LATITUDE_MIDDLE, longitude + OFFSET, "On " + name); } StreetEdgeBuilder createStreetEdgeBuilder(GraphRoutingTest.Builder factory) { diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/TestBanning.java b/application/src/test/java/org/opentripplanner/routing/algorithm/TestBanning.java index f3d023d032e..e89d33fe6fa 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/TestBanning.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/TestBanning.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.Collection; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/BikeRentalSnapshotTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/BikeRentalSnapshotTest.java index 90447ea9425..f50a47b6052 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/BikeRentalSnapshotTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/BikeRentalSnapshotTest.java @@ -23,25 +23,22 @@ public class BikeRentalSnapshotTest extends SnapshotTestBase { private static final Locale DEFAULT_LOCALE = Locale.getDefault(); - static GenericLocation p1 = new GenericLocation( - "SW Johnson St. & NW 24th Ave. (P1)", - null, + static GenericLocation p1 = GenericLocation.fromCoordinate( 45.52832, - -122.70059 + -122.70059, + "SW Johnson St. & NW 24th Ave. (P1)" ); - static GenericLocation p2 = new GenericLocation( - "NW Hoyt St. & NW 20th Ave. (P2)", - null, + static GenericLocation p2 = GenericLocation.fromCoordinate( 45.52704, - -122.69240 + -122.69240, + "NW Hoyt St. & NW 20th Ave. (P2)" ); - static GenericLocation p3 = new GenericLocation( - "NW Everett St. & NW 5th Ave. (P3)", - null, + static GenericLocation p3 = GenericLocation.fromCoordinate( 45.52523, - -122.67525 + -122.67525, + "NW Everett St. & NW 5th Ave. (P3)" ); @BeforeAll diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/CarSnapshotTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/CarSnapshotTest.java index 966a0a32b83..cac78ed91f8 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/CarSnapshotTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/CarSnapshotTest.java @@ -20,32 +20,28 @@ public class CarSnapshotTest extends SnapshotTestBase { private static final Locale DEFAULT_LOCALE = Locale.getDefault(); - static GenericLocation p1 = new GenericLocation( - "NW Pettygrove Ave. & NW 24th Ave. (P1)", - null, + static GenericLocation p1 = GenericLocation.fromCoordinate( 45.53261, - -122.70075 + -122.70075, + "NW Pettygrove Ave. & NW 24th Ave. (P1)" ); - static GenericLocation p2 = new GenericLocation( - "NW Marshall St. & NW 24th Ave. (P2)", - null, + static GenericLocation p2 = GenericLocation.fromCoordinate( 45.53046, - -122.70067 + -122.70067, + "NW Marshall St. & NW 24th Ave. (P2)" ); - static GenericLocation p3 = new GenericLocation( - "Chapman Elementary School (P3)", - null, + static GenericLocation p3 = GenericLocation.fromCoordinate( 45.53335, - -122.70517 + -122.70517, + "Chapman Elementary School (P3)" ); - static GenericLocation p4 = new GenericLocation( - "Legacy Good Samaritan Medical Center (P4)", - null, + static GenericLocation p4 = GenericLocation.fromCoordinate( 45.53060, - -122.69771 + -122.69771, + "Legacy Good Samaritan Medical Center (P4)" ); @BeforeAll diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/ElevationSnapshotTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/ElevationSnapshotTest.java index 4a1b05fabcd..b809f8e6f3c 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/ElevationSnapshotTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/ElevationSnapshotTest.java @@ -26,28 +26,29 @@ public class ElevationSnapshotTest extends SnapshotTestBase { private static final Locale DEFAULT_LOCALE = Locale.getDefault(); - static GenericLocation p1 = new GenericLocation( - "SW Johnson St. & NW 24th Ave. (P1)", - null, + static GenericLocation p1 = GenericLocation.fromCoordinate( 45.52832, - -122.70059 + -122.70059, + "SW Johnson St. & NW 24th Ave. (P1)" ); - static GenericLocation p2 = new GenericLocation( - "NW Hoyt St. & NW 20th Ave. (P2)", - null, + static GenericLocation p2 = GenericLocation.fromCoordinate( 45.52704, - -122.69240 + -122.69240, + "NW Hoyt St. & NW 20th Ave. (P2)" ); - static GenericLocation p3 = new GenericLocation( - "NW Everett St. & NW 5th Ave. (P3)", - null, + static GenericLocation p3 = GenericLocation.fromCoordinate( 45.52523, - -122.67525 + -122.67525, + "NW Everett St. & NW 5th Ave. (P3)" ); - static GenericLocation p4 = new GenericLocation("Sulzer Pump (P4)", null, 45.54549, -122.69659); + static GenericLocation p4 = GenericLocation.fromCoordinate( + 45.54549, + -122.69659, + "Sulzer Pump (P4)" + ); @BeforeAll public static void beforeClass() { diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java index 53d6ff0552a..f0f06b3dbbc 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java @@ -45,7 +45,6 @@ import org.opentripplanner.raptorlegacy._data.transit.TestTransitData; import org.opentripplanner.raptorlegacy._data.transit.TestTripPattern; import org.opentripplanner.raptorlegacy._data.transit.TestTripSchedule; -import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType; import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress; import org.opentripplanner.routing.algorithm.raptoradapter.transit.FlexAccessEgressAdapter; import org.opentripplanner.routing.algorithm.raptoradapter.transit.RaptorTransitData; @@ -198,10 +197,7 @@ void createItineraryWithOnBoardFlexAccess() { true, RoutingBookingInfo.NOT_SET ); - RaptorAccessEgress access = new FlexAccessEgressAdapter( - flexAccessEgress, - AccessEgressType.ACCESS - ); + RaptorAccessEgress access = new FlexAccessEgressAdapter(flexAccessEgress); Transfer transfer = new Transfer(S2.getIndex(), 0, EnumSet.of(StreetMode.WALK)); RaptorTransfer raptorTransfer = new DefaultRaptorTransfer(S1.getIndex(), 0, 0, transfer); RaptorAccessEgress egress = new DefaultAccessEgress(S2.getIndex(), state); diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/SnapshotTestBase.java b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/SnapshotTestBase.java index e6dd69432f5..5b5945ac8ae 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/SnapshotTestBase.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/SnapshotTestBase.java @@ -295,10 +295,13 @@ private String createDebugUrlForRequest(RouteRequest request) { private String formatPlace(GenericLocation location) { String formatted; - if (location.stopId != null) { - formatted = String.format("%s::%s", location.label, location.stopId); + if (location.stopId() != null) { + formatted = String.format("%s::%s", location.label(), location.stopId()); + } else if (location.wgsCoordinate() != null) { + var coord = location.wgsCoordinate(); + formatted = String.format("%s::%s,%s", location.label(), coord.latitude(), coord.longitude()); } else { - formatted = String.format("%s::%s,%s", location.label, location.lat, location.lng); + formatted = String.format("%s::null", location.label()); } return URLEncoder.encode(formatted, StandardCharsets.UTF_8); } diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/TransitSnapshotTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/TransitSnapshotTest.java index 26df46ac506..c390a03eee5 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/TransitSnapshotTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/TransitSnapshotTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.street.model.StreetMode; @@ -13,46 +14,43 @@ public class TransitSnapshotTest extends SnapshotTestBase { static GenericLocation ptc = GenericLocation.fromStopId( - "Rose Quarter Transit Center", - "prt", - "79-tc" + new FeedScopedId("prt", "79-tc"), + "Rose Quarter Transit Center" ); - static GenericLocation ps = GenericLocation.fromStopId("NE 12th & Couch", "prt", "6577"); + static GenericLocation ps = GenericLocation.fromStopId( + new FeedScopedId("prt", "6577"), + "NE 12th & Couch" + ); - static GenericLocation p0 = new GenericLocation( - "SE Stark St. & SE 17th Ave. (P0)", - null, + static GenericLocation p0 = GenericLocation.fromCoordinate( 45.519320, - -122.648567 + -122.648567, + "SE Stark St. & SE 17th Ave. (P0)" ); - static GenericLocation p1 = new GenericLocation( - "SE Morrison St. & SE 17th Ave. (P1)", - null, + static GenericLocation p1 = GenericLocation.fromCoordinate( 45.51726, - -122.64847 + -122.64847, + "SE Morrison St. & SE 17th Ave. (P1)" ); - static GenericLocation p2 = new GenericLocation( - "NW Northrup St. & NW 22nd Ave. (P2)", - null, + static GenericLocation p2 = GenericLocation.fromCoordinate( 45.53122, - -122.69659 + -122.69659, + "NW Northrup St. & NW 22nd Ave. (P2)" ); - static GenericLocation p3 = new GenericLocation( - "NW Northrup St. & NW 24th Ave. (P3)", - null, + static GenericLocation p3 = GenericLocation.fromCoordinate( 45.53100, - -122.70029 + -122.70029, + "NW Northrup St. & NW 24th Ave. (P3)" ); - static GenericLocation p4 = new GenericLocation( - "NE Thompson St. & NE 18th Ave. (P4)", - null, + static GenericLocation p4 = GenericLocation.fromCoordinate( 45.53896, - -122.64699 + -122.64699, + "NE Thompson St. & NE 18th Ave. (P4)" ); @BeforeAll diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouterTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouterTest.java index 9467e929d8a..0e2445345db 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouterTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressRouterTest.java @@ -10,7 +10,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; -import org.opentripplanner.graph_builder.module.nearbystops.SiteRepositoryResolver; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.algorithm.GraphRoutingTest; import org.opentripplanner.routing.api.request.RouteRequest; @@ -214,7 +213,7 @@ private GenericLocation location(WgsCoordinate coordinate) { } private GenericLocation location(FeedScopedId id) { - return new GenericLocation(null, id, null, null); + return GenericLocation.fromStopId(id); } private GenericLocation location(String id) { @@ -227,7 +226,11 @@ private RouteRequest requestFromTo(GenericLocation from, GenericLocation to) { private String nearbyStopDescription(NearbyStop nearbyStop) { if (nearbyStop.edges.isEmpty()) { - return "direct[" + nearbyStop.stop.getName() + "]"; + return ( + "direct[" + + timetableRepository.getSiteRepository().getStopLocation(nearbyStop.stopId).getName() + + "]" + ); } else { return "street[" + stateDescription(nearbyStop.state) + "]"; } @@ -272,9 +275,7 @@ private Collection findAccessEgressFromTo( var linkingRequest = LinkingContextRequestMapper.map(request); var linkingContext = linkingContextFactory.create(verticesContainer, linkingRequest); - return new AccessEgressRouter( - new SiteRepositoryResolver(timetableRepository.getSiteRepository()) - ).findAccessEgresses( + return AccessEgressRouter.findAccessEgresses( request, StreetMode.WALK, List.of(), diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java index 2cce035a5a9..63b3326e9b5 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java @@ -16,12 +16,12 @@ class DefaultAccessEgressTest { private static final int STOP = 5; - private static final State LAST_STATE = TestStateBuilder.ofWalking().streetEdge().build(); + private static final State FINAL_STATE = TestStateBuilder.ofWalking().streetEdge().build(); public static final Duration TIME_PENALTY = Duration.ofSeconds(1); public static final Cost COST_PENALTY = Cost.costOfSeconds(11); public static final TimeAndCost PENALTY = new TimeAndCost(TIME_PENALTY, COST_PENALTY); - private final DefaultAccessEgress subject = new DefaultAccessEgress(STOP, LAST_STATE); + private final DefaultAccessEgress subject = new DefaultAccessEgress(STOP, FINAL_STATE); private final RoutingAccessEgress subjectWithPenalty = subject.withPenalty(PENALTY); @Test @@ -31,7 +31,7 @@ void canNotAddPenaltyTwice() { @Test void durationInSeconds() { - int expected = (int) LAST_STATE.getElapsedTimeSeconds(); + int expected = (int) FINAL_STATE.getElapsedTimeSeconds(); assertEquals(expected, subject.durationInSeconds()); assertEquals(expected, subjectWithPenalty.durationInSeconds()); } @@ -62,8 +62,8 @@ void hasOpeningHours() { } @Test - void getLastState() { - assertEquals(LAST_STATE, subject.getLastState()); + void getFinalState() { + assertEquals(FINAL_STATE, subject.getFinalState()); } @Test @@ -101,4 +101,23 @@ void testToString() { subjectWithPenalty.toString() ); } + + /** + * Verify that the scalar values extracted during DefaultAccessEgress construction + * (duration, generalized cost, walk-only mode) are identical for reversed and unreversed + * State chains. This invariant allows deferring State.reverse() from AccessEgressMapper + * to GraphPath construction, where it is only applied to winning paths rather than all + * candidates. + */ + @Test + void scalarValuesAreIdenticalForReversedAndUnreversedState() { + var state = TestStateBuilder.ofWalking().streetEdge().streetEdge().streetEdge().build(); + + var fromUnreversed = new DefaultAccessEgress(STOP, state); + var fromReversed = new DefaultAccessEgress(STOP, state.reverse()); + + assertEquals(fromUnreversed.durationInSeconds(), fromReversed.durationInSeconds()); + assertEquals(fromUnreversed.c1(), fromReversed.c1()); + assertEquals(fromUnreversed.isWalkOnly(), fromReversed.isWalkOnly()); + } } diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransitDataTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransitDataTest.java index 30d1654b0db..85ba53e3565 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransitDataTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransitDataTest.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.framework.Deduplicator; @@ -31,7 +32,7 @@ class RaptorTransitDataTest { stopTime.setStop(stop); var stopPattern = new StopPattern(List.of(stopTime)); var route = TimetableRepositoryForTest.route("1").build(); - TRIP_PATTERN = TripPattern.of(TimetableRepositoryForTest.id("P1")) + TRIP_PATTERN = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(route) .withStopPattern(stopPattern) .build() diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java index 87926384a5c..86ff036e319 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TripPatternForDateTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.Frequency; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; @@ -48,7 +49,7 @@ void shouldExcludeAndIncludeBasedOnFrequency(List freqs) { var stopTime = new StopTime(); stopTime.setStop(STOP); StopPattern stopPattern = new StopPattern(List.of(stopTime)); - RoutingTripPattern tripPattern = TripPattern.of(TimetableRepositoryForTest.id("P1")) + RoutingTripPattern tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(ROUTE) .withStopPattern(stopPattern) .build() diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculatorTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculatorTest.java index 7698eac4546..208651dfed5 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculatorTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculatorTest.java @@ -60,11 +60,11 @@ public void transitArrivalCost() { } @Test - public void onTripRidingCost() { - assertEquals(0, subject.onTripRelativeRidingCost(0, TRIP_1), "Board cost"); - assertEquals(0, subject.onTripRelativeRidingCost(0, TRIP_2), "Board cost"); - assertEquals(-100, subject.onTripRelativeRidingCost(1, TRIP_1), "Board cost"); - assertEquals(-80, subject.onTripRelativeRidingCost(1, TRIP_2), "Board cost"); + public void transitCost() { + assertEquals(0, subject.transitCost(0, TRIP_1)); + assertEquals(0, subject.transitCost(0, TRIP_2)); + assertEquals(100, subject.transitCost(1, TRIP_1)); + assertEquals(80, subject.transitCost(1, TRIP_2)); } @Test diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java index 0c0dfa103b3..5f5cafe71ee 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/PatternCostCalculatorTest.java @@ -3,9 +3,9 @@ import static graphql.Assert.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.raptorlegacy._data.transit.TestRoute.route; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.agency; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.time.Duration; import java.util.List; @@ -168,10 +168,6 @@ public String toString() { return "RoutePenaltyTC {" + sb.substring(sb.isEmpty() ? 0 : 2) + "}"; } - boolean isDefault() { - return !(unPreferredAgency || unPreferredRoute); - } - RaptorCostCalculator createCostCalculator(TestTripSchedule schedule) { GeneralizedCostParameters costParams = GeneralizedCostParametersMapper.map( createRouteRequest(), diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/GeneralizedCostParametersMapperTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/GeneralizedCostParametersMapperTest.java index 0bd2b6a3a01..8411cbdc288 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/GeneralizedCostParametersMapperTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/GeneralizedCostParametersMapperTest.java @@ -1,8 +1,8 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.agency; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.route; import com.google.common.collect.ArrayListMultimap; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/DefaultTransitDataProviderFilterTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/DefaultTransitDataProviderFilterTest.java index 84a6569d9fe..58baadf15ac 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/DefaultTransitDataProviderFilterTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/DefaultTransitDataProviderFilterTest.java @@ -20,6 +20,7 @@ import org.opentripplanner.apis.transmodel.model.TransmodelTransportSubmode; import org.opentripplanner.core.model.accessibility.Accessibility; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; @@ -56,7 +57,7 @@ class DefaultTransitDataProviderFilterTest { private static final Route ROUTE = TimetableRepositoryForTest.route("1").build(); - private static final FeedScopedId TRIP_ID = TimetableRepositoryForTest.id("T1"); + private static final FeedScopedId TRIP_ID = FeedScopedIdForTestFactory.id("T1"); private static final RegularStop STOP_FOR_TEST = TEST_MODEL.stop("TEST:STOP", 0, 0).build(); @@ -99,7 +100,7 @@ void testWheelchairAccess(Accessibility wheelchair, WheelchairPreferences access stopTimeEnd.setStop(lastStop); var stopPattern = new StopPattern(List.of(stopTimeStart, stopTimeEnd)); - var tripPattern = TripPattern.of(TimetableRepositoryForTest.id("P1")) + var tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(TimetableRepositoryForTest.route("1").build()) .withStopPattern(stopPattern) .build() @@ -148,7 +149,7 @@ void testRealtimeCancelledStops(boolean includeRealtimeCancellations) { var stopTime3 = getStopTime("TEST:3", PickDrop.NONE); var stopTime4 = getStopTime("TEST:4", PickDrop.SCHEDULED); var stopPattern = new StopPattern(List.of(stopTime1, stopTime2, stopTime3, stopTime4)); - var tripPattern = TripPattern.of(TimetableRepositoryForTest.id("P1")) + var tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(TimetableRepositoryForTest.route("1").build()) .withStopPattern(stopPattern) .build() @@ -720,7 +721,7 @@ void includeRealtimeCancellationsTest() { @Test void testBikesAllowed() { RouteBuilder routeBuilder = TimetableRepositoryForTest.route("1"); - TripBuilder trip = Trip.of(TimetableRepositoryForTest.id("T1")).withRoute(routeBuilder.build()); + TripBuilder trip = Trip.of(FeedScopedIdForTestFactory.id("T1")).withRoute(routeBuilder.build()); assertEquals( BikeAccess.UNKNOWN, @@ -896,7 +897,7 @@ private TripPatternForDate createTestTripPatternForDate() { var stopTime = new StopTime(); stopTime.setStop(STOP_FOR_TEST); StopPattern stopPattern = new StopPattern(List.of(stopTime)); - RoutingTripPattern tripPattern = TripPattern.of(TimetableRepositoryForTest.id("P1")) + RoutingTripPattern tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(route) .withStopPattern(stopPattern) .build() @@ -1005,7 +1006,7 @@ private PatternAndTimes createPatternAndTimes( stopTime.setStop(STOP_FOR_TEST); StopPattern stopPattern = new StopPattern(List.of(stopTime)); - var tripPattern = TripPattern.of(TimetableRepositoryForTest.id("P1")) + var tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(route) .withStopPattern(stopPattern) .withMode(mode) @@ -1042,7 +1043,7 @@ private PatternAndTimes createPatternAndTimes( TransitMode tripMode, boolean containsMultipleModes ) { - Trip trip = Trip.of(TimetableRepositoryForTest.id(tripIdSuffix)) + Trip trip = Trip.of(FeedScopedIdForTestFactory.id(tripIdSuffix)) .withRoute(ROUTE) .withMode(tripMode) .withBikesAllowed(BikeAccess.NOT_ALLOWED) @@ -1058,7 +1059,7 @@ private PatternAndTimes createPatternAndTimes( StopPattern stopPattern = new StopPattern(List.of(stopTime)); - var tripPattern = TripPattern.of(TimetableRepositoryForTest.id("P1")) + var tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(ROUTE) .withStopPattern(stopPattern) .withMode(patternMode) diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java index 5913b42b29c..cf0eecd2516 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.time.LocalTime; diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java index ec681707f62..d0cdfdf3437 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TestRouteData.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.stream.Collectors; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.StopTime; import org.opentripplanner.raptor.spi.RaptorTimeTable; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripPatternForDate; @@ -56,7 +57,7 @@ public TestRouteData(Route route, List stops, List times) { .map(tripTimesByTrip::get) .collect(Collectors.toList()); - tripPattern = TripPattern.of(TimetableRepositoryForTest.id("TP:" + route)) + tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("TP:" + route)) .withRoute(this.route) .withStopPattern(new StopPattern(stopTimesFistTrip)) .withScheduledTimeTableBuilder(builder -> builder.addAllTripTimes(tripTimes)) @@ -163,7 +164,7 @@ private Trip parseTripInfo( List stops, Deduplicator deduplicator ) { - var trip = Trip.of(TimetableRepositoryForTest.id(route + "-" + stopTimesByTrip.size() + 1)) + var trip = Trip.of(FeedScopedIdForTestFactory.id(route + "-" + stopTimesByTrip.size() + 1)) .withRoute(this.route) .build(); var stopTimes = stopTimes(trip, stops, tripTimes); diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDatesTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDatesTest.java index 882dbc979b1..f7c3e235100 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDatesTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TripPatternForDatesTest.java @@ -7,6 +7,7 @@ import java.util.BitSet; import java.util.List; import org.junit.jupiter.api.Test; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.Frequency; import org.opentripplanner.model.StopTime; import org.opentripplanner.raptor.spi.SearchDirection; @@ -79,7 +80,7 @@ private static TripPatternForDates getTestSubjectWithExactFrequency() { stopTime2.setDepartureTime(300); stopTime2.setStopSequence(1); StopPattern stopPattern = new StopPattern(List.of(stopTime1, stopTime2)); - RoutingTripPattern tripPattern = TripPattern.of(TimetableRepositoryForTest.id("P1")) + RoutingTripPattern tripPattern = TripPattern.of(FeedScopedIdForTestFactory.id("P1")) .withRoute(ROUTE) .withStopPattern(stopPattern) .build() diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorkerTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorkerTest.java index 29571098e03..d3dc06d99e8 100644 --- a/application/src/test/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorkerTest.java +++ b/application/src/test/java/org/opentripplanner/routing/algorithm/via/ViaRoutingWorkerTest.java @@ -119,7 +119,7 @@ public void testViaRoutingWorker() { private RoutingResponse createRoutingResponse(RouteRequest req) { // request from A or C? var c = fromA.coordinate; - var firstOrSecondSearch = req.from().lng == c.longitude() && req.from().lat == c.latitude(); + var firstOrSecondSearch = req.from().wgsCoordinate().equals(c); var searchItineraries = firstOrSecondSearch ? firstSearch : secondSearch; diff --git a/application/src/test/java/org/opentripplanner/routing/core/ItineraryFareTest.java b/application/src/test/java/org/opentripplanner/routing/core/ItineraryFareTest.java index f85dc481c12..644684ccfb6 100644 --- a/application/src/test/java/org/opentripplanner/routing/core/ItineraryFareTest.java +++ b/application/src/test/java/org/opentripplanner/routing/core/ItineraryFareTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.PlanTestConstants.A; import static org.opentripplanner.model.plan.PlanTestConstants.B; import static org.opentripplanner.model.plan.PlanTestConstants.C; @@ -12,7 +13,6 @@ 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.transit.model._data.TimetableRepositoryForTest.id; import java.time.ZonedDateTime; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/routing/core/RouteRequestTest.java b/application/src/test/java/org/opentripplanner/routing/core/RouteRequestTest.java index 3a3a4fb0f05..c9817e74bc9 100644 --- a/application/src/test/java/org/opentripplanner/routing/core/RouteRequestTest.java +++ b/application/src/test/java/org/opentripplanner/routing/core/RouteRequestTest.java @@ -257,7 +257,7 @@ void testToString() { @Test void testValidateMissingFrom() { expectOneRoutingValidationException( - () -> minimal.copyOf().withFrom(GenericLocation.UNKNOWN).buildRequest(), + () -> minimal.copyOf().withFrom(null).buildRequest(), RoutingErrorCode.LOCATION_NOT_FOUND, InputField.FROM_PLACE ); @@ -266,7 +266,7 @@ void testValidateMissingFrom() { @Test void testValidateMissingTo() { expectOneRoutingValidationException( - () -> minimal.copyOf().withTo(GenericLocation.UNKNOWN).buildRequest(), + () -> minimal.copyOf().withTo(null).buildRequest(), RoutingErrorCode.LOCATION_NOT_FOUND, InputField.TO_PLACE ); diff --git a/application/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java b/application/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java index e72f5a29ad4..bbbdc836b6d 100644 --- a/application/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java @@ -33,7 +33,7 @@ import org.opentripplanner.ext.empiricaldelay.model.EmpiricalDelay; import org.opentripplanner.ext.empiricaldelay.model.TripDelays; import org.opentripplanner.ext.empiricaldelay.model.calendar.EmpiricalDelayCalendar; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.framework.model.Gram; import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary; import org.opentripplanner.model.plan.Emission; @@ -272,7 +272,7 @@ private void testRoundTrip( emissionRepository, empiricalDelayRepository, null, - new DefaultFareServiceFactory() + new GtfsFareServiceFactory() ); serializedObj.save(new FileDataSource(tempFile, FileType.GRAPH)); SerializedGraphObject deserializedGraph = SerializedGraphObject.load(tempFile); diff --git a/application/src/test/java/org/opentripplanner/routing/graphfinder/DirectGraphFinderTest.java b/application/src/test/java/org/opentripplanner/routing/graphfinder/DirectGraphFinderTest.java index f77521215cd..2c9f15fe6fc 100644 --- a/application/src/test/java/org/opentripplanner/routing/graphfinder/DirectGraphFinderTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graphfinder/DirectGraphFinderTest.java @@ -36,8 +36,8 @@ public void build() { @Test void findClosestStops() { - var ns1 = new NearbyStop(siteRepository.getRegularStop(S1.getId()), 0, null, null); - var ns2 = new NearbyStop(siteRepository.getRegularStop(S2.getId()), 1112, null, null); + var ns1 = new NearbyStop(S1.getId(), 0, null, null); + var ns2 = new NearbyStop(S2.getId(), 1112, null, null); var subject = new DirectGraphFinder(siteRepository::findRegularStops); var coordinate = new Coordinate(19.000, 47.500); diff --git a/application/src/test/java/org/opentripplanner/routing/graphfinder/NearbyStopTest.java b/application/src/test/java/org/opentripplanner/routing/graphfinder/NearbyStopTest.java index 332c34d9232..4925b2cc724 100644 --- a/application/src/test/java/org/opentripplanner/routing/graphfinder/NearbyStopTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graphfinder/NearbyStopTest.java @@ -2,25 +2,23 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class NearbyStopTest { - private static final TimetableRepositoryForTest MODEL = TimetableRepositoryForTest.of(); - @Test void testIsBetter() { // We only test the distance here, since the compareTo method used should have a more complete // unit-test including tests on state weight. - var a = new NearbyStop(MODEL.stop("A").build(), 20.0, null, null); - var b = new NearbyStop(MODEL.stop("A").build(), 30.0, null, null); + var a = new NearbyStop(id("A"), 20.0, null, null); + var b = new NearbyStop(id("A"), 30.0, null, null); assertTrue(a.isBetter(b)); assertFalse(b.isBetter(a)); - var sameDistance = new NearbyStop(MODEL.stop("A").build(), 20.0, null, null); + var sameDistance = new NearbyStop(id("A"), 20.0, null, null); assertFalse(a.isBetter(sameDistance)); assertFalse(sameDistance.isBetter(a)); } diff --git a/application/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java b/application/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java index 3c593756604..d98c407d7fd 100644 --- a/application/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitorTest.java @@ -1,10 +1,10 @@ package org.opentripplanner.routing.graphfinder; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.model.plan.PlanTestConstants.T11_00; import static org.opentripplanner.model.plan.PlanTestConstants.T11_05; import static org.opentripplanner.model.plan.PlanTestConstants.T11_10; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.route; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.tripPattern; diff --git a/application/src/test/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitorTest.java b/application/src/test/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitorTest.java index b805fe06190..495be852d2d 100644 --- a/application/src/test/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitorTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graphfinder/StopFinderTraverseVisitorTest.java @@ -14,14 +14,14 @@ class StopFinderTraverseVisitorTest { @Test void deduplicateStops() { - var visitor = new StopFinderTraverseVisitor(id -> STOP, 1000); + var visitor = new StopFinderTraverseVisitor(1000); assertEquals(List.of(), visitor.stopsFound()); var state1 = TestStateBuilder.ofWalking().streetEdge().stop(STOP).build(); visitor.visitVertex(state1); - final NearbyStop nearbyStop = NearbyStop.nearbyStopForState(state1, STOP); + final NearbyStop nearbyStop = NearbyStop.nearbyStopForState(state1, STOP.getId()); assertEquals(List.of(nearbyStop), visitor.stopsFound()); diff --git a/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java b/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java index de364988b6b..38397db025f 100644 --- a/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graphfinder/StreetGraphFinderTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Coordinate; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.routing.algorithm.GraphRoutingTest; import org.opentripplanner.routing.linking.LinkingContextFactory; import org.opentripplanner.routing.linking.VertexLinkerTestFactory; @@ -109,13 +110,13 @@ public void build() { street(C, D, 100, StreetTraversalPermission.ALL); tripPattern( - TP1 = TripPattern.of(TimetableRepositoryForTest.id("TP1")) + TP1 = TripPattern.of(FeedScopedIdForTestFactory.id("TP1")) .withRoute(R1) .withStopPattern(new StopPattern(List.of(st(S1), st(S2)))) .build() ); tripPattern( - TP2 = TripPattern.of(TimetableRepositoryForTest.id("TP2")) + TP2 = TripPattern.of(FeedScopedIdForTestFactory.id("TP2")) .withRoute(R2) .withStopPattern(new StopPattern(List.of(st(S1), st(S3)))) .build() @@ -130,16 +131,13 @@ public void build() { otpModel.graph(), new VertexCreationService(vertexLinker) ); - graphFinder = new StreetGraphFinder( - linkingContextFactory, - otpModel.timetableRepository().getSiteRepository()::getRegularStop - ); + graphFinder = new StreetGraphFinder(linkingContextFactory); } @Test void findClosestStops() { - var ns1 = new NearbyStop(stop(S1), 0, null, null); - var ns2 = new NearbyStop(stop(S2), 100, null, null); + var ns1 = new NearbyStop(S1.getId(), 0, null, null); + var ns2 = new NearbyStop(S2.getId(), 100, null, null); var coordinate = new Coordinate(19.000, 47.500); assertEquals(List.of(ns1), simplify(graphFinder.findClosestStops(coordinate, 10))); @@ -487,7 +485,7 @@ void findClosestPlacesWithACarParkFilter() { private List simplify(List closestStops) { return closestStops .stream() - .map(ns -> new NearbyStop(ns.stop, ns.distance, null, null)) + .map(ns -> new NearbyStop(ns.stopId, ns.distance, null, null)) .collect(Collectors.toList()); } diff --git a/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java b/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java index e8e5c522947..1268f5f18bb 100644 --- a/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java +++ b/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextFactoryTest.java @@ -6,7 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import com.google.common.collect.ImmutableMultimap; import java.util.Arrays; @@ -161,7 +161,10 @@ void stationId() { id -> Optional.empty() ); var container = new TemporaryVerticesContainer(); - var from = GenericLocation.fromStopId("station", OMEGA_ID.getFeedId(), OMEGA_ID.getId()); + var from = GenericLocation.fromStopId( + new FeedScopedId(OMEGA_ID.getFeedId(), OMEGA_ID.getId()), + "station" + ); var request = LinkingContextRequest.of() .withFrom(from) .withTo(stopToLocation(stopB)) @@ -192,15 +195,15 @@ void stationCentroidForCar() { } ); var container = new TemporaryVerticesContainer(); + String label1 = stationAlpha.getName().toString(); var from = GenericLocation.fromStopId( - stationAlpha.getName().toString(), - stationAlpha.getId().getFeedId(), - stationAlpha.getId().getId() + new FeedScopedId(stationAlpha.getId().getFeedId(), stationAlpha.getId().getId()), + label1 ); + String label = multiModalStation.getName().toString(); var to = GenericLocation.fromStopId( - multiModalStation.getName().toString(), - multiModalStation.getId().getFeedId(), - multiModalStation.getId().getId() + new FeedScopedId(multiModalStation.getId().getFeedId(), multiModalStation.getId().getId()), + label ); var request = LinkingContextRequest.of() .withFrom(from) @@ -224,7 +227,10 @@ void stationCentroidForCar() { @Test void centroid() { var container = new TemporaryVerticesContainer(); - var from = GenericLocation.fromStopId("station", ALPHA_ID.getFeedId(), ALPHA_ID.getId()); + var from = GenericLocation.fromStopId( + new FeedScopedId(ALPHA_ID.getFeedId(), ALPHA_ID.getId()), + "station" + ); var request = LinkingContextRequest.of() .withFrom(from) .withTo(stopToLocation(stopB)) @@ -302,9 +308,9 @@ void locationsShouldBeRoutableWithTheGivenModes() { @Test void verticesShouldInheritNamesFromLocations() { try (var container = new TemporaryVerticesContainer()) { - var from = new GenericLocation("First", null, 0.5, 0.5); - var via = new GenericLocation("Second", null, 0.4, 0.6); - var to = new GenericLocation("Third", null, 0.6, 0.4); + var from = GenericLocation.fromCoordinate(0.5, 0.5, "First"); + var via = GenericLocation.fromCoordinate(0.4, 0.6, "Second"); + var to = GenericLocation.fromCoordinate(0.6, 0.4, "Third"); var request = LinkingContextRequest.of() .withFrom(from) .withTo(to) @@ -378,7 +384,7 @@ void locationOutsideBoundsException() { var request = LinkingContextRequest.of() .withFrom(GenericLocation.fromCoordinate(80, 80)) .withTo(GenericLocation.fromCoordinate(85, 85)) - .withViaLocationsWithCoordinates(List.of(new GenericLocation("Via1", null, 87.0, 87.0))) + .withViaLocationsWithCoordinates(List.of(GenericLocation.fromCoordinate(87.0, 87.0, "Via1"))) .withDirectMode(StreetMode.WALK) .build(); var exception = assertThrows(RoutingValidationException.class, () -> @@ -424,12 +430,17 @@ void nonExistingPlaceIdWithCoordinatesShouldFallbackToCoordinates() { var nonExistingStopId = new FeedScopedId("F", "NonExistingStop"); // Create locations with both a non-existing stop ID and valid coordinates - var from = new GenericLocation("From", nonExistingStopId, stopA.getLat(), stopA.getLon()); - var to = new GenericLocation( - "To", + var from = GenericLocation.fromStopIdWithFallback( + nonExistingStopId, + stopA.getLat(), + stopA.getLon(), + "From" + ); + var to = GenericLocation.fromStopIdWithFallback( new FeedScopedId("F", "AnotherNonExisting"), stopD.getLat(), - stopD.getLon() + stopD.getLon(), + "To" ); var request = LinkingContextRequest.of() @@ -509,10 +520,10 @@ private Set toStops(Set fromVertices) { } private GenericLocation stopToLocation(RegularStop s) { + String label = s.getName().toString(); return GenericLocation.fromStopId( - s.getName().toString(), - s.getId().getFeedId(), - s.getId().getId() + new FeedScopedId(s.getId().getFeedId(), s.getId().getId()), + label ); } diff --git a/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextRequestTest.java b/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextRequestTest.java index 38f422761e4..d643ad89f9c 100644 --- a/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextRequestTest.java +++ b/application/src/test/java/org/opentripplanner/routing/linking/LinkingContextRequestTest.java @@ -85,8 +85,6 @@ void testEqualsAndHashCode() { @Test void testToString() { - var defaultRequest = LinkingContextRequest.of().withFrom(GenericLocation.UNKNOWN).build(); - assertEquals("LinkingContextRequest{from: Unknown location}", defaultRequest.toString()); assertEquals( "LinkingContextRequest{" + "from: (1.0, 2.0), " + diff --git a/application/src/test/java/org/opentripplanner/routing/linking/mapping/LinkingContextRequestMapperTest.java b/application/src/test/java/org/opentripplanner/routing/linking/mapping/LinkingContextRequestMapperTest.java index fc78b6cd3f3..7b8e84713fb 100644 --- a/application/src/test/java/org/opentripplanner/routing/linking/mapping/LinkingContextRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/routing/linking/mapping/LinkingContextRequestMapperTest.java @@ -15,8 +15,8 @@ class LinkingContextRequestMapperTest { - private static final GenericLocation FROM = new GenericLocation("from", null, 1.0, 0.0); - private static final GenericLocation TO = new GenericLocation("to", null, 0.0, 1.0); + private static final GenericLocation FROM = GenericLocation.fromCoordinate(1.0, 0.0, "from"); + private static final GenericLocation TO = GenericLocation.fromCoordinate(0.0, 1.0, "to"); private static final List VIA = List.of( new VisitViaLocation("via", null, List.of(), WgsCoordinate.GREENWICH) ); diff --git a/application/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java b/application/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java index 05728e492cf..1e61b73b3e8 100644 --- a/application/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java +++ b/application/src/test/java/org/opentripplanner/routing/trippattern/FrequencyEntryTest.java @@ -6,6 +6,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.Frequency; import org.opentripplanner.model.StopTime; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; @@ -28,7 +29,7 @@ public class FrequencyEntryTest { int time = 0; for (int i = 0; i < STOP_NUM; ++i) { - FeedScopedId id = TimetableRepositoryForTest.id(i + ""); + FeedScopedId id = FeedScopedIdForTestFactory.id(i + ""); RegularStop stop = TimetableRepositoryForTest.of().stop(id.getId(), 0.0, 0.0).build(); diff --git a/application/src/test/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfigTest.java b/application/src/test/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfigTest.java new file mode 100644 index 00000000000..9e690a991f4 --- /dev/null +++ b/application/src/test/java/org/opentripplanner/standalone/config/routerconfig/WarmupConfigTest.java @@ -0,0 +1,55 @@ +package org.opentripplanner.standalone.config.routerconfig; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.opentripplanner.standalone.config.framework.json.JsonSupport.jsonNodeForTest; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; +import org.opentripplanner.street.model.StreetMode; + +class WarmupConfigTest { + + @Test + void defaultModesWhenAbsent() { + var root = createNodeAdapter( + """ + { + warmup: { + from: { lat: 59.91, lon: 10.75 }, + to: { lat: 59.95, lon: 10.76 } + } + } + """ + ); + var config = WarmupConfig.mapWarmupConfig("warmup", root); + assertNotNull(config); + assertEquals(List.of(StreetMode.WALK, StreetMode.CAR_TO_PARK), config.accessModes()); + assertEquals(List.of(StreetMode.WALK, StreetMode.WALK), config.egressModes()); + } + + @Test + void mismatchedModeListSizesThrows() { + var root = createNodeAdapter( + """ + { + warmup: { + from: { lat: 59.91, lon: 10.75 }, + to: { lat: 59.95, lon: 10.76 }, + accessModes: ["WALK", "BIKE", "CAR_TO_PARK"], + egressModes: ["WALK", "BIKE"] + } + } + """ + ); + assertThrows(IllegalArgumentException.class, () -> + WarmupConfig.mapWarmupConfig("warmup", root) + ); + } + + private static NodeAdapter createNodeAdapter(String jsonText) { + return new NodeAdapter(jsonNodeForTest(jsonText), "Test"); + } +} diff --git a/application/src/test/java/org/opentripplanner/standalone/server/AlertMetricsTest.java b/application/src/test/java/org/opentripplanner/standalone/server/AlertMetricsTest.java index 5fc22e55e05..b0a0145543d 100644 --- a/application/src/test/java/org/opentripplanner/standalone/server/AlertMetricsTest.java +++ b/application/src/test/java/org/opentripplanner/standalone/server/AlertMetricsTest.java @@ -1,9 +1,9 @@ package org.opentripplanner.standalone.server; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.routing.alertpatch.AlertEffect.DETOUR; import static org.opentripplanner.routing.alertpatch.AlertSeverity.INFO; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/street/integration/BarrierRoutingTest.java b/application/src/test/java/org/opentripplanner/street/integration/BarrierRoutingTest.java index f28f4412b1e..c889b184f7c 100644 --- a/application/src/test/java/org/opentripplanner/street/integration/BarrierRoutingTest.java +++ b/application/src/test/java/org/opentripplanner/street/integration/BarrierRoutingTest.java @@ -196,7 +196,7 @@ private static String computePolyline( var linkingContextFactory = new LinkingContextFactory(graph, vertexCreationService); var linkingRequest = LinkingContextRequestMapper.map(request); var linkingContext = linkingContextFactory.create(temporaryVerticesContainer, linkingRequest); - var gpf = new GraphPathFinder(null); + var gpf = new GraphPathFinder(); var paths = gpf.graphPathFinderEntryPoint(request, linkingContext); GraphPathToItineraryMapper graphPathToItineraryMapper = new GraphPathToItineraryMapper( diff --git a/application/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java b/application/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java index e9f28b66a9e..46abc64884f 100644 --- a/application/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java +++ b/application/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java @@ -97,7 +97,7 @@ private static String computePolyline(Graph graph, GenericLocation from, Generic var linkingContextFactory = new LinkingContextFactory(graph, vertexCreationService); var linkingRequest = LinkingContextRequestMapper.map(request); var linkingContext = linkingContextFactory.create(temporaryVerticesContainer, linkingRequest); - var gpf = new GraphPathFinder(null); + var gpf = new GraphPathFinder(); var paths = gpf.graphPathFinderEntryPoint(request, linkingContext); GraphPathToItineraryMapper graphPathToItineraryMapper = new GraphPathToItineraryMapper( diff --git a/application/src/test/java/org/opentripplanner/street/integration/CarRoutingTest.java b/application/src/test/java/org/opentripplanner/street/integration/CarRoutingTest.java index c09f67b50c4..843f0209c68 100644 --- a/application/src/test/java/org/opentripplanner/street/integration/CarRoutingTest.java +++ b/application/src/test/java/org/opentripplanner/street/integration/CarRoutingTest.java @@ -146,7 +146,7 @@ private static String computePolyline(Graph graph, GenericLocation from, Generic var linkingContextFactory = new LinkingContextFactory(graph, vertexCreationService); var linkingRequest = LinkingContextRequestMapper.map(request); var linkingContext = linkingContextFactory.create(temporaryVerticesContainer, linkingRequest); - var gpf = new GraphPathFinder(null); + var gpf = new GraphPathFinder(); var paths = gpf.graphPathFinderEntryPoint(request, linkingContext); GraphPathToItineraryMapper graphPathToItineraryMapper = new GraphPathToItineraryMapper( diff --git a/application/src/test/java/org/opentripplanner/street/integration/SplitEdgeTurnRestrictionsTest.java b/application/src/test/java/org/opentripplanner/street/integration/SplitEdgeTurnRestrictionsTest.java index 8def307cdd1..5d168642719 100644 --- a/application/src/test/java/org/opentripplanner/street/integration/SplitEdgeTurnRestrictionsTest.java +++ b/application/src/test/java/org/opentripplanner/street/integration/SplitEdgeTurnRestrictionsTest.java @@ -178,7 +178,7 @@ private static String computeCarPolyline(Graph graph, GenericLocation from, Gene var linkingContextFactory = new LinkingContextFactory(graph, vertexCreationService); var linkingRequest = LinkingContextRequestMapper.map(request); var linkingContext = linkingContextFactory.create(temporaryVerticesContainer, linkingRequest); - var gpf = new GraphPathFinder(null); + var gpf = new GraphPathFinder(); var paths = gpf.graphPathFinderEntryPoint(request, linkingContext); GraphPathToItineraryMapper graphPathToItineraryMapper = new GraphPathToItineraryMapper( diff --git a/application/src/test/java/org/opentripplanner/street/integration/WalkRoutingTest.java b/application/src/test/java/org/opentripplanner/street/integration/WalkRoutingTest.java index fab37f07aa5..8029da967ac 100644 --- a/application/src/test/java/org/opentripplanner/street/integration/WalkRoutingTest.java +++ b/application/src/test/java/org/opentripplanner/street/integration/WalkRoutingTest.java @@ -99,7 +99,7 @@ private static List route( var linkingContextFactory = new LinkingContextFactory(graph, vertexCreationService); var linkingRequest = LinkingContextRequestMapper.map(request); var linkingContext = linkingContextFactory.create(temporaryVerticesContainer, linkingRequest); - var gpf = new GraphPathFinder(null); + var gpf = new GraphPathFinder(); return gpf.graphPathFinderEntryPoint(request, linkingContext); } } diff --git a/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java b/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java index 4db18162e69..9b5ca0d743a 100644 --- a/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/streetadapter/StreetSearchRequestMapperTest.java @@ -6,7 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.core.model.basic.Cost.costOfSeconds; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.Duration; import java.time.Instant; @@ -15,6 +15,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.opentripplanner.core.model.basic.Cost; +import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.api.request.RequestModes; import org.opentripplanner.routing.api.request.RouteRequest; @@ -25,7 +27,6 @@ import org.opentripplanner.street.search.intersection_model.DrivingDirection; import org.opentripplanner.street.search.intersection_model.IntersectionTraversalCalculator; import org.opentripplanner.street.search.intersection_model.IntersectionTraversalModel; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class StreetSearchRequestMapperTest { @@ -62,8 +63,8 @@ void mapFromToCoordinates() { void mapFromToStopIds() { var builder = builder(); - var from = GenericLocation.fromStopId("S1", "A", "STOP1"); - var to = GenericLocation.fromStopId("S2", "A", "STOP2"); + var from = GenericLocation.fromStopId(new FeedScopedId("A", "STOP1"), "S1"); + var to = GenericLocation.fromStopId(new FeedScopedId("A", "STOP2"), "S2"); var req = builder.withDateTime(INSTANT).withFrom(from).withTo(to).buildRequest(); @@ -79,7 +80,7 @@ void mapVehicleWalking() { Instant dateTime = INSTANT; builder.withDateTime(dateTime); - var from = new GenericLocation(null, id("STOP"), null, null); + var from = GenericLocation.fromStopId(id("STOP")); builder.withFrom(from); var to = GenericLocation.fromCoordinate(60.0, 20.0); builder.withTo(to); @@ -101,7 +102,7 @@ void mapVehicleWalking() { @ParameterizedTest @ValueSource(booleans = { true, false }) void mapTransferRequest(boolean arriveBy) { - var from = new GenericLocation(null, id("STOP"), null, null); + var from = GenericLocation.fromStopId(id("STOP")); var to = GenericLocation.fromCoordinate(60.0, 20.0); var builder = builder() .withArriveBy(arriveBy) @@ -332,7 +333,7 @@ void mapCarRentalArrivalRequest() { var dateTime = Instant.parse("2022-11-10T10:00:00Z"); var rentalDuration = Duration.ofHours(2); builder.withDateTime(dateTime); - var from = new GenericLocation(null, TimetableRepositoryForTest.id("STOP"), null, null); + var from = GenericLocation.fromStopId(FeedScopedIdForTestFactory.id("STOP")); builder.withFrom(from); var to = GenericLocation.fromCoordinate(60.0, 20.0); builder.withTo(to); diff --git a/application/src/test/java/org/opentripplanner/transfer/constrained/raptoradaptor/ConstrainedBoardingSearchTest.java b/application/src/test/java/org/opentripplanner/transfer/constrained/raptoradaptor/ConstrainedBoardingSearchTest.java index 0300126bd77..bbe54de6c81 100644 --- a/application/src/test/java/org/opentripplanner/transfer/constrained/raptoradaptor/ConstrainedBoardingSearchTest.java +++ b/application/src/test/java/org/opentripplanner/transfer/constrained/raptoradaptor/ConstrainedBoardingSearchTest.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.raptor.spi.RaptorBoardOrAlightEvent; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; @@ -27,7 +28,6 @@ import org.opentripplanner.transfer.constrained.model.StopTransferPoint; import org.opentripplanner.transfer.constrained.model.TransferConstraint; import org.opentripplanner.transfer.constrained.model.TripTransferPoint; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.network.RoutingTripPattern; import org.opentripplanner.transit.model.network.TripPattern; @@ -35,7 +35,7 @@ public class ConstrainedBoardingSearchTest { - private static final FeedScopedId ID = TimetableRepositoryForTest.id("ID"); + private static final FeedScopedId ID = FeedScopedIdForTestFactory.id("ID"); private static final TransferConstraint GUARANTEED_CONSTRAINT = TransferConstraint.of() .guaranteed() .build(); diff --git a/application/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java b/application/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java index 8c872b80147..0c67dcd7469 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java +++ b/application/src/test/java/org/opentripplanner/transit/model/_data/PatternTestModel.java @@ -1,6 +1,6 @@ package org.opentripplanner.transit.model._data; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import org.opentripplanner.core.model.id.FeedScopedId; import org.opentripplanner.transit.model.network.Route; diff --git a/application/src/test/java/org/opentripplanner/transit/model/_data/SiteRepositoryTestBuilder.java b/application/src/test/java/org/opentripplanner/transit/model/_data/SiteRepositoryTestBuilder.java index a90734efdab..5f342685d15 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/_data/SiteRepositoryTestBuilder.java +++ b/application/src/test/java/org/opentripplanner/transit/model/_data/SiteRepositoryTestBuilder.java @@ -1,6 +1,6 @@ package org.opentripplanner.transit.model._data; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.Optional; import java.util.function.Consumer; diff --git a/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryForTest.java b/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryForTest.java index b7f1df8587b..f632bd988ed 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryForTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryForTest.java @@ -1,5 +1,7 @@ package org.opentripplanner.transit.model._data; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; + import java.time.LocalTime; import java.util.Arrays; import java.util.List; @@ -104,10 +106,6 @@ public static TimetableRepositoryForTest of() { return new TimetableRepositoryForTest(SiteRepository.of()); } - public static FeedScopedId id(String id) { - return new FeedScopedId(FEED_ID, id); - } - public static Agency agency(String name) { return AGENCY.copy().withId(id(name)).withName(name).build(); } diff --git a/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryTestBuilder.java b/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryTestBuilder.java index 903d218fd63..b5cc7a426b1 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryTestBuilder.java +++ b/application/src/test/java/org/opentripplanner/transit/model/_data/TimetableRepositoryTestBuilder.java @@ -1,6 +1,6 @@ package org.opentripplanner.transit.model._data; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.time.ZoneId; diff --git a/application/src/test/java/org/opentripplanner/transit/model/_data/TransitTestEnvironment.java b/application/src/test/java/org/opentripplanner/transit/model/_data/TransitTestEnvironment.java index dc251229e99..f64c1773739 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/_data/TransitTestEnvironment.java +++ b/application/src/test/java/org/opentripplanner/transit/model/_data/TransitTestEnvironment.java @@ -1,10 +1,11 @@ package org.opentripplanner.transit.model._data; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.time.ZoneId; import org.opentripplanner.LocalTimeParser; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.routing.algorithm.raptoradapter.transit.RaptorTransitData; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RaptorTransitDataMapper; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.RealTimeRaptorTransitDataUpdater; diff --git a/application/src/test/java/org/opentripplanner/transit/model/basic/NoticeTest.java b/application/src/test/java/org/opentripplanner/transit/model/basic/NoticeTest.java index fbac179e9e7..9dd4ac9c4a6 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/basic/NoticeTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/basic/NoticeTest.java @@ -5,9 +5,10 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; class NoticeTest { @@ -15,7 +16,7 @@ class NoticeTest { private static final String TEXT = "text"; private static final String PUBLIC_CODE = "public code"; - private static final Notice SUBJECT = Notice.of(TimetableRepositoryForTest.id(ID)) + private static final Notice SUBJECT = Notice.of(id(ID)) .withPublicCode(PUBLIC_CODE) .withText(TEXT) .build(); @@ -44,7 +45,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withPublicCode("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withText("X").build())); } diff --git a/application/src/test/java/org/opentripplanner/transit/model/filter/transit/TripMatcherFactoryTest.java b/application/src/test/java/org/opentripplanner/transit/model/filter/transit/TripMatcherFactoryTest.java index 89c16e73f6a..dfbd06d9675 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/filter/transit/TripMatcherFactoryTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/filter/transit/TripMatcherFactoryTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.time.LocalDate; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/transit/model/framework/EntityByIdTest.java b/application/src/test/java/org/opentripplanner/transit/model/framework/EntityByIdTest.java index 23168453bd8..88bd876e96e 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/framework/EntityByIdTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/framework/EntityByIdTest.java @@ -7,16 +7,16 @@ import java.util.Collections; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; public class EntityByIdTest { - private static final FeedScopedId ID = TimetableRepositoryForTest.id("99"); + private static final FeedScopedId ID = FeedScopedIdForTestFactory.id("99"); private static final TestEntity E = TestEntity.of(ID).build(); private static final String E_TO_STRING = E.toString(); private static final String LIST_OF_E_TO_STRING = String.format("[%s]", E_TO_STRING); private static final String MAP_OF_E_TO_STRING = String.format("{%s=%s}", ID, E_TO_STRING); - private static final FeedScopedId FAKE_ID = TimetableRepositoryForTest.id("77"); + private static final FeedScopedId FAKE_ID = FeedScopedIdForTestFactory.id("77"); private final EntityById subject = new DefaultEntityById<>(); @Test diff --git a/application/src/test/java/org/opentripplanner/transit/model/network/GroupOfRoutesTest.java b/application/src/test/java/org/opentripplanner/transit/model/network/GroupOfRoutesTest.java index f3d2b053e1d..9a890f7b21a 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/network/GroupOfRoutesTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/network/GroupOfRoutesTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; public class GroupOfRoutesTest { @@ -17,7 +17,7 @@ public class GroupOfRoutesTest { private static final String NAME = "test_name"; private static final String DESCRIPTION = "description"; - private static final GroupOfRoutes SUBJECT = GroupOfRoutes.of(TimetableRepositoryForTest.id(ID)) + private static final GroupOfRoutes SUBJECT = GroupOfRoutes.of(FeedScopedIdForTestFactory.id(ID)) .withPrivateCode(PRIVATE_CODE) .withShortName(SHORT_NAME) .withName(NAME) @@ -51,7 +51,7 @@ public void sameValue() { // Make a copy, and set the same name (nothing is changed) var other = SUBJECT.copy().build(); assertTrue(SUBJECT.sameAs(other)); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withDescription("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withShortName("X").build())); diff --git a/application/src/test/java/org/opentripplanner/transit/model/network/RouteTest.java b/application/src/test/java/org/opentripplanner/transit/model/network/RouteTest.java index aaa5cbcdfad..0ccb3b47c51 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/network/RouteTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/network/RouteTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.SubMode; import org.opentripplanner.transit.model.basic.TransitMode; @@ -39,7 +40,7 @@ class RouteTest { private static final Integer GTFS_SORT_ORDER = 0; private static final String URL = "url"; public static final Agency AGENCY = TimetableRepositoryForTest.AGENCY; - private static final Route SUBJECT = Route.of(TimetableRepositoryForTest.id(ID)) + private static final Route SUBJECT = Route.of(FeedScopedIdForTestFactory.id(ID)) .withShortName(SHORT_NAME) .withLongName(LONG_NAME) .withDescription(DESCRIPTION) @@ -94,7 +95,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withShortName("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withLongName(new NonLocalizedString("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withDescription("X").build())); diff --git a/application/src/test/java/org/opentripplanner/transit/model/network/TripPatternTest.java b/application/src/test/java/org/opentripplanner/transit/model/network/TripPatternTest.java index 8243030c439..0037701691a 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/network/TripPatternTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/network/TripPatternTest.java @@ -5,8 +5,8 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.street.geometry.GeometryUtils.makeLineString; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.util.List; import org.junit.jupiter.api.Assertions; diff --git a/application/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java b/application/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java index de7c3295009..1811d8c7d78 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/organization/AgencyTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; class AgencyTest { @@ -19,7 +19,7 @@ class AgencyTest { private static final String FARE_URL = "http://fare.aaa.com"; private static final String LANG = "image"; - private static final Agency SUBJECT = Agency.of(TimetableRepositoryForTest.id(ID)) + private static final Agency SUBJECT = Agency.of(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withUrl(URL) .withTimezone(TIMEZONE) @@ -56,7 +56,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withUrl("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withTimezone("CET").build())); diff --git a/application/src/test/java/org/opentripplanner/transit/model/organization/BrandingTest.java b/application/src/test/java/org/opentripplanner/transit/model/organization/BrandingTest.java index c609195e55b..e7d5cd8b242 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/organization/BrandingTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/organization/BrandingTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; public class BrandingTest { @@ -18,7 +18,7 @@ public class BrandingTest { private static final String DESCRIPTION = "test_description"; private static final String IMAGE = "test_image"; - Branding subject = Branding.of(TimetableRepositoryForTest.id(ID)) + Branding subject = Branding.of(FeedScopedIdForTestFactory.id(ID)) .withShortName(SHORT_NAME) .withName(NAME) .withUrl(URL) @@ -52,7 +52,7 @@ void copy() { @Test void sameAs() { assertTrue(subject.sameAs(subject.copy().build())); - assertFalse(subject.sameAs(subject.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(subject.sameAs(subject.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(subject.sameAs(subject.copy().withName("X").build())); assertFalse(subject.sameAs(subject.copy().withShortName("X").build())); assertFalse(subject.sameAs(subject.copy().withUrl("X").build())); diff --git a/application/src/test/java/org/opentripplanner/transit/model/organization/OperatorTest.java b/application/src/test/java/org/opentripplanner/transit/model/organization/OperatorTest.java index e3fdb62b6a7..67414a86a87 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/organization/OperatorTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/organization/OperatorTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; class OperatorTest { @@ -16,7 +16,7 @@ class OperatorTest { private static final String URL = "http://info.aaa.com"; private static final String PHONE = "+47 95566333"; - private static final Operator SUBJECT = Operator.of(TimetableRepositoryForTest.id(ID)) + private static final Operator SUBJECT = Operator.of(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withUrl(URL) .withPhone(PHONE) @@ -45,7 +45,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withUrl("X").build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withPhone("X").build())); diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/AreaStopTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/AreaStopTest.java index bea8842364e..f2a23d51892 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/AreaStopTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/AreaStopTest.java @@ -11,6 +11,7 @@ import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.GeometryUtils; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; @@ -34,7 +35,7 @@ class AreaStopTest { private static AreaStopBuilder areaStopBuilder() { return SiteRepository.of() - .areaStop(TimetableRepositoryForTest.id(ID)) + .areaStop(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withDescription(DESCRIPTION) .withUrl(URL) @@ -69,7 +70,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse( SUBJECT.sameAs(SUBJECT.copy().withDescription(new NonLocalizedString("X")).build()) diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/BoardingAreaTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/BoardingAreaTest.java index d552ff731dd..b99b2ae18ec 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/BoardingAreaTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/BoardingAreaTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; @@ -21,7 +22,7 @@ class BoardingAreaTest { .stop("stopId") .build(); - private static final BoardingArea SUBJECT = BoardingArea.of(TimetableRepositoryForTest.id(ID)) + private static final BoardingArea SUBJECT = BoardingArea.of(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withDescription(DESCRIPTION) .withParentStop(PARENT_STOP) @@ -53,7 +54,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse( SUBJECT.sameAs(SUBJECT.copy().withDescription(new NonLocalizedString("X")).build()) diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/EntranceTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/EntranceTest.java index bc7673f2455..cb9fd5dc1c5 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/EntranceTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/EntranceTest.java @@ -10,6 +10,7 @@ import org.opentripplanner.core.model.accessibility.Accessibility; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; @@ -26,7 +27,7 @@ class EntranceTest { private static final TimetableRepositoryForTest TEST_MODEL = TimetableRepositoryForTest.of(); private static final Station PARENT_STATION = TEST_MODEL.station("stationId").build(); - private static final Entrance SUBJECT = Entrance.of(TimetableRepositoryForTest.id(ID)) + private static final Entrance SUBJECT = Entrance.of(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withDescription(DESCRIPTION) .withCode(CODE) @@ -66,7 +67,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse( SUBJECT.sameAs(SUBJECT.copy().withDescription(new NonLocalizedString("X")).build()) diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/FareZoneTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/FareZoneTest.java index 3e699a10b8f..536ded7c482 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/FareZoneTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/FareZoneTest.java @@ -7,13 +7,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; class FareZoneTest { private static final String ID = "1"; private static final String NAME = "name"; - private static final FareZone SUBJECT = FareZone.of(TimetableRepositoryForTest.id(ID)) + private static final FareZone SUBJECT = FareZone.of(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .build(); @@ -40,7 +40,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName("X").build())); } } diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/GroupOfStationsTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/GroupOfStationsTest.java index bce3d429a40..e5d689ffb94 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/GroupOfStationsTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/GroupOfStationsTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class GroupOfStationsTest { @@ -25,7 +26,7 @@ class GroupOfStationsTest { .build(); private static final GroupOfStations SUBJECT = GroupOfStations.of( - TimetableRepositoryForTest.id(ID) + FeedScopedIdForTestFactory.id(ID) ) .withName(NAME) .addChildStation(STATION) @@ -55,7 +56,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse( SUBJECT.sameAs( diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java index ee44afcb842..93d640e332d 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/GroupStopTest.java @@ -14,6 +14,7 @@ import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.service.SiteRepository; @@ -30,7 +31,7 @@ class GroupStopTest { Coordinates.BERLIN.getY() ).build(); private static final GroupStop SUBJECT = SiteRepository.of() - .groupStop(TimetableRepositoryForTest.id(ID)) + .groupStop(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .addLocation(STOP_LOCATION) .build(); @@ -49,7 +50,7 @@ void testGroupStopGeometry() { ).build(); GroupStop groupStop = SiteRepository.of() - .groupStop(TimetableRepositoryForTest.id(ID)) + .groupStop(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .addLocation(stopLocation1) .addLocation(stopLocation2) @@ -71,7 +72,7 @@ void testGroupStopEncompassingAreaGeometry() { ).build(); GroupStop groupStop = SiteRepository.of() - .groupStop(TimetableRepositoryForTest.id(ID)) + .groupStop(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .addLocation(stopLocation) .withEncompassingAreaGeometries(List.of(Polygons.BERLIN)) @@ -110,7 +111,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse( SUBJECT.sameAs( diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/MultiModalStationTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/MultiModalStationTest.java index 116843a2b70..ac5b965fe97 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/MultiModalStationTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/MultiModalStationTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; @@ -24,7 +25,7 @@ class MultiModalStationTest { public static final Set CHILD_STATIONS = Set.of(STATION_1, STATION_2); private static final MultiModalStation SUBJECT = MultiModalStation.of( - TimetableRepositoryForTest.id(ID) + FeedScopedIdForTestFactory.id(ID) ) .withName(NAME) .withChildStations(CHILD_STATIONS) @@ -55,7 +56,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withChildStations(Set.of(STATION_1)).build())); } diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/PathwayNodeTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/PathwayNodeTest.java index da54e83d080..209a7761db0 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/PathwayNodeTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/PathwayNodeTest.java @@ -10,6 +10,7 @@ import org.opentripplanner.core.model.accessibility.Accessibility; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; @@ -25,7 +26,7 @@ class PathwayNodeTest { public static final WgsCoordinate COORDINATE = new WgsCoordinate(0, 0); private static final StopLevel LEVEL = new StopLevel("level", 0); private static final Accessibility WHEELCHAIR_ACCESSIBILITY = Accessibility.POSSIBLE; - private static final PathwayNode SUBJECT = PathwayNode.of(TimetableRepositoryForTest.id(ID)) + private static final PathwayNode SUBJECT = PathwayNode.of(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withDescription(DESCRIPTION) .withCode(CODE) @@ -65,7 +66,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse( SUBJECT.sameAs(SUBJECT.copy().withDescription(new NonLocalizedString("X")).build()) diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/PathwayTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/PathwayTest.java index 61f8a653ec2..6c922943da9 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/PathwayTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/PathwayTest.java @@ -8,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; @@ -16,13 +17,13 @@ class PathwayTest { private static final String ID = "1:pathway"; private static final String NAME = "name"; private static final PathwayMode MODE = PathwayMode.ESCALATOR; - private static final PathwayNode FROM = PathwayNode.of(TimetableRepositoryForTest.id("1:node")) + private static final PathwayNode FROM = PathwayNode.of(FeedScopedIdForTestFactory.id("1:node")) .withCoordinate(new WgsCoordinate(20, 30)) .build(); private static final RegularStop TO = TimetableRepositoryForTest.of().stop("1:stop").build(); public static final int TRAVERSAL_TIME = 120; - private final Pathway subject = Pathway.of(TimetableRepositoryForTest.id(ID)) + private final Pathway subject = Pathway.of(FeedScopedIdForTestFactory.id(ID)) .withPathwayMode(MODE) .withSignpostedAs(NAME) .withFromStop(FROM) @@ -64,7 +65,7 @@ void copy() { @Test void sameAs() { assertTrue(subject.sameAs(subject.copy().build())); - assertFalse(subject.sameAs(subject.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(subject.sameAs(subject.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(subject.sameAs(subject.copy().withSignpostedAs("X").build())); assertFalse(subject.sameAs(subject.copy().withReverseSignpostedAs("X").build())); assertFalse(subject.sameAs(subject.copy().withPathwayMode(PathwayMode.ELEVATOR).build())); diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/RegularStopTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/RegularStopTest.java index a7b76de1aa5..8e107379643 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/RegularStopTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/RegularStopTest.java @@ -13,6 +13,7 @@ import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.SubMode; @@ -38,7 +39,7 @@ class RegularStopTest { private static final String PLATFORM_CODE = "platformCode"; private static final RegularStop SUBJECT = SiteRepository.of() - .regularStop(TimetableRepositoryForTest.id(ID)) + .regularStop(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withDescription(DESCRIPTION) .withCode(CODE) @@ -87,7 +88,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse( SUBJECT.sameAs(SUBJECT.copy().withDescription(new NonLocalizedString("X")).build()) @@ -113,7 +114,7 @@ void fareZonesAreSortedById() { var zoneB = FareZone.of(new FeedScopedId("F", "B")).build(); var stop = SiteRepository.of() - .regularStop(TimetableRepositoryForTest.id("sortTest")) + .regularStop(FeedScopedIdForTestFactory.id("sortTest")) .withName(NAME) .withCoordinate(COORDINATE) .addFareZone(zoneC) diff --git a/application/src/test/java/org/opentripplanner/transit/model/site/StationTest.java b/application/src/test/java/org/opentripplanner/transit/model/site/StationTest.java index 599b17321fd..29cd50621b4 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/site/StationTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/site/StationTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.i18n.I18NString; import org.opentripplanner.core.model.i18n.NonLocalizedString; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.WgsCoordinate; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; @@ -27,7 +28,7 @@ class StationTest { private static final TimetableRepositoryForTest TEST_MODEL = TimetableRepositoryForTest.of(); private static final Station PARENT_STATION = TEST_MODEL.station("stationId").build(); - private static final Station SUBJECT = Station.of(TimetableRepositoryForTest.id(ID)) + private static final Station SUBJECT = Station.of(FeedScopedIdForTestFactory.id(ID)) .withName(NAME) .withDescription(DESCRIPTION) .withCode(CODE) @@ -66,7 +67,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withName(new NonLocalizedString("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withCode("X").build())); assertFalse( diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java index 7398de93ca4..020ca003820 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimesTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_DWELL_TIME; import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME; diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java index fbf61d27435..f457213b1e8 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import java.util.BitSet; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/StopTimeKeyTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/StopTimeKeyTest.java index 60ff095ba9c..708bfc926ec 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/StopTimeKeyTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/StopTimeKeyTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; class StopTimeKeyTest { @@ -13,7 +13,7 @@ class StopTimeKeyTest { private static final int STOP_SEQUENCE_NUMBER = 1; private static final StopTimeKey SUBJECT = StopTimeKey.of( - TimetableRepositoryForTest.id(ID), + FeedScopedIdForTestFactory.id(ID), STOP_SEQUENCE_NUMBER ).build(); @@ -30,6 +30,6 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); } } diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/TimetableTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/TimetableTest.java index 6d4193aebc8..39fb2e673b1 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/TimetableTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/TimetableTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/TripOnServiceDateTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/TripOnServiceDateTest.java index 5b1c5a2eca3..48b5e81743f 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/TripOnServiceDateTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/TripOnServiceDateTest.java @@ -7,6 +7,7 @@ import java.time.LocalDate; import java.util.List; import org.junit.jupiter.api.Test; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; class TripOnServiceDateTest { @@ -14,12 +15,12 @@ class TripOnServiceDateTest { private static final String ID = "1"; private static final TripAlteration TRIP_ALTERATION = TripAlteration.CANCELLATION; private static final List REPLACEMENT_FOR = List.of( - TripOnServiceDate.of(TimetableRepositoryForTest.id("id1")).build() + TripOnServiceDate.of(FeedScopedIdForTestFactory.id("id1")).build() ); public static final LocalDate SERVICE_DATE = LocalDate.now(); public static final String TRIP_ID = "tripId"; private static final TripOnServiceDate SUBJECT = TripOnServiceDate.of( - TimetableRepositoryForTest.id(ID) + FeedScopedIdForTestFactory.id(ID) ) .withTrip(TimetableRepositoryForTest.trip(TRIP_ID).build()) .withServiceDate(SERVICE_DATE) @@ -44,7 +45,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse( SUBJECT.sameAs(SUBJECT.copy().withServiceDate(LocalDate.now().plusDays(1)).build()) ); @@ -53,7 +54,7 @@ void sameAs() { SUBJECT.sameAs( SUBJECT.copy() .withReplacementFor( - List.of(TripOnServiceDate.of(TimetableRepositoryForTest.id("id2")).build()) + List.of(TripOnServiceDate.of(FeedScopedIdForTestFactory.id("id2")).build()) ) .build() ) diff --git a/application/src/test/java/org/opentripplanner/transit/model/timetable/TripTest.java b/application/src/test/java/org/opentripplanner/transit/model/timetable/TripTest.java index 0a306acd161..ce6b151ad05 100644 --- a/application/src/test/java/org/opentripplanner/transit/model/timetable/TripTest.java +++ b/application/src/test/java/org/opentripplanner/transit/model/timetable/TripTest.java @@ -8,6 +8,7 @@ import org.opentripplanner.core.model.accessibility.Accessibility; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.SubMode; import org.opentripplanner.transit.model.basic.TransitMode; @@ -37,7 +38,7 @@ class TripTest { .build(); private static final FeedScopedId SERVICE_ID = FeedScopedId.of("x", "serviceId"); private static final FeedScopedId SHAPE_ID = FeedScopedId.of("x", "shapeId"); - private static final Trip SUBJECT = Trip.of(TimetableRepositoryForTest.id(ID)) + private static final Trip SUBJECT = Trip.of(FeedScopedIdForTestFactory.id(ID)) .withShortName(SHORT_NAME) .withRoute(ROUTE) .withDirection(DIRECTION) @@ -63,7 +64,7 @@ void shouldCopyFieldsFromRoute() { .withBikesAllowed(BIKE_ACCESS) .build(); - var SUBJECT = Trip.of(TimetableRepositoryForTest.id(ID)).withRoute(routeWithModes).build(); + var SUBJECT = Trip.of(FeedScopedIdForTestFactory.id(ID)).withRoute(routeWithModes).build(); assertEquals(TRANSIT_MODE, SUBJECT.getMode()); assertEquals(NETEX_SUBMODE, SUBJECT.getNetexSubMode()); @@ -98,7 +99,7 @@ void copy() { @Test void sameAs() { assertTrue(SUBJECT.sameAs(SUBJECT.copy().build())); - assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(TimetableRepositoryForTest.id("X")).build())); + assertFalse(SUBJECT.sameAs(SUBJECT.copy().withId(FeedScopedIdForTestFactory.id("X")).build())); assertFalse(SUBJECT.sameAs(SUBJECT.copy().withShortName("X").build())); assertFalse( SUBJECT.sameAs(SUBJECT.copy().withWheelchairBoarding(Accessibility.NOT_POSSIBLE).build()) diff --git a/application/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java b/application/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java index 3c6e477cec3..44c4f94d36c 100644 --- a/application/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java +++ b/application/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.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.basic.TransitMode.RAIL; diff --git a/application/src/test/java/org/opentripplanner/transit/service/ReplacementHelperTest.java b/application/src/test/java/org/opentripplanner/transit/service/ReplacementHelperTest.java index f71eb5b40d5..c805d6f1bf6 100644 --- a/application/src/test/java/org/opentripplanner/transit/service/ReplacementHelperTest.java +++ b/application/src/test/java/org/opentripplanner/transit/service/ReplacementHelperTest.java @@ -5,7 +5,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; import java.util.stream.StreamSupport; diff --git a/application/src/test/java/org/opentripplanner/transit/service/SiteRepositoryTest.java b/application/src/test/java/org/opentripplanner/transit/service/SiteRepositoryTest.java index d45a9281b16..19997c3be38 100644 --- a/application/src/test/java/org/opentripplanner/transit/service/SiteRepositoryTest.java +++ b/application/src/test/java/org/opentripplanner/transit/service/SiteRepositoryTest.java @@ -10,9 +10,9 @@ import org.locationtech.jts.geom.Geometry; import org.opentripplanner.core.model.i18n.NonLocalizedString; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.street.geometry.GeometryUtils; import org.opentripplanner.street.geometry.WgsCoordinate; -import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.site.AreaStop; import org.opentripplanner.transit.model.site.GroupOfStations; import org.opentripplanner.transit.model.site.GroupStop; @@ -28,7 +28,7 @@ class SiteRepositoryTest { COOR_A.asJtsCoordinate() ); public static final NonLocalizedString NAME = NonLocalizedString.ofNullable("Name"); - private static final FeedScopedId ID = TimetableRepositoryForTest.id("A"); + private static final FeedScopedId ID = FeedScopedIdForTestFactory.id("A"); private static final Station STATION = Station.of(ID) .withName(NAME) .withCoordinate(COOR_B) diff --git a/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java b/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java index bef23268dd6..98beae64963 100644 --- a/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java +++ b/application/src/test/java/org/opentripplanner/transit/service/TimetableRepositoryTest.java @@ -2,8 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.framework.application.OtpFileNames.BUILD_CONFIG_FILENAME; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.route; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.stopPattern; import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.tripPattern; @@ -14,7 +14,7 @@ import org.opentripplanner.ConstantsForTests; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.core.model.id.FeedScopedId; -import org.opentripplanner.ext.fares.service.gtfs.v1.DefaultFareServiceFactory; +import org.opentripplanner.ext.fares.service.gtfs.v1.GtfsFareServiceFactory; import org.opentripplanner.graph_builder.module.TimeZoneAdjusterModule; import org.opentripplanner.street.graph.Graph; import org.opentripplanner.test.support.ResourceLoader; @@ -44,7 +44,7 @@ void validateTimeZones() { graph, timetableRepository, ConstantsForTests.SIMPLE_GTFS, - new DefaultFareServiceFactory(), + new GtfsFareServiceFactory(), FAKE_FEED_ID ); @@ -66,7 +66,7 @@ void validateTimeZones() { graph, timetableRepository, RESOURCE_LOADER.file("kcm_gtfs.zip"), - new DefaultFareServiceFactory(), + new GtfsFareServiceFactory(), null ), ("The graph contains agencies with different time zones. " + @@ -89,7 +89,7 @@ void validateTimeZonesWithExplicitTimeZone() { graph, timetableRepository, ConstantsForTests.SIMPLE_GTFS, - new DefaultFareServiceFactory(), + new GtfsFareServiceFactory(), FAKE_FEED_ID ); @@ -98,7 +98,7 @@ void validateTimeZonesWithExplicitTimeZone() { graph, timetableRepository, RESOURCE_LOADER.file("kcm_gtfs.zip"), - new DefaultFareServiceFactory(), + new GtfsFareServiceFactory(), null ); diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java index b62778c4ff4..8d1c40d4308 100644 --- a/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java @@ -161,7 +161,6 @@ public SpeedTest( null, null, null, - null, null ); // Creating raptor transit data should be integrated into the TimetableRepository, but for now @@ -257,7 +256,9 @@ private void runSampleTest(SpeedTestProfile profile, int sample, int nSamples) { // We assume we are debugging and not measuring performance if we only run 1 test-case // one time; Hence skip JIT compiler warm-up. if (testCases.runJitWarmUp() || opts.profiles().length > 1) { - performRouting(testCases.getJitWarmUpCase()); + for (var tc : testCases.getJitWarmUpCases()) { + performRouting(tc); + } } ResultPrinter.logSingleTestHeader(profile); diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCaseDefinition.java b/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCaseDefinition.java index 906158f0991..afe7c9ef1c2 100644 --- a/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCaseDefinition.java +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCaseDefinition.java @@ -30,9 +30,9 @@ public String toString() { return String.format( "#%s %s - via:%s - %s, %s - via:%s - %s, %s-%s(%s)", id, - fromPlace.label, + fromPlace.label(), viaLocation != null ? viaLocation.label() : null, - toPlace.label, + toPlace.label(), coordinateString(fromPlace), viaLocation != null ? coordinateString(viaLocation.coordinateLocation()) : null, coordinateString(toPlace), @@ -59,6 +59,12 @@ public boolean arrivalTimeSet() { } private String coordinateString(GenericLocation location) { - return ValueObjectToStringBuilder.of().addCoordinate(location.lat, location.lng).toString(); + var coord = location.wgsCoordinate(); + if (coord == null) { + return ValueObjectToStringBuilder.of().addCoordinate(null, null).toString(); + } + return ValueObjectToStringBuilder.of() + .addCoordinate(coord.latitude(), coord.longitude()) + .toString(); } } diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCases.java b/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCases.java index a11ffc3cfc6..deea10eaec4 100644 --- a/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCases.java +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/TestCases.java @@ -1,8 +1,11 @@ package org.opentripplanner.transit.speed_test.model.testcase; import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -35,11 +38,38 @@ public boolean runJitWarmUp() { } /** - * If we need to warm up the JIT compiler, we run the last test-case. This avoids repeating the - * same test case twice if more than one test-case exist. + * Select test-cases to use for JIT warm-up. + *

      + * If there are three or fewer cases, all cases are returned. Otherwise, a small set of cases + * is selected. When there are multiple mode combinations, the first test-case for each mode + * combination is selected first. Additional cases are then sampled from across the list until + * at least three cases have been selected. */ - public TestCase getJitWarmUpCase() { - return cases.get(numberOfTestCases() - 1); + public List getJitWarmUpCases() { + if (numberOfTestCases() <= 3) { + return List.copyOf(cases); + } + var samples = new HashSet(); + + var groupByModes = cases.stream().collect(Collectors.groupingBy(tc -> tc.definition().modes())); + + // If more than 1 mode combination exists, return the first test-case for each + // mode combination + if (groupByModes.keySet().size() > 1) { + for (var it : groupByModes.values()) { + samples.add(it.getFirst()); + } + } + // There are at least 4 cases, so we split the set in 4 groups and pick the first element in + // the 3 last groups. + int step = numberOfTestCases() / 4; + int index = step; + + while (samples.size() < 3) { + samples.add(cases.get(index)); + index += step; + } + return samples.stream().sorted(Comparator.comparing(TestCase::id)).toList(); } public Iterable iterable() { diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/io/TestCaseDefinitionCsvFile.java b/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/io/TestCaseDefinitionCsvFile.java index 8c593638199..19935ee5645 100644 --- a/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/io/TestCaseDefinitionCsvFile.java +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/model/testcase/io/TestCaseDefinitionCsvFile.java @@ -33,15 +33,15 @@ TestCaseDefinition parseRow() throws IOException { parseTime("departure"), parseTime("arrival"), parseDuration("window"), - new GenericLocation( + genericLocation( parseString("origin"), - FeedScopedId.ofNullable(feedId, parseString("fromPlace")), + parseString("fromPlace"), parseDouble("fromLat"), parseDouble("fromLon") ), - new GenericLocation( + genericLocation( parseString("destination"), - FeedScopedId.ofNullable(feedId, parseString("toPlace")), + parseString("toPlace"), parseDouble("toLat"), parseDouble("toLon") ), @@ -51,6 +51,14 @@ TestCaseDefinition parseRow() throws IOException { ); } + private GenericLocation genericLocation(String label, String id, double lat, double lon) { + var feedScopedId = FeedScopedId.ofNullable(feedId, id); + if (feedScopedId == null) { + return GenericLocation.fromCoordinate(lat, lon, label); + } + return GenericLocation.fromStopIdWithFallback(feedScopedId, lat, lon, label); + } + @Nullable private VisitViaLocation parseViaLocation() { try { diff --git a/application/src/test/java/org/opentripplanner/updater/GtfsRealtimeFuzzyTripMatcherTest.java b/application/src/test/java/org/opentripplanner/updater/GtfsRealtimeFuzzyTripMatcherTest.java index 0fe87c9c150..287675e86c2 100644 --- a/application/src/test/java/org/opentripplanner/updater/GtfsRealtimeFuzzyTripMatcherTest.java +++ b/application/src/test/java/org/opentripplanner/updater/GtfsRealtimeFuzzyTripMatcherTest.java @@ -7,7 +7,7 @@ import java.time.LocalDate; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.transit.model._data.TransitTestEnvironment; import org.opentripplanner.transit.model._data.TransitTestEnvironmentBuilder; import org.opentripplanner.transit.model._data.TripInput; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/AddedTest.java b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/AddedTest.java index 5bf796cee26..868cc626f98 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/AddedTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/AddedTest.java @@ -8,7 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; import static org.opentripplanner.updater.trip.UpdateIncrementality.DIFFERENTIAL; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/ReplacementTest.java b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/ReplacementTest.java index e473b84a276..279f7281ff6 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/ReplacementTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/addition/ReplacementTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.i18n.I18NString; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancelAfterPatternChangeTest.java b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancelAfterPatternChangeTest.java index 6d40541ae63..68481023cea 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancelAfterPatternChangeTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancelAfterPatternChangeTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; import static org.opentripplanner.updater.trip.UpdateIncrementality.DIFFERENTIAL; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CanceledTripTest.java b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CanceledTripTest.java index 0d47bae9ee4..847ed6b09c9 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CanceledTripTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CanceledTripTest.java @@ -3,7 +3,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship.CANCELED; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; import org.junit.jupiter.api.Test; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancellationDeletionTest.java b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancellationDeletionTest.java index 004545dc76d..7945a920b11 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancellationDeletionTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/cancellation/CancellationDeletionTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; import static org.opentripplanner.updater.trip.UpdateIncrementality.DIFFERENTIAL; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/delay/SkippedTest.java b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/delay/SkippedTest.java index 4de05d4e6d2..d2c04ad8688 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/delay/SkippedTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/gtfs/moduletests/delay/SkippedTest.java @@ -7,7 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; import static org.opentripplanner.updater.trip.UpdateIncrementality.DIFFERENTIAL; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/siri/AddedTripBuilderTest.java b/application/src/test/java/org/opentripplanner/updater/trip/siri/AddedTripBuilderTest.java index 9993609a282..1f10b3b24af 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/siri/AddedTripBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/siri/AddedTripBuilderTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.calendar.CalendarServiceData; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; @@ -45,7 +46,7 @@ class AddedTripBuilderTest { private static final Agency AGENCY = TimetableRepositoryForTest.AGENCY; private static final ZoneId TIME_ZONE = AGENCY.getTimezone(); - private static final Operator OPERATOR = Operator.of(TimetableRepositoryForTest.id("OPERATOR_ID")) + private static final Operator OPERATOR = Operator.of(FeedScopedIdForTestFactory.id("OPERATOR_ID")) .withName("OPERATOR_NAME") .build(); private static final Route REPLACED_ROUTE = TimetableRepositoryForTest.route("REPLACED_ROUTE") @@ -53,8 +54,8 @@ class AddedTripBuilderTest { .withOperator(OPERATOR) .build(); private static final String LINE_REF = "ROUTE_ID"; - private static final FeedScopedId TRIP_ID = TimetableRepositoryForTest.id("TRIP_ID"); - private static final FeedScopedId DATED_SERVICE_JOURNEY_ID = TimetableRepositoryForTest.id( + private static final FeedScopedId TRIP_ID = FeedScopedIdForTestFactory.id("TRIP_ID"); + private static final FeedScopedId DATED_SERVICE_JOURNEY_ID = FeedScopedIdForTestFactory.id( "DATED_SERVICE_JOURNEY_ID" ); private static final LocalDate SERVICE_DATE = LocalDate.of(2023, 2, 17); @@ -96,7 +97,7 @@ void setUp() { // Crate a scheduled calendar, to have the SERVICE_DATE be within the transit feed coverage CalendarServiceData calendarServiceData = new CalendarServiceData(); - var cal_id = TimetableRepositoryForTest.id("CAL_1"); + var cal_id = FeedScopedIdForTestFactory.id("CAL_1"); calendarServiceData.putServiceDatesForServiceId( cal_id, List.of(SERVICE_DATE.minusDays(1), SERVICE_DATE, SERVICE_DATE.plusDays(1)) @@ -264,8 +265,8 @@ void testAddedTripOnAddedRoute() { var firstTrip = firstAddedTrip.tripTimes().getTrip(); - var tripId2 = TimetableRepositoryForTest.id("TRIP_ID_2"); - var datedServiceJourneyId2 = TimetableRepositoryForTest.id("DATED_SERVICE_JOURNEY_ID_2"); + var tripId2 = FeedScopedIdForTestFactory.id("TRIP_ID_2"); + var datedServiceJourneyId2 = FeedScopedIdForTestFactory.id("DATED_SERVICE_JOURNEY_ID_2"); var secondAddedTrip = new AddedTripBuilder( transitService, @@ -570,7 +571,7 @@ void testGetTransportMode( String subMode ) { // Arrange - var route = Route.of(TimetableRepositoryForTest.id(LINE_REF)) + var route = Route.of(FeedScopedIdForTestFactory.id(LINE_REF)) .withShortName(SHORT_NAME) .withAgency(AGENCY) .withMode(TransitMode.valueOf(replacedRouteMode)) diff --git a/application/src/test/java/org/opentripplanner/updater/trip/siri/ModifiedTripBuilderTest.java b/application/src/test/java/org/opentripplanner/updater/trip/siri/ModifiedTripBuilderTest.java index fd595c9d11d..5ba43aefe29 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/siri/ModifiedTripBuilderTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/siri/ModifiedTripBuilderTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.calendar.CalendarServiceData; @@ -69,7 +70,7 @@ class ModifiedTripBuilderTest { .withStopPattern(TimetableRepositoryForTest.stopPattern(STOP_A_1, STOP_B_1, STOP_C_1)) .build(); - private static final FeedScopedId SERVICE_ID = TimetableRepositoryForTest.id("CAL_1"); + private static final FeedScopedId SERVICE_ID = FeedScopedIdForTestFactory.id("CAL_1"); private static final Trip TRIP = TimetableRepositoryForTest.trip("TRIP") .withRoute(ROUTE) diff --git a/application/src/test/java/org/opentripplanner/updater/trip/siri/SiriFuzzyTripMatcherTest.java b/application/src/test/java/org/opentripplanner/updater/trip/siri/SiriFuzzyTripMatcherTest.java index 324da7e5189..93974cc0e16 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/siri/SiriFuzzyTripMatcherTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/siri/SiriFuzzyTripMatcherTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.updater.trip.siri; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateErrorType.MULTIPLE_FUZZY_TRIP_MATCHES; import static org.opentripplanner.updater.spi.UpdateErrorType.NO_FUZZY_TRIP_MATCH; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertFailure; diff --git a/application/src/test/java/org/opentripplanner/updater/trip/siri/moduletests/extrajourney/ExtraJourneyTest.java b/application/src/test/java/org/opentripplanner/updater/trip/siri/moduletests/extrajourney/ExtraJourneyTest.java index 49d588120ac..63cbae11541 100644 --- a/application/src/test/java/org/opentripplanner/updater/trip/siri/moduletests/extrajourney/ExtraJourneyTest.java +++ b/application/src/test/java/org/opentripplanner/updater/trip/siri/moduletests/extrajourney/ExtraJourneyTest.java @@ -3,7 +3,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertFailure; import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertSuccess; diff --git a/application/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java b/application/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java index 78b033ce175..797a030c1e2 100644 --- a/application/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java +++ b/application/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java @@ -2,8 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.standalone.config.framework.json.JsonSupport.newNodeAdapterForTest; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import com.google.common.util.concurrent.Futures; import java.util.List; diff --git a/application/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java b/application/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java index 7554426a665..cdc5bf45d0a 100644 --- a/application/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java +++ b/application/src/test/java/org/opentripplanner/updater/vehicle_position/RealtimeVehicleMatcherTest.java @@ -27,6 +27,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.core.model.id.FeedScopedId; +import org.opentripplanner.core.model.id.FeedScopedIdForTestFactory; import org.opentripplanner.model.StopTime; import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.standalone.config.routerconfig.updaters.VehiclePositionsUpdaterConfig; @@ -53,7 +54,7 @@ public class RealtimeVehicleMatcherTest { ); ZoneId zoneId = ZoneIds.BERLIN; String tripId = "trip1"; - FeedScopedId scopedTripId = TimetableRepositoryForTest.id(tripId); + FeedScopedId scopedTripId = FeedScopedIdForTestFactory.id(tripId); @Test public void matchRealtimeVehiclesToTrip() { @@ -126,7 +127,7 @@ public void sequenceId() { var service = new DefaultRealtimeVehicleService(null); var tripId = "trip1"; - var scopedTripId = TimetableRepositoryForTest.id(tripId); + var scopedTripId = FeedScopedIdForTestFactory.id(tripId); var trip1 = TimetableRepositoryForTest.trip(tripId).build(); var stopTimes = List.of( @@ -275,8 +276,8 @@ public void clearOldTrips() { var tripId1 = "trip1"; var tripId2 = "trip2"; - var scopedTripId1 = TimetableRepositoryForTest.id(tripId1); - var scopedTripId2 = TimetableRepositoryForTest.id(tripId2); + var scopedTripId1 = FeedScopedIdForTestFactory.id(tripId1); + var scopedTripId2 = FeedScopedIdForTestFactory.id(tripId2); var trip1 = TimetableRepositoryForTest.trip(tripId1).build(); var trip2 = TimetableRepositoryForTest.trip(tripId2).build(); diff --git a/application/src/test/java/org/opentripplanner/updater/vehicle_rental/GeofencingVertexUpdaterTest.java b/application/src/test/java/org/opentripplanner/updater/vehicle_rental/GeofencingVertexUpdaterTest.java index 46b65252a6e..a61219e5290 100644 --- a/application/src/test/java/org/opentripplanner/updater/vehicle_rental/GeofencingVertexUpdaterTest.java +++ b/application/src/test/java/org/opentripplanner/updater/vehicle_rental/GeofencingVertexUpdaterTest.java @@ -2,9 +2,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.opentripplanner.core.model.id.FeedScopedIdForTestFactory.id; import static org.opentripplanner.street.model.StreetModelForTest.intersectionVertex; import static org.opentripplanner.street.model.StreetModelForTest.streetEdge; -import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/application/src/test/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterTest.java b/application/src/test/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterTest.java index 47f13569b30..a638747f9db 100644 --- a/application/src/test/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterTest.java +++ b/application/src/test/java/org/opentripplanner/updater/vehicle_rental/VehicleRentalUpdaterTest.java @@ -30,6 +30,7 @@ class VehicleRentalUpdaterTest { public static final VehicleRentalUpdaterParameters PARAMS = new VehicleRentalUpdaterParameters( "A", Duration.ofMinutes(1), + Duration.ZERO, new FakeParams() ); public static final DefaultVehicleRentalService SERVICE = new DefaultVehicleRentalService(); diff --git a/application/src/test/java/org/opentripplanner/warmup/WarmupQueryValidationTest.java b/application/src/test/java/org/opentripplanner/warmup/WarmupQueryValidationTest.java new file mode 100644 index 00000000000..a6857db3b89 --- /dev/null +++ b/application/src/test/java/org/opentripplanner/warmup/WarmupQueryValidationTest.java @@ -0,0 +1,56 @@ +package org.opentripplanner.warmup; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import graphql.parser.Parser; +import graphql.schema.GraphQLSchema; +import graphql.validation.Validator; +import java.util.Locale; +import org.junit.jupiter.api.Test; +import org.opentripplanner._support.time.ZoneIds; +import org.opentripplanner.api.model.transit.DefaultFeedIdMapper; +import org.opentripplanner.apis.gtfs.SchemaFactory; +import org.opentripplanner.apis.support.graphql.injectdoc.ApiDocumentationProfile; +import org.opentripplanner.apis.transmodel.TransmodelGraphQLSchemaFactory; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitTuningParametersTestFactory; +import org.opentripplanner.routing.api.request.RouteRequest; + +/** + * Validates that the warmup GraphQL queries are syntactically and structurally valid + * against the actual schemas. This catches field renames, removed arguments, or + * invalid enum values at build time rather than at runtime. + */ +class WarmupQueryValidationTest { + + private static final GraphQLSchema GTFS_SCHEMA = SchemaFactory.createSchemaWithDefaultInjection( + RouteRequest.defaultValue() + ); + + private static final GraphQLSchema TRANSMODEL_SCHEMA = new TransmodelGraphQLSchemaFactory( + RouteRequest.defaultValue(), + ZoneIds.OSLO, + TransitTuningParametersTestFactory.forTest(), + new DefaultFeedIdMapper(), + ApiDocumentationProfile.DEFAULT + ).create(); + + @Test + void transmodelQueryIsValid() { + var errors = new Validator().validateDocument( + TRANSMODEL_SCHEMA, + Parser.parse(TransmodelWarmupQueryExecutor.QUERY), + Locale.ROOT + ); + assertEquals(0, errors.size(), errors.toString()); + } + + @Test + void gtfsQueryIsValid() { + var errors = new Validator().validateDocument( + GTFS_SCHEMA, + Parser.parse(GtfsWarmupQueryExecutor.QUERY), + Locale.ROOT + ); + assertEquals(0, errors.size(), errors.toString()); + } +} diff --git a/application/src/test/resources/standalone/config/router-config.json b/application/src/test/resources/standalone/config/router-config.json index fd3d00cbf25..b454e817954 100644 --- a/application/src/test/resources/standalone/config/router-config.json +++ b/application/src/test/resources/standalone/config/router-config.json @@ -473,6 +473,13 @@ "maxPrimingIdleTime": "1s" } ], + "warmup": { + "api": "transmodel", + "from": { "lat": 59.9139, "lon": 10.7522 }, + "to": { "lat": 59.95, "lon": 10.76 }, + "accessModes": ["WALK", "CAR_TO_PARK"], + "egressModes": ["WALK", "WALK"] + }, "rideHailingServices": [ { "type": "uber-car-hailing", diff --git a/astar/src/main/java/org/opentripplanner/astar/model/ShortestPathTree.java b/astar/src/main/java/org/opentripplanner/astar/model/ShortestPathTree.java index 20320d5b93b..c6a5f8fef23 100644 --- a/astar/src/main/java/org/opentripplanner/astar/model/ShortestPathTree.java +++ b/astar/src/main/java/org/opentripplanner/astar/model/ShortestPathTree.java @@ -2,9 +2,7 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import javax.annotation.Nullable; import org.opentripplanner.astar.spi.AStarEdge; @@ -45,29 +43,6 @@ public ShortestPathTree(DominanceFunction dominanceFunction) { stateSets = new SegmentedIdentityMap<>(25_000); } - /** @return a list of GraphPaths, sometimes empty but never null. */ - @SuppressWarnings("unchecked") - public List> getPaths(Vertex dest) { - Object existing = stateSets.get(dest); - if (existing == null) { - return Collections.emptyList(); - } - List> ret = new LinkedList<>(); - if (!(existing instanceof List)) { - State s = (State) existing; - if (s.isFinal()) { - ret.add(new GraphPath<>(s)); - } - } else { - for (State s : (List) existing) { - if (s.isFinal()) { - ret.add(new GraphPath<>(s)); - } - } - } - return ret; - } - /** @return a single optimal, optionally back-optimized path to the given vertex. */ public GraphPath getPath(Vertex dest) { State s = getState(dest); @@ -162,27 +137,6 @@ public State getState(Vertex dest) { return ret; } - /** - * Returns a collection of 'interesting' states for the given Vertex. Depending on the - * implementation, this could contain a single optimal state, a set of Pareto-optimal states, or - * even states that are not known to be optimal but are judged interesting by some other - * criteria. - * - * @param dest the vertex of interest - * @return a collection of 'interesting' states at that vertex - */ - @SuppressWarnings("unchecked") - public List getStates(Vertex dest) { - Object existing = stateSets.get(dest); - if (existing == null) { - return null; - } - if (existing instanceof List) { - return (List) existing; - } - return List.of((State) existing); - } - /** * The visit method should be called upon extracting a State from a priority queue. It checks * whether the State is still worth visiting (i.e. whether it has been dominated since it was diff --git a/astar/src/test/java/org/opentripplanner/astar/model/ShortestPathTreeTest.java b/astar/src/test/java/org/opentripplanner/astar/model/ShortestPathTreeTest.java index 37c97fd2370..148f1311496 100644 --- a/astar/src/test/java/org/opentripplanner/astar/model/ShortestPathTreeTest.java +++ b/astar/src/test/java/org/opentripplanner/astar/model/ShortestPathTreeTest.java @@ -7,7 +7,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Collection; -import java.util.List; import org.junit.jupiter.api.Test; import org.opentripplanner.astar.TestState; import org.opentripplanner.astar.TestVertex; @@ -31,7 +30,6 @@ void singleState() { assertTrue(spt.add(s)); assertTrue(spt.visit(s)); assertEquals(s, spt.getState(v)); - assertEquals(List.of(s), spt.getStates(v)); } @Test @@ -46,7 +44,6 @@ void dominatedStateReplacement() { assertFalse(spt.visit(worse)); assertTrue(spt.visit(better)); assertEquals(better, spt.getState(v)); - assertEquals(List.of(better), spt.getStates(v)); } @Test @@ -58,7 +55,6 @@ void dominatedStateRejected() { assertTrue(spt.add(better)); assertFalse(spt.add(worse)); - assertEquals(List.of(better), spt.getStates(v)); } @Test @@ -72,7 +68,6 @@ void coDominantStates() { assertTrue(spt.add(s2)); assertTrue(spt.visit(s1)); assertTrue(spt.visit(s2)); - assertEquals(List.of(s1, s2), spt.getStates(v)); } @Test @@ -88,12 +83,6 @@ void visitReturnsFalseForAbsentVertex() { assertFalse(spt.visit(s2)); } - @Test - void getStatesNullForAbsentVertex() { - var spt = new ShortestPathTree<>(BY_WEIGHT); - assertNull(spt.getStates(new TestVertex())); - } - @Test void getStateNullForAbsentVertex() { var spt = new ShortestPathTree<>(BY_WEIGHT); diff --git a/doc/dev/decisionrecords/APIGraphQLDesign.md b/doc/dev/decisionrecords/APIGraphQLDesign.md index a5d353002df..1121046f2af 100644 --- a/doc/dev/decisionrecords/APIGraphQLDesign.md +++ b/doc/dev/decisionrecords/APIGraphQLDesign.md @@ -7,8 +7,9 @@ of the OTP developers is the Production Ready GraphQL book by Marc-André Giroux ## Pagination -We use the [pagination](https://graphql.org/learn/pagination/) (a.k. Relay) specification for paging over entities like stations, -stops, trips and routes. Very often OTP has a _finite_ list of entities in memory. +We use the [pagination](https://graphql.org/learn/pagination/) (a.k.a. Relay) specification for paging over entities like stations, +stops, trips and routes. Very often OTP has a _finite_ list of entities in memory. The route request has an OTP custom pagination +feature - it is not finite and very complex. ## Refetching @@ -43,3 +44,13 @@ We allow breaking API changes in these cases: > Use feature xyz instead. > Note! This feature is subject for removal in September 2027! If you want this to stay, please > notify the OTP community. + +## Conventions + +### Small `input` value-objects should have required fields, no default field values + +An input representing a single concept or thing should carry all relevant fields even if +some of the values are common or have a natural default. Examples of such input types are +`InputCoordinate(lat, long)` and `InputLinearFunction(constant, coefficient)`. Small input +value-object types could be scalars, but if they have multiple fields using an input type +is a better match. This simplifies validation and default value injection. diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index 82374bef57f..e79a218d8f6 100644 --- a/doc/user/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -85,12 +85,12 @@ Sections follow that describe particular settings in more depth. |    [sharedGroupFilePattern](#nd_sharedGroupFilePattern) | `regexp` | Pattern for matching shared group NeTEx files in a NeTEx bundle. | *Optional* | `"(\w{3})-.*-shared\.xml"` | 2.0 | |    [ferryIdsNotAllowedForBicycle](#nd_ferryIdsNotAllowedForBicycle) | `string[]` | List ferries which do not allow bikes. | *Optional* | | 2.0 | | [osm](#osm) | `object[]` | Configure properties for a given OpenStreetMap feed. | *Optional* | | 2.2 | -|       includeOsmSubwayEntrances | `boolean` | Whether to include subway entrances from the OSM data. Overrides the value specified in `osmDefaults`. | *Optional* | `false` | 2.7 | +|       includeOsmStationEntrances | `boolean` | Whether to include station entrances from the OSM data. Overrides the value specified in `osmDefaults`. | *Optional* | `false` | 2.10 | |       [osmTagMapping](#osm_0_osmTagMapping) | `enum` | The named set of mapping rules applied when parsing OSM tags. Overrides the value specified in `osmDefaults`. | *Optional* | `"default"` | 2.2 | |       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | |       timeZone | `time-zone` | The timezone used to resolve opening hours in OSM data. Overrides the value specified in `osmDefaults`. | *Optional* | | 2.2 | | osmDefaults | `object` | Default properties for OpenStreetMap feeds. | *Optional* | | 2.2 | -|    includeOsmSubwayEntrances | `boolean` | Whether to include subway entrances from the OSM data. | *Optional* | `false` | 2.7 | +|    includeOsmStationEntrances | `boolean` | Whether to include station entrances from the OSM data. | *Optional* | `false` | 2.10 | |    [osmTagMapping](#od_osmTagMapping) | `enum` | The named set of mapping rules applied when parsing OSM tags. | *Optional* | `"default"` | 2.2 | |    timeZone | `time-zone` | The timezone used to resolve opening hours in OSM data. | *Optional* | | 2.2 | | [transferParametersForMode](#transferParametersForMode) | `enum map of object` | Configures mode-specific properties for transfer calculations. | *Optional* | | 2.7 | diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index 998addc9a59..1f17ce66ca2 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -21,6 +21,20 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Add replacement mode filter to the GTFS API's planConnection query [#7437](https://github.com/opentripplanner/OpenTripPlanner/pull/7437) - Fix default boarding/alighting rules for SIRI ET ExtraJourneys (#7506) [#7510](https://github.com/opentripplanner/OpenTripPlanner/pull/7510) - Reduce allocations when building linestrings [#7516](https://github.com/opentripplanner/OpenTripPlanner/pull/7516) +- Improve SpeedTest warm-up [#7534](https://github.com/opentripplanner/OpenTripPlanner/pull/7534) +- Remove `GraphVisualizer` and inefficient methods in `ShortestPathTree` [#7526](https://github.com/opentripplanner/OpenTripPlanner/pull/7526) +- Fix cost mismatch by anchoring pattern-ride pareto cost to last stop [#7504](https://github.com/opentripplanner/OpenTripPlanner/pull/7504) +- Add NO_DIRECT_MODE_CONNECTION routing error code for direct-only searches [#7494](https://github.com/opentripplanner/OpenTripPlanner/pull/7494) +- De-duplicate boardinglocations on areas [#7508](https://github.com/opentripplanner/OpenTripPlanner/pull/7508) +- Speed up tag lookup during OSM processing [#7536](https://github.com/opentripplanner/OpenTripPlanner/pull/7536) +- Include entrances that are part of a stop area relation, change build config field `includeOsmSubwayEntrances` to `includeOsmStationEntrances` [#7170](https://github.com/opentripplanner/OpenTripPlanner/pull/7170) +- Add application warmup feature to run routing queries during startup [#7509](https://github.com/opentripplanner/OpenTripPlanner/pull/7509) +- Add `startupRetryPeriod` to GBFS feed configuration [#7525](https://github.com/opentripplanner/OpenTripPlanner/pull/7525) +- Add support for transit group priority in the GTFS API [#7451](https://github.com/opentripplanner/OpenTripPlanner/pull/7451) +- Set default fares to [#7545](https://github.com/opentripplanner/OpenTripPlanner/pull/7545) +- Reduce memory consumption when parsing very large NeTEx files [#7563](https://github.com/opentripplanner/OpenTripPlanner/pull/7563) +- Remove relation route name application [#7539](https://github.com/opentripplanner/OpenTripPlanner/pull/7539) +- Skip transit search when no access or egress mode is set [#7547](https://github.com/opentripplanner/OpenTripPlanner/pull/7547) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.9.0 (2026-03-18) diff --git a/doc/user/Frontends.md b/doc/user/Frontends.md index 1482a2a229c..51887042fa1 100644 --- a/doc/user/Frontends.md +++ b/doc/user/Frontends.md @@ -22,12 +22,6 @@ OpenTripPlanner comes with an integrated debug web frontend. The frontend source The current debug client is a React/TypeScript Single Page App (SPA) that can be served locally or accessed over a content delivery network (CDN). Unlike the original debug client, it connects to the OTP Java backend via the GraphQL API using the Transmodel vocabulary. By default, it is available at the root URL (`http://localhost:8080/` in local operation). -There is another of software that might qualify as an OTP client: a Java Swing application making use of the Processing visualization library, -located in the [GraphVisualizer class](https://github.com/opentripplanner/OpenTripPlanner/blob/dev-2.x/application/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java). -While it would not be accurate to call this a "native" desktop application (as it's cross-platform Java) it is not a web app. This very developer-centric -UI is also over a decade old and has been very sparsely maintained, but continues to exist because it can visualize the progress of searches through the -street network, providing some insight into the internals of the routing algorithms that are not otherwise visible. - ## Working with the Debug Frontend While the debug frontend is enabled by default as of this writing, it may not be in the future, and you may wish to disable it if you've chosen to use a different frontend. diff --git a/doc/user/GBFS-Config.md b/doc/user/GBFS-Config.md index e9ced7b3591..2878c4cc312 100644 --- a/doc/user/GBFS-Config.md +++ b/doc/user/GBFS-Config.md @@ -30,6 +30,7 @@ Furthermore, support is limited to the following form factors: | [network](#u_1_network) | `string` | The name of the network to override the one derived from the source data. | *Optional* | | 1.5 | | overloadingAllowed | `boolean` | Allow leaving vehicles at a station even though there are no free slots. | *Optional* | `false` | 2.2 | | [sourceType](#u_1_sourceType) | `enum` | What source of vehicle rental updater to use. | *Required* | | 1.5 | +| [startupRetryPeriod](#u_1_startupRetryPeriod) | `duration` | How long to retry loading the vehicle rental data source on startup if it initially fails. | *Optional* | `"PT0S"` | 2.10 | | url | `string` | The URL to download the data from. | *Required* | | 1.5 | | [headers](#u_1_headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 1.5 | | [rentalPickupTypes](#u_1_rentalPickupTypes) | `enum set` | This is temporary and will be removed in a future version of OTP. Use this to specify the type of rental data that is allowed to be read from the data source. | *Optional* | | 2.7 | @@ -84,6 +85,18 @@ GBFS feeds must include a system_id which will be used as the default `network`. What source of vehicle rental updater to use. +

      startupRetryPeriod

      + +**Since version:** `2.10` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"PT0S"` +**Path:** /updaters/[1] + +How long to retry loading the vehicle rental data source on startup if it initially fails. + +The first time the data source is loaded, OTP will retry for this duration every +5 seconds before giving up. This is useful to handle temporary network failures during +OTP startup. Set to `PT0S` to disable retries. + +

      headers

      **Since version:** `1.5` ∙ **Type:** `map of string` ∙ **Cardinality:** `Optional` diff --git a/doc/user/RouterConfiguration.md b/doc/user/RouterConfiguration.md index ba4a481e6b4..b0083600435 100644 --- a/doc/user/RouterConfiguration.md +++ b/doc/user/RouterConfiguration.md @@ -31,51 +31,61 @@ A full list of them can be found in the [RouteRequest](RouteRequest.md). -| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | -|-------------------------------------------------------------------------------------------|:---------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|---------------|:-----:| -| [configVersion](#configVersion) | `string` | Deployment version of the *router-config.json*. | *Optional* | | 2.1 | -| [flex](sandbox/Flex.md) | `object` | Configuration for flex routing. | *Optional* | | 2.1 | -| gtfsApi | `object` | Configuration for the GTFS GraphQL API. | *Optional* | | 2.8 | -|    [tracingTags](#gtfsApi_tracingTags) | `string[]` | Used to group requests based on headers or query parameters when monitoring OTP. | *Optional* | | na | -| [ojpApi](sandbox/OjpApi.md) | `object` | Configuration for the OJP API. | *Optional* | | 2.9 | -| [rideHailingServices](sandbox/RideHailing.md) | `object[]` | Configuration for interfaces to external ride hailing services like Uber. | *Optional* | | 2.3 | -| [routingDefaults](RouteRequest.md) | `object` | The default parameters for the routing query. | *Optional* | | 2.0 | -| [server](#server) | `object` | Configuration for router server. | *Optional* | | 2.4 | -|    [apiDocumentationProfile](#server_apiDocumentationProfile) | `enum` | List of available custom documentation profiles. A profile is used to inject custom documentation like type and field description or a deprecated reason. Currently, ONLY the Transmodel API supports this feature. | *Optional* | `"default"` | 2.7 | -|    [apiProcessingTimeout](#server_apiProcessingTimeout) | `duration` | Maximum processing time for an API request | *Optional* | `"PT-1S"` | 2.4 | -|    [httpResponseTimeMetrics](#server_httpResponseTimeMetrics) | `object` | Configuration for HTTP response time metrics. | *Optional* | | 2.9 | -|    [traceParameters](#server_traceParameters) | `object[]` | Trace OTP request using HTTP request/response parameter(s) combined with logging. | *Optional* | | 2.4 | -|          generateIdIfMissing | `boolean` | If `true` a unique value is generated if no http request header is provided, or the value is missing. | *Optional* | `false` | 2.4 | -|          httpRequestHeader | `string` | The header-key to use when fetching the trace parameter value | *Optional* | | 2.4 | -|          httpResponseHeader | `string` | The header-key to use when saving the value back into the http response | *Optional* | | 2.4 | -|          [logKey](#server_traceParameters_0_logKey) | `string` | The log event key used. | *Optional* | | 2.4 | -| timetableUpdates | `object` | Global configuration for timetable updaters. | *Optional* | | 2.2 | -|    [maxSnapshotFrequency](#timetableUpdates_maxSnapshotFrequency) | `duration` | How long a snapshot should be cached. | *Optional* | `"PT1S"` | 2.2 | -|    purgeExpiredData | `boolean` | Should expired real-time data be purged from the graph. Apply to GTFS-RT and Siri updates. | *Optional* | `true` | 2.2 | -| [transit](#transit) | `object` | Configuration for transit searches with RAPTOR. | *Optional* | | na | -|    [iterationDepartureStepInSeconds](#transit_iterationDepartureStepInSeconds) | `integer` | Step for departure times between each RangeRaptor iterations. | *Optional* | `60` | na | -|    [maxNumberOfTransfers](#transit_maxNumberOfTransfers) | `integer` | This parameter is used to allocate enough memory space for Raptor. | *Optional* | `12` | na | -|    [maxSearchWindow](#transit_maxSearchWindow) | `duration` | Upper limit of the request parameter searchWindow. | *Optional* | `"PT24H"` | 2.4 | -|    [scheduledTripBinarySearchThreshold](#transit_scheduledTripBinarySearchThreshold) | `integer` | This threshold is used to determine when to perform a binary trip schedule search. | *Optional* | `50` | na | -|    [searchThreadPoolSize](#transit_searchThreadPoolSize) | `integer` | Split a travel search in smaller jobs and run them in parallel to improve performance. | *Optional* | `0` | na | -|    [transferCacheMaxSize](#transit_transferCacheMaxSize) | `integer` | The maximum number of distinct transfers parameters to cache pre-calculated transfers for. | *Optional* | `25` | na | -|    [dynamicSearchWindow](#transit_dynamicSearchWindow) | `object` | The dynamic search window coefficients used to calculate the EDT, LAT and SW. | *Optional* | | 2.1 | -|       [maxWindow](#transit_dynamicSearchWindow_maxWindow) | `duration` | Upper limit for the search-window calculation. | *Optional* | `"PT3H"` | 2.2 | -|       [minTransitTimeCoefficient](#transit_dynamicSearchWindow_minTransitTimeCoefficient) | `double` | The coefficient to multiply with `minTransitTime`. | *Optional* | `0.5` | 2.1 | -|       [minWaitTimeCoefficient](#transit_dynamicSearchWindow_minWaitTimeCoefficient) | `double` | The coefficient to multiply with `minWaitTime`. | *Optional* | `0.5` | 2.1 | -|       [minWindow](#transit_dynamicSearchWindow_minWindow) | `duration` | The constant minimum duration for a raptor-search-window. | *Optional* | `"PT40M"` | 2.2 | -|       [stepMinutes](#transit_dynamicSearchWindow_stepMinutes) | `integer` | Used to set the steps the search-window is rounded to. | *Optional* | `10` | 2.1 | -|    [pagingSearchWindowAdjustments](#transit_pagingSearchWindowAdjustments) | `duration[]` | The provided array of durations is used to increase the search-window for the next/previous page. | *Optional* | | na | -|    [stopBoardAlightDuringTransferCost](#transit_stopBoardAlightDuringTransferCost) | `enum map of integer` | Costs for boarding and alighting during transfers at stops with a given transfer priority. | *Optional* | | 2.0 | -|    [transferCacheRequests](#transit_transferCacheRequests) | `object[]` | Routing requests to use for pre-filling the stop-to-stop transfer cache. | *Optional* | | 2.3 | -| transmodelApi | `object` | Configuration for the Transmodel GraphQL API. | *Optional* | | 2.1 | -|    [hideFeedId](#transmodelApi_hideFeedId) | `boolean` | Hide the FeedId in all API output, and add it to input. | *Optional* | `false` | na | -|    [maxNumberOfResultFields](#transmodelApi_maxNumberOfResultFields) | `integer` | The maximum number of fields in a GraphQL result | *Optional* | `1000000` | 2.6 | -|    [tracingHeaderTags](#transmodelApi_tracingHeaderTags) | `string[]` | Used to group requests when monitoring OTP. | *Optional* | | na | -| [triasApi](sandbox/TriasApi.md) | `object` | Configuration for the TRIAS API. | *Optional* | | 2.8 | -| [updaters](Realtime-Updaters.md) | `object[]` | Configuration for the updaters that import various types of data into OTP. | *Optional* | | 1.5 | -| [vectorTiles](sandbox/MapboxVectorTilesApi.md) | `object` | Vector tile configuration | *Optional* | | na | -| [vehicleRentalServiceDirectory](sandbox/VehicleRentalServiceDirectory.md) | `object` | Configuration for the vehicle rental service directory using GBFS v3 manifest. | *Optional* | | 2.0 | +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|-------------------------------------------------------------------------------------------|:---------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|----------------|:-----:| +| [configVersion](#configVersion) | `string` | Deployment version of the *router-config.json*. | *Optional* | | 2.1 | +| [flex](sandbox/Flex.md) | `object` | Configuration for flex routing. | *Optional* | | 2.1 | +| gtfsApi | `object` | Configuration for the GTFS GraphQL API. | *Optional* | | 2.8 | +|    [tracingTags](#gtfsApi_tracingTags) | `string[]` | Used to group requests based on headers or query parameters when monitoring OTP. | *Optional* | | na | +| [ojpApi](sandbox/OjpApi.md) | `object` | Configuration for the OJP API. | *Optional* | | 2.9 | +| [rideHailingServices](sandbox/RideHailing.md) | `object[]` | Configuration for interfaces to external ride hailing services like Uber. | *Optional* | | 2.3 | +| [routingDefaults](RouteRequest.md) | `object` | The default parameters for the routing query. | *Optional* | | 2.0 | +| [server](#server) | `object` | Configuration for router server. | *Optional* | | 2.4 | +|    [apiDocumentationProfile](#server_apiDocumentationProfile) | `enum` | List of available custom documentation profiles. A profile is used to inject custom documentation like type and field description or a deprecated reason. Currently, ONLY the Transmodel API supports this feature. | *Optional* | `"default"` | 2.7 | +|    [apiProcessingTimeout](#server_apiProcessingTimeout) | `duration` | Maximum processing time for an API request | *Optional* | `"PT-1S"` | 2.4 | +|    [httpResponseTimeMetrics](#server_httpResponseTimeMetrics) | `object` | Configuration for HTTP response time metrics. | *Optional* | | 2.9 | +|    [traceParameters](#server_traceParameters) | `object[]` | Trace OTP request using HTTP request/response parameter(s) combined with logging. | *Optional* | | 2.4 | +|          generateIdIfMissing | `boolean` | If `true` a unique value is generated if no http request header is provided, or the value is missing. | *Optional* | `false` | 2.4 | +|          httpRequestHeader | `string` | The header-key to use when fetching the trace parameter value | *Optional* | | 2.4 | +|          httpResponseHeader | `string` | The header-key to use when saving the value back into the http response | *Optional* | | 2.4 | +|          [logKey](#server_traceParameters_0_logKey) | `string` | The log event key used. | *Optional* | | 2.4 | +| timetableUpdates | `object` | Global configuration for timetable updaters. | *Optional* | | 2.2 | +|    [maxSnapshotFrequency](#timetableUpdates_maxSnapshotFrequency) | `duration` | How long a snapshot should be cached. | *Optional* | `"PT1S"` | 2.2 | +|    purgeExpiredData | `boolean` | Should expired real-time data be purged from the graph. Apply to GTFS-RT and Siri updates. | *Optional* | `true` | 2.2 | +| [transit](#transit) | `object` | Configuration for transit searches with RAPTOR. | *Optional* | | na | +|    [iterationDepartureStepInSeconds](#transit_iterationDepartureStepInSeconds) | `integer` | Step for departure times between each RangeRaptor iterations. | *Optional* | `60` | na | +|    [maxNumberOfTransfers](#transit_maxNumberOfTransfers) | `integer` | This parameter is used to allocate enough memory space for Raptor. | *Optional* | `12` | na | +|    [maxSearchWindow](#transit_maxSearchWindow) | `duration` | Upper limit of the request parameter searchWindow. | *Optional* | `"PT24H"` | 2.4 | +|    [scheduledTripBinarySearchThreshold](#transit_scheduledTripBinarySearchThreshold) | `integer` | This threshold is used to determine when to perform a binary trip schedule search. | *Optional* | `50` | na | +|    [searchThreadPoolSize](#transit_searchThreadPoolSize) | `integer` | Split a travel search in smaller jobs and run them in parallel to improve performance. | *Optional* | `0` | na | +|    [transferCacheMaxSize](#transit_transferCacheMaxSize) | `integer` | The maximum number of distinct transfers parameters to cache pre-calculated transfers for. | *Optional* | `25` | na | +|    [dynamicSearchWindow](#transit_dynamicSearchWindow) | `object` | The dynamic search window coefficients used to calculate the EDT, LAT and SW. | *Optional* | | 2.1 | +|       [maxWindow](#transit_dynamicSearchWindow_maxWindow) | `duration` | Upper limit for the search-window calculation. | *Optional* | `"PT3H"` | 2.2 | +|       [minTransitTimeCoefficient](#transit_dynamicSearchWindow_minTransitTimeCoefficient) | `double` | The coefficient to multiply with `minTransitTime`. | *Optional* | `0.5` | 2.1 | +|       [minWaitTimeCoefficient](#transit_dynamicSearchWindow_minWaitTimeCoefficient) | `double` | The coefficient to multiply with `minWaitTime`. | *Optional* | `0.5` | 2.1 | +|       [minWindow](#transit_dynamicSearchWindow_minWindow) | `duration` | The constant minimum duration for a raptor-search-window. | *Optional* | `"PT40M"` | 2.2 | +|       [stepMinutes](#transit_dynamicSearchWindow_stepMinutes) | `integer` | Used to set the steps the search-window is rounded to. | *Optional* | `10` | 2.1 | +|    [pagingSearchWindowAdjustments](#transit_pagingSearchWindowAdjustments) | `duration[]` | The provided array of durations is used to increase the search-window for the next/previous page. | *Optional* | | na | +|    [stopBoardAlightDuringTransferCost](#transit_stopBoardAlightDuringTransferCost) | `enum map of integer` | Costs for boarding and alighting during transfers at stops with a given transfer priority. | *Optional* | | 2.0 | +|    [transferCacheRequests](#transit_transferCacheRequests) | `object[]` | Routing requests to use for pre-filling the stop-to-stop transfer cache. | *Optional* | | 2.3 | +| transmodelApi | `object` | Configuration for the Transmodel GraphQL API. | *Optional* | | 2.1 | +|    [hideFeedId](#transmodelApi_hideFeedId) | `boolean` | Hide the FeedId in all API output, and add it to input. | *Optional* | `false` | na | +|    [maxNumberOfResultFields](#transmodelApi_maxNumberOfResultFields) | `integer` | The maximum number of fields in a GraphQL result | *Optional* | `1000000` | 2.6 | +|    [tracingHeaderTags](#transmodelApi_tracingHeaderTags) | `string[]` | Used to group requests when monitoring OTP. | *Optional* | | na | +| [triasApi](sandbox/TriasApi.md) | `object` | Configuration for the TRIAS API. | *Optional* | | 2.8 | +| [updaters](Realtime-Updaters.md) | `object[]` | Configuration for the updaters that import various types of data into OTP. | *Optional* | | 1.5 | +| [vectorTiles](sandbox/MapboxVectorTilesApi.md) | `object` | Vector tile configuration | *Optional* | | na | +| [vehicleRentalServiceDirectory](sandbox/VehicleRentalServiceDirectory.md) | `object` | Configuration for the vehicle rental service directory using GBFS v3 manifest. | *Optional* | | 2.0 | +| [warmup](#warmup) | `object` | Configure application warmup by running transit searches during startup. | *Optional* | | 2.10 | +|    [api](#warmup_api) | `enum` | Which GraphQL API to use for warmup queries. | *Optional* | `"transmodel"` | 2.10 | +|    [accessModes](#warmup_accessModes) | `string[]` | Access modes to cycle through in warmup queries. | *Optional* | | 2.10 | +|    [egressModes](#warmup_egressModes) | `string[]` | Egress modes to cycle through in warmup queries. | *Optional* | | 2.10 | +|    from | `object` | Origin location for warmup searches. | *Optional* | | 2.10 | +|       lat | `double` | Latitude of the origin. | *Required* | | 2.10 | +|       lon | `double` | Longitude of the origin. | *Required* | | 2.10 | +|    to | `object` | Destination location for warmup searches. | *Optional* | | 2.10 | +|       lat | `double` | Latitude of the destination. | *Required* | | 2.10 | +|       lon | `double` | Longitude of the destination. | *Required* | | 2.10 | @@ -482,6 +492,131 @@ Enforce rate limiting based on query complexity; Queries that return too much da Used to group requests when monitoring OTP. +

      warmup

      + +**Since version:** `2.10` ∙ **Type:** `object` ∙ **Cardinality:** `Optional` +**Path:** / + +Configure application warmup by running transit searches during startup. + +When configured, OTP runs transit routing queries between the given locations +during startup. This warms up the application (JIT compilation, GraphQL schema +caches, routing data structures, etc.) before production traffic arrives. +Queries start after the Raptor transit data is created and stop when all updaters +are primed (the health endpoint would return "UP"). +If no updaters are configured, no warmup queries are run. + + +

      api

      + +**Since version:** `2.10` ∙ **Type:** `enum` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"transmodel"` +**Path:** /warmup +**Enum values:** `transmodel` | `gtfs` + +Which GraphQL API to use for warmup queries. + + - `transmodel` Use the TransModel GraphQL API for warmup queries. + - `gtfs` Use the GTFS GraphQL API for warmup queries. + + +

      accessModes

      + +**Since version:** `2.10` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` +**Path:** /warmup + +Access modes to cycle through in warmup queries. + +Ordered list of `StreetMode` values used as access modes. Each entry is paired with the egress mode at the same index. - `not-set` + - `walk` Walking some or all of the way of the route. + - `bike` Cycling for the entirety of the route or taking a bicycle onto the public transport and cycling from the arrival station to the destination. + + Taking a bicycle onto transit is only possible if information about the permission to do so is supplied in the source data. In GTFS this field + is called `bikesAllowed`. + - `bike-to-park` Leaving the bicycle at the departure station and walking from the arrival station to the destination. + This mode needs to be combined with at least one transit mode otherwise it behaves like an ordinary bicycle journey. + + _Prerequisite:_ Bicycle parking stations present in the OSM file and visible to OTP by enabling the property `staticBikeParkAndRide` during graph build. + - `bike-rental` Taking a rented, shared-mobility bike for part or the entirety of the route. + + _Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. + See [Configuring GBFS](GBFS-Config.md) on how to add one. + - `scooter-rental` Walking to a scooter rental point, riding a scooter to a scooter rental drop-off point, and walking the rest of the way. + This can include scooter rental at fixed locations or free-floating services. + + _Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. + See [Configuring GBFS](GBFS-Config.md) on how to add one. + - `car` Driving your own car the entirety of the route. + This can be combined with transit, where will return routes with a [Kiss & Ride](https://en.wikipedia.org/wiki/Park_and_ride#Kiss_and_ride_/_kiss_and_fly) component. + This means that the car is not parked in a permanent parking area but rather the passenger is dropped off (for example, at an airport) and the driver continues driving the car away from the drop off location. + - `car-to-park` Driving a car to the park-and-ride facilities near a station and taking publictransport. + This mode needs to be combined with at least one transit mode otherwise, it behaves like an ordinary car journey. + _Prerequisite:_ Park-and-ride areas near the stations need to be present in the OSM input file. + - `car-pickup` Walking to a pickup point along the road, driving to a drop-off point along the road, and walking the rest of the way.
      This can include various taxi-services or kiss & ride. + - `car-rental` Walk to a car rental point, drive to a car rental drop-off point and walk the rest of the way. + This can include car rental at fixed locations or free-floating services. + + _Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. + See [Configuring GBFS](GBFS-Config.md) on how to add one. + - `car-hailing` Using a car hailing app like Uber or Lyft to get to a train station or all the way to the destination. + + See [the sandbox documentation](sandbox/RideHailing.md) on how to configure it. + - `carpool` Carpool or rideshare with other passengers going in the same direction. + + This is the request mode for enabling carpooling in street route searches. + + Use this _street_ mode, if your data source for trips is SIRI, not GTFS static. + - `flexible` Encompasses all types of on-demand and flexible transportation for example GTFS Flex or NeTEx Flexible Stop Places. + + +

      egressModes

      + +**Since version:** `2.10` ∙ **Type:** `string[]` ∙ **Cardinality:** `Optional` +**Path:** /warmup + +Egress modes to cycle through in warmup queries. + +Ordered list of `StreetMode` values used as egress modes. Each entry is paired with the access mode at the same index. - `not-set` + - `walk` Walking some or all of the way of the route. + - `bike` Cycling for the entirety of the route or taking a bicycle onto the public transport and cycling from the arrival station to the destination. + + Taking a bicycle onto transit is only possible if information about the permission to do so is supplied in the source data. In GTFS this field + is called `bikesAllowed`. + - `bike-to-park` Leaving the bicycle at the departure station and walking from the arrival station to the destination. + This mode needs to be combined with at least one transit mode otherwise it behaves like an ordinary bicycle journey. + + _Prerequisite:_ Bicycle parking stations present in the OSM file and visible to OTP by enabling the property `staticBikeParkAndRide` during graph build. + - `bike-rental` Taking a rented, shared-mobility bike for part or the entirety of the route. + + _Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. + See [Configuring GBFS](GBFS-Config.md) on how to add one. + - `scooter-rental` Walking to a scooter rental point, riding a scooter to a scooter rental drop-off point, and walking the rest of the way. + This can include scooter rental at fixed locations or free-floating services. + + _Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. + See [Configuring GBFS](GBFS-Config.md) on how to add one. + - `car` Driving your own car the entirety of the route. + This can be combined with transit, where will return routes with a [Kiss & Ride](https://en.wikipedia.org/wiki/Park_and_ride#Kiss_and_ride_/_kiss_and_fly) component. + This means that the car is not parked in a permanent parking area but rather the passenger is dropped off (for example, at an airport) and the driver continues driving the car away from the drop off location. + - `car-to-park` Driving a car to the park-and-ride facilities near a station and taking publictransport. + This mode needs to be combined with at least one transit mode otherwise, it behaves like an ordinary car journey. + _Prerequisite:_ Park-and-ride areas near the stations need to be present in the OSM input file. + - `car-pickup` Walking to a pickup point along the road, driving to a drop-off point along the road, and walking the rest of the way.
      This can include various taxi-services or kiss & ride. + - `car-rental` Walk to a car rental point, drive to a car rental drop-off point and walk the rest of the way. + This can include car rental at fixed locations or free-floating services. + + _Prerequisite:_ Vehicle or station locations need to be added to OTP from dynamic data feeds. + See [Configuring GBFS](GBFS-Config.md) on how to add one. + - `car-hailing` Using a car hailing app like Uber or Lyft to get to a train station or all the way to the destination. + + See [the sandbox documentation](sandbox/RideHailing.md) on how to configure it. + - `carpool` Carpool or rideshare with other passengers going in the same direction. + + This is the request mode for enabling carpooling in street route searches. + + Use this _street_ mode, if your data source for trips is SIRI, not GTFS static. + - `flexible` Encompasses all types of on-demand and flexible transportation for example GTFS Flex or NeTEx Flexible Stop Places. + + @@ -961,6 +1096,25 @@ Used to group requests when monitoring OTP. "maxPrimingIdleTime" : "1s" } ], + "warmup" : { + "api" : "transmodel", + "from" : { + "lat" : 59.9139, + "lon" : 10.7522 + }, + "to" : { + "lat" : 59.95, + "lon" : 10.76 + }, + "accessModes" : [ + "WALK", + "CAR_TO_PARK" + ], + "egressModes" : [ + "WALK", + "WALK" + ] + }, "rideHailingServices" : [ { "type" : "uber-car-hailing", diff --git a/doc/user/StopAreas.md b/doc/user/StopAreas.md index 46acbf326bc..81f801f9e5a 100644 --- a/doc/user/StopAreas.md +++ b/doc/user/StopAreas.md @@ -19,7 +19,7 @@ An example: [Huopalahti railway station in Helsinki](https://www.openstreetmap. - Set role=platform to these platform members - Platforms must be also proper areas, tagged as routable highways and `area=yes`. Also a single tag `public_transport=platform` will do. - Model required entrance points and stop linking points inside platform geometry and make them relation members, too. - - Entrances can be tagged using `railway=subway_entrance`, `highway=elevator`, `entrance=yes` or `entrance=main`. + - Entrances can be tagged using `railway=subway_entrance`, `railway=train_station_entrance`, `public_transport=entrance`, `entrance=yes`, `entrance=main` or `highway=elevator`. - Stop linking points should be tagged as [boarding locations](BoardingLocations.md), e.g. using the tag `public_transport=platform`. - Connect entrance points to the street network. Stop linking points can have reference tags to instruct how to link them with public transit stops as instructed in the document about [boarding locations](BoardingLocations.md). diff --git a/doc/user/sandbox/Carpooling.md b/doc/user/sandbox/Carpooling.md index c2475efcd41..a11a5503b55 100644 --- a/doc/user/sandbox/Carpooling.md +++ b/doc/user/sandbox/Carpooling.md @@ -72,7 +72,6 @@ The carpooling service matches passengers with compatible carpool trips based on - **Availability**: Checks if seats are available in the vehicle - **Time Compatibility**: Ensures the trip timing works for the passenger - **Route Alignment**: Validates that pickup and dropoff locations are reasonably close to the driver's route -- **Direction**: Verifies the passenger's travel direction aligns with the trip route The system automatically calculates the optimal pickup and dropoff points along the driver's route that minimize additional travel time while respecting all constraints. @@ -81,7 +80,6 @@ The system automatically calculates the optimal pickup and dropoff points along To ensure a good experience for all users, the system enforces several constraints: - **Vehicle Capacity**: Never exceeds the maximum number of seats -- **Route Logic**: Prevents backtracking or illogical detours - **Existing Passenger Protection**: Limits additional delay to existing passengers (maximum 5 minutes) - **Driver Deviation Budget**: Respects the driver's maximum acceptable detour time (currently 15 minutes) diff --git a/domain-core/src/main/resources/Message.properties b/domain-core/src/main/resources/Message.properties index eafca543bd4..471713abfd4 100644 --- a/domain-core/src/main/resources/Message.properties +++ b/domain-core/src/main/resources/Message.properties @@ -19,6 +19,7 @@ GEOCODE_TO_NOT_FOUND = Destination is unknown. Can you be a bit more descr GEOCODE_FROM_TO_NOT_FOUND = Both origin and destination are unknown. Can you be a bit more descriptive? GEOCODE_INTERMEDIATE_NOT_FOUND = An intermediate destination is unknown. Can you be a bit more descriptive?. TOO_CLOSE = Origin is within a trivial distance of the destination. +NO_DIRECT_MODE_CONNECTION = No route found for the requested direct mode. The origin and destination may be too far apart or not connected by the requested mode of transport. UNDERSPECIFIED_TRIANGLE = All of triangleSafetyFactor, triangleSlopeFactor, and triangleTimeFactor must be set if any are TRIANGLE_NOT_AFFINE = The values of triangleSafetyFactor, triangleSlopeFactor, and triangleTimeFactor must sum to 1 diff --git a/domain-core/src/main/resources/WayProperties.properties b/domain-core/src/main/resources/WayProperties.properties index 64f1b11312f..b3febc025a5 100644 --- a/domain-core/src/main/resources/WayProperties.properties +++ b/domain-core/src/main/resources/WayProperties.properties @@ -11,7 +11,6 @@ name.bridleway=bridleway name.corridor=corridor name.indoor_area=indoor area -name.otp_route_ref=Route {otp:route_ref} name.platform_ref=Platform {ref} name.platform=platform diff --git a/domain-core/src/main/resources/WayProperties_de.properties b/domain-core/src/main/resources/WayProperties_de.properties index 1ea1ada4c15..71e040d661c 100644 --- a/domain-core/src/main/resources/WayProperties_de.properties +++ b/domain-core/src/main/resources/WayProperties_de.properties @@ -12,7 +12,6 @@ name.bridleway=Reitweg name.corridor=Korridor name.indoor_area=Innenbereich -name.otp_route_ref=Route {otp:route_ref} name.platform_ref=Plattform {ref} name.platform=Plattform diff --git a/domain-core/src/main/resources/WayProperties_fi.properties b/domain-core/src/main/resources/WayProperties_fi.properties index 8ab02ec91e2..3cd50db8960 100644 --- a/domain-core/src/main/resources/WayProperties_fi.properties +++ b/domain-core/src/main/resources/WayProperties_fi.properties @@ -11,7 +11,6 @@ name.bridleway=ratsupolku name.corridor=käytävä name.indoor_area=sisätila -name.otp_route_ref=linja {otp:route_ref} name.platform_ref=laituri {ref} name.platform=laituri diff --git a/domain-core/src/main/resources/WayProperties_fr.properties b/domain-core/src/main/resources/WayProperties_fr.properties index 57b7a970e5c..9aea533b20b 100644 --- a/domain-core/src/main/resources/WayProperties_fr.properties +++ b/domain-core/src/main/resources/WayProperties_fr.properties @@ -12,7 +12,6 @@ name.bridleway=sentier \u00e9questre name.corridor=couloir name.indoor_area=zone int\u00e9rieure -name.otp_route_ref=Ligne {otp:route_ref} name.platform_ref=Plate-forme {ref} name.platform=plate-forme diff --git a/domain-core/src/main/resources/WayProperties_hu.properties b/domain-core/src/main/resources/WayProperties_hu.properties index 1163d9f96be..1cc2980322e 100644 --- a/domain-core/src/main/resources/WayProperties_hu.properties +++ b/domain-core/src/main/resources/WayProperties_hu.properties @@ -11,7 +11,6 @@ name.bridleway=nyomvonal name.corridor=folyoso name.indoor_area=belteri terulet -name.otp_route_ref={otp:route_ref} name.platform_ref={ref} name.platform=kocsi\u00E1ll\u00E1s / v\u00E1g\u00E1ny diff --git a/domain-core/src/main/resources/WayProperties_nl.properties b/domain-core/src/main/resources/WayProperties_nl.properties index 29991f07a4e..381956f56d2 100644 --- a/domain-core/src/main/resources/WayProperties_nl.properties +++ b/domain-core/src/main/resources/WayProperties_nl.properties @@ -12,7 +12,6 @@ name.bridleway=ruiterpad name.corridor=gang name.indoor_area=binnenruimte -name.otp_route_ref=Lijn {otp:route_ref} name.platform_ref=Spoor {ref} name.platform=spoor diff --git a/domain-core/src/main/resources/WayProperties_no.properties b/domain-core/src/main/resources/WayProperties_no.properties index 6a3c406ea83..ae211f90ead 100644 --- a/domain-core/src/main/resources/WayProperties_no.properties +++ b/domain-core/src/main/resources/WayProperties_no.properties @@ -11,7 +11,6 @@ name.bridleway=bridleway name.corridor=korridor name.indoor_area=innendors omrade -name.otp_route_ref=Rute {otp:route_ref} name.platform_ref=Plattform {ref} name.platform=plattform diff --git a/domain-core/src/main/resources/WayProperties_sv.properties b/domain-core/src/main/resources/WayProperties_sv.properties index 810251b57af..159dc43dc4e 100644 --- a/domain-core/src/main/resources/WayProperties_sv.properties +++ b/domain-core/src/main/resources/WayProperties_sv.properties @@ -11,7 +11,6 @@ name.bridleway=ridvägem name.corridor=korridor name.indoor_area=inomhusomrade -name.otp_route_ref=linje {otp:route_ref} name.platform_ref=plattform {ref} name.platform=plattformen diff --git a/pom.xml b/pom.xml index 55664090ff4..47e229f9249 100644 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,7 @@ 1.5.32 10.4.0 1.16.5 - 2.0.15.2 + 2.0.15.3 5.6.0 4.34.1 2.0.6 @@ -311,7 +311,7 @@ but we need the Maven project version as well, so we perform substitution. --> io.github.git-commit-id git-commit-id-maven-plugin - 9.0.2 + 10.0.0 @@ -336,7 +336,7 @@ org.openrewrite.maven rewrite-maven-plugin - 6.34.0 + 6.37.0 true @@ -355,7 +355,7 @@ org.openrewrite.recipe rewrite-static-analysis - 2.30.1 + 2.33.1 @@ -379,7 +379,7 @@ com.puppycrawl.tools checkstyle - 13.3.0 + 13.4.0 diff --git a/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java b/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java index f9456edb333..860d132e622 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/MultiCriteriaRoutingStrategy.java @@ -169,20 +169,18 @@ private void board( } final int boardC1 = calculateCostAtBoardTime(prevArrival, boarding); - final int relativeBoardC1 = boardC1 + calculateOnTripRelativeCost(boardTime, trip); - patternRides.add( - patternRideFactory.createPatternRide( - prevArrival, - stopIndex, - boarding.stopPositionInPattern(), - boardTime, - boardC1, - relativeBoardC1, - trip - ) + var patternRide = patternRideFactory.createPatternRide( + prevArrival, + stopIndex, + boarding.stopPositionInPattern(), + boardTime, + boardC1, + relativeBoardC1, + trip ); + patternRides.add(patternRide); } private void boardWithRegularTransfer( @@ -247,13 +245,13 @@ private int calculateCostAtBoardTime( } /** - * Calculate a cost for riding a trip. It should include the cost from the beginning of the - * journey all the way until a trip is boarded. The cost is used to compare trips boarding the - * same pattern with the same number of transfers. It is ok for the cost to be relative to any + * Calculate a cost for riding a trip. The cost is used to compare trips boarding in the same + * pattern with the same number of transfers. It is ok for the cost to be relative to any * point in place or time - as long as it can be used to compare to paths that started at the - * origin in the same iteration, having used the same number-of-rounds to board the same trip. + * origin in the same iteration, having used the same number-of-rounds to board trips in the same + * pattern. */ private int calculateOnTripRelativeCost(int boardTime, T tripSchedule) { - return c1Calculator.onTripRelativeRidingCost(boardTime, tripSchedule); + return c1Calculator.transitCost(tripSchedule.relativeTravelDuration(boardTime), tripSchedule); } } diff --git a/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java b/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java index 57873a9d93c..7d77aa6d53c 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/rangeraptor/path/DestinationArrivalPaths.java @@ -243,7 +243,6 @@ private void assertGeneralizedCostIsCalculatedCorrectByMapper( RaptorPath path ) { if (path.c1() != destArrival.c1()) { - // TODO - Bug: Cost mismatch stop-arrivals and paths #3623 THROTTLE_MISS_MATCH.throttle(() -> LOG.warn( "Cost mismatch - Mapper: {}, stop-arrivals: {}, path: {} {}", diff --git a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java index b5114391bb7..2544c445734 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostCalculator.java @@ -1,10 +1,13 @@ package org.opentripplanner.raptor.spi; -/** - * The responsibility is to calculate multi-criteria value (like the generalized cost). - *

      - * The implementation should be immutable and thread safe. - */ +/// +/// The responsibility is to calculate multi-criteria value (like the generalized cost). +/// +/// The implementation should be immutable and thread safe. +/// +/// See {@link RaptorCostConverter} for resolution and units for time/duration and +/// c1(generalized-cost). +/// public interface RaptorCostCalculator { /** * The cost is zero (0) if it's not calculated or if the cost "element" have no cost associated. @@ -28,31 +31,35 @@ int boardingCost( ); /** - * Calculate cost of boarding a trip. This should be the cost of the waiting time, any board and - * transfer cost, and the penalty for the board stop visit. This cost should NOT include the - * previous stop arrival cost, but the incremental cost to be added to the previous stop arrival - * cost. + * Calculate the cost of riding a trip for the given {@code transitDuration} in seconds. */ - int onTripRelativeRidingCost(int boardTime, T tripScheduledBoarded); + int transitCost(int transitDuration, T tripScheduledBoarded); /** * Calculate the value when arriving by transit. */ - int transitArrivalCost(int boardCost, int alightSlack, int transitTime, T trip, int toStopIndex); + int transitArrivalCost( + int boardCost, + int alightSlack, + int transitDuration, + T trip, + int toStopIndex + ); /** - * Calculate the value, when waiting between the last transit and egress paths + * Calculate the cost of waiting, when waiting between two transit legs. The wait duration is + * in seconds, and include board and alight slack. */ - int waitCost(int waitTimeInSeconds); + int waitCost(int waitDuration); /** * Used for estimating the remaining value for a criteria at a given stop arrival. The calculated * value should be an optimistic estimate for the heuristics to work properly. So, to calculate - * the generalized cost for given the {@code minTravelTime} and {@code minNumTransfers} returning - * the greatest value, which is guaranteed to be less than the - * real value would be correct and a good choice. + * the generalized cost for given the {@code minTravelDuration} and {@code minNumTransfers} + * returning the greatest value, which is guaranteed to be less than the real value + * would be correct and a good choice. */ - int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStopIndex); + int calculateRemainingMinCost(int minTravelDuration, int minNumTransfers, int fromStopIndex); /** * This method allows the cost calculator to add cost in addition to the generalized-cost of the diff --git a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostConverter.java b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostConverter.java index bc8d187d54d..eb0ef019403 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostConverter.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorCostConverter.java @@ -2,17 +2,19 @@ import java.time.Duration; -/** - * Convert Raptor internal cost to OTP domain model cost, and back. This is provided by the Raptor - * module as a utility, but not used in Raptor except in unit-tests. - *

      - * Inside Raptor the cost unit is 1/100 of a "transit second" using {@code int} as type. In the - * OTP internal domain the unit used for cost is one "transit second" with type {@code double}. - * Cost in raptor is calculated using ints to speed up the calculations and to save memory. - *

      - * The reason for using 1/100 of a second resolution is that we want a cost factor of {@code 0.99} - * to win over a cost factor of {@code 1.00}. - */ +/// Convert Raptor internal cost to OTP domain model cost, and back. This can also be used to +/// convert between time/duration in seconds to Raptor cost. Raptor does not require the cost to +/// be in `transit-centi-seconds`(1/100 seconds), the caller/user may use any resolution for cost. +/// For time/duration the resolution is 1 second. +/// +/// This is provided by the Raptor module as a utility, but not used in Raptor except in unit-tests. +/// +/// Inside Raptor the cost unit is 1/100 of a "transit second" using {@code int} as type. In the +/// OTP internal domain the unit used for cost is one "transit second" with type {@code double}. +/// Cost in raptor is calculated using `int`s to speed up the calculations and to save memory. +/// +/// The reason for using 1/100 of a second resolution is that we want a cost factor of {@code 0.99} +/// to win over a cost factor of {@code 1.00}. public final class RaptorCostConverter { private static final int NOT_SET = -1; diff --git a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorSlackProvider.java b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorSlackProvider.java index c233155f6e9..3ca17ad83a9 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorSlackProvider.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorSlackProvider.java @@ -37,7 +37,7 @@ public interface RaptorSlackProvider { int alightSlack(int slackIndex); /** - * Return the {@link #boardSlack(int) plus {@link #alightSlack(int) + * Return the {@link #boardSlack(int)} plus {@link #alightSlack(int)} * slack. *

      * Unit: seconds. diff --git a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java index 877d8244936..fc290c9c0f2 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/spi/RaptorTripSchedule.java @@ -8,7 +8,7 @@ * child of, and lives in the context of a trip pattern. *

      * The purpose of hiding these attributes behind an interface is to allow the implementation to - * chose the most efficient underlying implementation that suits its needs. + * choose the most efficient underlying representation that suits its needs. */ public interface RaptorTripSchedule { /** @@ -28,10 +28,10 @@ public interface RaptorTripSchedule { /** * Search for the arrival time for the given stopIndex. This is not optimized for performance. * - * @param startStopPos the stop position in pattern to start search(inclusive). - * @param stopIndex the stopIndex to find the arrival time for. + * @param startStopPos the stop position in pattern to start the search (inclusive). + * @param stopIndex the stop index to find the arrival time for. * @return the arrival time in seconds at the given stop - * @throws IndexOutOfBoundsException if stopIndex not found + * @throws IndexOutOfBoundsException if {@code stopIndex} is not found */ default int arrival(int startStopPos, int stopIndex) { return arrival(pattern().findStopPositionAfter(startStopPos, stopIndex)); @@ -41,39 +41,58 @@ default int arrival(int startStopPos, int stopIndex) { * The departure time at the given stop position in pattern. * * @param stopPosInPattern the stop position. - * @return the arrival time in seconds at the given stop + * @return the departure time in seconds at the given stop */ int departure(int stopPosInPattern); /** * Search for the departure time for the given stopIndex. This is not optimized for performance. * - * @param startStopPos the stop position in pattern to start the search(inclusive). - * @param stopIndex the stopIndex to find the departure time for. - * @return the arrival time in seconds at the given stop - * @throws IndexOutOfBoundsException if stopIndex not found + * @param startStopPos the stop position in pattern to start the search (inclusive). + * @param stopIndex the stop index to find the departure time for. + * @return the departure time in seconds at the given stop + * @throws IndexOutOfBoundsException if stopIndex is not found */ default int departure(int startStopPos, int stopIndex) { return departure(pattern().findStopPositionAfter(startStopPos, stopIndex)); } + /// The relative-travel-duration is a proxy for time spent on transit from the boarding stop to + /// the alight stop. We do not know the alight stop, so it is impossible to calculate the + /// "correct" time. The only thing that matters is that the relative difference between two + /// boardings is correct. Compute a relative-time that can be used to compare the travel-time + /// cost for any two boardings in the same pattern. + /// + /// Two invariants must hold: + /// + /// - For two boardings at stop positions `i` and `j` on the same trip (where `i` comes before + /// `j` in the pattern), `relativeTravelDuration(boardAtI) - relativeTravelDuration(boardAtJ)` + /// must equal the actual transit duration from stop `i` to stop `j`. If the board time at + /// position 3 is 10:00 and at the next stop is 10:05, then the value at position 3 is larger + /// by 5*60s = 300 than at the next stop. + /// - All trips in the same pattern should return the same value for at least one stop. If you + /// choose to anchor the relative duration to the last stop, and the value for one trip at the + /// last stop is 56_000, then it must be 56_000 at the last stop for all other trips in the + /// same pattern. + int relativeTravelDuration(int boardTime); + /** * Return the pattern for this trip. */ RaptorTripPattern pattern(); /** - * Search for departure-stop-position for the given trip, earliest-departure-time and stop index. - * We need the time in addition to the stop in cases were the trip pattern visit the same stop - * twice. Also the time is not sufficient, since more than one stop could have the exact same - * departure time. + * Search for the arrival stop position for the given trip, latest arrival time, and stop index. + * We need the time in addition to the stop in cases where the trip pattern visits the same stop + * twice. Also, the time alone is not sufficient, since more than one stop could have the exact + * same arrival time. *

      - * Raptor save memory by NOT storing the board/arrival stop positions in pattern; Hence we need - * this method when mapping into a itinerary or raptor path. + * Raptor saves memory by NOT storing board/alight stop positions in the pattern; therefore, we + * need this method when mapping to an itinerary or Raptor path. *

      - * Avoid using this during routing, it is not optimized for performance. + * Avoid using this during routing, as it is not optimized for performance. * - * @return the stop-position in the trip pattern if found, if not -1 is returned. + * @return the stop position in the trip pattern if found; otherwise, -1 */ default int findArrivalStopPosition(int latestArrivalTime, int stop) { RaptorTripPattern p = pattern(); @@ -89,17 +108,17 @@ default int findArrivalStopPosition(int latestArrivalTime, int stop) { } /** - * Search for departure-stop-position for the given trip, earliest-departure-time and stop index. - * We need the time in addition to the stop in cases were the trip pattern visit the same stop - * twice. Also the time is not sufficient, since more than one stop could have the exact same - * departure time. + * Search for the departure stop position for the given trip, earliest departure time, and stop + * index. We need the time in addition to the stop in cases where the trip pattern visits the + * same stop twice. Also, the time alone is not sufficient, since more than one stop could have + * the exact same departure time. *

      - * Raptor save memory by NOT storing the board/arrival stop positions in pattern; Hence we need - * this method when mapping into a itinerary or raptor path. + * Raptor saves memory by NOT storing board/alight stop positions in the pattern; therefore, we + * need this method when mapping to an itinerary or Raptor path. *

      - * Avoid using this during routing, it is not optimized for performance. + * Avoid using this during routing, as it is not optimized for performance. * - * @return the stop-position in the trip pattern if found, if not -1 is returned. + * @return the stop position in the trip pattern if found; otherwise, -1 */ default int findDepartureStopPosition(int earliestDepartureTime, int stop) { var p = pattern(); @@ -116,10 +135,10 @@ default int findDepartureStopPosition(int earliestDepartureTime, int stop) { } /** - * Find all departure-stop-positions for a stop index after given earliest-departure-time. This is - * useful because trip can pass through the same stop more than once - if the stop pattern is circular. - * This method returns all stop positions, while the {@link #findDepartureStopPosition} only returns the first - * stop-position found. + * Find all departure stop positions for a stop index after the given earliest departure time. + * This is useful because a trip can pass through the same stop more than once if the stop pattern + * is circular. This method returns all stop positions, while + * {@link #findDepartureStopPosition} returns only the first stop position found. * * @return list of all valid stop positions for a given stop index */ diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java index 83ba0d982aa..181b8507f03 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoComparator.java @@ -1,22 +1,41 @@ package org.opentripplanner.raptor.util.paretoset; -/** - * Comparator used by the {@link ParetoSet} to compare to elements for dominance. There is 4 - * outcomes of a comparison between a left and right vector: - *

        - *
      • Left dominates right - At least one left criteria dominates, and no right dominance exist - *
      • Right dominates left - At least one right criteria dominates, and no left dominance exist - *
      • Mutual dominance - At least one left criteria dominates right and at least one right criteria dominates left - *
      • No dominance - all criteria is equals or no dominance exist - *
      - * To implement the comparator you only need to implement the comparison in one direction - if dominance exist. - * - * @param The pareto set element type - */ +/// Compares two elements in a {@link ParetoSet} for Pareto dominance. +/// +/// A comparison between a `left` and `right` element can produce four mutually exclusive outcomes: +/// +/// - {@link ParetoDominance#LEFT} +/// - {@link ParetoDominance#RIGHT} +/// - {@link ParetoDominance#MUTUAL} +/// - {@link ParetoDominance#NONE} +/// +/// Implementations only need to provide one directional check by implementing +/// {@link #leftDominanceExist(Object, Object)}. +/// +/// @param the Pareto set element type. +/// @FunctionalInterface public interface ParetoComparator { /** - * At least one of the left criteria dominates one of the corresponding right criteria. + * Returns {@code true} if at least one criterion in {@code left} is better then the + * corresponding criterion in {@code right}. */ boolean leftDominanceExist(T left, T right); + + /** + * Returns {@code true} if either element dominates the other. + */ + default boolean dominanceExist(T left, T right) { + return leftDominanceExist(left, right) || leftDominanceExist(right, left); + } + + /** + * Compares {@code left} and {@code right} and returns the corresponding {@link ParetoDominance} + * outcome. + */ + default ParetoDominance compare(T left, T right) { + final boolean l = leftDominanceExist(left, right); + final boolean r = leftDominanceExist(right, left); + return ParetoDominance.of(l, r); + } } diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java new file mode 100644 index 00000000000..7ea20cdf52e --- /dev/null +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoDominance.java @@ -0,0 +1,60 @@ +package org.opentripplanner.raptor.util.paretoset; + +/// Pareto Dominance can have 4 values when comparing two values `x`, `y`. The 4 values are +/// mutually exclusive. Remember x and y can be vectors with one or more criteria. +public enum ParetoDominance { + /// Left-Dominance (`x` ≺ `y`), when `x` is strictly better than`y`. `[1, 1, 3]` ≺ `[1, 7, 3]`. + LEFT('≺'), + /// Right-Dominance (`x` ≻ `y`), when `y` is strictly better than`x`. `[1, 7, 3]` ≻ `[1, 1, 3]`. + RIGHT('≻'), + /// Mutual-Dominance (Incomparable/Indifferent) (`x` ∥ `y`). Neither solution is superior to the + /// other. Both solutions are part of the Pareto optimal set (Pareto front). This happens when + /// the `x` is better in one criteria, and `y` is better in another: `[1, 7, 3]` ∥ `[7, 1, 3]`. + MUTUAL('∥'), + /// No Dominate (Strictly equal) (`x` ≡ `y`). Neither `x` dominates `y` nor `y` dominates + /// `x`, AND `x` and `y` are equal in all objective values: `[1, 7, 3]` ≡ `[1, 7, 3]`. + NONE('≡'); + + private char symbol; + + ParetoDominance(char symbol) { + this.symbol = symbol; + } + + /// Create a dominance value from two directional dominance flags. For `x`(left) and `y`(right): + /// @param leftDominanceExist `x` has at least one criteria that is better than `y`. + /// @param rightDominanceExist `y` has at least one criteria that is better than `x`. + public static ParetoDominance of(boolean leftDominanceExist, boolean rightDominanceExist) { + if (leftDominanceExist) { + return rightDominanceExist ? MUTUAL : LEFT; + } else { + return rightDominanceExist ? RIGHT : NONE; + } + } + + /** + * Parse a dominance value from either its symbol or enum name. + * + *

      Accepted symbols are `≺`, `≻`, `∥`, and `≡`. If the input is not a single-character symbol, + * the value is parsed as an enum name (case-insensitive). + */ + public static ParetoDominance of(String value) { + if (value.length() == 1) { + char ch = value.charAt(0); + for (var it : values()) { + if (it.symbol == ch) { + return it; + } + } + } + return valueOf(value.toUpperCase()); + } + + /** + * Return the symbolic representation of this dominance value. + */ + @Override + public String toString() { + return Character.toString(symbol); + } +} diff --git a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java index 658d0d79a32..1d2f0fbc239 100644 --- a/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java +++ b/raptor/src/main/java/org/opentripplanner/raptor/util/paretoset/ParetoSet.java @@ -97,38 +97,29 @@ public final boolean add(T newValue) { return false; } - boolean mutualDominanceExist = false; - boolean equivalentVectorExist = false; - - for (int i = 0; i < size; ++i) { + loop: for (int i = 0; i < size; ++i) { T it = elements[i]; - boolean leftDominance = leftDominanceExist(newValue, it); - boolean rightDominance = rightDominanceExist(newValue, it); - - if (leftDominance && rightDominance) { - mutualDominanceExist = true; - } else if (leftDominance) { - removeDominatedElementsFromRestOfSetAndAddNewElement(newValue, i); - return true; - } else if (rightDominance) { - goodElement = elements[i]; - notifyElementRejected(newValue, it); - return false; - } else { - equivalentVectorExist = true; + switch (comparator.compare(newValue, it)) { + case MUTUAL: + continue loop; + case LEFT: + removeDominatedElementsFromRestOfSetAndAddNewElement(newValue, i); + return true; + case RIGHT: + goodElement = it; + notifyElementRejected(newValue, it); + return false; + case NONE: + // newValue is strictly equal to an existing value + notifyElementRejected(newValue, it); + return false; } } - if (mutualDominanceExist && !equivalentVectorExist) { - assertEnoughSpaceInSet(); - acceptAndAppendValue(newValue); - return true; - } - - // No dominance found, newValue is equivalent with all values in the set - notifyElementRejected(newValue, elements[0]); - return false; + assertEnoughSpaceInSet(); + acceptAndAppendValue(newValue); + return true; } public final void clear() { @@ -159,31 +150,22 @@ public final boolean qualify(T newValue) { return false; } - boolean mutualDominanceExist = false; - boolean equivalentVectorExist = false; + loop: for (int i = size - 1; i >= 0; --i) { + var it = elements[i]; - for (int i = size - 1; i >= 0; --i) { - boolean leftDominance = leftDominanceExist(newValue, elements[i]); - boolean rightDominance = rightDominanceExist(newValue, elements[i]); - - if (leftDominance && rightDominance) { - if (equivalentVectorExist) { + switch (comparator.compare(newValue, it)) { + case MUTUAL: + continue loop; + case LEFT: + return true; + case RIGHT: + goodElement = it; return false; - } - mutualDominanceExist = true; - } else if (leftDominance) { - return true; - } else if (rightDominance) { - goodElement = elements[i]; - return false; - } else { - if (mutualDominanceExist) { + case NONE: return false; - } - equivalentVectorExist = true; } } - return mutualDominanceExist; + return true; } /** diff --git a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestCostCalculator.java b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestCostCalculator.java index ee8a1432e50..93a8485503b 100644 --- a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestCostCalculator.java +++ b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestCostCalculator.java @@ -68,23 +68,19 @@ public int boardingCost( } @Override - public int onTripRelativeRidingCost(int boardTime, TestTripSchedule tripScheduledBoarded) { - // The relative-transit-time is time spent on transit. We do not know the alight-stop, so - // it is impossible to calculate the "correct" time. But the only thing that maters is that - // the relative difference between to boardings are correct, assuming riding the same trip. - // So, we can use the negative board time as relative-transit-time. - return -boardTime * TRANSIT_RELUCTANCE; + public int transitCost(int transitDuration, TestTripSchedule tripScheduledBoarded) { + return transitDuration * TRANSIT_RELUCTANCE; } @Override public int transitArrivalCost( int boardCost, int alightSlack, - int transitTime, + int transitDuration, TestTripSchedule trip, int toStopIndex ) { - int cost = boardCost + TRANSIT_RELUCTANCE * transitTime + waitFactor * alightSlack; + int cost = boardCost + transitCost(transitDuration, trip) + waitFactor * alightSlack; // Add transfer cost on all alighting events. // If it turns out to be the last one this cost will be removed during costEgress phase. @@ -101,18 +97,22 @@ public int waitCost(int waitTimeInSeconds) { } @Override - public int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStopIndex) { + public int calculateRemainingMinCost( + int minTravelDuration, + int minNumTransfers, + int fromStopIndex + ) { if (minNumTransfers > -1) { return ( boardCost + ((boardCost + transferCost) * minNumTransfers) + - (TRANSIT_RELUCTANCE * minTravelTime) + (TRANSIT_RELUCTANCE * minTravelDuration) ); } else { // Remove cost that was added during alighting similar as we do in the costEgress() method return stopBoardAlightTransferCosts == null - ? (TRANSIT_RELUCTANCE * minTravelTime) - : (TRANSIT_RELUCTANCE * minTravelTime) - stopBoardAlightTransferCosts[fromStopIndex]; + ? (TRANSIT_RELUCTANCE * minTravelDuration) + : (TRANSIT_RELUCTANCE * minTravelDuration) - stopBoardAlightTransferCosts[fromStopIndex]; } } diff --git a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripSchedule.java b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripSchedule.java index 01e5a17c4f5..6a08cbe7480 100644 --- a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripSchedule.java +++ b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripSchedule.java @@ -52,6 +52,11 @@ public int departure(int stopPosInPattern) { return departureTimes[stopPosInPattern]; } + @Override + public int relativeTravelDuration(int boardTime) { + return arrivalTimes[arrivalTimes.length - 1] - boardTime; + } + @Override public RaptorTripPattern pattern() { return pattern; diff --git a/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripScheduleTest.java b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripScheduleTest.java new file mode 100644 index 00000000000..4b5d7ec9f35 --- /dev/null +++ b/raptor/src/test/java/org/opentripplanner/raptor/_data/transit/TestTripScheduleTest.java @@ -0,0 +1,56 @@ +package org.opentripplanner.raptor._data.transit; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Verify that {@link TestTripSchedule} satisfies the {@code relativeTravelDuration} contract defined + * in {@link org.opentripplanner.raptor.spi.RaptorTripSchedule#relativeTravelDuration(int)}. + */ +class TestTripScheduleTest { + + private static final int HEADWAY = 600; + + /** + * Two trips in the same pattern, 10 minutes apart. + * + *

      +   *   Stop:    A       B       C
      +   *   Trip 1: 10:00  10:05  10:10
      +   *   Trip 2: 10:10  10:15  10:20  (Trip 1 + 10 min headway)
      +   * 
      + */ + private final TestTripSchedule trip1 = TestTripSchedule.schedule("10:00 10:05 10:10").build(); + private final TestTripSchedule trip2 = TestTripSchedule.schedule() + .times(trip1.arrival(0) + HEADWAY, trip1.arrival(1) + HEADWAY, trip1.arrival(2) + HEADWAY) + .build(); + + @Test + void relativeTravelTimeDecreasesByActualTransitTimeBetweenStops() { + int boardAtA = trip1.departure(0); + int boardAtB = trip1.departure(1); + int actualTimeBetweenAAndB = boardAtB - boardAtA; + + assertEquals( + actualTimeBetweenAAndB, + trip1.relativeTravelDuration(boardAtA) - trip1.relativeTravelDuration(boardAtB) + ); + } + + @Test + void relativeTravelTimeIsIdenticalAcrossTripsInTheSamePatternForEquivalentBoardingPositions() { + assertEquals( + trip1.relativeTravelDuration(trip1.departure(0)), + trip2.relativeTravelDuration(trip2.departure(0)) + ); + assertEquals( + trip1.relativeTravelDuration(trip1.departure(1)), + trip2.relativeTravelDuration(trip2.departure(1)) + ); + assertEquals( + trip1.relativeTravelDuration(trip1.departure(2)), + trip2.relativeTravelDuration(trip2.departure(2)) + ); + } +} diff --git a/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoComparatorTest.java b/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoComparatorTest.java new file mode 100644 index 00000000000..cc019cf8934 --- /dev/null +++ b/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoComparatorTest.java @@ -0,0 +1,41 @@ +package org.opentripplanner.raptor.util.paretoset; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.LEFT; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.MUTUAL; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.NONE; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.RIGHT; + +import org.junit.jupiter.api.Test; + +public class ParetoComparatorTest { + + private static final ParetoComparator COMPARATOR = (l, r) -> + l.v1 < r.v1 || l.v2 < r.v2; + private static final TestVector A_5_5 = new TestVector("a", 5, 5); + private static final TestVector B_5_5 = new TestVector("b", 5, 5); + private static final TestVector C_3_5 = new TestVector("c", 3, 5); + private static final TestVector D_5_3 = new TestVector("d", 5, 3); + + @Test + public void dominanceExist() { + // Vectors are equal - no dominance + assertFalse(COMPARATOR.dominanceExist(A_5_5, B_5_5)); + + // Dominance exist + assertTrue(COMPARATOR.dominanceExist(C_3_5, B_5_5)); + assertTrue(COMPARATOR.dominanceExist(A_5_5, C_3_5)); + assertTrue(COMPARATOR.dominanceExist(C_3_5, D_5_3)); + } + + @Test + public void compare() { + assertEquals(NONE, COMPARATOR.compare(A_5_5, B_5_5)); + assertEquals(NONE, COMPARATOR.compare(A_5_5, A_5_5)); + assertEquals(LEFT, COMPARATOR.compare(C_3_5, B_5_5)); + assertEquals(RIGHT, COMPARATOR.compare(A_5_5, C_3_5)); + assertEquals(MUTUAL, COMPARATOR.compare(C_3_5, D_5_3)); + } +} diff --git a/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoDominanceTest.java b/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoDominanceTest.java new file mode 100644 index 00000000000..fde3b212896 --- /dev/null +++ b/raptor/src/test/java/org/opentripplanner/raptor/util/paretoset/ParetoDominanceTest.java @@ -0,0 +1,44 @@ +package org.opentripplanner.raptor.util.paretoset; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.LEFT; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.MUTUAL; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.NONE; +import static org.opentripplanner.raptor.util.paretoset.ParetoDominance.RIGHT; + +import org.junit.jupiter.api.Test; + +class ParetoDominanceTest { + + @Test + void testOf() { + assertEquals(MUTUAL, ParetoDominance.of("mutual")); + assertEquals(LEFT, ParetoDominance.of("left")); + assertEquals(RIGHT, ParetoDominance.of("right")); + assertEquals(NONE, ParetoDominance.of("none")); + assertEquals(LEFT, ParetoDominance.of("≺")); + assertEquals(RIGHT, ParetoDominance.of("≻")); + assertEquals(NONE, ParetoDominance.of("≡")); + assertEquals(MUTUAL, ParetoDominance.of("∥")); + + // Mixed case + assertEquals(LEFT, ParetoDominance.of("LEFT")); + assertEquals(LEFT, ParetoDominance.of("lEfT")); + } + + @Test + void testOfLeftRight() { + assertEquals(LEFT, ParetoDominance.of(true, false)); + assertEquals(RIGHT, ParetoDominance.of(false, true)); + assertEquals(MUTUAL, ParetoDominance.of(true, true)); + assertEquals(NONE, ParetoDominance.of(false, false)); + } + + @Test + void testToString() { + assertEquals("≺", LEFT.toString()); + assertEquals("≻", RIGHT.toString()); + assertEquals("≡", NONE.toString()); + assertEquals("∥", MUTUAL.toString()); + } +} diff --git a/smoke-tests/denver/build-config.json b/smoke-tests/denver/build-config.json index f1ccbb9197b..2f2be00c7be 100644 --- a/smoke-tests/denver/build-config.json +++ b/smoke-tests/denver/build-config.json @@ -5,5 +5,6 @@ "feedId" : "denver", "source" : "https://www.rtd-denver.com/files/gtfs/google_transit.zip" } - ] + ], + "fares": "gtfs" } diff --git a/smoke-tests/portland/build-config.json b/smoke-tests/portland/build-config.json index 0ae51393c88..32de27dd8b6 100644 --- a/smoke-tests/portland/build-config.json +++ b/smoke-tests/portland/build-config.json @@ -8,5 +8,6 @@ "feedId": "TriMet", "source": "https://developer.trimet.org/schedule/gtfs.zip" } - ] + ], + "fares": "gtfs" } \ No newline at end of file diff --git a/street/src/main/java/org/opentripplanner/street/model/vertex/StreetLocation.java b/street/src/main/java/org/opentripplanner/street/model/vertex/StreetLocation.java index 4af761dbdb9..e3e0a4d5858 100644 --- a/street/src/main/java/org/opentripplanner/street/model/vertex/StreetLocation.java +++ b/street/src/main/java/org/opentripplanner/street/model/vertex/StreetLocation.java @@ -2,6 +2,7 @@ import org.locationtech.jts.geom.Coordinate; import org.opentripplanner.core.model.i18n.I18NString; +import org.opentripplanner.utils.lang.DoubleUtils; /** * Represents a location on a street, somewhere between the two corners. This is used when computing @@ -43,9 +44,12 @@ public int hashCode() { } public boolean equals(Object o) { - if (o instanceof StreetLocation) { - StreetLocation other = (StreetLocation) o; - return other.getCoordinate().equals(getCoordinate()); + if (o instanceof StreetLocation other) { + // avoid allocations of Coordinate by comparing the doubles directly + return ( + DoubleUtils.doubleEquals(other.getLat(), this.getLat()) && + DoubleUtils.doubleEquals(other.getLon(), this.getLon()) + ); } return false; } diff --git a/street/src/main/java/org/opentripplanner/street/search/state/State.java b/street/src/main/java/org/opentripplanner/street/search/state/State.java index 5bae96f41c9..0a34fcf649a 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/State.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/State.java @@ -6,7 +6,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; import javax.annotation.Nullable; import org.opentripplanner.astar.spi.AStarState; import org.opentripplanner.service.vehiclerental.model.RentalVehicleType.PropulsionType; @@ -19,9 +18,10 @@ import org.opentripplanner.street.search.TraverseMode; import org.opentripplanner.street.search.intersection_model.IntersectionTraversalCalculator; import org.opentripplanner.street.search.request.StreetSearchRequest; +import org.opentripplanner.utils.lang.DoubleUtils; import org.opentripplanner.utils.tostring.ToStringBuilder; -public class State implements AStarState, Cloneable { +public final class State implements AStarState { private static final State[] EMPTY_STATES = {}; private final StreetSearchRequest request; @@ -29,26 +29,26 @@ public class State implements AStarState, Cloneable { /* Data which is likely to change at most traversals */ // the current time at this state, in milliseconds since UNIX epoch - protected long time_ms; + final long time_ms; // accumulated weight up to this state - public double weight; + public final double weight; // associate this state with a vertex in the graph - protected Vertex vertex; + final Vertex vertex; // allow path reconstruction from states - protected State backState; + @Nullable + private final State backState; - public Edge backEdge; - - /* StateData contains data which is unlikely to change as often */ - public StateData stateData; + @Nullable + public final Edge backEdge; // how far have we traversed through the graph - public double traversalDistance_m; + public final double traversalDistance_m; - /* CONSTRUCTORS */ + /* StateData contains data which is unlikely to change as often */ + public final StateData stateData; /** * Create an initial state, forcing vertex to the specified value. Useful for tests, etc. @@ -62,11 +62,12 @@ public State(Vertex vertex, StreetSearchRequest streetSearchRequest) { ); } - public State(Vertex vertex, Instant startTime, StateData stateData, StreetSearchRequest request) { + State(Vertex vertex, Instant startTime, StateData stateData, StreetSearchRequest request) { this.request = request; this.weight = 0; - this.vertex = vertex; + this.vertex = Objects.requireNonNull(vertex); this.backState = null; + this.backEdge = null; this.stateData = stateData; if (request.arriveBy() && !vertex.rentalRestrictions().noDropOffNetworks().isEmpty()) { this.stateData.noRentalDropOffZonesAtStartOfReverseSearch = vertex @@ -77,6 +78,26 @@ public State(Vertex vertex, Instant startTime, StateData stateData, StreetSearch this.time_ms = startTime.toEpochMilli(); } + public State( + StreetSearchRequest request, + double weight, + Vertex vertex, + @Nullable State backState, + @Nullable Edge backEdge, + StateData stateData, + double traversalDistance_m, + long time_ms + ) { + this.request = Objects.requireNonNull(request); + this.weight = DoubleUtils.requireNonNegative(weight); + this.vertex = Objects.requireNonNull(vertex); + this.backState = backState; + this.backEdge = backEdge; + this.stateData = Objects.requireNonNull(stateData); + this.traversalDistance_m = DoubleUtils.requireNonNegative(traversalDistance_m); + this.time_ms = time_ms; + } + /** * Create an initial state representing the beginning of a search for the given routing request. * Initial "parent-less" states can only be created at the beginning of a trip. elsewhere, all @@ -141,13 +162,6 @@ public static boolean isEmpty(State[] s) { return s.length == 0; } - /** - * Takes a stream of states and converts it to an array while removing nulls. - */ - public static State[] ofStream(Stream states) { - return states.filter(Objects::nonNull).toArray(State[]::new); - } - /** * Create a state editor to produce a child of this state, which will be the result of traversing * the given edge. @@ -480,16 +494,6 @@ public boolean containsOnlyWalkMode() { return true; } - protected State clone() { - State ret; - try { - ret = (State) super.clone(); - } catch (CloneNotSupportedException e1) { - throw new IllegalStateException("This is not happening"); - } - return ret; - } - /** * Returns an efficient iterable that allows traversing the edge chain backwards. */ @@ -513,13 +517,6 @@ public String toString() { .toString(); } - void checkNegativeWeight() { - double dw = this.weight - backState.weight; - if (dw < 0) { - throw new NegativeWeightException(dw + " on edge " + backEdge); - } - } - private int getAbsTimeDeltaMilliseconds() { return Math.abs(getTimeDeltaMilliseconds()); } @@ -536,10 +533,12 @@ private State reversedClone() { // these must be getTime(), not getTimeAccurate(), so that the reversed path (which does not // have arriveBy true anymore) has times which round correctly, as the rounding rules // depend on arriveBy - StreetSearchRequest reversedRequest = request - .copyOfReversed(getTime()) - .withUseRentalAvailability(false) - .build(); + var builder = request.copyOfReversed(getTime()); + // mutating the builder is a hot spot, only do it if needed + if (request.mode().includesRenting()) { + builder.withUseRentalAvailability(false); + } + var reversedRequest = builder.build(); StateData newStateData = stateData.clone(); newStateData.backMode = null; return new State(this.vertex, getTime(), newStateData, reversedRequest); diff --git a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java index d832064c587..72de50b0c9c 100644 --- a/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java +++ b/street/src/main/java/org/opentripplanner/street/search/state/StateEditor.java @@ -23,10 +23,14 @@ public class StateEditor { private static final Logger LOG = LoggerFactory.getLogger(StateEditor.class); - - protected State child; - - private boolean spawned = false; + private final StreetSearchRequest request; + private final State backState; + private final Edge backEdge; + private final Vertex vertex; + private StateData stateData; + private double weight; + private long time_ms; + private double traversalDistance_m; private boolean defectiveTraversal = false; @@ -34,45 +38,44 @@ public class StateEditor { /* CONSTRUCTORS */ + /** + * The very first state in the chain before any iteration has started. + */ public StateEditor(Vertex v, StreetSearchRequest request) { - child = new State(v, request); + this.request = request; + this.stateData = new State(v, request).stateData; + this.backState = null; + this.backEdge = null; + this.vertex = v; + this.time_ms = request.startTime().toEpochMilli(); + this.weight = 0; + this.traversalDistance_m = 0; } public StateEditor(State parent, Edge e) { - child = parent.clone(); - child.backState = parent; - child.backEdge = e; + this.request = parent.getRequest(); + this.stateData = parent.stateData; + this.backState = parent; + this.backEdge = e; + this.time_ms = parent.time_ms; + this.weight = parent.weight; + this.traversalDistance_m = parent.traversalDistance_m; final Vertex parentVertex = parent.vertex; - if (e == null) { - child.backState = null; - child.vertex = parentVertex; - child.stateData = child.stateData.clone(); - return; - } - final Vertex fromVertex = e.getFromVertex(); final Vertex toVertex = e.getToVertex(); - if (fromVertex == null || toVertex == null) { - child.vertex = parentVertex; - child.stateData = child.stateData.clone(); - LOG.error("From or to vertex is null for {}", e); - defectiveTraversal = true; - return; - } - // Note that we use equals(), not ==, here to allow for dynamically created vertices if (parentVertex.equals(fromVertex)) { // from and to vertices are the same on eg. vehicle rental and parking vertices, thus, we // can't know the direction of travel from the above check. The expression below is simplified // fromVertex.equals(toVertex) ? parent.getOptions().arriveBy : false; traversingBackward = fromVertex.equals(toVertex) && parent.getRequest().arriveBy(); - child.vertex = toVertex; + this.vertex = toVertex; } else if (parentVertex.equals(toVertex)) { traversingBackward = true; - child.vertex = fromVertex; + this.vertex = fromVertex; } else { // Parent state is not at either end of edge. LOG.warn("Edge is not connected to parent state: {}", e); @@ -80,6 +83,7 @@ public StateEditor(State parent, Edge e) { LOG.warn(" to vertex: {}", toVertex); LOG.warn(" parent vertex: {}", parentVertex); defectiveTraversal = true; + this.vertex = null; } if (traversingBackward != parent.getRequest().arriveBy()) { @@ -102,38 +106,31 @@ public StateEditor(State parent, Edge e) { */ @Nullable public State makeState() { - // check that this editor has not been used already - if (spawned) { - throw new IllegalStateException("A StateEditor can only be used once."); - } - // if something was flagged incorrect, do not make a new state if (defectiveTraversal) { - LOG.error("Defective traversal flagged on edge " + child.backEdge); + LOG.error("Defective traversal flagged on edge " + backEdge); return null; } - if (child.backState != null) { - // make it impossible to use a state with lower weight than its - // parent. - child.checkNegativeWeight(); - + if (backState != null) { // check that time changes are coherent with edge traversal // direction - if ( - traversingBackward - ? (child.getTimeDeltaMilliseconds() > 0) - : (child.getTimeDeltaMilliseconds() < 0) - ) { - LOG.trace( - "Time was incremented the wrong direction during state editing. {}", - child.backEdge - ); + double timeDelta = time_ms - backState.getTimeMilliseconds(); + if (traversingBackward ? (timeDelta > 0) : (timeDelta < 0)) { + LOG.trace("Time was incremented the wrong direction during state editing. {}", backEdge); return null; } } - spawned = true; - return child; + return new State( + request, + weight, + vertex, + backState, + backEdge, + stateData, + traversalDistance_m, + time_ms + ); } /** @@ -145,7 +142,7 @@ public State[] makeStateArray() { } public String toString() { - return "StateEditor{" + child + "}"; + return "StateEditor{" + backState + "}"; } /* PUBLIC METHODS TO MODIFY A STATE BEFORE IT IS USED */ @@ -154,24 +151,17 @@ public String toString() { public void incrementWeight(double weight) { if (Double.isInfinite(weight) || Double.isNaN(weight)) { - LOG.warn( - "A state's weight is being incremented by " + - weight + - " while traversing edge " + - child.backEdge + throw new IllegalArgumentException( + "A state's weight is being incremented by " + weight + " while traversing edge " + backEdge ); - defectiveTraversal = true; - return; } if (weight < 0) { - LOG.warn( + throw new IllegalArgumentException( "A state's weight is being incremented by a negative amount while traversing edge " + - child.backEdge + backEdge ); - defectiveTraversal = true; - return; } - child.weight += weight; + this.weight += weight; } /** @@ -182,13 +172,12 @@ public void incrementWeight(double weight) { public void incrementTimeInMilliseconds(long milliseconds) { if (milliseconds < 0) { LOG.warn( - "A state's time is being incremented by a negative amount while traversing edge " + - child.getBackEdge() + "A state's time is being incremented by a negative amount while traversing edge " + backEdge ); defectiveTraversal = true; return; } - child.time_ms += (traversingBackward ? -milliseconds : milliseconds); + this.time_ms += (traversingBackward ? -milliseconds : milliseconds); } public void incrementTimeInSeconds(long seconds) { @@ -202,63 +191,63 @@ public void incrementTraversalDistanceMeters(double length) { if (length < 0) { throw new IllegalArgumentException("Traversal distance cannot be negative"); } - child.traversalDistance_m += length; + this.traversalDistance_m += length; } /* Basic Setters */ public void resetEnteredNoThroughTrafficArea() { - if (!child.stateData.enteredNoThroughTrafficArea) { + if (!stateData.enteredNoThroughTrafficArea) { return; } cloneStateDataAsNeeded(); - child.stateData.enteredNoThroughTrafficArea = false; + stateData.enteredNoThroughTrafficArea = false; } public void setEnteredNoThroughTrafficArea() { - if (child.stateData.enteredNoThroughTrafficArea) { + if (stateData.enteredNoThroughTrafficArea) { return; } cloneStateDataAsNeeded(); - child.stateData.enteredNoThroughTrafficArea = true; + stateData.enteredNoThroughTrafficArea = true; } public void leaveNoRentalDropOffArea() { - if (!child.stateData.insideNoRentalDropOffArea) { + if (!stateData.insideNoRentalDropOffArea) { return; } cloneStateDataAsNeeded(); - child.stateData.insideNoRentalDropOffArea = false; + stateData.insideNoRentalDropOffArea = false; } public void enterNoRentalDropOffArea() { - if (child.stateData.insideNoRentalDropOffArea) { + if (stateData.insideNoRentalDropOffArea) { return; } cloneStateDataAsNeeded(); - child.stateData.insideNoRentalDropOffArea = true; + stateData.insideNoRentalDropOffArea = true; } public void setBackMode(TraverseMode mode) { - if (mode == child.stateData.backMode) { + if (mode == stateData.backMode) { return; } cloneStateDataAsNeeded(); - child.stateData.backMode = mode; + stateData.backMode = mode; } public void setBackWalkingBike(boolean walkingBike) { - if (walkingBike == child.stateData.backWalkingBike) { + if (walkingBike == stateData.backWalkingBike) { return; } cloneStateDataAsNeeded(); - child.stateData.backWalkingBike = walkingBike; + stateData.backWalkingBike = walkingBike; } public void beginFloatingVehicleRenting( @@ -269,18 +258,18 @@ public void beginFloatingVehicleRenting( ) { cloneStateDataAsNeeded(); if (reverse) { - child.stateData.vehicleRentalState = VehicleRentalState.BEFORE_RENTING; - child.stateData.currentMode = TraverseMode.WALK; - child.stateData.vehicleRentalNetwork = null; - child.stateData.rentalVehicleFormFactor = null; - child.stateData.rentalVehiclePropulsionType = null; - child.stateData.insideNoRentalDropOffArea = false; + stateData.vehicleRentalState = VehicleRentalState.BEFORE_RENTING; + stateData.currentMode = TraverseMode.WALK; + stateData.vehicleRentalNetwork = null; + stateData.rentalVehicleFormFactor = null; + stateData.rentalVehiclePropulsionType = null; + stateData.insideNoRentalDropOffArea = false; } else { - child.stateData.vehicleRentalState = VehicleRentalState.RENTING_FLOATING; - child.stateData.currentMode = formFactor.traverseMode; - child.stateData.vehicleRentalNetwork = network; - child.stateData.rentalVehicleFormFactor = formFactor; - child.stateData.rentalVehiclePropulsionType = propulsionType; + stateData.vehicleRentalState = VehicleRentalState.RENTING_FLOATING; + stateData.currentMode = formFactor.traverseMode; + stateData.vehicleRentalNetwork = network; + stateData.rentalVehicleFormFactor = formFactor; + stateData.rentalVehiclePropulsionType = propulsionType; } } @@ -293,20 +282,20 @@ public void beginVehicleRentingAtStation( ) { cloneStateDataAsNeeded(); if (reverse) { - child.stateData.mayKeepRentedVehicleAtDestination = mayKeep; - child.stateData.vehicleRentalState = VehicleRentalState.BEFORE_RENTING; - child.stateData.currentMode = TraverseMode.WALK; - child.stateData.vehicleRentalNetwork = null; - child.stateData.rentalVehicleFormFactor = null; - child.stateData.rentalVehiclePropulsionType = null; - child.stateData.backWalkingBike = false; + stateData.mayKeepRentedVehicleAtDestination = mayKeep; + stateData.vehicleRentalState = VehicleRentalState.BEFORE_RENTING; + stateData.currentMode = TraverseMode.WALK; + stateData.vehicleRentalNetwork = null; + stateData.rentalVehicleFormFactor = null; + stateData.rentalVehiclePropulsionType = null; + stateData.backWalkingBike = false; } else { - child.stateData.mayKeepRentedVehicleAtDestination = mayKeep; - child.stateData.vehicleRentalState = VehicleRentalState.RENTING_FROM_STATION; - child.stateData.currentMode = formFactor.traverseMode; - child.stateData.vehicleRentalNetwork = network; - child.stateData.rentalVehicleFormFactor = formFactor; - child.stateData.rentalVehiclePropulsionType = propulsionType; + stateData.mayKeepRentedVehicleAtDestination = mayKeep; + stateData.vehicleRentalState = VehicleRentalState.RENTING_FROM_STATION; + stateData.currentMode = formFactor.traverseMode; + stateData.vehicleRentalNetwork = network; + stateData.rentalVehicleFormFactor = formFactor; + stateData.rentalVehiclePropulsionType = propulsionType; } } @@ -318,20 +307,20 @@ public void dropOffRentedVehicleAtStation( ) { cloneStateDataAsNeeded(); if (reverse) { - child.stateData.mayKeepRentedVehicleAtDestination = false; - child.stateData.vehicleRentalState = VehicleRentalState.RENTING_FROM_STATION; - child.stateData.currentMode = formFactor.traverseMode; - child.stateData.vehicleRentalNetwork = network; - child.stateData.rentalVehicleFormFactor = formFactor; - child.stateData.rentalVehiclePropulsionType = propulsionType; + stateData.mayKeepRentedVehicleAtDestination = false; + stateData.vehicleRentalState = VehicleRentalState.RENTING_FROM_STATION; + stateData.currentMode = formFactor.traverseMode; + stateData.vehicleRentalNetwork = network; + stateData.rentalVehicleFormFactor = formFactor; + stateData.rentalVehiclePropulsionType = propulsionType; } else { - child.stateData.mayKeepRentedVehicleAtDestination = false; - child.stateData.vehicleRentalState = VehicleRentalState.HAVE_RENTED; - child.stateData.currentMode = TraverseMode.WALK; - child.stateData.vehicleRentalNetwork = null; - child.stateData.rentalVehicleFormFactor = null; - child.stateData.rentalVehiclePropulsionType = null; - child.stateData.backWalkingBike = false; + stateData.mayKeepRentedVehicleAtDestination = false; + stateData.vehicleRentalState = VehicleRentalState.HAVE_RENTED; + stateData.currentMode = TraverseMode.WALK; + stateData.vehicleRentalNetwork = null; + stateData.rentalVehicleFormFactor = null; + stateData.rentalVehiclePropulsionType = null; + stateData.backWalkingBike = false; } } @@ -343,22 +332,22 @@ public void dropFloatingVehicle( ) { cloneStateDataAsNeeded(); if (reverse) { - child.stateData.mayKeepRentedVehicleAtDestination = false; - child.stateData.vehicleRentalState = VehicleRentalState.RENTING_FLOATING; - child.stateData.currentMode = formFactor != null + stateData.mayKeepRentedVehicleAtDestination = false; + stateData.vehicleRentalState = VehicleRentalState.RENTING_FLOATING; + stateData.currentMode = formFactor != null ? formFactor.traverseMode - : StreetModeToRentalTraverseModeMapper.map(child.getRequest().mode()); - child.stateData.vehicleRentalNetwork = network; - child.stateData.rentalVehicleFormFactor = formFactor; - child.stateData.rentalVehiclePropulsionType = propulsionType; + : StreetModeToRentalTraverseModeMapper.map(request.mode()); + stateData.vehicleRentalNetwork = network; + stateData.rentalVehicleFormFactor = formFactor; + stateData.rentalVehiclePropulsionType = propulsionType; } else { - child.stateData.mayKeepRentedVehicleAtDestination = false; - child.stateData.vehicleRentalState = VehicleRentalState.HAVE_RENTED; - child.stateData.currentMode = TraverseMode.WALK; - child.stateData.vehicleRentalNetwork = null; - child.stateData.rentalVehicleFormFactor = null; - child.stateData.rentalVehiclePropulsionType = null; - child.stateData.backWalkingBike = false; + stateData.mayKeepRentedVehicleAtDestination = false; + stateData.vehicleRentalState = VehicleRentalState.HAVE_RENTED; + stateData.currentMode = TraverseMode.WALK; + stateData.vehicleRentalNetwork = null; + stateData.rentalVehicleFormFactor = null; + stateData.rentalVehiclePropulsionType = null; + stateData.backWalkingBike = false; } } @@ -371,8 +360,8 @@ public void setVehicleParked(boolean vehicleParked, TraverseMode nonTransitMode) resetEnteredNoThroughTrafficArea(); cloneStateDataAsNeeded(); - child.stateData.vehicleParked = vehicleParked; - child.stateData.currentMode = nonTransitMode; + stateData.vehicleParked = vehicleParked; + stateData.currentMode = nonTransitMode; } /** @@ -381,38 +370,38 @@ public void setVehicleParked(boolean vehicleParked, TraverseMode nonTransitMode) */ public void setFromState(State state) { cloneStateDataAsNeeded(); - child.stateData.currentMode = state.stateData.currentMode; - child.stateData.carPickupState = state.stateData.carPickupState; - child.stateData.vehicleParked = state.stateData.vehicleParked; - child.stateData.backWalkingBike = state.stateData.backWalkingBike; + stateData.currentMode = state.stateData.currentMode; + stateData.carPickupState = state.stateData.carPickupState; + stateData.vehicleParked = state.stateData.vehicleParked; + stateData.backWalkingBike = state.stateData.backWalkingBike; } public void setCarPickupState(CarPickupState carPickupState) { cloneStateDataAsNeeded(); - child.stateData.carPickupState = carPickupState; + stateData.carPickupState = carPickupState; switch (carPickupState) { - case WALK_TO_PICKUP, WALK_FROM_DROP_OFF -> child.stateData.currentMode = TraverseMode.WALK; - case IN_CAR -> child.stateData.currentMode = TraverseMode.CAR; + case WALK_TO_PICKUP, WALK_FROM_DROP_OFF -> stateData.currentMode = TraverseMode.WALK; + case IN_CAR -> stateData.currentMode = TraverseMode.CAR; } } public void setTimeSeconds(long seconds) { - child.time_ms = 1000 * seconds; + this.time_ms = 1000 * seconds; } public void setTimeMilliseconds(long milliseconds) { - child.time_ms = milliseconds; + this.time_ms = milliseconds; } /* PUBLIC GETTER METHODS */ public State getBackState() { - return child.getBackState(); + return backState; } public void resetStartedInNoDropOffZone() { cloneStateDataAsNeeded(); - child.stateData.noRentalDropOffZonesAtStartOfReverseSearch = Set.of(); + stateData.noRentalDropOffZonesAtStartOfReverseSearch = Set.of(); } /* PRIVATE METHODS */ @@ -423,8 +412,8 @@ public void resetStartedInNoDropOffZone() { * older states. */ private void cloneStateDataAsNeeded() { - if (child.backState != null && child.stateData == child.backState.stateData) { - child.stateData = child.stateData.clone(); + if (backState != null && stateData == backState.stateData) { + this.stateData = backState.stateData.clone(); } } } diff --git a/street/src/test/java/org/opentripplanner/street/search/state/StateEditorTest.java b/street/src/test/java/org/opentripplanner/street/search/state/StateEditorTest.java index a8eea5e9dff..da5a23fe870 100644 --- a/street/src/test/java/org/opentripplanner/street/search/state/StateEditorTest.java +++ b/street/src/test/java/org/opentripplanner/street/search/state/StateEditorTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Nested; @@ -27,7 +27,7 @@ public final void testIncrementTimeInMilliseconds() { stateEditor.setTimeSeconds(0); stateEditor.incrementTimeInMilliseconds(999999999); - assertEquals(999999999, stateEditor.child.getTimeMilliseconds()); + assertEquals(999999999, stateEditor.makeState().getTimeMilliseconds()); } @Test @@ -45,9 +45,7 @@ public final void testNanWeightIncrement() { StateEditor stateEditor = new StateEditor(vertex, StreetSearchRequest.of().build()); stateEditor.setTimeSeconds(0); - stateEditor.incrementWeight(Double.NaN); - - assertNull(stateEditor.makeState()); + assertThrows(IllegalArgumentException.class, () -> stateEditor.incrementWeight(Double.NaN)); } @Test @@ -55,9 +53,9 @@ public final void testInfinityWeightIncrement() { StateEditor stateEditor = new StateEditor(vertex, StreetSearchRequest.of().build()); stateEditor.setTimeSeconds(0); - stateEditor.incrementWeight(Double.NEGATIVE_INFINITY); - - assertNull(stateEditor.makeState(), "Infinity weight increment"); + assertThrows(IllegalArgumentException.class, () -> + stateEditor.incrementWeight(Double.NEGATIVE_INFINITY) + ); } @Nested diff --git a/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java b/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java index 59ef4f3fbb4..24912a93eec 100644 --- a/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java +++ b/street/src/test/java/org/opentripplanner/street/search/strategy/DominanceFunctionTest.java @@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.street.model.StreetModelFactory.intersectionVertex; -import java.time.Instant; import org.junit.jupiter.api.Test; import org.opentripplanner.astar.spi.DominanceFunction; import org.opentripplanner.street.model.StreetMode; @@ -14,7 +13,7 @@ import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.state.StateData; -public class DominanceFunctionTest { +class DominanceFunctionTest { @Test public void testGeneralDominanceFunction() { @@ -26,10 +25,8 @@ public void testGeneralDominanceFunction() { StreetSearchRequest streetSearchRequest = StreetSearchRequest.of().build(); StateData stateData = StateData.getBaseCaseStateData(streetSearchRequest); - State stateA = new State(fromVertex, Instant.EPOCH, stateData, streetSearchRequest); - State stateB = new State(toVertex, Instant.EPOCH, stateData, streetSearchRequest); - stateA.weight = 1; - stateB.weight = 2; + State stateA = new State(streetSearchRequest, 1, fromVertex, null, null, stateData, 0, 0); + State stateB = new State(streetSearchRequest, 2, toVertex, null, null, stateData, 0, 0); assertTrue(minimumWeightDominanceFunction.betterOrEqualAndComparable(stateA, stateB)); assertFalse(minimumWeightDominanceFunction.betterOrEqualAndComparable(stateB, stateA)); @@ -50,7 +47,7 @@ public void noDropOffZone() { StateData stateData = StateData.getBaseCaseStateData(req); - State outsideZone = new State(fromVertex, Instant.EPOCH, stateData, req); + State outsideZone = new State(req, 0, fromVertex, null, null, stateData, 0, 0); assertFalse(outsideZone.isInsideNoRentalDropOffArea()); var edge = StreetModelFactory.streetEdge(fromVertex, toVertex); @@ -58,7 +55,6 @@ public void noDropOffZone() { var editor = outsideZone.edit(edge); editor.enterNoRentalDropOffArea(); var insideZone = editor.makeState(); - insideZone.weight = 1; assertFalse(dominanceF.betterOrEqualAndComparable(insideZone, outsideZone)); assertFalse(dominanceF.betterOrEqualAndComparable(outsideZone, insideZone)); diff --git a/test/performance/helsinki/build-config.json b/test/performance/helsinki/build-config.json index efbd90135bf..4000edc9b2c 100644 --- a/test/performance/helsinki/build-config.json +++ b/test/performance/helsinki/build-config.json @@ -28,7 +28,7 @@ "osmDefaults": { "timeZone": "Europe/Helsinki", "osmTagMapping": "finland", - "includeOsmSubwayEntrances": true + "includeOsmStationEntrances": true }, "demDefaults": { "elevationUnitMultiplier": 0.1 diff --git a/utils/src/main/java/org/opentripplanner/utils/lang/DoubleUtils.java b/utils/src/main/java/org/opentripplanner/utils/lang/DoubleUtils.java index 3bef4d0917b..1c1596fbca9 100644 --- a/utils/src/main/java/org/opentripplanner/utils/lang/DoubleUtils.java +++ b/utils/src/main/java/org/opentripplanner/utils/lang/DoubleUtils.java @@ -74,6 +74,16 @@ public static boolean doubleEquals(double a, double b) { return Double.compare(a, b) == 0; } + /** + * Throws an exception if the value is less than zero. + */ + public static double requireNonNegative(double value) { + if (value < 0) { + throw new IllegalArgumentException("Value is required to be non-negative, but was: " + value); + } + return value; + } + public static double requireInRange(double value, double min, double max) { return requireInRange(value, min, max, "value"); }