diff --git a/pom.xml b/pom.xml index 96b597390cb..6a239d54510 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ </scm> <properties> - <otp.serialization.version.id>15</otp.serialization.version.id> + <otp.serialization.version.id>16</otp.serialization.version.id> <!-- Lib versions - keep list sorted on property name --> <geotools.version>25.2</geotools.version> <jackson.version>2.12.5</jackson.version> diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/ScheduledDeviatedTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/ScheduledDeviatedTripTest.java index dd6731bccc2..06677ce570d 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/ScheduledDeviatedTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/ScheduledDeviatedTripTest.java @@ -203,7 +203,7 @@ public void shouldNotInterpolateFlexTimes() { var feedId = graph.getFeedIds().iterator().next(); var pattern = graph.tripPatternForId.get(new FeedScopedId(feedId, "090z:0:01")); - assertEquals(3, pattern.getStops().size()); + assertEquals(3, pattern.numberOfStops()); var tripTimes = pattern.getScheduledTimetable().getTripTimes(0); var arrivalTime = tripTimes.getArrivalTime(1); diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/UnscheduledTripTest.java index 0873c0823e7..53b7dc0a09b 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/UnscheduledTripTest.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import java.net.URISyntaxException; import java.util.List; import java.util.Set; import java.util.stream.Collectors; diff --git a/src/ext/java/org/opentripplanner/ext/reportapi/model/CsvReportBuilder.java b/src/ext/java/org/opentripplanner/ext/reportapi/model/CsvReportBuilder.java index f9d3d8b8cf9..cfd01782b24 100644 --- a/src/ext/java/org/opentripplanner/ext/reportapi/model/CsvReportBuilder.java +++ b/src/ext/java/org/opentripplanner/ext/reportapi/model/CsvReportBuilder.java @@ -6,7 +6,7 @@ /** * A very simple CSV builder to create CSV reports. * <p> - * This class helps formatting common types like time, duration and enums. + * This class helps to format common types like time, duration and enums. */ class CsvReportBuilder { private final String sep; @@ -51,12 +51,12 @@ void addText(String text) { } void addNumber(Number num) { - buf.append(num.toString()); + buf.append(num == null ? "" : num.toString()); sep(); } void addBoolean(Boolean b) { - buf.append(b.toString()); + buf.append(b == null ? "" : b.toString()); sep(); } diff --git a/src/ext/java/org/opentripplanner/ext/reportapi/model/TransfersReport.java b/src/ext/java/org/opentripplanner/ext/reportapi/model/TransfersReport.java index 9dabe644984..975da258b25 100644 --- a/src/ext/java/org/opentripplanner/ext/reportapi/model/TransfersReport.java +++ b/src/ext/java/org/opentripplanner/ext/reportapi/model/TransfersReport.java @@ -4,22 +4,31 @@ import static org.opentripplanner.util.time.DurationUtils.durationToStr; import java.util.List; -import org.locationtech.jts.geom.Coordinate; import org.opentripplanner.common.geometry.SphericalDistanceLibrary; -import org.opentripplanner.model.Stop; +import org.opentripplanner.model.Station; +import org.opentripplanner.model.StopLocation; +import org.opentripplanner.model.Trip; +import org.opentripplanner.model.TripPattern; +import org.opentripplanner.model.WgsCoordinate; import org.opentripplanner.model.transfer.ConstrainedTransfer; +import org.opentripplanner.model.transfer.RouteStationTransferPoint; +import org.opentripplanner.model.transfer.RouteStopTransferPoint; +import org.opentripplanner.model.transfer.StationTransferPoint; import org.opentripplanner.model.transfer.StopTransferPoint; import org.opentripplanner.model.transfer.TransferPoint; +import org.opentripplanner.model.transfer.TripTransferPoint; import org.opentripplanner.routing.graph.GraphIndex; /** * This class is used to export transfers for human verification to a CSV file. This is useful * when trying to debug the rather complicated NeTEx data format or to get the GTFS transfers in a - * more human readable form. It can also be used to test transfer functionality, since it is easy + * more human-readable form. It can also be used to test transfer functionality, since it is easy * to read and find special test-cases when needed. */ public class TransfersReport { + private static final boolean BOARD = true; + private static final boolean ALIGHT = false; private static final int NOT_SET = -1; @@ -41,32 +50,42 @@ public static String export(List<ConstrainedTransfer> transfers, GraphIndex inde String export() { buf.addHeader( - "Id", "Operator", "FromTripId", "FromTrip", "FromStop", - "ToTripId", "ToTrip", "ToStop", "ArrivalTime", "DepartureTime", "TransferTime", - "Walk", "Priority", "MaxWaitTime", "StaySeated", "Guaranteed" + "Id", "Operator", "From", "FromId", "FromRoute", "FromTrip", "FromStop", + "FromSpecificity", "To", "ToId", "ToRoute", "ToTrip", "ToStop", "ToSpecificity", + "ArrivalTime", "DepartureTime", "TransferTime", "Walk", "Priority", "MaxWaitTime", + "StaySeated", "Guaranteed" ); transfers.forEach(t -> { - var from = pointInfo(t.getFrom(), true); - var to = pointInfo(t.getTo(), false); - var dist = (from.c == null || to.c == null) + var from = pointInfo(t.getFrom(), ALIGHT); + var to = pointInfo(t.getTo(), BOARD); + var dist = (from.coordinate == null || to.coordinate == null) ? "" : String.format( "%.0fm", - SphericalDistanceLibrary.fastDistance(from.c, to.c) + SphericalDistanceLibrary.fastDistance( + from.coordinate.asJtsCoordinate(), + to.coordinate.asJtsCoordinate() + ) ); var duration = (from.time == NOT_SET || to.time == NOT_SET) ? "" : durationToStr(to.time - from.time); var c = t.getTransferConstraint(); buf.addText(t.getId() == null ? "" : t.getId().getId()); - buf.addText(t.getFrom().getTrip().getOperator().getId().getId()); - buf.addText(from.tripId); + buf.addText((from.operator.isEmpty() ? to : from).operator); + buf.addText(from.type); + buf.addText(from.entityId); + buf.addText(from.route); buf.addText(from.trip); - buf.addText(from.loc); - buf.addText(to.tripId); + buf.addText(from.location()); + buf.addNumber(from.specificity); + buf.addText(to.type); + buf.addText(to.entityId); + buf.addText(to.route); buf.addText(to.trip); - buf.addText(to.loc); + buf.addText(to.location()); + buf.addNumber(to.specificity); buf.addTime(from.time, NOT_SET); buf.addTime(to.time, NOT_SET); buf.addText(duration); @@ -80,47 +99,106 @@ String export() { return buf.toString(); } - private TxPoint pointInfo( - TransferPoint p, - boolean arrival - ) { + private TxPoint pointInfo(TransferPoint p, boolean boarding) { var r = new TxPoint(); - if (p instanceof StopTransferPoint) { - r.loc = p.getStop().getName(); - return r; - } - var ptn = index.getPatternForTrip().get(p.getTrip()); - var trip = p.getTrip(); - var route = trip.getRoute(); - - r.tripId = trip.getId().getId(); - r.trip = route.getName() + " " + route.getMode() + " " + route.getLongName() - + " " + trip.getTripHeadsign(); - r.c = null; + if(p instanceof TripTransferPoint) { + var tp = (TripTransferPoint)p; + var trip = tp.getTrip(); + var route = trip.getRoute(); + var ptn = index.getPatternForTrip().get(trip); + r.operator = trip.getOperator().getId().getId(); + r.type = "Trip"; + r.entityId = trip.getId().getId(); + r.route = route.getName() + " " + route.getMode() + " " + route.getLongName(); + r.trip = trip.getTripHeadsign(); + var stop = ptn.getStop(tp.getStopPositionInPattern()); + addLocation(r, ptn, stop, trip, boarding); + } + else if(p instanceof RouteStopTransferPoint) { + var rp = (RouteStopTransferPoint)p; + var route = rp.getRoute(); + var ptn = index.getPatternsForRoute().get(route).stream().findFirst().orElse(null); + r.operator = route.getOperator().getId().getId(); + r.type = "Route"; + r.entityId = route.getId().getId(); + r.route = route.getName() + " " + route.getMode() + " " + route.getLongName(); + addLocation(r, ptn, rp.getStop(), null, boarding); + } + else if(p instanceof RouteStationTransferPoint) { + var rp = (RouteStationTransferPoint)p; + var route = rp.getRoute(); + r.operator = route.getOperator().getId().getId(); + r.type = "Route"; + r.entityId = route.getId().getId(); + r.route = route.getName() + " " + route.getMode() + " " + route.getLongName(); + r.loc += rp.getStation().getName(); + r.coordinate = rp.getStation().getCoordinate(); + } + else if(p instanceof StopTransferPoint) { + var sp = (StopTransferPoint)p; + StopLocation stop = sp.getStop(); + r.type = "Stop"; + r.entityId = stop.getId().getId(); + r.loc = stop.getName(); + r.coordinate = stop.getCoordinate(); + } + else if(p instanceof StationTransferPoint) { + var sp = (StationTransferPoint)p; + Station station = sp.getStation(); + r.type = "Station"; + r.entityId = station.getId().getId(); + r.loc = station.getName(); + r.coordinate = station.getCoordinate(); + } + r.specificity = p.getSpecificityRanking(); + r.coordinate = null; + return r; + } + private static void addLocation( + TxPoint r, + TripPattern pattern, + StopLocation stop, + Trip trip, + boolean boarding + ) { + if(pattern == null) { + r.loc += stop.getName() + " [Pattern no found]"; + return; + } + int stopPosition = pattern.findStopPosition(stop); + r.coordinate = stop.getCoordinate(); - if (ptn.getStops().size() > p.getStopPosition()) { - int pos = p.getStopPosition(); - var stop = ptn.getStops().get(pos); - var tt = ptn.getScheduledTimetable().getTripTimes(trip); - r.loc += stop.getName() + " [" + pos + "]" + " " + stop.getCoordinate(); - r.time = arrival ? tt.getScheduledArrivalTime(pos) : tt.getScheduledDepartureTime(pos); - r.c = stop.getCoordinate().asJtsCoordinate(); + if(stopPosition<0) { + r.loc += "[Stop not found in pattern: " + stop.getName() + "]"; + return; } - else { - r.loc += "[Stop index not found: " + p.getStopPosition() + "]"; + r.loc += stop.getName() + " [" + stopPosition + "]"; + + if(trip != null) { + var tt = pattern.getScheduledTimetable().getTripTimes(trip); + r.time = boarding + ? tt.getScheduledDepartureTime(stopPosition) + : tt.getScheduledArrivalTime(stopPosition) + ; } - r.loc += " " + p.getSpecificityRanking(); - return r; } static class TxPoint { + private String operator = ""; + private String type = ""; + private String entityId = ""; private String loc = ""; - private String tripId = ""; private String trip = ""; - private Coordinate c = null; + private String route = ""; + private Integer specificity = null; + private WgsCoordinate coordinate = null; private int time = NOT_SET; + + String location() { + return coordinate == null ? loc : loc + " " + coordinate; + } } } diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java b/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java index 4f48f61dac5..e4980ef728c 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java @@ -1,12 +1,12 @@ package org.opentripplanner.ext.siri; import java.time.ZonedDateTime; -import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.ArrayList; import org.opentripplanner.model.FeedScopedId; import org.opentripplanner.model.Route; import org.opentripplanner.model.Station; @@ -234,7 +234,7 @@ private static void initCache(RoutingService index) { } } } - String lastStopId = tripPattern.getStops().get(tripPattern.getStops().size()-1).getId().getId(); + String lastStopId = tripPattern.lastStop().getId().getId(); TripTimes tripTimes = tripPattern.getScheduledTimetable().getTripTimes(trip); if (tripTimes != null) { @@ -385,7 +385,7 @@ public int getTripArrivalTime(FeedScopedId tripId) { * Returns a match of tripIds that match the provided values. */ public List<FeedScopedId> getTripIdForInternalPlanningCodeServiceDateAndMode( - String internalPlanningCode, + String internalPlanningCode, ServiceDate serviceDate, TransitMode mode, String transportSubmode diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java index ed8908fb732..60ab16b4be7 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSource.java @@ -1,6 +1,21 @@ package org.opentripplanner.ext.siri; +import static org.opentripplanner.ext.siri.TimetableHelper.createModifiedStopTimes; +import static org.opentripplanner.ext.siri.TimetableHelper.createModifiedStops; +import static org.opentripplanner.ext.siri.TimetableHelper.createUpdatedTripTimes; +import static org.opentripplanner.model.PickDrop.NONE; +import static org.opentripplanner.model.PickDrop.SCHEDULED; + import com.google.common.base.Preconditions; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TimeZone; +import java.util.concurrent.locks.ReentrantLock; import org.opentripplanner.common.model.T2; import org.opentripplanner.model.Agency; import org.opentripplanner.model.FeedScopedId; @@ -787,7 +802,7 @@ private boolean handleModifiedTrip(Graph graph, String feedId, EstimatedVehicleJ for (TripTimes tripTimes : times) { Trip trip = tripTimes.getTrip(); for (TripPattern pattern : patterns) { - if (tripTimes.getNumStops() == pattern.getStopPattern().getStops().length) { + if (tripTimes.getNumStops() == pattern.numberOfStops()) { if (!tripTimes.isCanceled()) { /* UPDATED and MODIFIED tripTimes should be handled the same way to always allow latest realtime-update @@ -1015,7 +1030,7 @@ private Set<TripPattern> getPatternsForTrip(Set<Trip> matches, VehicleActivitySt } var firstStop = tripPattern.getStop(0); - var lastStop = tripPattern.getStop(tripPattern.getStops().size() - 1); + var lastStop = tripPattern.lastStop(); String siriOriginRef = monitoredVehicleJourney.getOriginRef().getValue(); @@ -1130,8 +1145,8 @@ private TripPattern getPatternForTrip(Trip trip, EstimatedVehicleJourney journey } - var firstStop = tripPattern.getStop(0); - var lastStop = tripPattern.getStop(tripPattern.getStops().size() - 1); + var firstStop = tripPattern.firstStop(); + var lastStop = tripPattern.lastStop(); if (serviceDates.contains(journeyDate)) { boolean firstStopIsMatch = firstStop.getId().getId().equals(journeyFirstStopId); @@ -1256,9 +1271,9 @@ private Set<Trip> getTripForJourney(Set<Trip> trips, EstimatedVehicleJourney jou TripPattern pattern = routingService.getPatternForTrip().get(trip); - if (stopNumber < pattern.getStopPattern().getStops().length) { + if (stopNumber < pattern.numberOfStops()) { boolean firstReportedStopIsFound = false; - var stop = pattern.getStopPattern().getStops()[stopNumber - 1]; + var stop = pattern.getStop(stopNumber - 1); if (firstStopId.equals(stop.getId().getId())) { firstReportedStopIsFound = true; } diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriTripPatternCache.java b/src/ext/java/org/opentripplanner/ext/siri/SiriTripPatternCache.java index 3fab23b0578..d6b1f058535 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriTripPatternCache.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriTripPatternCache.java @@ -3,6 +3,11 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimaps; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.validation.constraints.NotNull; import org.opentripplanner.model.Stop; import org.opentripplanner.model.StopLocation; import org.opentripplanner.model.StopPattern; @@ -13,12 +18,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.validation.constraints.NotNull; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - /** * A synchronized cache of trip patterns that are added to the graph due to GTFS-realtime messages. */ @@ -129,9 +128,10 @@ public synchronized TripPattern getOrCreateTripPattern( * Remove previously added TripPatterns for the trip currently being updated - if the stopPattern does not match */ TripPattern cachedTripPattern = updatedTripPatternsForTripCache.get(tripServiceDateKey); - if (cachedTripPattern != null && !tripPattern - .getStopPattern() - .equals(cachedTripPattern.getStopPattern())) { + if ( + cachedTripPattern != null && + !tripPattern.stopPatternIsEqual(cachedTripPattern) + ) { int sizeBefore = patternsForStop.values().size(); long t1 = System.currentTimeMillis(); patternsForStop.values().removeAll(Arrays.asList(cachedTripPattern)); diff --git a/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java b/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java index ab72dd0be9a..5703ddf8290 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java +++ b/src/ext/java/org/opentripplanner/ext/siri/TimetableHelper.java @@ -1,15 +1,27 @@ package org.opentripplanner.ext.siri; +import static java.util.Collections.EMPTY_LIST; +import static org.opentripplanner.model.PickDrop.NONE; +import static org.opentripplanner.model.PickDrop.SCHEDULED; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TimeZone; +import javax.xml.datatype.Duration; +import lombok.val; import org.opentripplanner.model.FeedScopedId; -import org.opentripplanner.model.Stop; import org.opentripplanner.model.StopLocation; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.Timetable; import org.opentripplanner.model.TimetableSnapshot; import org.opentripplanner.model.Trip; +import org.opentripplanner.routing.RoutingService; import org.opentripplanner.routing.algorithm.raptor.transit.mappers.DateMapper; import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.routing.RoutingService; import org.opentripplanner.routing.trippattern.RealTimeState; import org.opentripplanner.routing.trippattern.TripTimes; import org.slf4j.Logger; @@ -25,19 +37,6 @@ import uk.org.siri.siri20.RecordedCall; import uk.org.siri.siri20.VehicleActivityStructure; -import javax.xml.datatype.Duration; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.TimeZone; - -import static java.util.Collections.EMPTY_LIST; -import static org.opentripplanner.model.PickDrop.NONE; -import static org.opentripplanner.model.PickDrop.SCHEDULED; - public class TimetableHelper { private static final Logger LOG = LoggerFactory.getLogger(TimetableHelper.class); @@ -95,8 +94,6 @@ public static TripTimes createUpdatedTripTimes(final Graph graph, Timetable time boolean stopPatternChanged = false; - var modifiedStops = timetable.getPattern().getStopPattern().getStops(); - Trip trip = getTrip(tripId, timetable); List<StopTime> modifiedStopTimes = createModifiedStopTimes(timetable, oldTimes, journey, trip, new RoutingService(graph)); @@ -119,7 +116,7 @@ public static TripTimes createUpdatedTripTimes(final Graph graph, Timetable time int departureFromPreviousStop = 0; int lastArrivalDelay = 0; int lastDepartureDelay = 0; - for (var stop : modifiedStops) { + for (var stop : timetable.getPattern().getStops()) { boolean foundMatch = false; for (RecordedCall recordedCall : recordedCalls) { @@ -286,8 +283,7 @@ public static TripTimes createUpdatedTripTimes(final Graph graph, Timetable time } if (!foundMatch) { - if (timetable.getPattern().getStopPattern().getPickup(callCounter) == NONE && - timetable.getPattern().getStopPattern().getDropoff(callCounter) == NONE) { + if (timetable.getPattern().isBoardAndAlightAt(callCounter, NONE)) { // When newTimes contains stops without pickup/dropoff - set both arrival/departure to previous stop's departure // This necessary to accommodate the case when delay is reduced/eliminated between to stops with pickup/dropoff, and // multiple non-pickup/dropoff stops are in between. @@ -331,7 +327,7 @@ public static TripTimes createUpdatedTripTimes(final Graph graph, Timetable time return null; } - if (newTimes.getNumStops() != timetable.getPattern().getStopPattern().getStops().length) { + if (newTimes.getNumStops() != timetable.getPattern().numberOfStops()) { return null; } @@ -372,15 +368,15 @@ public static List<StopLocation> createModifiedStops(Timetable timetable, Estima } //Get all scheduled stops - var stops = timetable.getPattern().getStopPattern().getStops(); + val pattern = timetable.getPattern(); // Keeping track of visited stop-objects to allow multiple visits to a stop. List<Object> alreadyVisited = new ArrayList<>(); List<StopLocation> modifiedStops = new ArrayList<>(); - for (int i = 0; i < stops.length; i++) { - StopLocation stop = stops[i]; + for (int i = 0; i < pattern.numberOfStops(); i++) { + StopLocation stop = pattern.getStop(i); boolean foundMatch = false; if (i < recordedCalls.size()) { @@ -481,8 +477,8 @@ public static List<StopTime> createModifiedStopTimes(Timetable timetable, TripTi stopTime.setStop(stop); stopTime.setTrip(trip); stopTime.setStopSequence(i); - stopTime.setDropOffType(timetable.getPattern().getStopPattern().getDropoff(i)); - stopTime.setPickupType(timetable.getPattern().getStopPattern().getPickup(i)); + stopTime.setDropOffType(timetable.getPattern().getAlightType(i)); + stopTime.setPickupType(timetable.getPattern().getBoardType(i)); stopTime.setArrivalTime(oldTimes.getScheduledArrivalTime(i)); stopTime.setDepartureTime(oldTimes.getScheduledDepartureTime(i)); stopTime.setStopHeadsign(oldTimes.getHeadsign(i)); @@ -597,7 +593,6 @@ public static TripTimes createUpdatedTripTimes(Timetable timetable, Graph graph, if (update == null) { return null; } - final List<StopLocation> stops = timetable.getPattern().getStops(); VehicleActivityStructure.MonitoredVehicleJourney monitoredVehicleJourney = activity.getMonitoredVehicleJourney(); @@ -615,11 +610,12 @@ public static TripTimes createUpdatedTripTimes(Timetable timetable, Graph graph, int arrivalDelay = 0; int departureDelay = 0; + val pattern = timetable.getPattern(); for (int index = 0; index < newTimes.getNumStops(); ++index) { if (!matchFound) { // Delay is set on a single stop at a time. When match is found - propagate delay on all following stops - final var stop = stops.get(index); + final var stop = pattern.getStop(index); matchFound = stop.getId().getId().equals(monitoredCall.getStopPointRef().getValue()); diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/InterchangeType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/InterchangeType.java index 506186272f8..563ffb95c20 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/InterchangeType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/timetable/InterchangeType.java @@ -5,9 +5,13 @@ import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; +import java.util.function.Function; import org.opentripplanner.ext.transmodelapi.model.EnumTypes; +import org.opentripplanner.model.Route; +import org.opentripplanner.model.Trip; import org.opentripplanner.model.transfer.ConstrainedTransfer; import org.opentripplanner.model.transfer.TransferConstraint; +import org.opentripplanner.model.transfer.TransferPoint; public class InterchangeType { @@ -52,36 +56,36 @@ public static GraphQLObjectType create( .deprecate( "This is the same as using the `fromServiceJourney { line }` field.") .type(lineType) - .dataFetcher(env -> transfer(env).getFrom().getTrip().getRoute()) + .dataFetcher(env -> transferRoute(env, ConstrainedTransfer::getFrom)) .build()) .field(GraphQLFieldDefinition.newFieldDefinition() .name("ToLine") .deprecate( "This is the same as using the `toServiceJourney { line }` field.") .type(lineType) - .dataFetcher(env -> transfer(env).getTo().getTrip().getRoute()) + .dataFetcher(env -> transferRoute(env, ConstrainedTransfer::getTo)) .build()) .field(GraphQLFieldDefinition.newFieldDefinition() - .name("FromServiceJourney") + .name("fromServiceJourney") .type(serviceJourneyType) - .deprecate("Use fromServiceJourney instead") - .dataFetcher(env -> transfer(env).getFrom().getTrip()) + .dataFetcher(env -> transferTrip(env, ConstrainedTransfer::getFrom)) .build()) .field(GraphQLFieldDefinition.newFieldDefinition() - .name("ToServiceJourney") + .name("toServiceJourney") .type(serviceJourneyType) - .deprecate("Use toServiceJourney instead") - .dataFetcher(env -> transfer(env).getTo().getTrip()) + .dataFetcher(env -> transferTrip(env, ConstrainedTransfer::getTo)) .build()) .field(GraphQLFieldDefinition.newFieldDefinition() - .name("fromServiceJourney") + .name("FromServiceJourney") .type(serviceJourneyType) - .dataFetcher(env -> transfer(env).getFrom().getTrip()) + .deprecate("Use fromServiceJourney instead") + .dataFetcher(env -> transferTrip(env, ConstrainedTransfer::getFrom)) .build()) .field(GraphQLFieldDefinition.newFieldDefinition() - .name("toServiceJourney") + .name("ToServiceJourney") .type(serviceJourneyType) - .dataFetcher(env -> transfer(env).getTo().getTrip()) + .deprecate("Use toServiceJourney instead") + .dataFetcher(env -> transferTrip(env, ConstrainedTransfer::getTo)) .build()) .build(); } @@ -90,6 +94,27 @@ private static ConstrainedTransfer transfer(DataFetchingEnvironment environment) return environment.getSource(); } + private static TransferPoint transferPoint( + DataFetchingEnvironment environment, + Function<ConstrainedTransfer, TransferPoint> fromTo + ) { + return fromTo.apply(transfer(environment)); + } + + private static Trip transferTrip( + DataFetchingEnvironment environment, + Function<ConstrainedTransfer, TransferPoint> fromTo + ) { + return TransferPoint.getTrip(transferPoint(environment, fromTo)); + } + + private static Route transferRoute( + DataFetchingEnvironment environment, + Function<ConstrainedTransfer, TransferPoint> fromTo + ) { + return TransferPoint.getRoute(transferPoint(environment, fromTo)); + } + private static TransferConstraint constraint(DataFetchingEnvironment environment) { return transfer(environment).getTransferConstraint(); } diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java index 07c81f4e379..5fb77cd7f7c 100644 --- a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java @@ -1,5 +1,10 @@ package org.opentripplanner.ext.vectortiles.layers.stops; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.opentripplanner.common.model.T2; @@ -9,12 +14,6 @@ import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.vertextype.TransitStopVertex; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; - public class DigitransitStopPropertyMapper extends PropertyMapper<TransitStopVertex> { private final Graph graph; @@ -41,11 +40,14 @@ public Collection<T2<String, Object>> map(TransitStopVertex input) { .orElse(null); String patterns = JSONArray.toJSONString(patternsForStop.stream().map(tripPattern -> { - JSONObject pattern = new JSONObject(); - pattern.put("headsign", tripPattern.getScheduledTimetable().getTripTimes().get(0).getHeadsign(tripPattern.getStopIndex(stop))); - pattern.put("type", tripPattern.getRoute().getMode().name()); - pattern.put("shortName", tripPattern.getRoute().getShortName()); - return pattern; + int stopPos = tripPattern.findStopPosition(stop); + var headsign = stopPos < 0 ? "Not Available" : + tripPattern.getScheduledTimetable().getTripTimes().get(0).getHeadsign(stopPos); + return new JSONObject(Map.of( + "headsign", headsign, + "type", tripPattern.getRoute().getMode().name(), + "shortName", tripPattern.getRoute().getShortName() + )); }).collect(Collectors.toList())); return List.of( diff --git a/src/main/java/org/opentripplanner/common/model/T2.java b/src/main/java/org/opentripplanner/common/model/T2.java index 8a73ea3f30f..3b4744c6d32 100644 --- a/src/main/java/org/opentripplanner/common/model/T2.java +++ b/src/main/java/org/opentripplanner/common/model/T2.java @@ -1,6 +1,7 @@ package org.opentripplanner.common.model; import java.io.Serializable; +import java.util.Objects; /** * An ordered pair of objects of potentially different types @@ -25,22 +26,8 @@ public int hashCode() { @Override public boolean equals(Object object) { if (!(object instanceof T2)) { return false; } - - var other = (T2) object; - - if (first == null) { - if (other.first != null) { return false; } - } else { - if (!first.equals(other.first)) { return false; } - } - - if (second == null) { - if (other.second != null) { return false; } - } else { - if (!second.equals(other.second)) { return false; } - } - - return true; + var other = (T2<?,?>) object; + return Objects.equals(first, other.first) && Objects.equals(second, other.second); } @Override diff --git a/src/main/java/org/opentripplanner/graph_builder/module/geometry/GeometryAndBlockProcessor.java b/src/main/java/org/opentripplanner/graph_builder/module/geometry/GeometryAndBlockProcessor.java index f3bac8976d6..a3622ad732b 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/geometry/GeometryAndBlockProcessor.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/geometry/GeometryAndBlockProcessor.java @@ -238,8 +238,8 @@ private void interline(Collection<TripPattern> tripPatterns, Graph graph) { } TripPattern prevPattern = patternForTripTimes.get(prev); TripPattern currPattern = patternForTripTimes.get(curr); - var fromStop = prevPattern.getStop(prevPattern.getStops().size() - 1); - var toStop = currPattern.getStop(0); + var fromStop = prevPattern.lastStop(); + var toStop = currPattern.firstStop(); double teleportationDistance = SphericalDistanceLibrary.fastDistance( fromStop.getLat(), fromStop.getLon(), diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/TransferMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/TransferMapper.java index 2b6582fa3a7..fee50cf1f0e 100644 --- a/src/main/java/org/opentripplanner/gtfs/mapping/TransferMapper.java +++ b/src/main/java/org/opentripplanner/gtfs/mapping/TransferMapper.java @@ -1,26 +1,30 @@ package org.opentripplanner.gtfs.mapping; -import java.util.ArrayList; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; import java.util.Collection; -import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.function.BiFunction; +import java.util.Objects; +import java.util.function.Predicate; import java.util.stream.Collectors; -import javax.annotation.Nullable; import org.onebusaway.gtfs.model.Transfer; import org.opentripplanner.model.Route; +import org.opentripplanner.model.Station; import org.opentripplanner.model.Stop; import org.opentripplanner.model.StopLocation; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.Trip; import org.opentripplanner.model.TripStopTimes; import org.opentripplanner.model.transfer.ConstrainedTransfer; +import org.opentripplanner.model.transfer.RouteStationTransferPoint; +import org.opentripplanner.model.transfer.RouteStopTransferPoint; +import org.opentripplanner.model.transfer.StationTransferPoint; import org.opentripplanner.model.transfer.StopTransferPoint; import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.model.transfer.TransferPoint; import org.opentripplanner.model.transfer.TransferPriority; import org.opentripplanner.model.transfer.TripTransferPoint; +import org.opentripplanner.util.logging.ThrottleLogger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,6 +37,7 @@ class TransferMapper { private static final Logger LOG = LoggerFactory.getLogger(TransferMapper.class); + private static final Logger FIXED_ROUTE_ERROR = ThrottleLogger.throttle(LOG); /** * This transfer is recommended over other transfers. The routing algorithm should prefer this @@ -70,6 +75,9 @@ class TransferMapper { private final TripStopTimes stopTimesByTrip; + private final Multimap<Route, Trip> tripsByRoute = ArrayListMultimap.create(); + + TransferMapper( RouteMapper routeMapper, StationMapper stationMapper, @@ -98,69 +106,45 @@ static TransferPriority mapTypeToPriority(int type) { } Collection<ConstrainedTransfer> map(Collection<org.onebusaway.gtfs.model.Transfer> allTransfers) { - List<ConstrainedTransfer> result = new ArrayList<>(); + setup(!allTransfers.isEmpty()); - for (org.onebusaway.gtfs.model.Transfer it : allTransfers) { - result.addAll(map(it)); - } - return result; + return allTransfers.stream().map(this::map) + .filter(Objects::nonNull) + .collect(Collectors.toList()); } - /** - * Map from GTFS to OTP model, {@code null} safe. - */ - Collection<ConstrainedTransfer> map(org.onebusaway.gtfs.model.Transfer original) { - return original == null ? List.of() : doMap(original); - } - - private Collection<ConstrainedTransfer> doMap(org.onebusaway.gtfs.model.Transfer rhs) { - + ConstrainedTransfer map(org.onebusaway.gtfs.model.Transfer rhs) { Trip fromTrip = tripMapper.map(rhs.getFromTrip()); Trip toTrip = tripMapper.map(rhs.getToTrip()); - Route fromRoute = routeMapper.map(rhs.getFromRoute()); - Route toRoute = routeMapper.map(rhs.getToRoute()); + TransferConstraint constraint = mapConstraint(rhs, fromTrip, toTrip); // TODO TGR - Create a transfer for this se issue #3369 int transferTime = rhs.getMinTransferTime(); // If this transfer do not give any advantages in the routing, then drop it - if(constraint.noConstraints()) { + if(constraint.isRegularTransfer()) { if(transferTime > 0) { LOG.info("Transfer skipped, issue #3369: " + rhs); } else { LOG.warn("Transfer skipped - no effect on routing: " + rhs); } - return List.of(); + return null; } - // Transfers may be specified using parent stations - // (https://developers.google.com/transit/gtfs/reference/transfers-file) - // "If the stop ID refers to a station that contains multiple stops, this transfer rule - // applies to all stops in that station." we thus expand transfers that use parent stations - // to all the member stops. - - var fromStops = getStopOrChildStops(rhs.getFromStop()); - var toStops = getStopOrChildStops(rhs.getToStop()); - - Collection<TransferPoint> fromPoints = mapTransferPoints(fromStops, fromTrip, fromRoute); - Collection<TransferPoint> toPoints = mapTransferPoints(toStops, toTrip, toRoute); - - Collection<ConstrainedTransfer> result = new ArrayList<>(); - - for (TransferPoint fromPoint : fromPoints) { - for (TransferPoint toPoint : toPoints) { - var transfer = new ConstrainedTransfer( - null, - fromPoint, - toPoint, - constraint - ); - result.add(transfer); - } + TransferPoint fromPoint = mapTransferPoint(rhs.getFromStop(), rhs.getFromRoute(), fromTrip, false); + TransferPoint toPoint = mapTransferPoint(rhs.getToStop(), rhs.getToRoute(), toTrip, true); + + return new ConstrainedTransfer(null, fromPoint, toPoint, constraint); + } + + private void setup(boolean run) { + if(!run) { return; } + + for (Trip trip : tripMapper.getMappedTrips()) { + tripsByRoute.put(trip.getRoute(), trip); } - return result; } private TransferConstraint mapConstraint(Transfer rhs, Trip fromTrip, Trip toTrip) { @@ -173,64 +157,71 @@ private TransferConstraint mapConstraint(Transfer rhs, Trip fromTrip, Trip toTri return builder.build(); } - private Collection<TransferPoint> mapTransferPoints( - Collection<StopLocation> stops, - Trip trip, - Route route + private TransferPoint mapTransferPoint( + org.onebusaway.gtfs.model.Stop rhsStopOrStation, + org.onebusaway.gtfs.model.Route rhsRoute, + Trip trip, + boolean boardTrip ) { - Collection<TransferPoint> result = new ArrayList<>(); - if (trip != null) { - result.addAll(createTransferPointForTrip(stops, trip, TripTransferPoint::new)); - } - else if (route != null) { - /* - TODO - This code result in a OutOfMemory exception, fin out why and fix it - - See issue https://github.com/opentripplanner/OpenTripPlanner/issues/3429 - for (Trip tripInRoute : tripsByRoute.get(route)) { - result.addAll( - createTransferPointForTrip( - stops, - tripInRoute, - (t,i) -> new RouteTransferPoint(route, t, i) - ) - ); - } - */ + Route route = routeMapper.map(rhsRoute); + Station station = null; + Stop stop = null; + + // A transfer is specified using Stops and/or Station, according to the GTFS specification: + // + // If the stop ID refers to a station that contains multiple stops, this transfer rule + // applies to all stops in that station. + // + // Source: https://developers.google.com/transit/gtfs/reference/transfers-file + + if (rhsStopOrStation.getLocationType() == 0) { + stop = stopMapper.map(rhsStopOrStation); } else { - for (var stop : stops) { - result.add(new StopTransferPoint(stop)); - } - + station = stationMapper.map(rhsStopOrStation); } - return result; + if(trip != null) { + // A trip may visit the same stop twice, but we ignore that and only add the first stop + // we find. Pattern that start and end at the same stop is supported. + int stopPositionInPattern = stopPosition(trip, stop, station, boardTrip); + return stopPositionInPattern < 0 ? null : new TripTransferPoint(trip, stopPositionInPattern); + } + else if(route != null) { + if(stop != null) { return new RouteStopTransferPoint(route, stop); } + else if(station != null) { return new RouteStationTransferPoint(route, station); } + } + else if(stop != null) { + return new StopTransferPoint(stop); + } + else if(station != null) { + return new StationTransferPoint(station); + } + + throw new IllegalStateException("Should not get here!"); } - private Collection<TransferPoint> createTransferPointForTrip( - Collection<StopLocation> stops, - Trip trip, - BiFunction<Trip, Integer, TransferPoint> createPoint - ) { - Collection<TransferPoint> result = new ArrayList<>(); + private int stopPosition(Trip trip, Stop stop, Station station, boolean boardTrip) { List<StopTime> stopTimes = stopTimesByTrip.get(trip); - for (int i = 0; i < stopTimes.size(); ++i) { + + // We can board at the first stop, but not alight. + final int firstStopPos = boardTrip ? 0 : 1; + // We can alight at the last stop, but not board, the lastStopPos is exclusive + final int lastStopPos = stopTimes.size() - (boardTrip ? 1 : 0); + + Predicate<StopLocation> stopMatches = station != null + ? (s) -> (s instanceof Stop && ((Stop)s).getParentStation() == station) + : (s) -> s == stop; + + for (int i = firstStopPos; i < lastStopPos; i++) { StopTime stopTime = stopTimes.get(i); + if(boardTrip && !stopTime.getPickupType().isRoutable()) { continue; } + if(!boardTrip && !stopTime.getDropOffType().isRoutable()) { continue; } - //noinspection SuspiciousMethodCalls - if (stops.contains(stopTime.getStop())) { - result.add(createPoint.apply(trip, i)); + if(stopMatches.test(stopTime.getStop())) { + return i; } } - return result; - } - - private Collection<StopLocation> getStopOrChildStops(org.onebusaway.gtfs.model.Stop gtfsStop) { - if (gtfsStop.getLocationType() == 0) { - return Collections.singletonList(stopMapper.map(gtfsStop)); - } - else { - return stationMapper.map(gtfsStop).getChildStops(); - } + return -1; } private boolean sameBlockId(Trip a, Trip b) { @@ -239,16 +230,4 @@ private boolean sameBlockId(Trip a, Trip b) { } return a.getBlockId() != null && a.getBlockId().equals(b.getBlockId()); } - - @Nullable - private Map<Route,List<Trip>> createTripsByRouteMapIfRouteTransfersExist( - Collection<Trip> trips, - Collection<org.onebusaway.gtfs.model.Transfer> allTransfers - ) { - if(allTransfers.stream().anyMatch(t -> t.getFromRoute() != null || t.getToRoute() != null)) { - return trips.stream().collect(Collectors.groupingBy(Trip::getRoute)); - } - // Return null, not an empty map to enforce NPE if used when no Route exist - return null; - } } diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/TripMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/TripMapper.java index 5709e758fb5..fe25987874e 100644 --- a/src/main/java/org/opentripplanner/gtfs/mapping/TripMapper.java +++ b/src/main/java/org/opentripplanner/gtfs/mapping/TripMapper.java @@ -3,7 +3,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; -import javax.annotation.Nullable; import org.opentripplanner.model.Direction; import org.opentripplanner.model.Trip; import org.opentripplanner.util.MapUtils; @@ -31,6 +30,11 @@ Trip map(org.onebusaway.gtfs.model.Trip orginal) { return orginal == null ? null : mappedTrips.computeIfAbsent(orginal, this::doMap); } + Collection<Trip> getMappedTrips() { + return mappedTrips.values(); + } + + private Trip doMap(org.onebusaway.gtfs.model.Trip rhs) { Trip lhs = new Trip(AgencyAndIdMapper.mapAgencyAndId(rhs.getId())); @@ -49,7 +53,6 @@ private Trip doMap(org.onebusaway.gtfs.model.Trip rhs) { return lhs; } - @Nullable private static int mapDirectionId(org.onebusaway.gtfs.model.Trip trip) { try { String directionId = trip.getDirectionId(); diff --git a/src/main/java/org/opentripplanner/model/PickDrop.java b/src/main/java/org/opentripplanner/model/PickDrop.java index a350a76b367..989be759743 100644 --- a/src/main/java/org/opentripplanner/model/PickDrop.java +++ b/src/main/java/org/opentripplanner/model/PickDrop.java @@ -16,6 +16,10 @@ public enum PickDrop { this.gtfsCode = gtfsCode; } + public boolean is(PickDrop value) { + return this == value; + } + public boolean isRoutable() { return routable; } diff --git a/src/main/java/org/opentripplanner/model/Route.java b/src/main/java/org/opentripplanner/model/Route.java index 799f891bc84..1766db99ddb 100644 --- a/src/main/java/org/opentripplanner/model/Route.java +++ b/src/main/java/org/opentripplanner/model/Route.java @@ -175,7 +175,7 @@ public String getName() { @Override public String toString() { - return "<Route " + getId() + " " + shortName + ">"; + return "<Route " + getId() + " " + getName() + ">"; } public String getNetexSubmode() { diff --git a/src/main/java/org/opentripplanner/model/Station.java b/src/main/java/org/opentripplanner/model/Station.java index 29dfa02c2ed..2b070c26832 100644 --- a/src/main/java/org/opentripplanner/model/Station.java +++ b/src/main/java/org/opentripplanner/model/Station.java @@ -63,14 +63,37 @@ public Station( this.url = url; this.timezone = timezone; this.priority = priority == null ? DEFAULT_PRIORITY : priority; - this.geometry = computeGeometry(coordinate, childStops); + // Initialize the geometry with an empty set of children + this.geometry = computeGeometry(coordinate, Set.of()); + } + + /** + * Create a minimal Station object for unit-test use, where the test only care about id, name and + * coordinate. The feedId is static set to "F" + */ + public static Station stationForTest(String idAndName, double lat, double lon) { + return new Station( + new FeedScopedId("F", idAndName), + idAndName, + new WgsCoordinate(lat, lon), + idAndName, + "Station " + idAndName, + null, + null, + StopTransferPriority.ALLOWED + ); } + public void addChildStop(Stop stop) { this.childStops.add(stop); this.geometry = computeGeometry(coordinate, childStops); } + public boolean includes(StopLocation stop) { + return childStops.contains(stop); + } + @Override public String toString() { return "<Station " + getId() + ">"; diff --git a/src/main/java/org/opentripplanner/model/Stop.java b/src/main/java/org/opentripplanner/model/Stop.java index 3dac1f7b23c..ccd53d0eba4 100644 --- a/src/main/java/org/opentripplanner/model/Stop.java +++ b/src/main/java/org/opentripplanner/model/Stop.java @@ -7,8 +7,6 @@ import java.util.TimeZone; import javax.validation.constraints.NotNull; import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.GeometryCollection; -import org.locationtech.jts.geom.Point; import org.opentripplanner.common.geometry.GeometryUtils; /** @@ -68,12 +66,17 @@ public Stop( this.netexSubmode = netexSubmode; } - /** - * Create a minimal Stop object for unit-test use, where the test only care about id, name and - * coordinate. The feedId is static set to "TEST" - */ + /** @see #stopForTest(String, double, double, Station) */ public static Stop stopForTest(String idAndName, double lat, double lon) { - return new Stop( + return stopForTest(idAndName, lat, lon, null); + } + + /** + * Create a minimal Stop object for unit-test use, where the test only care about id, name and + * coordinate. The feedId is static set to "F" + */ + public static Stop stopForTest(String idAndName, double lat, double lon, Station parent) { + var stop = new Stop( new FeedScopedId("F", idAndName), idAndName, idAndName, @@ -88,9 +91,10 @@ public static Stop stopForTest(String idAndName, double lat, double lon) { null, null ); + stop.setParentStation(parent); + return stop; } - public void addBoardingArea(BoardingArea boardingArea) { if (boardingAreas == null) { boardingAreas = new HashSet<>(); diff --git a/src/main/java/org/opentripplanner/model/StopPattern.java b/src/main/java/org/opentripplanner/model/StopPattern.java index 47d6e2fc6cc..f8adc5732f0 100644 --- a/src/main/java/org/opentripplanner/model/StopPattern.java +++ b/src/main/java/org/opentripplanner/model/StopPattern.java @@ -7,6 +7,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; /** * This class represents what is called a JourneyPattern in Transmodel: the sequence of stops at @@ -32,10 +34,11 @@ * A StopPattern is very closely related to a TripPattern -- it essentially serves as the unique * key for a TripPattern. Should the route be included in the StopPattern? */ -public class StopPattern implements Serializable { +public final class StopPattern implements Serializable { private static final long serialVersionUID = 20140101L; - + public static final int NOT_FOUND = -1; + private final StopLocation[] stops; private final PickDrop[] pickups; private final PickDrop[] dropoffs; @@ -63,30 +66,32 @@ public StopPattern (Collection<StopTime> stopTimes) { } } - /** - * Raptor should not be allowed to board or alight flex stops because they have fake - * coordinates (centroids) and might not have times. - */ - private static PickDrop computePickDrop(StopLocation stop, PickDrop pickDrop) { - if(stop instanceof FlexStopLocation) { - return PickDrop.NONE; - } - else { - return pickDrop; + int getSize() { + return stops.length; + } + + /** Find the given stop position in the sequence, return -1 if not found. */ + int findStopPosition(StopLocation stop) { + for (int i=0; i<stops.length; ++i) { + if(stops[i] == stop) { return i; } } + return -1; } - /** - * @param stopId in agency_id format - */ - public boolean containsStop (String stopId) { - if (stopId == null) { return false; } - for (StopLocation stop : stops) if (stopId.equals(stop.getId().toString())) { return true; } - return false; + int findBoardingPosition(StopLocation stop) { + return findStopPosition(0, stops.length-1, (s) -> s == stop, stop); } - public int getSize() { - return stops.length; + int findAlightPosition(StopLocation stop) { + return findStopPosition(1, stops.length, (s) -> s == stop, stop); + } + + int findBoardingPosition(Station station) { + return findStopPosition(0, stops.length-1, station::includes, station); + } + + int findAlightPosition(Station station) { + return findStopPosition(1, stops.length, station::includes, station); } public boolean equals(Object other) { @@ -126,7 +131,7 @@ public String toString() { * want a way to consistently identify trips across versions of a GTFS feed, when the feed * publisher cannot ensure stable trip IDs. Therefore we define some additional hash functions. */ - public HashCode semanticHash(HashFunction hashFunction) { + HashCode semanticHash(HashFunction hashFunction) { Hasher hasher = hashFunction.newHasher(); int size = stops.length; for (int s = 0; s < size; s++) { @@ -145,19 +150,63 @@ public HashCode semanticHash(HashFunction hashFunction) { return hasher.hash(); } - public StopLocation[] getStops() { - return stops; + /** Get a copy of the internal collection of stops. */ + List<StopLocation> getStops() { + return List.of(stops); + } + + StopLocation getStop(int stopPosInPattern) { + return stops[stopPosInPattern]; + } + + PickDrop getPickup(int stopPosInPattern) { + return pickups[stopPosInPattern]; + } + + PickDrop getDropoff(int stopPosInPattern) { + return dropoffs[stopPosInPattern]; } - public StopLocation getStop(int i) { - return stops[i]; + /** Returns whether passengers can alight at a given stop */ + boolean canAlight(int stopPosInPattern) { + return dropoffs[stopPosInPattern].isRoutable(); } - public PickDrop getPickup(int i) { - return pickups[i]; + /** Returns whether passengers can board at a given stop */ + boolean canBoard(int stopPosInPattern) { + return pickups[stopPosInPattern].isRoutable(); } - public PickDrop getDropoff(int i) { - return dropoffs[i]; + /** + * Returns whether passengers can board at a given stop. + * This is an inefficient method iterating over the stops, do not use it in routing. + */ + boolean canBoard(StopLocation stop) { + // We skip the last stop, not allowed for boarding + for (int i=0; i<stops.length-1; ++i) { + if(stop == stops[i] && canBoard(i)) { return true; } + } + return false; + } + + /** + * Raptor should not be allowed to board or alight flex stops because they have fake + * coordinates (centroids) and might not have times. + */ + private static PickDrop computePickDrop(StopLocation stop, PickDrop pickDrop) { + if(stop instanceof FlexStopLocation) { return PickDrop.NONE; } + else { return pickDrop; } + } + + private int findStopPosition( + final int start, + final int end, + final Predicate<StopLocation> match, + final Object entity + ) { + for (int i=start; i<end; ++i) { + if(match.test(stops[i])) { return i; } + } + throw new IllegalArgumentException("Stop/Station not found: " + entity); } } diff --git a/src/main/java/org/opentripplanner/model/Timetable.java b/src/main/java/org/opentripplanner/model/Timetable.java index 2986e35c587..5f730e74534 100644 --- a/src/main/java/org/opentripplanner/model/Timetable.java +++ b/src/main/java/org/opentripplanner/model/Timetable.java @@ -5,6 +5,11 @@ import com.google.transit.realtime.GtfsRealtime.TripUpdate; import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeEvent; import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate; +import java.io.Serializable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; import org.opentripplanner.model.calendar.ServiceDate; import org.opentripplanner.routing.core.ServiceDay; import org.opentripplanner.routing.trippattern.FrequencyEntry; @@ -12,12 +17,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.Serializable; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.TimeZone; - /** * Timetables provide most of the TripPattern functionality. Each TripPattern may possess more than @@ -93,7 +92,7 @@ public boolean temporallyViable(ServiceDay sd, long searchTime, int bestWait, bo * actions to compact the data structure such as trimming and deduplicating arrays. */ public void finish() { - int nStops = pattern.getStopPattern().getSize(); + int nStops = pattern.numberOfStops(); // Concatenate raw TripTimes and those referenced from FrequencyEntries List<TripTimes> allTripTimes = Lists.newArrayList(tripTimes); diff --git a/src/main/java/org/opentripplanner/model/Trip.java b/src/main/java/org/opentripplanner/model/Trip.java index 694241caaf2..5035e51f3c6 100644 --- a/src/main/java/org/opentripplanner/model/Trip.java +++ b/src/main/java/org/opentripplanner/model/Trip.java @@ -136,8 +136,8 @@ public void setTripShortName(String tripShortName) { } /** - * Return human friendly short info to identify the trip when mode, from/to stop and - * times are known. This method is meant for logging, and should not be exposed in any API. + * Return human friendly short info to identify the trip when mode, from/to stop and times are + * known. This method is meant for debug/logging, and should not be exposed in any API. */ public String logInfo() { if(hasValue(tripShortName)) { return tripShortName; } diff --git a/src/main/java/org/opentripplanner/model/TripPattern.java b/src/main/java/org/opentripplanner/model/TripPattern.java index ca6ef3348e4..710e35becbe 100644 --- a/src/main/java/org/opentripplanner/model/TripPattern.java +++ b/src/main/java/org/opentripplanner/model/TripPattern.java @@ -7,22 +7,10 @@ import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; import com.google.common.io.BaseEncoding; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.LineString; -import org.opentripplanner.common.geometry.CompactLineString; -import org.opentripplanner.common.geometry.GeometryUtils; -import org.opentripplanner.graph_builder.DataImportIssueStore; -import org.opentripplanner.graph_builder.issues.NonUniqueRouteName; -import org.opentripplanner.routing.trippattern.FrequencyEntry; -import org.opentripplanner.routing.trippattern.TripTimes; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; import java.util.BitSet; import java.util.Collection; import java.util.HashSet; @@ -31,6 +19,16 @@ import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.LineString; +import org.opentripplanner.common.geometry.CompactLineString; +import org.opentripplanner.common.geometry.GeometryUtils; +import org.opentripplanner.graph_builder.DataImportIssueStore; +import org.opentripplanner.graph_builder.issues.NonUniqueRouteName; +import org.opentripplanner.routing.trippattern.FrequencyEntry; +import org.opentripplanner.routing.trippattern.TripTimes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Represents a group of trips on a route, with the same direction id that all call at the same @@ -46,7 +44,7 @@ * generated in the format FeedId:Agency:RouteId:DirectionId:PatternNumber. For NeTEx the * JourneyPattern id is used. */ -public class TripPattern extends TransitEntity implements Cloneable, Serializable { +public final class TripPattern extends TransitEntity implements Cloneable, Serializable { private static final Logger LOG = LoggerFactory.getLogger(TripPattern.class); @@ -56,6 +54,12 @@ public class TripPattern extends TransitEntity implements Cloneable, Serializabl private final Route route; + /** + * The stop-pattern help us reuse the same stops in several trip-patterns; Hence + * saving memory. The field should not be accessible outside the class, and all access + * is done through method delegation, like the {@link #numberOfStops()} and + * {@link #canBoard(int)} methods. + */ private final StopPattern stopPattern; private final Timetable scheduledTimetable = new Timetable(this); @@ -85,14 +89,26 @@ public TripPattern(FeedScopedId id, Route route, StopPattern stopPattern) { this.stopPattern = stopPattern; } + /** The human-readable, unique name for this trip pattern. */ + public String getName() { + return name; + } + public void setName(String name) { this.name = name; } + /** + * The GTFS Route of all trips in this pattern. + */ + public Route getRoute() { + return route; + } + /** * Convenience method to get the route traverse mode, the mode for all trips in this pattern. */ - public final TransitMode getMode() { + public TransitMode getMode() { return route.getMode(); } @@ -100,17 +116,17 @@ public final String getNetexSubmode() { return route.getNetexSubmode(); } - public LineString getHopGeometry(int stopIndex) { + public LineString getHopGeometry(int stopPosInPattern) { if (hopGeometries != null) { return CompactLineString.uncompactLineString( - hopGeometries[stopIndex], + hopGeometries[stopPosInPattern], false ); } else { return GeometryUtils.getGeometryFactory().createLineString( new Coordinate[]{ - coordinate(stopPattern.getStops()[stopIndex]), - coordinate(stopPattern.getStops()[stopIndex + 1]) + coordinate(stopPattern.getStop(stopPosInPattern)), + coordinate(stopPattern.getStop(stopPosInPattern + 1)) } ); } @@ -136,13 +152,13 @@ public void setHopGeometry(int i, LineString hopGeometry) { * @param other TripPattern to copy geometry from */ public void setHopGeometriesFromPattern(TripPattern other) { - this.hopGeometries = new byte[this.getStops().size() - 1][]; + this.hopGeometries = new byte[numberOfStops() - 1][]; // This accounts for the new TripPattern provided by a real-time update and the one that is // being replaced having a different number of stops. In that case the geometry will be // preserved up until the first mismatching stop, and a straight line will be used for // all segments after that. - int sizeOfShortestPattern = Math.min(this.getStops().size(), other.getStops().size()); + int sizeOfShortestPattern = Math.min(numberOfStops(), other.numberOfStops()); for (int i = 0; i < sizeOfShortestPattern - 1; i++) { if (other.getHopGeometry(i) != null @@ -155,8 +171,8 @@ public void setHopGeometriesFromPattern(TripPattern other) { this.setHopGeometry(i, GeometryUtils.getGeometryFactory().createLineString( new Coordinate[]{ - coordinate(getStopPattern().getStops()[i]), - coordinate(getStopPattern().getStops()[i + 1]) + coordinate(stopPattern.getStop(i)), + coordinate(stopPattern.getStop(i + 1)) } ) ); @@ -178,43 +194,63 @@ public int numHopGeometries() { return hopGeometries.length; } - private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - // The serialized graph contains cyclic references TripPattern <--> Timetable. - // The Timetable must be indexed from here (rather than in its own readObject method) - // to ensure that the stops field it uses in TripPattern is already deserialized. - scheduledTimetable.finish(); + public int numberOfStops() { + return stopPattern.getSize(); } - public StopLocation getStop(int stopIndex) { - return stopPattern.getStops()[stopIndex]; + public StopLocation getStop(int stopPosInPattern) { + return stopPattern.getStop(stopPosInPattern); } + public StopLocation firstStop() { + return getStop(0); + } - public int getStopIndex(Stop stop) { - return Arrays.asList(stopPattern.getStops()).indexOf(stop); + public StopLocation lastStop() { + return getStop(stopPattern.getSize()-1); } + /** Read only list of stops */ public List<StopLocation> getStops() { - return Arrays.asList(stopPattern.getStops()); + return stopPattern.getStops(); } - public Trip getTrip(int tripIndex) { - return getTrips().get(tripIndex); + public int findStopPosition(StopLocation stop) { + return stopPattern.findStopPosition(stop); } - public int getTripIndex(Trip trip) { - return getTrips().indexOf(trip); + public int findBoardingStopPositionInPattern(Station station) { + return stopPattern.findBoardingPosition(station); + } + + public int findAlightStopPositionInPattern(Station station) { + return stopPattern.findAlightPosition(station); + } + + public int findBoardingStopPositionInPattern(StopLocation stop) { + return stopPattern.findBoardingPosition(stop); + } + + public int findAlightStopPositionInPattern(StopLocation stop) { + return stopPattern.findAlightPosition(stop); } /** Returns whether passengers can alight at a given stop */ public boolean canAlight(int stopIndex) { - return stopPattern.getDropoff(stopIndex).isRoutable(); + return stopPattern.canAlight(stopIndex); } /** Returns whether passengers can board at a given stop */ public boolean canBoard(int stopIndex) { - return stopPattern.getPickup(stopIndex).isRoutable(); + return stopPattern.canBoard(stopIndex); + } + + /** + * Returns whether passengers can board at a given stop. + * This is an inefficient method iterating over the stops, do not use it in routing. + */ + public boolean canBoard(StopLocation stop) { + return stopPattern.canBoard(stop); } /** Returns whether a given stop is wheelchair-accessible. */ @@ -230,6 +266,22 @@ public PickDrop getBoardType(int stopIndex) { return stopPattern.getPickup(stopIndex); } + public boolean isBoardAndAlightAt(int stopIndex, PickDrop value) { + return getBoardType(stopIndex).is(value) && getAlightType(stopIndex).is(value); + } + + public boolean stopPatternIsEqual(TripPattern other) { + return stopPattern.equals(other.stopPattern); + } + + public Trip getTrip(int tripIndex) { + return getTrips().get(tripIndex); + } + + public int getTripIndex(Trip trip) { + return getTrips().indexOf(trip); + } + /* METHODS THAT DELEGATE TO THE SCHEDULED TIMETABLE */ // TODO: These should probably be deprecated. That would require grabbing the scheduled timetable, @@ -240,9 +292,10 @@ public PickDrop getBoardType(int stopIndex) { * trip as one of the scheduled trips on this pattern. */ public void add(TripTimes tt) { - // Only scheduled trips (added at graph build time, rather than directly to the timetable via updates) are in this list. - getTrips().add(tt.getTrip()); + // Only scheduled trips (added at graph build time, rather than directly to the timetable + // via updates) are in this list. scheduledTimetable.addTripTimes(tt); + // Check that all trips added to this pattern are on the initially declared route. // Identity equality is valid on GTFS entity objects. if (this.route != tt.getTrip().getRoute()) { @@ -306,27 +359,7 @@ public Direction getDirection() { * to search for trips/TripIds in the Timetable rather than the enclosing TripPattern. */ public List<Trip> getTrips() { - return scheduledTimetable.getTripTimes().stream().map(t -> t.getTrip()).collect(Collectors.toList()); - } - - /** The human-readable, unique name for this trip pattern. */ - public String getName() { - return name; - } - - /** - * The GTFS Route of all trips in this pattern. - */ - public Route getRoute() { - return route; - } - - /** - * All trips in this pattern call at this sequence of stops. This includes information about GTFS - * pick-up and drop-off types. - */ - public StopPattern getStopPattern() { - return stopPattern; + return scheduledTimetable.getTripTimes().stream().map(TripTimes::getTrip).collect(Collectors.toList()); } /** @@ -433,19 +466,19 @@ public static void generateUniqueNames ( Multimap<StopLocation, TripPattern> vias = ArrayListMultimap.create(); for (TripPattern pattern : routeTripPatterns) { - List<StopLocation> stops = pattern.getStops(); - StopLocation start = stops.get(0); - StopLocation end = stops.get(stops.size() - 1); + StopLocation start = pattern.firstStop(); + StopLocation end = pattern.lastStop(); starts.put(start, pattern); ends.put(end, pattern); - for (StopLocation stop : stops) vias.put(stop, pattern); + for (StopLocation stop : pattern.getStops()) { + vias.put(stop, pattern); + } } PATTERN : for (TripPattern pattern : routeTripPatterns) { - List<StopLocation> stops = pattern.getStops(); StringBuilder sb = new StringBuilder(routeName); /* First try to name with destination. */ - var end = stops.get(stops.size() - 1); + var end = pattern.lastStop(); sb.append(" to " + stopNameAndId(end)); if (ends.get(end).size() == 1) { pattern.setName(sb.toString()); @@ -453,7 +486,7 @@ public static void generateUniqueNames ( } /* Then try to name with origin. */ - var start = stops.get(0); + var start = pattern.firstStop(); sb.append(" from " + stopNameAndId(start)); if (starts.get(start).size() == 1) { pattern.setName((sb.toString())); @@ -470,7 +503,7 @@ public static void generateUniqueNames ( } /* Still not unique; try (end, start, via) for each via. */ - for (var via : stops) { + for (var via : pattern.getStops()) { if (via.equals(start) || via.equals(end)) continue; Set<TripPattern> intersection = new HashSet<>(); intersection.addAll(remainingPatterns); @@ -612,6 +645,14 @@ public String getFeedId() { return route.getId().getFeedId(); } + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + // The serialized graph contains cyclic references TripPattern <--> Timetable. + // The Timetable must be indexed from here (rather than in its own readObject method) + // to ensure that the stops field it uses in TripPattern is already deserialized. + scheduledTimetable.finish(); + } + private static Coordinate coordinate(StopLocation s) { return new Coordinate(s.getLon(), s.getLat()); } diff --git a/src/main/java/org/opentripplanner/model/TripTimeOnDate.java b/src/main/java/org/opentripplanner/model/TripTimeOnDate.java index 742496e7f60..9242cc630a1 100644 --- a/src/main/java/org/opentripplanner/model/TripTimeOnDate.java +++ b/src/main/java/org/opentripplanner/model/TripTimeOnDate.java @@ -1,12 +1,11 @@ package org.opentripplanner.model; -import org.opentripplanner.routing.core.ServiceDay; -import org.opentripplanner.routing.trippattern.RealTimeState; -import org.opentripplanner.routing.trippattern.TripTimes; - import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import org.opentripplanner.routing.core.ServiceDay; +import org.opentripplanner.routing.trippattern.RealTimeState; +import org.opentripplanner.routing.trippattern.TripTimes; /** * Represents a Trip at a specific stop index and on a specific service day. This is a read-only @@ -58,7 +57,7 @@ public static Comparator<TripTimeOnDate> compareByDeparture() { } public FeedScopedId getStopId() { - return tripPattern.getStopPattern().getStop(stopIndex).getId(); + return tripPattern.getStop(stopIndex).getId(); } public int getStopIndex() { @@ -119,8 +118,7 @@ public boolean isRealtime() { public boolean isCancelledStop() { return tripTimes.isCancelledStop(stopIndex) || - tripPattern.getStopPattern().getPickup(stopIndex) == PickDrop.CANCELLED - && tripPattern.getStopPattern().getDropoff(stopIndex) == PickDrop.CANCELLED; + tripPattern.isBoardAndAlightAt(stopIndex, PickDrop.CANCELLED); } /** Return {code true} if stop is cancelled, or trip is canceled/replaced */ @@ -153,13 +151,13 @@ public String getHeadsign() { public PickDrop getPickupType() { return tripTimes.isCanceled() || tripTimes.isCancelledStop(stopIndex) ? PickDrop.CANCELLED - : tripPattern.getStopPattern().getPickup(stopIndex); + : tripPattern.getBoardType(stopIndex); } public PickDrop getDropoffType() { return tripTimes.isCanceled() || tripTimes.isCancelledStop(stopIndex) ? PickDrop.CANCELLED - : tripPattern.getStopPattern().getDropoff(stopIndex); + : tripPattern.getAlightType(stopIndex); } public StopTimeKey getStopTimeKey() { diff --git a/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java b/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java index c863c221a97..65abc21aed0 100644 --- a/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java +++ b/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java @@ -338,19 +338,24 @@ private void fixOrRemovePatternsWhichReferenceNoneExistingTrips() { /** Remove all transfers witch reference none existing trips */ private void removeTransfersForNoneExistingTrips() { int orgSize = transfers.size(); - transfers.removeIf(this::transferTripsDoesNotExist); + transfers.removeIf(this::transferTripReferencesDoNotExist); logRemove("Trip", orgSize, transfers.size(), "Transfer to/from trip does not exist."); } /** Return {@code true} if the from/to trip reference is none null, but do not exist. */ - private boolean transferTripsDoesNotExist(ConstrainedTransfer t) { - return transferTripPointDoesNotExist(t.getFrom()) - || transferTripPointDoesNotExist(t.getTo()); + private boolean transferTripReferencesDoNotExist(ConstrainedTransfer t) { + return transferPointTripReferenceDoesNotExist(t.getFrom()) + || transferPointTripReferenceDoesNotExist(t.getTo()); } - /** Return true if the trip is a valid reference; {@code null} or exist. */ - private boolean transferTripPointDoesNotExist(TransferPoint p) { - return p.getTrip() != null && !tripsById.containsKey(p.getTrip().getId()); + /** + * Return {@code true} if the the point is a trip-transfer-point and the trip reference + * is missing. + */ + private boolean transferPointTripReferenceDoesNotExist(TransferPoint point) { + if(!point.isTripTransferPoint()) { return false; } + var trip = point.asTripTransferPoint().getTrip(); + return !tripsById.containsKey(trip.getId()); } private static void logRemove(String type, int orgSize, int newSize, String reason) { diff --git a/src/main/java/org/opentripplanner/model/transfer/ConstrainedTransfer.java b/src/main/java/org/opentripplanner/model/transfer/ConstrainedTransfer.java index 5bcae61ef81..2fa329c8f0f 100644 --- a/src/main/java/org/opentripplanner/model/transfer/ConstrainedTransfer.java +++ b/src/main/java/org/opentripplanner/model/transfer/ConstrainedTransfer.java @@ -1,4 +1,3 @@ -/* This file is based on code copied from project OneBusAway, see the LICENSE file for further information. */ package org.opentripplanner.model.transfer; import java.io.Serializable; @@ -19,6 +18,8 @@ public final class ConstrainedTransfer implements RaptorConstrainedTransfer, Serializable { private static final long serialVersionUID = 1L; + private static final int FROM_RANKING_COEFFICIENT = 11; + private static final int TO_RANKING_COEFFICIENT = 10; private final FeedScopedId id; @@ -65,20 +66,46 @@ public TransferConstraint getTransferConstraint() { } public boolean noConstraints() { - return constraint.noConstraints(); - } - - public boolean matchesStopPos(int fromStopPos, int toStopPos) { - return from.getStopPosition() == fromStopPos && to.getStopPosition() == toStopPos; + return constraint.isRegularTransfer(); } /** * <a href="https://developers.google.com/transit/gtfs/reference/gtfs-extensions#specificity-of-a-transfer"> * Specificity of a transfer * </a> + * + * The ranking implemented here is slightly modified: + * <ul> + * <li> + * The specification do not say anything about Stations even if Stations can be used to + * specify a transfer-point. In OTP stops are more specific than station, so we use the + * following transfer-point ranking: + * <ol> + * <li>Station: 0 (zero)</li> + * <li>Stop: 1</li> + * <li>Route: 2</li> + * <li>Trip: 3</li> + * </ol> + * </li> + * <li> + * Two transfers may have the same ranking if we add together the from-point and + * to-point ranking. + * For example, {@code from trip(3) + to stop(1) == from route(2) + to route(2)} + * have the same ranking. To avoid this problem, we give the from-point a small + * advantage. We multiply the from point with 11 and the to point with 10, this + * break the ties in favor of the from point. In the example above the + * ConstrainedTransfer specificityRanking is: + * <pre> + * Case 1: from trip to stop := 11 * 3 + 10 * 1 = 43 + * Case 2: from route to route := 11 * 2 + 10 * 2 = 42 + * </pre> + * Case 1 has the highest ranking. + * </li> + * </ul> */ public int getSpecificityRanking() { - return from.getSpecificityRanking() + to.getSpecificityRanking(); + return from.getSpecificityRanking() * FROM_RANKING_COEFFICIENT + + to.getSpecificityRanking() * TO_RANKING_COEFFICIENT; } @Override diff --git a/src/main/java/org/opentripplanner/model/transfer/RouteStationTransferPoint.java b/src/main/java/org/opentripplanner/model/transfer/RouteStationTransferPoint.java new file mode 100644 index 00000000000..8eeead89c0a --- /dev/null +++ b/src/main/java/org/opentripplanner/model/transfer/RouteStationTransferPoint.java @@ -0,0 +1,49 @@ +package org.opentripplanner.model.transfer; + +import java.io.Serializable; +import org.opentripplanner.model.Route; +import org.opentripplanner.model.Station; +import org.opentripplanner.model.base.ValueObjectToStringBuilder; + +public final class RouteStationTransferPoint implements TransferPoint, Serializable { + + private static final long serialVersionUID = 1L; + + private final Route route; + private final Station station; + + public RouteStationTransferPoint(Route route, Station station) { + this.route = route; + this.station = station; + } + + public Route getRoute() { + return route; + } + + public Station getStation() { + return station; + } + + @Override + public boolean appliesToAllTrips() { + return true; + } + + @Override + public int getSpecificityRanking() { return 2; } + + @Override + public boolean isRouteStationTransferPoint() { return true; } + + @Override + public String toString() { + return ValueObjectToStringBuilder.of() + .addText("<Route ") + .addObj(route.getId()) + .addText(", station ") + .addObj(station.getId()) + .addText(">") + .toString(); + } +} diff --git a/src/main/java/org/opentripplanner/model/transfer/RouteStopTransferPoint.java b/src/main/java/org/opentripplanner/model/transfer/RouteStopTransferPoint.java new file mode 100644 index 00000000000..7ef454c78e4 --- /dev/null +++ b/src/main/java/org/opentripplanner/model/transfer/RouteStopTransferPoint.java @@ -0,0 +1,49 @@ +package org.opentripplanner.model.transfer; + +import java.io.Serializable; +import org.opentripplanner.model.Route; +import org.opentripplanner.model.StopLocation; +import org.opentripplanner.model.base.ValueObjectToStringBuilder; + +public final class RouteStopTransferPoint implements TransferPoint, Serializable { + + private static final long serialVersionUID = 1L; + + private final Route route; + private final StopLocation stop; + + public RouteStopTransferPoint(Route route, StopLocation stop) { + this.route = route; + this.stop = stop; + } + + public Route getRoute() { + return route; + } + + public StopLocation getStop() { + return stop; + } + + @Override + public boolean appliesToAllTrips() { + return true; + } + + @Override + public int getSpecificityRanking() { return 3; } + + @Override + public boolean isRouteStopTransferPoint() { return true; } + + @Override + public String toString() { + return ValueObjectToStringBuilder.of() + .addText("<Route ") + .addObj(route.getId()) + .addText(", stop ") + .addObj(stop.getId()) + .addText(">") + .toString(); + } +} diff --git a/src/main/java/org/opentripplanner/model/transfer/RouteTransferPoint.java b/src/main/java/org/opentripplanner/model/transfer/RouteTransferPoint.java deleted file mode 100644 index 7fb190d6417..00000000000 --- a/src/main/java/org/opentripplanner/model/transfer/RouteTransferPoint.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.opentripplanner.model.transfer; - -import java.io.Serializable; -import org.opentripplanner.model.Route; -import org.opentripplanner.model.Trip; - -/** - * This is a specialized version of the {@link TripTransferPoint}, it represent a - * given trip of the GTFS Route transfer. It override the specificity-ranking. Except for that, - * it behave like its super type. So, when looking up tran - * <p> - * By expanding a route into trips, we can drop expanded-trips(lower specificity ranking) - * if a "real" trip-transfers-point exist. - */ -public class RouteTransferPoint extends TripTransferPoint implements Serializable { - - private static final long serialVersionUID = 1L; - - private final Route route; - - public RouteTransferPoint(Route route, Trip trip, int stopPosition) { - super(trip, stopPosition); - this.route = route; - } - - @Override - public int getSpecificityRanking() { return 1; } - - @Override - public String toString() { - return "(route: " + route.getId() - + ", trip: " + getTrip().getId() - + ", stopPos: " + getStopPosition() + ")"; - } -} diff --git a/src/main/java/org/opentripplanner/model/transfer/StationTransferPoint.java b/src/main/java/org/opentripplanner/model/transfer/StationTransferPoint.java new file mode 100644 index 00000000000..400ff3e8ad8 --- /dev/null +++ b/src/main/java/org/opentripplanner/model/transfer/StationTransferPoint.java @@ -0,0 +1,42 @@ +package org.opentripplanner.model.transfer; + +import java.io.Serializable; +import org.opentripplanner.model.Station; +import org.opentripplanner.model.base.ValueObjectToStringBuilder; + +public final class StationTransferPoint implements TransferPoint, Serializable { + + private static final long serialVersionUID = 1L; + + private final Station station; + + + public StationTransferPoint(Station station) { + this.station = station; + } + + public Station getStation() { + return station; + } + + @Override + public boolean appliesToAllTrips() { + return true; + } + + @Override + public int getSpecificityRanking() { + return 0; + } + + @Override + public boolean isStationTransferPoint() { return true; } + + public String toString() { + return ValueObjectToStringBuilder.of() + .addText("<Station ") + .addObj(station.getId()) + .addText(">") + .toString(); + } +} diff --git a/src/main/java/org/opentripplanner/model/transfer/StopTransferPoint.java b/src/main/java/org/opentripplanner/model/transfer/StopTransferPoint.java index 8c21d77db58..b9b2c0c7a14 100644 --- a/src/main/java/org/opentripplanner/model/transfer/StopTransferPoint.java +++ b/src/main/java/org/opentripplanner/model/transfer/StopTransferPoint.java @@ -1,8 +1,6 @@ package org.opentripplanner.model.transfer; import java.io.Serializable; -import java.util.Objects; -import org.opentripplanner.model.Stop; import org.opentripplanner.model.StopLocation; public class StopTransferPoint implements TransferPoint, Serializable { @@ -11,35 +9,29 @@ public class StopTransferPoint implements TransferPoint, Serializable { private final StopLocation stop; + public StopTransferPoint(StopLocation stop) { this.stop = stop; } - @Override public StopLocation getStop() { return stop; } @Override - public int getSpecificityRanking() { - return 0; + public boolean appliesToAllTrips() { + return true; } @Override - public String toString() { - return "(stop: " + stop.getId() + ")"; + public int getSpecificityRanking() { + return 1; } @Override - public boolean equals(Object o) { - if (this == o) { return true; } - if (!(o instanceof StopTransferPoint)) { return false; } - final StopTransferPoint that = (StopTransferPoint) o; - return Objects.equals(stop.getId(), that.stop.getId()); - } + public boolean isStopTransferPoint() { return true; } - @Override - public int hashCode() { - return Objects.hash(stop.getId()); + public String toString() { + return "<Stop " + stop.getId() + ">"; } } diff --git a/src/main/java/org/opentripplanner/model/transfer/TransferConstraint.java b/src/main/java/org/opentripplanner/model/transfer/TransferConstraint.java index c9ec89e7c25..dcaec3966a1 100644 --- a/src/main/java/org/opentripplanner/model/transfer/TransferConstraint.java +++ b/src/main/java/org/opentripplanner/model/transfer/TransferConstraint.java @@ -20,6 +20,12 @@ public class TransferConstraint implements Serializable, RaptorTransferConstrain private static final long serialVersionUID = 1L; + + /** + * A regular transfer is a transfer with no constraints. + */ + public static final TransferConstraint REGULAR_TRANSFER = create().build(); + /** * STAY_SEATED is not a priority, but we assign a cost to it to be able to compare it with other * transfers with a priority and the {@link #GUARANTIED_TRANSFER_COST}. @@ -91,6 +97,9 @@ public TransferPriority getPriority() { return priority; } + /** + * Also known as interlining of GTFS trips with the same block id. + */ public boolean isStaySeated() { return staySeated; } @@ -104,15 +113,40 @@ public boolean isGuaranteed() { * if the alight-slack or board-slack is too tight. We ignore slack for facilitated transfers. * <p> * This is an aggregated field, which encapsulates an OTP specific rule. A facilitated transfer - * is either stay-seated or guaranteed. High priority transfers are not. + * is either stay-seated or guaranteed. High priority transfers are not facilitated. */ public boolean isFacilitated() { return staySeated || guaranteed; } + + /** + * This switch enables transfers in Raptor, ignoring transfer constraints with for example + * only priority set. + */ + public boolean useInRaptorRouting() { + return isStaySeated() || isGuaranteed() || isNotAllowed(); + } + + @Override + public boolean isNotAllowed() { + return priority == NOT_ALLOWED; + } + + @Override + public boolean isRegularTransfer() { + // Note! The 'maxWaitTime' is only valid with the guaranteed flag set, so we + // do not need to check it here + return !(staySeated || guaranteed || priority.isConstrained()); + } + /** * Maximum time after scheduled departure time the connecting transport is guarantied to wait * for the delayed trip. + * <p> + * THIS IS NOT CONSIDERED IN RAPTOR. OTP relies on real-time data for this, so if the "from" + * vehicle is delayed, then the real time system is also responsible for propagating the delay + * onto the "to" trip. */ public int getMaxWaitTime() { return maxWaitTime; @@ -135,7 +169,7 @@ public boolean equals(Object o) { } public String toString() { - if(noConstraints()) { return "{no constraints}"; } + if(isRegularTransfer()) { return "{no constraints}"; } return ToStringBuilder.of() .addEnum("priority", priority, ALLOWED) @@ -145,12 +179,6 @@ public String toString() { .toString(); } - public boolean noConstraints() { - // Note! The 'maxWaitTime' is only valid with the guaranteed flag set, so we - // do not need to check it here - return !(staySeated || guaranteed || priority.isConstrained()); - } - /** * Calculate a cost for prioritizing transfers in a path, to select the best path with respect to * transfers. This cost is not related in any way to the path generalized-cost. It takes only the @@ -183,14 +211,13 @@ private int facilitatedCost() { return NONE_FACILITATED_COST; } - - - public static Builder create() { return new Builder(); } - private boolean isMaxWaitTimeSet() { return maxWaitTime != MAX_WAIT_TIME_NOT_SET; } + + public static Builder create() { return new Builder(); } + public static class Builder { private TransferPriority priority = ALLOWED; private boolean staySeated = false; diff --git a/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java b/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java index f476572a77b..dd22f1bc407 100644 --- a/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java +++ b/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java @@ -1,13 +1,13 @@ package org.opentripplanner.model.transfer; -import org.opentripplanner.model.StopLocation; +import javax.annotation.Nullable; +import org.opentripplanner.model.Route; import org.opentripplanner.model.Trip; - /** * This interface is used to represent a point or location where a transfer start from or end. * - * <p>There are 3 different Transfer points: + * <p>There are 4 different Transfer points: * <ol> * <li> * {@link StopTransferPoint} This apply to all trip stopping at the given stop. @@ -15,63 +15,105 @@ * <p>This is the least specific type, and is overridden if a more specific type exist. * </li> * <li> - * A {@link RouteTransferPoint} is a from/to point for a Route at the given stop. This only - * exist in GTFS, not in the Nordic NeTex profile. To support this we expand the route into - * all trips defined for it, and create {@link RouteTransferPoint} for each trip. We do the - * expansion because a Route may have more than on TripPattern and we want to use the stop - * position in pattern, not the stop for matching actual transfers. The reason is that - * real-time updates could invalidate a (route+stop)-transfer-point, since the stop could - * change to another platform(very common for railway stations). To account for this the - * RT-update would have to patch the (route&stop)-transfer-point. We simplify the RT-updates - * by converting expanding (route+stop) to (trip+stop position). + * {@link StationTransferPoint} This applies to all trips stopping at a stop part of the given + * station. + * <p>The specificity-ranking is above {@link StationTransferPoint}s and less than + * {@link RouteStationTransferPoint}. + * </li> + * <li> + * A {@link RouteStationTransferPoint} is a from/to point for a Route at the given stop. This + * only exists in GTFS, not in the Nordic NeTex profile. * * <p>The specificity-ranking is above {@link StopTransferPoint}s and less than + * {@link RouteStopTransferPoint}. + * </li> + * <li> + * A {@link RouteStopTransferPoint} is a from/to point for a Route at the given station. This + * only exists in GTFS, not in the Nordic NeTex profile. + * + * <p>The specificity-ranking is above {@link RouteStationTransferPoint}s and less than * {@link TripTransferPoint}. * </li> * <li> * {@link TripTransferPoint} A transfer from/to a Trip at the given stop position(not stop). - * GTFS Transfers specify a transfer from/to a trip and stop. But in OTP we map the stop to a - * stop position in pattern instead. This make sure that the transfer is still valid after a - * real-time update where the stop is changed. Especially for train stations changing the - * train platform is common and by using the stop position in pattern not the stop this - * become more robust. So, the OTP implementation follow the NeTEx Interchange definition - * here, not the GTFS specification. - * - * <p>This is the most specific point type, and will override both {@link RouteTransferPoint} - * and {@link StopTransferPoint} if more than one match exist. + * The GTFS Transfers may specify a transfer from/to a trip and stop/station. But in OTP we + * map the stop to a stop position in pattern. The OTP model {@link TripTransferPoint} does NOT + * reference the stop/station, but the {@code stopPositionInPattern} instead. There is two + * reasons for this. In NeTEx the an interchange is from a trip and stop-point, so this model + * fits better with NeTEx. The second reason is that real-time updates could invalidate the + * trip-transfer-point, since the stop could change to another platform(common for railway + * stations). To account for this the RT-update would need to patch the trip-transfer-point. + * We simplify the RT-updates by converting the stop to a stop-position-in-pattern. + * <p> + * This is the most specific point type. * </li> * </ol> + * <p> */ public interface TransferPoint { - int NOT_AVAILABLE = -1; + /** Return {@code true} if this transfer point apply to all trips in pattern */ + boolean appliesToAllTrips(); - default StopLocation getStop() { - return null; + /** + * <a href="https://developers.google.com/transit/gtfs/reference/gtfs-extensions#specificity-of-a-transfer"> + * Specificity of a transfer + * </a> + */ + int getSpecificityRanking(); + + /** is a Trip specific transfer point */ + default boolean isTripTransferPoint() { return false; } + + default TripTransferPoint asTripTransferPoint() { return (TripTransferPoint) this; } + + /** is a Route specific transfer point */ + default boolean isRouteStationTransferPoint() { return false; } + + default RouteStationTransferPoint asRouteStationTransferPoint() { + return (RouteStationTransferPoint) this; } - default Trip getTrip() { - return null; + /** is a Route specific transfer point */ + default boolean isRouteStopTransferPoint() { return false; } + + default RouteStopTransferPoint asRouteStopTransferPoint() { + return (RouteStopTransferPoint) this; } + /** is a Stop specific transfer point (no Trip or Route) */ + default boolean isStopTransferPoint() { return false; } + + default StopTransferPoint asStopTransferPoint() { return (StopTransferPoint) this; } + + /** is a Station specific transfer point (no Trip or Route) */ + default boolean isStationTransferPoint() { return false; } + + default StationTransferPoint asStationTransferPoint() { return (StationTransferPoint) this; } + + /** - * If the given transfer point is a {@link TripTransferPoint}, this method return the stop - * position in the trip pattern. If this transfer point is just a stop or a stop+route this - * method return {@link #NOT_AVAILABLE}. + * Utility method witch can be used in APIs to get the trip, if it exists, from a transfer point. */ - default int getStopPosition() { - return NOT_AVAILABLE; + @Nullable + static Trip getTrip(TransferPoint point) { + return point.isTripTransferPoint() ? point.asTripTransferPoint().getTrip() : null; } /** - * <a href="https://developers.google.com/transit/gtfs/reference/gtfs-extensions#specificity-of-a-transfer"> - * Specificity of a transfer - * </a> + * Utility method witch can be used in APIs to get the route, if it exists, from a transfer point. */ - int getSpecificityRanking(); - - default boolean matches(Trip trip, int stopPos) { - // Note! We use "==" here since there should not be duplicate instances of trips - return getStopPosition() == stopPos && getTrip() == trip; + @Nullable + static Route getRoute(TransferPoint point) { + if(point.isTripTransferPoint()) { + return point.asTripTransferPoint().getTrip().getRoute(); + } + if(point.isRouteStopTransferPoint()) { + return point.asRouteStopTransferPoint().getRoute(); + } + if(point.isRouteStationTransferPoint()) { + return point.asRouteStationTransferPoint().getRoute(); + } + return null; } } diff --git a/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java b/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java new file mode 100644 index 00000000000..87bb8bf1bea --- /dev/null +++ b/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java @@ -0,0 +1,103 @@ +package org.opentripplanner.model.transfer; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.opentripplanner.common.model.T2; +import org.opentripplanner.model.Route; +import org.opentripplanner.model.Station; +import org.opentripplanner.model.StopLocation; +import org.opentripplanner.model.Trip; + +/** + * A map from any TransferPoint to an instances of type E. This is used to look up + * entities by trip and stop. The {@link TransferPoint} class only plays a role when the map is + * created. + */ +class TransferPointMap<E> { + private final Map<T2<Trip, Integer>, E> tripMap = new HashMap<>(); + private final Map<T2<Route, StopLocation>, E> routeStopMap = new HashMap<>(); + private final Map<T2<Route, Station>, E> routeStationMap = new HashMap<>(); + private final Map<StopLocation, E> stopMap = new HashMap<>(); + private final Map<Station, E> stationMap = new HashMap<>(); + + void put(TransferPoint point, E e) { + if(point.isTripTransferPoint()) { + var tp = point.asTripTransferPoint(); + tripMap.put(tripKey(tp.getTrip(), tp.getStopPositionInPattern()), e); + } + else if(point.isRouteStopTransferPoint()) { + var rp = point.asRouteStopTransferPoint(); + routeStopMap.put(routeStopKey(rp.getRoute(), rp.getStop()), e); + } + else if(point.isRouteStationTransferPoint()) { + var rp = point.asRouteStationTransferPoint(); + routeStationMap.put(routeStationKey(rp.getRoute(), rp.getStation()), e); + } + else if(point.isStopTransferPoint()) { + stopMap.put(point.asStopTransferPoint().getStop(), e); + } + else if(point.isStationTransferPoint()) { + stationMap.put(point.asStationTransferPoint().getStation(), e); + } + else { + throw new IllegalArgumentException("Unknown TransferPoint type: " + point); + } + } + + E computeIfAbsent(TransferPoint point, Supplier<E> creator) { + if(point.isTripTransferPoint()) { + var tp = point.asTripTransferPoint(); + return tripMap.computeIfAbsent(tripKey(tp.getTrip(), tp.getStopPositionInPattern()), k -> creator.get()); + } + else if(point.isRouteStopTransferPoint()) { + var rp = point.asRouteStopTransferPoint(); + return routeStopMap.computeIfAbsent(routeStopKey(rp.getRoute(), rp.getStop()), k -> creator.get()); + } + else if(point.isRouteStationTransferPoint()) { + var rp = point.asRouteStationTransferPoint(); + return routeStationMap.computeIfAbsent(routeStationKey(rp.getRoute(), rp.getStation()), k -> creator.get()); + } + else if(point.isStopTransferPoint()) { + var sp = point.asStopTransferPoint(); + return stopMap.computeIfAbsent(sp.getStop(), k -> creator.get()); + } + else if(point.isStationTransferPoint()) { + var sp = point.asStationTransferPoint(); + return stationMap.computeIfAbsent(sp.getStation(), k -> creator.get()); + } + throw new IllegalArgumentException("Unknown TransferPoint type: " + point); + } + + /** + * List all elements witch matches any of the transfer points added to the map. + */ + List<E> get(Trip trip, StopLocation stop, int stopPointInPattern) { + var list = Stream.of( + tripMap.get(tripKey(trip, stopPointInPattern)), + routeStopMap.get(routeStopKey(trip.getRoute(), stop)), + routeStationMap.get(routeStationKey(trip.getRoute(), stop.getParentStation())), + stopMap.get(stop), + stationMap.get(stop.getParentStation()) + ) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + return list; + } + + private static T2<Trip, Integer> tripKey(Trip trip, int stopPositionInPattern) { + return new T2<>(trip, stopPositionInPattern); + } + + private static T2<Route, StopLocation> routeStopKey(Route route, StopLocation stop) { + return new T2<>(route, stop); + } + + private static T2<Route, Station> routeStationKey(Route route, Station station) { + return new T2<>(route, station); + } +} diff --git a/src/main/java/org/opentripplanner/model/transfer/TransferService.java b/src/main/java/org/opentripplanner/model/transfer/TransferService.java index 8e6d574c36a..8f9674e48b5 100644 --- a/src/main/java/org/opentripplanner/model/transfer/TransferService.java +++ b/src/main/java/org/opentripplanner/model/transfer/TransferService.java @@ -1,21 +1,16 @@ package org.opentripplanner.model.transfer; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; +import static java.util.Comparator.comparingInt; + import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; -import java.util.Map; +import java.util.Set; import javax.annotation.Nullable; -import org.opentripplanner.common.model.P2; -import org.opentripplanner.common.model.T2; -import org.opentripplanner.model.Stop; import org.opentripplanner.model.StopLocation; import org.opentripplanner.model.Trip; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * This class represents all transfer information in the graph. Transfers are grouped by @@ -26,166 +21,57 @@ */ public class TransferService implements Serializable { - private static final Logger LOG = LoggerFactory.getLogger(TransferService.class); - - /** Index of guaranteed transfers by the to/destination point. */ - private final Multimap<TripTransferPoint, ConstrainedTransfer> constrainedTransferByToPoint; - - /** - * Table which contains transfers between two trips/routes - */ - private final Map<P2<TripTransferPoint>, ConstrainedTransfer> trip2tripTransfers; - - /** - * Table which contains transfers between a trip/route and a stops - */ - private final Map<T2<TripTransferPoint, StopLocation>, ConstrainedTransfer> trip2StopTransfers; - - /** - * Table which contains transfers between a stop and a trip/route - */ - private final Map<T2<StopLocation, TripTransferPoint>, ConstrainedTransfer> stop2TripTransfers; + private final List<ConstrainedTransfer> transfersList; /** - * Table which contains transfers between two stops + * A map of map may seem a bit odd, but the first map have the FROM-transfer-point + * as its key, while the second map have the TO-transfer-point as its key. This allows us to + * support all combination of (Trip, Route, Stop and Station) in total 16 possible combination + * of keys to the ConstrainedTransfer. */ - private final Map<P2<StopLocation>, ConstrainedTransfer> stop2StopTransfers; + private final TransferPointMap<TransferPointMap<ConstrainedTransfer>> transfersMap; public TransferService() { - this.constrainedTransferByToPoint = ArrayListMultimap.create(); - this.trip2tripTransfers = new HashMap<>(); - this.trip2StopTransfers = new HashMap<>(); - this.stop2TripTransfers = new HashMap<>(); - this.stop2StopTransfers = new HashMap<>(); + this.transfersList = new ArrayList<>(); + this.transfersMap = new TransferPointMap<>(); } public void addAll(Collection<ConstrainedTransfer> transfers) { + Set<ConstrainedTransfer> set = new HashSet<>(transfersList); + for (ConstrainedTransfer transfer : transfers) { - add(transfer); + if(!set.contains(transfer)) { + add(transfer); + set.add(transfer); + } } } public List<ConstrainedTransfer> listAll() { - var list = new ArrayList<ConstrainedTransfer>(); - list.addAll(trip2tripTransfers.values()); - list.addAll(trip2StopTransfers.values()); - list.addAll(stop2TripTransfers.values()); - list.addAll(stop2StopTransfers.values()); - return list; - } - - public Collection<ConstrainedTransfer> listConstrainedTransfersTo(Trip toTrip, int toStopIndex) { - return constrainedTransferByToPoint.get(new TripTransferPoint(toTrip, toStopIndex)); + return transfersList; } @Nullable public ConstrainedTransfer findTransfer( - StopLocation fromStop, - StopLocation toStop, Trip fromTrip, - Trip toTrip, int fromStopPosition, - int toStopPosition - ) { - var fromTripKey = new TripTransferPoint(fromTrip, fromStopPosition); - var toTripKey = new TripTransferPoint(toTrip, toStopPosition); - ConstrainedTransfer result; - - // Check the highest specificity ranked transfers first (trip-2-trip) - result = trip2tripTransfers.get(new P2<>(fromTripKey, toTripKey)); - if (result != null) { return result; } - - // Then check the next specificity ranked transfers (trip-2-stop and stop-2-trip) - result = trip2StopTransfers.get(new T2<>(fromTripKey, toStop)); - if (result != null) { return result; } - - // Then check the next specificity ranked transfers (trip-2-stop and stop-2-trip) - result = stop2TripTransfers.get(new T2<>(fromStop, toTripKey)); - if (result != null) { return result; } - - // If no specificity ranked transfers found return stop-2-stop transfers (lowest ranking) - return stop2StopTransfers.get(new P2<>(fromStop, toStop)); - } - - void add(ConstrainedTransfer transfer) { - TransferPoint from = transfer.getFrom(); - TransferPoint to = transfer.getTo(); - - addFacilitatedTransfer(transfer); - - if (from instanceof TripTransferPoint) { - var fromTrip = (TripTransferPoint) from; - if (to instanceof TripTransferPoint) { - var key = new P2<>(fromTrip, (TripTransferPoint) to); - if (doAddTransferBasedOnSpecificityRanking(transfer, trip2tripTransfers.get(key))) { - trip2tripTransfers.put(key, transfer); - } - } - else { - var key = new T2<>(fromTrip, to.getStop()); - if (doAddTransferBasedOnSpecificityRanking(transfer, trip2StopTransfers.get(key))) { - trip2StopTransfers.put(key, transfer); - } - } - } - else if (to instanceof TripTransferPoint) { - var key = new T2<>(from.getStop(), (TripTransferPoint) to); - if (doAddTransferBasedOnSpecificityRanking(transfer, stop2TripTransfers.get(key))) { - stop2TripTransfers.put(key, transfer); - } - } - else { - var key = new P2<>(from.getStop(), to.getStop()); - if (doAddTransferBasedOnSpecificityRanking(transfer, stop2StopTransfers.get(key))) { - stop2StopTransfers.put(key, transfer); - } - } - } - - /** - * A transfer goes from/to a stop, route* or trip. Route transfers are expanded to all trips - * using the special {@link RouteTransferPoint} subtype of {@link TripTransferPoint}. This - * expansion make sure that there can only be one match for each combination of from and to - * combination (from -> to): - * <ol> - * <li> trip -> trip - * <li> trip -> stop - * <li> stop -> trip - * <li> stop -> stop - * </ol> - * For each pair of the above combination we can drop the transfers that have a the lowest - * specificity-ranking, thus using maps instead of multi-maps. - */ - private boolean doAddTransferBasedOnSpecificityRanking( - ConstrainedTransfer newTransfer, - ConstrainedTransfer existingTransfer + StopLocation fromStop, + Trip toTrip, + int toStopPosition, + StopLocation toStop ) { - if (existingTransfer == null) { return true; } - - if (existingTransfer.getSpecificityRanking() < newTransfer.getSpecificityRanking()) { - return true; - } - if (existingTransfer.getSpecificityRanking() > newTransfer.getSpecificityRanking()) { - return false; - } - if (existingTransfer.equals(newTransfer)) { - return false; - } - LOG.warn( - "Two colliding transfers A and B with the same specificity-ranking is imported, B is " - + "dropped. A={}, B={}", existingTransfer, newTransfer - ); - return false; + return transfersMap.get(fromTrip, fromStop, fromStopPosition).stream() + .map(map2 -> map2.get(toTrip, toStop, toStopPosition)) + .flatMap(Collection::stream) + .max(comparingInt(ConstrainedTransfer::getSpecificityRanking)) + .orElse(null); } - private void addFacilitatedTransfer(ConstrainedTransfer transfer) { - var c = transfer.getTransferConstraint(); - var toPoint = transfer.getTo(); + private void add(ConstrainedTransfer transfer) { + var from = transfer.getFrom(); + var to = transfer.getTo(); - if(c.isFacilitated()) { - if(toPoint instanceof TripTransferPoint) { - constrainedTransferByToPoint.put((TripTransferPoint) toPoint, transfer); - } - } + transfersMap.computeIfAbsent(from, TransferPointMap::new).put(to, transfer); + transfersList.add(transfer); } } diff --git a/src/main/java/org/opentripplanner/model/transfer/TransferType.java b/src/main/java/org/opentripplanner/model/transfer/TransferType.java deleted file mode 100644 index af62c5ac0e3..00000000000 --- a/src/main/java/org/opentripplanner/model/transfer/TransferType.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.opentripplanner.model.transfer; - -public enum TransferType { - /** - * This transfer is recommended over other transfers. The routing algorithm should prefer - * this transfer compared to other transfers, for example by assigning a lower weight to it. - */ - RECOMMENDED(0), - /** - * This means the departing vehicle will wait for the arriving one and leave sufficient time - * for a rider to transfer between routes. - */ - GUARANTEED(1), - /** - * This is a regular transfer that is defined in the transit data (as opposed to - * OpenStreetMap data). In the case that both are present, this should take precedence. - * Because the the duration of the transfer is given and not the distance, walk speed will - * have no effect on this. - */ - MIN_TIME(2), - /** - * Transfers between these stops (and route/trip) is not possible (or not allowed), even if - * a transfer is already defined via OpenStreetMap data or in transit data. - */ - FORBIDDEN(3); - - TransferType(int gtfsCode) { - this.gtfsCode = gtfsCode; - } - - public final int gtfsCode; - - public static TransferType valueOfGtfsCode(int gtfsCode) { - for (TransferType value : values()) { - if (value.gtfsCode == gtfsCode) { - return value; - } - } - throw new IllegalArgumentException("Unknown GTFS TransferType: " + gtfsCode); - } -} diff --git a/src/main/java/org/opentripplanner/model/transfer/TripTransferPoint.java b/src/main/java/org/opentripplanner/model/transfer/TripTransferPoint.java index 6c427aaa682..dc27085ddcd 100644 --- a/src/main/java/org/opentripplanner/model/transfer/TripTransferPoint.java +++ b/src/main/java/org/opentripplanner/model/transfer/TripTransferPoint.java @@ -1,62 +1,49 @@ package org.opentripplanner.model.transfer; import java.io.Serializable; -import java.util.Objects; import org.opentripplanner.model.Trip; +import org.opentripplanner.model.base.ValueObjectToStringBuilder; -public class TripTransferPoint implements TransferPoint, Serializable { +public final class TripTransferPoint implements TransferPoint, Serializable { private static final long serialVersionUID = 1L; private final Trip trip; - private final int stopPosition; + private final int stopPositionInPattern; - public TripTransferPoint(Trip trip, int stopPosition) { + public TripTransferPoint(Trip trip, int stopPositionInPattern) { this.trip = trip; - this.stopPosition = stopPosition; + this.stopPositionInPattern = stopPositionInPattern; } - @Override - public final Trip getTrip() { + public Trip getTrip() { return trip; } - @Override - public final int getStopPosition() { - return stopPosition; + public int getStopPositionInPattern() { + return stopPositionInPattern; } - /** - * <a href="https://developers.google.com/transit/gtfs/reference/gtfs-extensions#specificity-of-a-transfer"> - * GTFS Specificity of a transfer - * </a> - * {@link #equals(Object)} - */ - @Override - public int getSpecificityRanking() { return 2; } - @Override - public String toString() { - return "(trip: " + trip.getId() + ", stopPos: " + stopPosition + ")"; + public boolean appliesToAllTrips() { + return false; } - /** - * This equals is intentionally final and enforce equality based on the *trip* and - * *stop-position*. Any sub-type is equal if the trip and stop-position match, the type is not - * used. This allow us to create sub-types and override the {@link #getSpecificityRanking()}. - */ @Override - public final boolean equals(Object o) { - if (this == o) { return true; } - if (!(o instanceof TripTransferPoint)) { return false; } + public int getSpecificityRanking() { return 4; } - TripTransferPoint that = (TripTransferPoint) o; - return stopPosition == that.stopPosition && trip.getId().equals(that.trip.getId()); - } + @Override + public boolean isTripTransferPoint() { return true; } @Override - public final int hashCode() { - return Objects.hash(trip.getId(), stopPosition); + public String toString() { + return ValueObjectToStringBuilder.of() + .addText("<Trip ") + .addObj(trip.getId()) + .addText(", stopPos ") + .addNum(stopPositionInPattern) + .addText(">") + .toString(); } } diff --git a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java index 5f9ac5e1763..be0e3fd9072 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java @@ -22,7 +22,6 @@ import org.opentripplanner.model.StopTime; import org.opentripplanner.model.TransitEntity; import org.opentripplanner.model.Trip; -import org.opentripplanner.model.TripPattern; import org.opentripplanner.model.impl.OtpTransitServiceBuilder; import org.opentripplanner.netex.index.api.NetexEntityIndexReadOnlyView; import org.opentripplanner.netex.mapping.calendar.CalendarServiceBuilder; @@ -389,8 +388,8 @@ private void mapTripPatterns(Map<String, FeedScopedId> serviceIds) { transitBuilder.getStopTimesSortedByTrip().put(it.getKey(), it.getValue()); transitBuilder.getTripsById().add(it.getKey()); } - for (TripPattern it : result.tripPatterns) { - transitBuilder.getTripPatterns().put(it.getStopPattern(), it); + for (var it : result.tripPatterns.entries()) { + transitBuilder.getTripPatterns().put(it.getKey(), it.getValue()); } stopTimesByNetexId.putAll(result.stopTimeByNetexId); groupMapper.scheduledStopPointsIndex.putAll(result.scheduledStopPointsIndex); diff --git a/src/main/java/org/opentripplanner/netex/mapping/StationMapper.java b/src/main/java/org/opentripplanner/netex/mapping/StationMapper.java index 1eeaf424238..90400c80932 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/StationMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/StationMapper.java @@ -26,7 +26,7 @@ Station map(StopPlace stopPlace) { stopPlace.getDescription() != null ? stopPlace.getDescription().getValue() : null, null, null, - TransferPriorityMapper.mapToDomain(stopPlace.getWeighting()) + StopTransferPriorityMapper.mapToDomain(stopPlace.getWeighting()) ); if (station.getCoordinate() == null) { diff --git a/src/main/java/org/opentripplanner/netex/mapping/TransferPriorityMapper.java b/src/main/java/org/opentripplanner/netex/mapping/StopTransferPriorityMapper.java similarity index 95% rename from src/main/java/org/opentripplanner/netex/mapping/TransferPriorityMapper.java rename to src/main/java/org/opentripplanner/netex/mapping/StopTransferPriorityMapper.java index 4a4c22af136..53a0e22b50e 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/TransferPriorityMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/StopTransferPriorityMapper.java @@ -4,7 +4,7 @@ import org.opentripplanner.model.StopTransferPriority; import org.rutebanken.netex.model.InterchangeWeightingEnumeration; -class TransferPriorityMapper { +class StopTransferPriorityMapper { @Nullable static StopTransferPriority mapToDomain(InterchangeWeightingEnumeration value) { diff --git a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java index cd6a9ecb9cf..9f330405136 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapper.java @@ -188,7 +188,7 @@ TripPatternMapperResult mapTripPattern(JourneyPattern journeyPattern) { createTripTimes(trips, tripPattern); - result.tripPatterns.add(tripPattern); + result.tripPatterns.put(stopPattern, tripPattern); return result; } diff --git a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapperResult.java b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapperResult.java index bc18a6f1340..3106ee51ef0 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapperResult.java +++ b/src/main/java/org/opentripplanner/netex/mapping/TripPatternMapperResult.java @@ -1,10 +1,11 @@ package org.opentripplanner.netex.mapping; import com.google.common.collect.ArrayListMultimap; -import java.util.ArrayList; +import com.google.common.collect.Multimap; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.opentripplanner.model.StopPattern; import org.opentripplanner.model.StopTime; import org.opentripplanner.model.Trip; import org.opentripplanner.model.TripPattern; @@ -22,7 +23,7 @@ class TripPatternMapperResult { final Map<Trip, List<StopTime>> tripStopTimes = new HashMap<>(); - final List<TripPattern> tripPatterns = new ArrayList<>(); + final Multimap<StopPattern, TripPattern> tripPatterns = ArrayListMultimap.create(); /** * stopTimes by the timetabled-passing-time id diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java index 593359484be..fbf041bd0c3 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java @@ -350,7 +350,7 @@ private List<StopArrival> extractIntermediateStops(TransitPathLeg<TripSchedule> TripSchedule tripSchedule = pathLeg.trip(); for (int i = boardStopIndexInPattern + 1; i < alightStopIndexInPattern; i++) { - var stop = tripPattern.getStopPattern().getStops()[i]; + var stop = tripPattern.getStop(i); Place place = mapStopToPlace(stop, i, tripSchedule.getOriginalTripTimes()); StopArrival visit = new StopArrival( diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/FlexAccessEgressAdapter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/FlexAccessEgressAdapter.java index ce8c7f1b4af..a4296a54f5a 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/FlexAccessEgressAdapter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/FlexAccessEgressAdapter.java @@ -12,7 +12,7 @@ public FlexAccessEgressAdapter( FlexAccessEgress flexAccessEgress, boolean isEgress, StopIndexForRaptor stopIndex ) { super( - stopIndex.indexByStop.get(flexAccessEgress.stop), + stopIndex.indexOf(flexAccessEgress.stop), isEgress ? flexAccessEgress.lastState.reverse() : flexAccessEgress.lastState ); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/StopIndexForRaptor.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/StopIndexForRaptor.java index 39bbacaca6c..a9f72d398c1 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/StopIndexForRaptor.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/StopIndexForRaptor.java @@ -1,12 +1,12 @@ package org.opentripplanner.routing.algorithm.raptor.transit; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.opentripplanner.model.StopLocation; import org.opentripplanner.model.StopTransferPriority; +import org.opentripplanner.model.TripPattern; import org.opentripplanner.routing.algorithm.raptor.transit.cost.RaptorCostConverter; /** @@ -26,38 +26,62 @@ * The scope of instances of this class is limited to the mapping process, the final state is * stored in the {@link TransitLayer}. */ -public class StopIndexForRaptor { - public final List<StopLocation> stopsByIndex; - public final Map<StopLocation, Integer> indexByStop = new HashMap<>(); +public final class StopIndexForRaptor { + private final List<StopLocation> stopsByIndex; + private final Map<StopLocation, Integer> indexByStop = new HashMap<>(); public final int[] stopBoardAlightCosts; public StopIndexForRaptor(Collection<StopLocation> stops, TransitTuningParameters tuningParameters) { - this.stopsByIndex = new ArrayList<>(stops); + this.stopsByIndex = List.copyOf(stops); initializeIndexByStop(); this.stopBoardAlightCosts = createStopBoardAlightCosts(stopsByIndex, tuningParameters); } + public StopLocation stopByIndex(int index) { + return stopsByIndex.get(index); + } + + public int indexOf(StopLocation stop) { + return indexByStop.get(stop); + } + + public int size() { + return stopsByIndex.size(); + } + /** - * Create map between stop and index used by Raptor to stop objects in original graph + * Create a list of stop indexes for a given list of stops. */ - void initializeIndexByStop() { - for(int i = 0; i< stopsByIndex.size(); ++i) { - indexByStop.put(stopsByIndex.get(i), i); + public int[] listStopIndexesForStops(List<StopLocation> stops) { + int[] stopIndex = new int[stops.size()]; + + for (int i = 0; i < stops.size(); i++) { + stopIndex[i] = indexByStop.get(stops.get(i)); } + return stopIndex; } /** * Create a list of stop indexes for a given list of stops. */ - public int[] listStopIndexesForStops(StopLocation[] stops) { - int[] stopIndex = new int[stops.length]; + public int[] listStopIndexesForPattern(TripPattern pattern) { + int[] stopIndex = new int[pattern.numberOfStops()]; - for (int i = 0; i < stops.length; i++) { - stopIndex[i] = indexByStop.get(stops[i]); + for (int i = 0; i < pattern.numberOfStops(); i++) { + stopIndex[i] = indexByStop.get(pattern.getStop(i)); } return stopIndex; } + /** + * Create map between stop and index used by Raptor to stop objects in original graph + */ + private void initializeIndexByStop() { + for(int i = 0; i< stopsByIndex.size(); ++i) { + indexByStop.put(stopsByIndex.get(i), i); + } + } + /** * Create static board/alight cost for Raptor to include for each stop. */ diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/TransitLayer.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/TransitLayer.java index 59574f974db..22f2a46071c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/TransitLayer.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/TransitLayer.java @@ -76,12 +76,12 @@ public TransitLayer( } public int getIndexByStop(Stop stop) { - return stopIndex.indexByStop.get(stop); + return stopIndex.indexOf(stop); } @Nullable public StopLocation getStopByIndex(int stop) { - return stop != -1 ? this.stopIndex.stopsByIndex.get(stop) : null; + return stop == -1 ? null : this.stopIndex.stopByIndex(stop); } public StopIndexForRaptor getStopIndex() { @@ -104,7 +104,7 @@ public ZoneId getTransitDataZoneId() { } public int getStopCount() { - return stopIndex.stopsByIndex.size(); + return stopIndex.size(); } @Nullable diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/TripPatternWithRaptorStopIndexes.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/TripPatternWithRaptorStopIndexes.java index c7607b8c670..4ac400cfa96 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/TripPatternWithRaptorStopIndexes.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/TripPatternWithRaptorStopIndexes.java @@ -1,47 +1,44 @@ package org.opentripplanner.routing.algorithm.raptor.transit; -import gnu.trove.map.TIntObjectMap; -import gnu.trove.map.hash.TIntObjectHashMap; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; import org.opentripplanner.model.FeedScopedId; import org.opentripplanner.model.TransitMode; import org.opentripplanner.model.TripPattern; -import org.opentripplanner.model.transfer.ConstrainedTransfer; -import org.opentripplanner.routing.algorithm.raptor.transit.request.ConstrainedBoardingSearch; +import org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer.ConstrainedBoardingSearch; +import org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer.TransferForPattern; +import org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer.TransferForPatternByStopPos; import org.opentripplanner.transit.raptor.api.transit.RaptorConstrainedTripScheduleBoardingSearch; import org.opentripplanner.transit.raptor.api.transit.RaptorTripPattern; public class TripPatternWithRaptorStopIndexes { - private final TripPattern pattern; + private final TripPattern pattern; private final int[] stopIndexes; /** - * List of transfers TO this pattern for each stop position in pattern used by Raptor during - * the FORWARD search. + * List of transfers TO this pattern for each stop position in pattern used by Raptor during the + * FORWARD search. */ - private final TIntObjectMap<List<ConstrainedTransfer>> constrainedTransfersForwardSearch = - new TIntObjectHashMap<>(); + private final TransferForPatternByStopPos + constrainedTransfersForwardSearch = new TransferForPatternByStopPos(); /** * List of transfers FROM this pattern for each stop position in pattern used by Raptor during * the REVERSE search. */ - private final TIntObjectMap<List<ConstrainedTransfer>> constrainedTransfersReverseSearch = - new TIntObjectHashMap<>(); + private final TransferForPatternByStopPos + constrainedTransfersReverseSearch = new TransferForPatternByStopPos(); public TripPatternWithRaptorStopIndexes( - int[] stopIndexes, - TripPattern pattern + TripPattern pattern, + int[] stopIndexes ) { - this.stopIndexes = stopIndexes; this.pattern = pattern; + this.stopIndexes = stopIndexes; } - public FeedScopedId getId() { return pattern.getId(); } + public FeedScopedId getId() {return pattern.getId();} public TransitMode getTransitMode() { return pattern.getMode(); @@ -74,20 +71,19 @@ public RaptorConstrainedTripScheduleBoardingSearch<TripSchedule> constrainedTran return new ConstrainedBoardingSearch(false, constrainedTransfersReverseSearch); } + @Override + public int hashCode() { + return Objects.hash(getId()); + } @Override public boolean equals(Object o) { - if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { return false; } + if (this == o) {return true;} + if (o == null || getClass() != o.getClass()) {return false;} TripPatternWithRaptorStopIndexes that = (TripPatternWithRaptorStopIndexes) o; return getId() == that.getId(); } - @Override - public int hashCode() { - return Objects.hash(getId()); - } - @Override public String toString() { return "TripPattern{" + @@ -96,26 +92,32 @@ public String toString() { '}'; } - /** These are public to allow the mappers to inject transfers */ - public void addTransferConstraintsForwardSearch(ConstrainedTransfer tx) { - // In the Raptor search the transfer is looked up using the target - // trip, the trip boarded after the transfer is done for a forward search. - add(constrainedTransfersForwardSearch, tx, tx.getTo().getStopPosition()); + /** + * This is public to allow the mappers to inject transfers + */ + public void addTransferConstraintsForwardSearch( + int targetStopPosition, + TransferForPattern transferForPattern + ) { + constrainedTransfersForwardSearch.add(targetStopPosition, transferForPattern); } - /** These are public to allow the mappers to inject transfers */ - public void addTransferConstraintsReverseSearch(ConstrainedTransfer tx) { - // In the Raptor search the transfer is looked up using the target - // trip. Thus, the transfer "from trip" should be used in a reverse search. - add(constrainedTransfersReverseSearch, tx, tx.getFrom().getStopPosition()); + /** + * This is public to allow the mappers to inject transfers + */ + public void addTransferConstraintsReverseSearch( + int targetStopPosition, + TransferForPattern transferForPattern + ) { + constrainedTransfersReverseSearch.add(targetStopPosition, transferForPattern); } - private static <T> void add(TIntObjectMap<List<T>> index, T e, int pos) { - var list = index.get(pos); - if(list == null) { - list = new ArrayList<>(); - index.put(pos, list); - } - list.add(e); + /** + * This method should be called AFTER all transfers are added, and before the + * pattern is used in a Raptor search. + */ + public void sortConstrainedTransfers() { + constrainedTransfersForwardSearch.sortOnSpecificityRanking(); + constrainedTransfersReverseSearch.sortOnSpecificityRanking(); } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearch.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearch.java new file mode 100644 index 00000000000..810ea2099f4 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearch.java @@ -0,0 +1,154 @@ +package org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer; + +import java.util.List; +import java.util.stream.Collectors; +import org.opentripplanner.common.model.T2; +import org.opentripplanner.model.Trip; +import org.opentripplanner.model.transfer.TransferConstraint; +import org.opentripplanner.routing.algorithm.raptor.transit.TripSchedule; +import org.opentripplanner.transit.raptor.api.transit.RaptorConstrainedTripScheduleBoardingSearch; +import org.opentripplanner.transit.raptor.api.transit.RaptorTimeTable; +import org.opentripplanner.transit.raptor.api.transit.RaptorTransferConstraint; +import org.opentripplanner.transit.raptor.api.transit.RaptorTripScheduleBoardOrAlightEvent; + + +/** + * The responsibility of this class is to provide transfer constraints to the Raptor search + * for a given pattern. The instance is stateful and not thread-safe. The current stop + * position is checked for transfers, then the provider is asked to list all transfers + * between the current pattern and the source trip stop arrival. The source is the "from" + * point in a transfer for a forward search, and the "to" point in the reverse search. + */ +public final class ConstrainedBoardingSearch + implements RaptorConstrainedTripScheduleBoardingSearch<TripSchedule> { + + /** + * Abort the search after looking at 5 valid boardings. In the case where this happens, one of + * these trips are probably a better match. We abort to avoid stepping through all trips, + * possibly a large number (several days). + */ + private static final int ABORT_SEARCH_AFTER_N_VAILD_NORMAL_TRIPS = 5; + + private static final ConstrainedBoardingSearchStrategy FORWARD_STRATEGY = new ConstrainedBoardingSearchForward(); + private static final ConstrainedBoardingSearchStrategy REVERSE_STRATEGY = new ConstrainedBoardingSearchReverse(); + + /** Handle forward and reverse specific tasks */ + private final ConstrainedBoardingSearchStrategy translator; + + /** + * List of transfers for each stop position in pattern + */ + private final TransferForPatternByStopPos transfers; + + private List<TransferForPattern> currentTransfers; + private int currentTargetStopPos; + + public ConstrainedBoardingSearch( + boolean forwardSearch, + TransferForPatternByStopPos transfers + ) { + this.transfers = transfers; + this.translator = forwardSearch ? FORWARD_STRATEGY : REVERSE_STRATEGY; + } + + @Override + public boolean transferExist(int targetStopPos) { + if(transfers == null) { return false; } + + // Get all guaranteed transfers for the target pattern at the target stop position + this.currentTransfers = transfers.get(targetStopPos); + this.currentTargetStopPos = targetStopPos; + return currentTransfers != null; + } + + @Override + public RaptorTripScheduleBoardOrAlightEvent<TripSchedule> find( + RaptorTimeTable<TripSchedule> timetable, + TripSchedule sourceTripSchedule, + int sourceStopIndex, + int sourceArrivalTime + ) { + var transfers = findMatchingTransfers(sourceTripSchedule, sourceStopIndex); + + if(transfers.isEmpty()) { return null; } + + T2<Integer, RaptorTransferConstraint> tripInfo = findTimetableTripInfo( + timetable, + transfers, + currentTargetStopPos, + sourceArrivalTime + ); + + if(tripInfo == null) { return null; } + + final int tripIndex = tripInfo.first; + final var transferConstraint = tripInfo.second; + + var trip = timetable.getTripSchedule(tripIndex); + int departureTime = translator.time(trip, currentTargetStopPos); + + return new ConstrainedTransferBoarding<>( + transferConstraint, tripIndex, trip, currentTargetStopPos, departureTime + ); + } + + private List<TransferForPattern> findMatchingTransfers( + TripSchedule tripSchedule, + int stopIndex + ) { + final Trip trip = tripSchedule.getOriginalTripTimes().getTrip(); + return currentTransfers.stream() + .filter(t -> t.matchesSourcePoint(stopIndex, trip)) + .collect(Collectors.toList()); + } + + /** + * Find the trip to board (trip index) and the transfer constraint + */ + public T2<Integer, RaptorTransferConstraint> findTimetableTripInfo( + RaptorTimeTable<TripSchedule> timetable, + List<TransferForPattern> transfers, + int stopPos, + int sourceTime + ) { + int nAllowedBoardings = 0; + boolean useNextNormalTrip = false; + + var index = translator.scheduleIndexIterator(timetable); + outer: + while (index.hasNext()) { + final int i = index.next(); + var it = timetable.getTripSchedule(i); + + // Forward: boardTime, Reverse: alightTime + int time = translator.time(it, stopPos); + + if (translator.timeIsBefore(time, sourceTime)) { continue; } + ++nAllowedBoardings; + + var targetTrip = it.getOriginalTripTimes().getTrip(); + + for (TransferForPattern tx : transfers) { + if (tx.applyToAllTargetTrips()) { + return new T2<>(i, tx.getTransferConstraint()); + } + else if (tx.applyToTargetTrip(targetTrip)) { + if (tx.getTransferConstraint().isNotAllowed()) { + useNextNormalTrip = true; + continue outer; + } + else { + return new T2<>(i, tx.getTransferConstraint()); + } + } + } + if (useNextNormalTrip) { + return new T2<>(i, TransferConstraint.REGULAR_TRANSFER); + } + if(nAllowedBoardings == ABORT_SEARCH_AFTER_N_VAILD_NORMAL_TRIPS) { + return null; + } + } + return null; + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchForward.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchForward.java new file mode 100644 index 00000000000..bd25193ae03 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchForward.java @@ -0,0 +1,26 @@ +package org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer; + +import org.opentripplanner.routing.algorithm.raptor.transit.TripSchedule; +import org.opentripplanner.transit.raptor.api.transit.IntIterator; +import org.opentripplanner.transit.raptor.api.transit.RaptorTimeTable; +import org.opentripplanner.transit.raptor.api.transit.RaptorTripSchedule; +import org.opentripplanner.transit.raptor.util.IntIterators; + +class ConstrainedBoardingSearchForward + implements ConstrainedBoardingSearchStrategy { + + @Override + public int time(RaptorTripSchedule schedule, int stopPos) { + return schedule.departure(stopPos); + } + + @Override + public boolean timeIsBefore(int time0, int time1) { + return time0 < time1; + } + + @Override + public IntIterator scheduleIndexIterator(RaptorTimeTable<TripSchedule> timetable) { + return IntIterators.intIncIterator(0, timetable.numberOfTripSchedules()); + } +} \ No newline at end of file diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchReverse.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchReverse.java new file mode 100644 index 00000000000..887b6707e4e --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchReverse.java @@ -0,0 +1,25 @@ +package org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer; + +import org.opentripplanner.routing.algorithm.raptor.transit.TripSchedule; +import org.opentripplanner.transit.raptor.api.transit.IntIterator; +import org.opentripplanner.transit.raptor.api.transit.RaptorTimeTable; +import org.opentripplanner.transit.raptor.api.transit.RaptorTripSchedule; +import org.opentripplanner.transit.raptor.util.IntIterators; + +class ConstrainedBoardingSearchReverse implements ConstrainedBoardingSearchStrategy { + + @Override + public int time(RaptorTripSchedule schedule, int stopPos) { + return schedule.arrival(stopPos); + } + + @Override + public boolean timeIsBefore(int time0, int time1) { + return time0 > time1; + } + + @Override + public IntIterator scheduleIndexIterator(RaptorTimeTable<TripSchedule> timetable) { + return IntIterators.intDecIterator(timetable.numberOfTripSchedules(), 0); + } +} \ No newline at end of file diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchStrategy.java new file mode 100644 index 00000000000..b31dbf46a0b --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchStrategy.java @@ -0,0 +1,38 @@ +package org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer; + +import org.opentripplanner.routing.algorithm.raptor.transit.TripSchedule; +import org.opentripplanner.transit.raptor.api.transit.IntIterator; +import org.opentripplanner.transit.raptor.api.transit.RaptorTimeTable; +import org.opentripplanner.transit.raptor.api.transit.RaptorTripSchedule; + + +/** + * Used to search forward and in reverse. + */ +interface ConstrainedBoardingSearchStrategy { + + /** + * <ol> + * <li>In a forward search return the DEPARTURE time. + * <li>In a reverse search return the ARRIVAL time. + * </ol> + */ + int time(RaptorTripSchedule schedule, int stopPos); + + /** + * <ol> + * <li>In a forward search the time is before another time if it is in the PAST. + * <li>In a reverse search the time is before another time if it is in the FUTURE. + * </ol> + */ + boolean timeIsBefore(int time0, int time1); + + /** + * <ol> + * <li>In a forward search iterate in departure order. + * <li>In a reverse search iterate in reverse departure order, + * starting with the last trip in the schedule. + * </ol> + */ + IntIterator scheduleIndexIterator(RaptorTimeTable<TripSchedule> timetable); +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedTransferBoarding.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedTransferBoarding.java similarity index 66% rename from src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedTransferBoarding.java rename to src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedTransferBoarding.java index c73a19313e3..6022b37cf65 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedTransferBoarding.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedTransferBoarding.java @@ -1,23 +1,26 @@ -package org.opentripplanner.routing.algorithm.raptor.transit.request; +package org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer; -import org.opentripplanner.model.transfer.TransferConstraint; +import javax.validation.constraints.NotNull; +import org.opentripplanner.transit.raptor.api.transit.RaptorTransferConstraint; import org.opentripplanner.transit.raptor.api.transit.RaptorTripSchedule; import org.opentripplanner.transit.raptor.api.transit.RaptorTripScheduleBoardOrAlightEvent; - +/** + * A boarding event passed to Raptor to perform a boarding. + */ public class ConstrainedTransferBoarding<T extends RaptorTripSchedule> implements RaptorTripScheduleBoardOrAlightEvent<T> { - private final TransferConstraint constraint; + private final RaptorTransferConstraint constraint; private final int tripIndex; private final T trip; private final int stopPositionInPattern; private final int time; ConstrainedTransferBoarding( - TransferConstraint constraint, + @NotNull RaptorTransferConstraint constraint, int tripIndex, - T trip, + @NotNull T trip, int stopPositionInPattern, int time ) { @@ -32,6 +35,7 @@ public class ConstrainedTransferBoarding<T extends RaptorTripSchedule> public int getTripIndex() { return tripIndex; } @Override + @NotNull public T getTrip() { return trip; } @Override @@ -41,7 +45,6 @@ public class ConstrainedTransferBoarding<T extends RaptorTripSchedule> public int getTime() { return time; } @Override - public TransferConstraint getTransferConstraint() { - return constraint; - } + @NotNull + public RaptorTransferConstraint getTransferConstraint() { return constraint; } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferForPattern.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferForPattern.java new file mode 100644 index 00000000000..f86d44303e1 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferForPattern.java @@ -0,0 +1,71 @@ +package org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer; + +import javax.annotation.Nullable; +import org.opentripplanner.model.Trip; +import org.opentripplanner.transit.raptor.api.transit.RaptorTransferConstraint; + + +/** + * Encapsulate the information needed to identify a transfer during a Raptor search for a given + * pattern. + */ +public class TransferForPattern implements Comparable<TransferForPattern> { + + /** + * Used to filter transfers based on the source-stop-arrival. + */ + private final TransferPointMatcher sourcePoint; + + /** + * If {@code null} the constraint apply to all trips + */ + @Nullable + private final Trip targetTrip; + private final RaptorTransferConstraint transferConstraint; + private final int specificityRanking; + + TransferForPattern( + TransferPointMatcher sourcePoint, + @Nullable Trip targetTrip, + int specificityRanking, + RaptorTransferConstraint transferConstraint + ) { + this.sourcePoint = sourcePoint; + this.targetTrip = targetTrip; + this.specificityRanking = specificityRanking; + this.transferConstraint = transferConstraint; + } + + public RaptorTransferConstraint getTransferConstraint() { + return transferConstraint; + } + + public boolean matchesSourcePoint(int stopIndex, Trip trip) { + return sourcePoint.match(stopIndex, trip); + } + + /** + * A transfer either apply to all target-trips (station-, stop- and route-transfer-points) or + * to a specific trip (trip-transfer-point). + */ + public boolean applyToAllTargetTrips() { + return targetTrip == null; + } + + /** + * return {@code true} if this transfer apply to the specified trip, and only that trip. + * @see #applyToAllTargetTrips() + */ + public boolean applyToTargetTrip(Trip targetTrip) { + return this.targetTrip == targetTrip; + } + + /** + * Transfers should be sorted after the specificityRanking, this make sure the + * transfer with the highest ranking is used by raptor. + */ + @Override + public int compareTo(TransferForPattern o) { + return o.specificityRanking - specificityRanking; + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferForPatternByStopPos.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferForPatternByStopPos.java new file mode 100644 index 00000000000..99fb866c156 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferForPatternByStopPos.java @@ -0,0 +1,37 @@ +package org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer; + + +import gnu.trove.map.TIntObjectMap; +import gnu.trove.map.hash.TIntObjectHashMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Index to a list of transfers by the stop position in pattern + */ +public class TransferForPatternByStopPos { + + private final TIntObjectMap<List<TransferForPattern>> transfers = new TIntObjectHashMap<>(); + + + /** + * Sort in decreasing specificityRanking order + */ + public void sortOnSpecificityRanking() { + transfers.forEachValue(it -> { Collections.sort(it); return true; }); + } + + public void add(int targetStopPos, TransferForPattern transfer) { + var c = transfers.get(targetStopPos); + if(c == null) { + c = new ArrayList<>(); + transfers.put(targetStopPos, c); + } + c.add(transfer); + } + + public List<TransferForPattern> get(int targetStopPos) { + return transfers.get(targetStopPos); + } +} \ No newline at end of file diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferIndexGenerator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferIndexGenerator.java new file mode 100644 index 00000000000..2da68731dfa --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferIndexGenerator.java @@ -0,0 +1,268 @@ +package org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer; + +import static org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer.TransferPointForPatternFactory.createTransferPointForPattern; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.ToIntFunction; +import java.util.stream.Collectors; +import org.opentripplanner.model.Route; +import org.opentripplanner.model.Station; +import org.opentripplanner.model.StopLocation; +import org.opentripplanner.model.Trip; +import org.opentripplanner.model.TripPattern; +import org.opentripplanner.model.transfer.ConstrainedTransfer; +import org.opentripplanner.model.transfer.RouteStationTransferPoint; +import org.opentripplanner.model.transfer.RouteStopTransferPoint; +import org.opentripplanner.model.transfer.StationTransferPoint; +import org.opentripplanner.model.transfer.StopTransferPoint; +import org.opentripplanner.model.transfer.TransferPoint; +import org.opentripplanner.model.transfer.TripTransferPoint; +import org.opentripplanner.routing.algorithm.raptor.transit.StopIndexForRaptor; +import org.opentripplanner.routing.algorithm.raptor.transit.TripPatternWithRaptorStopIndexes; + +public class TransferIndexGenerator { + private static final boolean BOARD = true; + private static final boolean ALIGHT = false; + + private final Collection<ConstrainedTransfer> constrainedTransfers; + private final Map<Station, List<TripPatternWithRaptorStopIndexes>> patternsByStation = new HashMap<>(); + private final Map<StopLocation, List<TripPatternWithRaptorStopIndexes>> patternsByStop = new HashMap<>(); + private final Map<Route, List<TripPatternWithRaptorStopIndexes>> patternsByRoute = new HashMap<>(); + private final Map<Trip, List<TripPatternWithRaptorStopIndexes>> patternsByTrip = new HashMap<>(); + private final StopIndexForRaptor stopIndex; + + public TransferIndexGenerator( + Collection<ConstrainedTransfer> constrainedTransfers, + Collection<TripPatternWithRaptorStopIndexes> tripPatterns, + StopIndexForRaptor stopIndex + ) { + this.constrainedTransfers = constrainedTransfers; + this.stopIndex = stopIndex; + setupPatternByTripIndex(tripPatterns); + } + + public void generateTransfers() { + for (ConstrainedTransfer tx : constrainedTransfers) { + var c = tx.getTransferConstraint(); + // Only add transfers witch have an effect on the Raptor routing here. + // Some transfers only have the priority set, and that is used in optimized- + // transfers, but not in Raptor. + if (!c.useInRaptorRouting()) { continue; } + + findTPoints(tx.getFrom(), ALIGHT).stream() + .filter(TPoint::canAlight) + .forEachOrdered(fromPoint -> { + for (var toPoint : findTPoints(tx.getTo(), BOARD)) { + if (toPoint.canBoard() && !fromPoint.equals(toPoint)) { + fromPoint.addTransferConstraints(tx, toPoint); + } + } + }); + } + sortAllTransfersByRanking(); + } + + private void sortAllTransfersByRanking() { + for (var patterns : patternsByRoute.values()) { + for (var pattern : patterns) { + pattern.sortConstrainedTransfers(); + } + } + } + + private void setupPatternByTripIndex(Collection<TripPatternWithRaptorStopIndexes> tripPatterns) { + for (TripPatternWithRaptorStopIndexes pattern : tripPatterns) { + TripPattern tripPattern = pattern.getPattern(); + + patternsByRoute + .computeIfAbsent(tripPattern.getRoute(), t -> new ArrayList<>()) + .add(pattern); + + for (Trip trip : tripPattern.getTrips()) { + patternsByTrip.computeIfAbsent(trip, t -> new ArrayList<>()).add(pattern); + } + + for (StopLocation stop : tripPattern.getStops()) { + patternsByStop.computeIfAbsent(stop, t -> new ArrayList<>()).add(pattern); + Station station = stop.getParentStation(); + if (station != null) { + patternsByStation.computeIfAbsent(station, t -> new ArrayList<>()).add(pattern); + } + } + } + } + + private Collection<TPoint> findTPoints(TransferPoint txPoint, boolean boarding) { + if (txPoint.isStationTransferPoint()) { + return findTPoints(txPoint.asStationTransferPoint()); + } + else if (txPoint.isStopTransferPoint()) { + return findTPoints(txPoint.asStopTransferPoint()); + } + else if (txPoint.isRouteStationTransferPoint()) { + return findTPoint(txPoint.asRouteStationTransferPoint(), boarding); + } + else if (txPoint.isRouteStopTransferPoint()) { + return findTPoint(txPoint.asRouteStopTransferPoint(), boarding); + } + else { + return findTPoints(txPoint.asTripTransferPoint()); + } + } + + private List<TPoint> findTPoints(StationTransferPoint point) { + var station = point.getStation(); + var patterns = patternsByStation.get(station); + + if(patterns == null) { return List.of(); } + + var sourcePoint = createTransferPointForPattern(station, stopIndex); + var result = new ArrayList<TPoint>(); + + for (TripPatternWithRaptorStopIndexes pattern : patterns) { + var tripPattern = pattern.getPattern(); + for (int pos = 0; pos < tripPattern.numberOfStops(); ++pos) { + if (point.getStation() == tripPattern.getStop(pos).getParentStation()) { + result.add(new TPoint(pattern, sourcePoint, null, pos)); + } + } + } + return result; + } + + private List<TPoint> findTPoints(StopTransferPoint point) { + var stop = point.asStopTransferPoint().getStop(); + var patterns = patternsByStop.get(stop); + + if(patterns == null) { return List.of(); } + + var sourcePoint = createTransferPointForPattern(stopIndex.indexOf(stop)); + var result = new ArrayList<TPoint>(); + + for (TripPatternWithRaptorStopIndexes pattern : patterns) { + var p = pattern.getPattern(); + for (int pos = 0; pos < p.numberOfStops(); ++pos) { + if (point.getStop() == p.getStop(pos)) { + result.add(new TPoint(pattern, sourcePoint, null, pos)); + } + } + } + return result; + } + + private List<TPoint> findTPoint(RouteStationTransferPoint point, boolean boarding) { + return findTPointForRoute( + point.getRoute(), + boarding ? p -> p.findBoardingStopPositionInPattern(point.getStation()) + : p -> p.findAlightStopPositionInPattern(point.getStation()) + ); + } + + private List<TPoint> findTPoint(RouteStopTransferPoint point, boolean boarding) { + return findTPointForRoute( + point.getRoute(), + boarding ? p -> p.findBoardingStopPositionInPattern(point.getStop()) + : p -> p.findAlightStopPositionInPattern(point.getStop()) + ); + } + + private List<TPoint> findTPointForRoute( + Route route, + ToIntFunction<TripPattern> resolveStopPosInPattern + ) { + var patterns = patternsByRoute.get(route); + + // A route should have a pattern(trip), but it does not hurt to check here + if(patterns == null) { return List.of(); } + + var points = new ArrayList<TPoint>(); + + for (var pattern : patterns) { + int stopPosInPattern = resolveStopPosInPattern.applyAsInt(pattern.getPattern()); + int stopIndex = pattern.stopIndex(stopPosInPattern); + var sourcePoint = createTransferPointForPattern(route, stopIndex); + points.add(new TPoint(pattern, sourcePoint, null, stopPosInPattern)); + } + return points; + } + + private List<TPoint> findTPoints(TripTransferPoint point) { + var trip = point.getTrip(); + // All trips have at least one pattern, no need to chech for null here + var patterns = patternsByTrip.get(trip); + int stopPosInPattern = point.getStopPositionInPattern(); + int stopIndex = patterns.get(0).stopIndex(stopPosInPattern); + var sourcePoint = createTransferPointForPattern(trip, stopIndex); + return patterns.stream() + .map(p -> new TPoint(p, sourcePoint, trip, stopPosInPattern)) + .collect(Collectors.toList()); + } + + private static class TPoint { + TripPatternWithRaptorStopIndexes pattern; + TransferPointMatcher sourcePoint; + Trip trip; + int stopPosition; + + private TPoint( + TripPatternWithRaptorStopIndexes pattern, + TransferPointMatcher sourcePoint, + Trip trip, + int stopPosition + ) { + this.pattern = pattern; + this.sourcePoint = sourcePoint; + this.trip = trip; + this.stopPosition = stopPosition; + } + + boolean canBoard() { + // We prevent boarding at the last stop, this might be enforced by the + // canBoard method, but we do not trust it here. + int lastStopPosition = pattern.getPattern().numberOfStops() - 1; + return stopPosition != lastStopPosition && pattern.getPattern().canBoard(stopPosition); + } + + boolean canAlight() { + // We prevent alighting at the first stop, this might be enforced by the + // canAlight method, but we do not trust it here. + return stopPosition != 0 && pattern.getPattern().canAlight(stopPosition); + } + + void addTransferConstraints(ConstrainedTransfer tx, TPoint to) { + int rank = tx.getSpecificityRanking(); + var c = tx.getTransferConstraint(); + + // Forward search + to.pattern.addTransferConstraintsForwardSearch( + to.stopPosition, + new TransferForPattern(sourcePoint, to.trip, rank, c) + ); + // Reverse search + pattern.addTransferConstraintsReverseSearch( + stopPosition, + new TransferForPattern(to.sourcePoint, trip, rank, c) + ); + } + + @Override + public boolean equals(Object o) { + if (this == o) {return true;} + if (!(o instanceof TPoint)) {return false;} + final TPoint tPoint = (TPoint) o; + return stopPosition == tPoint.stopPosition + && Objects.equals(pattern, tPoint.pattern) + && Objects.equals(trip, tPoint.trip); + } + + @Override + public int hashCode() { + return Objects.hash(pattern, trip, stopPosition); + } + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferPointForPatternFactory.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferPointForPatternFactory.java new file mode 100644 index 00000000000..ebe5a75e68f --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferPointForPatternFactory.java @@ -0,0 +1,109 @@ +package org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer; + +import java.util.function.IntFunction; +import org.opentripplanner.model.Route; +import org.opentripplanner.model.Station; +import org.opentripplanner.model.StopLocation; +import org.opentripplanner.model.Trip; +import org.opentripplanner.routing.algorithm.raptor.transit.StopIndexForRaptor; + + +/** + * This class generate TransferPoints adapted to Raptor. The internal model + * {@link org.opentripplanner.model.transfer.TransferPoint} can not be used by Raptor as is, so + * we transform them into {@link TransferPointMatcher}. For example to speed ut the search in + * Raptor we avoid fetching Stops from memory and instead uses a {@code stopIndex}. This index is + * not necessarily fixed, but generated for the + * {@link org.opentripplanner.routing.algorithm.raptor.transit.TransitLayer}, so we need to + * generate + */ + + +final class TransferPointForPatternFactory { + + /** private constructor to prevent utility class from instantiation */ + private TransferPointForPatternFactory() { /* empty */ } + + static TransferPointMatcher createTransferPointForPattern( + Station station, + StopIndexForRaptor stopIndex + ) { + return new StationSP(stopIndex::stopByIndex, station); + } + + static TransferPointMatcher createTransferPointForPattern(int stopIndex) { + return new StopSP(stopIndex); + } + + static TransferPointMatcher createTransferPointForPattern(Route route, int sourceStopIndex) { + return new RouteSP(route, sourceStopIndex); + } + + static TransferPointMatcher createTransferPointForPattern(Trip trip, int sourceStopIndex) { + return new TripSP(trip, sourceStopIndex); + } + + private static class StationSP implements TransferPointMatcher { + + // This is potentially slow, can be replaced with a set of stopIndexes for the + // station to improve performance - not tested + private final IntFunction<StopLocation> toStop; + private final Station station; + + private StationSP(IntFunction<StopLocation> toStop, Station station) { + this.toStop = toStop; + this.station = station; + } + + @Override + public boolean match(int stopIndex, Trip trip) { + return station == toStop.apply(stopIndex).getParentStation(); + } + } + + private static class StopSP implements TransferPointMatcher { + + private final int stopIndex; + + private StopSP(int stopIndex) { + this.stopIndex = stopIndex; + } + + @Override + public boolean match(int stopIndex, Trip trip) { + return this.stopIndex == stopIndex; + } + } + + private static class RouteSP implements TransferPointMatcher { + + private final Route route; + private final int stopIndex; + + private RouteSP(Route route, int stopIndex) { + this.route = route; + this.stopIndex = stopIndex; + } + + @Override + public boolean match(int stopIndex, Trip trip) { + return this.stopIndex == stopIndex && this.route == trip.getRoute(); + } + } + + private static class TripSP implements TransferPointMatcher { + + private final Trip trip; + private final int stopIndex; + + private TripSP(Trip trip, int stopIndex) { + this.trip = trip; + this.stopIndex = stopIndex; + } + + @Override + public boolean match(int stopIndex, Trip trip) { + return this.stopIndex == stopIndex && this.trip == trip; + } + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferPointMatcher.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferPointMatcher.java new file mode 100644 index 00000000000..9e81daa7d25 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferPointMatcher.java @@ -0,0 +1,10 @@ +package org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer; + +import org.opentripplanner.model.Trip; + +/** + * This class is used to match a given trip and stop index. + */ +interface TransferPointMatcher { + boolean match(int stopIndex, Trip trip); +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/cost/DefaultCostCalculator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/cost/DefaultCostCalculator.java index bda8c7263a7..8b9a1990c0d 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/cost/DefaultCostCalculator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/cost/DefaultCostCalculator.java @@ -68,7 +68,7 @@ public int boardingCost( RaptorTripSchedule trip, RaptorTransferConstraint transferConstraints ) { - if(transferConstraints == null) { + if(transferConstraints.isRegularTransfer()) { return boardingCostRegularTransfer( firstBoarding, prevArrivalTime, diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/AccessEgressMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/AccessEgressMapper.java index 882ced15f3a..897fa9c3ba0 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/AccessEgressMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/AccessEgressMapper.java @@ -1,5 +1,9 @@ package org.opentripplanner.routing.algorithm.raptor.transit.mappers; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import org.opentripplanner.ext.flex.FlexAccessEgress; import org.opentripplanner.model.Stop; import org.opentripplanner.routing.algorithm.raptor.transit.AccessEgress; @@ -7,11 +11,6 @@ import org.opentripplanner.routing.algorithm.raptor.transit.StopIndexForRaptor; import org.opentripplanner.routing.graphfinder.NearbyStop; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - public class AccessEgressMapper { private final StopIndexForRaptor stopIndex; @@ -22,8 +21,9 @@ public AccessEgressMapper(StopIndexForRaptor stopIndex) { public AccessEgress mapNearbyStop(NearbyStop nearbyStop, boolean isEgress) { if (!(nearbyStop.stop instanceof Stop)) { return null; } + return new AccessEgress( - stopIndex.indexByStop.get(nearbyStop.stop), + stopIndex.indexOf(nearbyStop.stop), isEgress ? nearbyStop.state.reverse() : nearbyStop.state ); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TransferIndexGenerator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TransferIndexGenerator.java deleted file mode 100644 index f024ea9c8a2..00000000000 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TransferIndexGenerator.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.opentripplanner.routing.algorithm.raptor.transit.mappers; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import org.opentripplanner.model.Trip; -import org.opentripplanner.model.transfer.ConstrainedTransfer; -import org.opentripplanner.model.transfer.TransferService; -import org.opentripplanner.routing.algorithm.raptor.transit.TripPatternWithRaptorStopIndexes; - -public class TransferIndexGenerator { - private final TransferService transferService; - private final Map<Trip, TripPatternWithRaptorStopIndexes> patternByTrip = new HashMap<>(); - - private TransferIndexGenerator(TransferService transferService) { - this.transferService = transferService; - } - - public static void generateTransfers( - TransferService transferService, - Collection<TripPatternWithRaptorStopIndexes> tripPatterns - ) { - var generator = new TransferIndexGenerator(transferService); - - generator.setupPatternByTripIndex(tripPatterns); - - for (TripPatternWithRaptorStopIndexes pattern : tripPatterns) { - generator.generateTransfers(pattern); - } - } - - private void setupPatternByTripIndex(Collection<TripPatternWithRaptorStopIndexes> tripPatterns) { - for (TripPatternWithRaptorStopIndexes pattern : tripPatterns) { - for (Trip trip : pattern.getPattern().getTrips()) { - patternByTrip.put(trip, pattern); - } - } - } - - private void generateTransfers(TripPatternWithRaptorStopIndexes pattern) { - for (Trip trip : pattern.getPattern().getTrips()) { - int nStops = pattern.getPattern().getStops().size(); - for (int stopPos=0; stopPos < nStops; ++stopPos) { - var transfers= transferService.listConstrainedTransfersTo(trip, stopPos); - for (ConstrainedTransfer tx : transfers) { - var c = tx.getTransferConstraint(); - if(c.isFacilitated()) { - var fromTrip = tx.getFrom().getTrip(); - var toTrip = tx.getTo().getTrip(); - if (fromTrip != null && toTrip != null) { - var fromPattern = patternByTrip.get(fromTrip); - if (fromPattern != null) { - pattern.addTransferConstraintsForwardSearch(tx); - fromPattern.addTransferConstraintsReverseSearch(tx); - } - } - } - } - } - } - } -} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TransfersMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TransfersMapper.java index 0be15e583fc..ef5b8d1dced 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TransfersMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TransfersMapper.java @@ -21,14 +21,14 @@ static List<List<Transfer>> mapTransfers( List<List<Transfer>> transferByStopIndex = new ArrayList<>(); - for (int i = 0; i < stopIndex.stopsByIndex.size(); ++i) { - var stop = stopIndex.stopsByIndex.get(i); + for (int i = 0; i < stopIndex.size(); ++i) { + var stop = stopIndex.stopByIndex(i); ArrayList<Transfer> list = new ArrayList<>(); transferByStopIndex.add(list); for (PathTransfer pathTransfer : transfersByStop.get(stop)) { if (pathTransfer.to instanceof Stop) { - int toStopIndex = stopIndex.indexByStop.get(pathTransfer.to); + int toStopIndex = stopIndex.indexOf((Stop)pathTransfer.to); Transfer newTransfer; if (pathTransfer.getEdges() != null) { newTransfer = new Transfer( diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TransitLayerMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TransitLayerMapper.java index 504074741cc..c5048fa3e3f 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TransitLayerMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TransitLayerMapper.java @@ -23,6 +23,7 @@ import org.opentripplanner.routing.algorithm.raptor.transit.TransitTuningParameters; import org.opentripplanner.routing.algorithm.raptor.transit.TripPatternForDate; import org.opentripplanner.routing.algorithm.raptor.transit.TripPatternWithRaptorStopIndexes; +import org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer.TransferIndexGenerator; import org.opentripplanner.routing.algorithm.raptor.transit.request.RaptorRequestTransferCache; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.trippattern.TripTimes; @@ -76,10 +77,11 @@ private TransitLayer map(TransitTuningParameters tuningParameters) { transferByStopIndex = mapTransfers(stopIndex, graph.transfersByStop); if(OTPFeature.TransferConstraints.isOn()) { - TransferIndexGenerator.generateTransfers( - graph.getTransferService(), - newTripPatternForOld.values() - ); + new TransferIndexGenerator( + graph.getTransferService().listAll(), + newTripPatternForOld.values(), + stopIndex + ).generateTransfers(); } var transferCache = new RaptorRequestTransferCache(tuningParameters.transferCacheMaxSize()); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TripPatternMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TripPatternMapper.java index 6aaf89bef0a..9250f85d790 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TripPatternMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TripPatternMapper.java @@ -1,12 +1,11 @@ package org.opentripplanner.routing.algorithm.raptor.transit.mappers; -import org.opentripplanner.model.TripPattern; -import org.opentripplanner.routing.algorithm.raptor.transit.StopIndexForRaptor; -import org.opentripplanner.routing.algorithm.raptor.transit.TripPatternWithRaptorStopIndexes; - import java.util.Collection; import java.util.HashMap; import java.util.Map; +import org.opentripplanner.model.TripPattern; +import org.opentripplanner.routing.algorithm.raptor.transit.StopIndexForRaptor; +import org.opentripplanner.routing.algorithm.raptor.transit.TripPatternWithRaptorStopIndexes; public class TripPatternMapper { @@ -23,8 +22,8 @@ static Map<TripPattern, TripPatternWithRaptorStopIndexes> mapOldTripPatternToRap for (TripPattern oldTripPattern : oldTripPatterns) { TripPatternWithRaptorStopIndexes newTripPattern = new TripPatternWithRaptorStopIndexes( - stopIndex.listStopIndexesForStops(oldTripPattern.getStopPattern().getStops()), - oldTripPattern + oldTripPattern, + stopIndex.listStopIndexesForStops(oldTripPattern.getStops()) ); newTripPatternForOld.put(oldTripPattern, newTripPattern); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearch.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearch.java deleted file mode 100644 index 12b562d666a..00000000000 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearch.java +++ /dev/null @@ -1,199 +0,0 @@ -package org.opentripplanner.routing.algorithm.raptor.transit.request; - -import gnu.trove.map.TIntObjectMap; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import org.opentripplanner.common.model.T2; -import org.opentripplanner.model.Trip; -import org.opentripplanner.model.transfer.ConstrainedTransfer; -import org.opentripplanner.model.transfer.TransferConstraint; -import org.opentripplanner.model.transfer.TransferPoint; -import org.opentripplanner.routing.algorithm.raptor.transit.TripSchedule; -import org.opentripplanner.transit.raptor.api.transit.RaptorConstrainedTripScheduleBoardingSearch; -import org.opentripplanner.transit.raptor.api.transit.RaptorTimeTable; -import org.opentripplanner.transit.raptor.api.transit.RaptorTripSchedule; -import org.opentripplanner.transit.raptor.api.transit.RaptorTripScheduleBoardOrAlightEvent; - - -/** - * The responsibility of this class is to provide transfer constraints to the Raptor search - * for a given pattern. The instance is stateful and not thread-safe. The current stop - * position is checked for transfers, then the provider is asked to list all transfers - * between the current pattern and the source trip stop arrival. The source is the "from" - * point in a transfer for a forward search, and the "to" point in the reverse search. - */ -public final class ConstrainedBoardingSearch - implements RaptorConstrainedTripScheduleBoardingSearch<TripSchedule> { - - private static final DirectionHelper FORWARD_HELPER = new ForwardDirectionHelper(); - private static final DirectionHelper REVERSE_HELPER = new ReverseDirectionHelper(); - - private final DirectionHelper translator; - - /** - * List of transfers for each stop position in pattern - */ - private final TIntObjectMap<List<ConstrainedTransfer>> transfers; - - private List<ConstrainedTransfer> currentTransfers; - private int currentTargetStopPos; - - public ConstrainedBoardingSearch( - boolean forwardSearch, - TIntObjectMap<List<ConstrainedTransfer>> transfers - ) { - this.translator = forwardSearch ? FORWARD_HELPER : REVERSE_HELPER; - this.transfers = transfers; - } - - @Override - public boolean transferExist(int targetStopPos) { - if(transfers == null) { return false; } - - // Get all guaranteed transfers for the target pattern at the target stop position - this.currentTransfers = transfers.get(targetStopPos); - this.currentTargetStopPos = targetStopPos; - return currentTransfers != null; - } - - @Override - public RaptorTripScheduleBoardOrAlightEvent<TripSchedule> find( - RaptorTimeTable<TripSchedule> timetable, - TripSchedule sourceTripSchedule, - int sourceStopIndex, - int sourceArrivalTime - ) { - final Trip sourceTrip = sourceTripSchedule.getOriginalTripTimes().getTrip(); - final int sourceStopPos = translator.findSourceStopPosition( - sourceTripSchedule, sourceArrivalTime, sourceStopIndex - ); - - var list = findMatchingTransfers(sourceTrip, sourceStopPos); - - if(list.isEmpty()) { return null; } - - var tripInfo = translator.findTimetableTripInfo( - timetable, - list, - currentTargetStopPos, - sourceArrivalTime - ); - - if(tripInfo == null) { return null; } - - final int tripIndex = tripInfo.first; - final TransferConstraint transferConstraint = tripInfo.second; - - var trip = timetable.getTripSchedule(tripIndex); - int departureTime = translator.time(trip, currentTargetStopPos); - - return new ConstrainedTransferBoarding<>( - transferConstraint, tripIndex, trip, currentTargetStopPos, departureTime - ); - } - - private Collection<ConstrainedTransfer> findMatchingTransfers( - Trip sourceTrip, - int sourceStopPos - ) { - var result = new ArrayList<ConstrainedTransfer>(); - for (ConstrainedTransfer tx : currentTransfers) { - var sourcePoint = translator.source(tx); - if(sourcePoint.matches(sourceTrip, sourceStopPos)) { - result.add(tx); - } - } - return result; - } - - private interface DirectionHelper { - TransferPoint source(ConstrainedTransfer tx); - TransferPoint target(ConstrainedTransfer tx); - int time(RaptorTripSchedule schedule, int stopPos); - int findSourceStopPosition(RaptorTripSchedule schedule, int timeLimit, int stop); - /** Find the trip to board (trip index) and the transfer constraint */ - T2<Integer, TransferConstraint> findTimetableTripInfo( - RaptorTimeTable<TripSchedule> timetable, - Collection<ConstrainedTransfer> transfers, - int stopPos, - int sourceTime - ); - } - - private static class ForwardDirectionHelper implements DirectionHelper { - @Override public TransferPoint source(ConstrainedTransfer tx) { return tx.getFrom(); } - @Override public TransferPoint target(ConstrainedTransfer tx) { return tx.getTo(); } - @Override public int time(RaptorTripSchedule schedule, int stopPos) { - return schedule.departure(stopPos); - } - @Override - public int findSourceStopPosition(RaptorTripSchedule schedule, int timeLimit, int stop) { - return schedule.findArrivalStopPosition(timeLimit, stop); - } - @Override - public T2<Integer, TransferConstraint> findTimetableTripInfo( - RaptorTimeTable<TripSchedule> timetable, - Collection<ConstrainedTransfer> transfers, - int stopPos, - int sourceArrivalTime - ) { - // Abort after 6 hours - int maxLimit = sourceArrivalTime + 3600 * 6; - - for (int i = 0; i < timetable.numberOfTripSchedules(); i++) { - var it = timetable.getTripSchedule(i); - int departureTime = it.departure(stopPos); - if(departureTime < sourceArrivalTime) { continue; } - if(departureTime > maxLimit) { return null; } - - var targetTrip = it.getOriginalTripTimes().getTrip(); - - for (ConstrainedTransfer tx : transfers) { - if(targetTrip == tx.getTo().getTrip()) { - return new T2<>(i, tx.getTransferConstraint()); - } - } - } - return null; - } - } - - private static class ReverseDirectionHelper implements DirectionHelper { - @Override public TransferPoint source(ConstrainedTransfer tx) { return tx.getTo(); } - @Override public TransferPoint target(ConstrainedTransfer tx) { return tx.getFrom(); } - @Override public int time(RaptorTripSchedule schedule, int stopPos) { - return schedule.arrival(stopPos); - } - @Override - public int findSourceStopPosition(RaptorTripSchedule schedule, int timeLimit, int stop) { - return schedule.findDepartureStopPosition(timeLimit, stop); - } - @Override - public T2<Integer, TransferConstraint> findTimetableTripInfo( - RaptorTimeTable<TripSchedule> timetable, - Collection<ConstrainedTransfer> transfers, - int stopPos, - int sourceDepartureTime - ) { - // Abort after 6 hours - int minLimit = sourceDepartureTime - 3600 * 6; - - for (int i = 0; i < timetable.numberOfTripSchedules(); i++) { - var it = timetable.getTripSchedule(i); - int arrivalTime = it.arrival(stopPos); - if(arrivalTime < minLimit) { continue; } - if(arrivalTime > sourceDepartureTime) { return null; } - - var targetTrip = it.getOriginalTripTimes().getTrip(); - - for (ConstrainedTransfer tx : transfers) { - if(targetTrip == tx.getFrom().getTrip()) { - return new T2<>(i ,tx.getTransferConstraint()); - } - } - } - return null; - } - } -} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/RaptorRoutingRequestTransitData.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/RaptorRoutingRequestTransitData.java index 6fcda026fdb..82bc51111dd 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/RaptorRoutingRequestTransitData.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/RaptorRoutingRequestTransitData.java @@ -135,12 +135,12 @@ public RaptorConstrainedTransfer findConstrainedTransfer( TripSchedule fromTrip, int fromStopPosition, TripSchedule toTrip, int toStopPosition ) { return transferService.findTransfer( - transitLayer.getStopByIndex(fromTrip.pattern().stopIndex(fromStopPosition)), - transitLayer.getStopByIndex(toTrip.pattern().stopIndex(toStopPosition)), fromTrip.getOriginalTripTimes().getTrip(), - toTrip.getOriginalTripTimes().getTrip(), fromStopPosition, - toStopPosition + transitLayer.getStopByIndex(fromTrip.pattern().stopIndex(fromStopPosition)), + toTrip.getOriginalTripTimes().getTrip(), + toStopPosition, + transitLayer.getStopByIndex(toTrip.pattern().stopIndex(toStopPosition)) ); } }; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGenerator.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGenerator.java index 8cb93f2927b..7f0f3ec9121 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGenerator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGenerator.java @@ -123,6 +123,10 @@ private Collection<TripToTripTransfer<T>> transferFromSameStop(TripStopTime<T> f final int stop = from.stop(); var tx = transferServiceAdaptor.findTransfer(from, toTrip, stop); + if(tx != null && tx.getTransferConstraint().isNotAllowed()) { + return List.of(); + } + final int earliestDepartureTime = earliestDepartureTime( from.time(), SAME_STOP_TRANSFER_TIME, tx ); @@ -154,6 +158,9 @@ private Collection<? extends TripToTripTransfer<T>> findStandardTransfers(TripSt int toStop = it.stop(); ConstrainedTransfer tx = transferServiceAdaptor.findTransfer(from, toTrip, toStop); + if(tx != null && tx.getTransferConstraint().isNotAllowed()) { + continue; + } int earliestDepartureTime = earliestDepartureTime(from.time(), it.durationInSeconds(), tx); int toTripStopPos = toTrip.findDepartureStopPosition(earliestDepartureTime, toStop); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferServiceAdaptor.java b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferServiceAdaptor.java index d597dee0d1e..8988f9a3cf6 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferServiceAdaptor.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferServiceAdaptor.java @@ -1,7 +1,7 @@ package org.opentripplanner.routing.algorithm.transferoptimization.services; import java.util.function.IntFunction; -import org.opentripplanner.model.Stop; +import javax.annotation.Nullable; import org.opentripplanner.model.StopLocation; import org.opentripplanner.model.Trip; import org.opentripplanner.model.transfer.ConstrainedTransfer; @@ -51,14 +51,15 @@ public static <T extends RaptorTripSchedule> TransferServiceAdaptor<T> noop() { /** * Find transfer in the same stop for the given from location and to trip/stop. */ + @Nullable protected ConstrainedTransfer findTransfer(TripStopTime<T> from, T toTrip, int toStop) { return transferService.findTransfer( - stop(from.stop()), - stop(toStop), trip(from.trip()), - trip(toTrip), from.stopPosition(), - toTrip.findDepartureStopPosition(from.time(), toStop) + stop(from.stop()), + trip(toTrip), + toTrip.findDepartureStopPosition(from.time(), toStop), + stop(toStop) ); } diff --git a/src/main/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitor.java b/src/main/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitor.java index 8b49106a4fa..45e52221c6f 100644 --- a/src/main/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitor.java +++ b/src/main/java/org/opentripplanner/routing/graphfinder/PlaceFinderTraverseVisitor.java @@ -1,5 +1,11 @@ package org.opentripplanner.routing.graphfinder; +import static java.util.stream.Collectors.toList; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import org.opentripplanner.model.FeedScopedId; import org.opentripplanner.model.Stop; import org.opentripplanner.model.TransitMode; @@ -7,19 +13,12 @@ import org.opentripplanner.routing.RoutingService; import org.opentripplanner.routing.algorithm.astar.TraverseVisitor; import org.opentripplanner.routing.algorithm.astar.strategies.SkipEdgeStrategy; -import org.opentripplanner.routing.vehicle_rental.VehicleRentalPlace; import org.opentripplanner.routing.core.State; import org.opentripplanner.routing.graph.Edge; import org.opentripplanner.routing.graph.Vertex; -import org.opentripplanner.routing.vertextype.VehicleRentalStationVertex; +import org.opentripplanner.routing.vehicle_rental.VehicleRentalPlace; import org.opentripplanner.routing.vertextype.TransitStopVertex; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static java.util.stream.Collectors.toList; +import org.opentripplanner.routing.vertextype.VehicleRentalStationVertex; /** * A TraverseVisitor used in finding various types of places while walking the street graph. @@ -125,7 +124,7 @@ private void handlePatternsAtStop(Stop stop, double distance) { .filter(pattern -> filterByModes == null || filterByModes.contains(pattern.getMode())) .filter(pattern -> filterByRoutes == null || filterByRoutes.contains(pattern.getRoute().getId())) - .filter(pattern -> pattern.canBoard(pattern.getStopIndex(stop))) + .filter(pattern -> pattern.canBoard(stop)) .collect(toList()); for (TripPattern pattern : patterns) { diff --git a/src/main/java/org/opentripplanner/routing/stoptimes/StopTimesHelper.java b/src/main/java/org/opentripplanner/routing/stoptimes/StopTimesHelper.java index ff073bb5d6e..ae881af4ab0 100644 --- a/src/main/java/org/opentripplanner/routing/stoptimes/StopTimesHelper.java +++ b/src/main/java/org/opentripplanner/routing/stoptimes/StopTimesHelper.java @@ -1,6 +1,19 @@ package org.opentripplanner.routing.stoptimes; +import static org.opentripplanner.routing.stoptimes.ArrivalDeparture.ARRIVALS; +import static org.opentripplanner.routing.stoptimes.ArrivalDeparture.DEPARTURES; +import static org.opentripplanner.util.time.DateConstants.ONE_DAY_SECONDS; + import com.google.common.collect.MinMaxPriorityQueue; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.Queue; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopLocation; import org.opentripplanner.model.StopTimesInPattern; @@ -14,20 +27,6 @@ import org.opentripplanner.routing.core.ServiceDay; import org.opentripplanner.routing.trippattern.TripTimes; -import java.time.Instant; -import java.time.LocalDate; -import java.time.ZoneId; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.Date; -import java.util.List; -import java.util.Queue; - -import static org.opentripplanner.routing.stoptimes.ArrivalDeparture.ARRIVALS; -import static org.opentripplanner.routing.stoptimes.ArrivalDeparture.DEPARTURES; -import static org.opentripplanner.util.time.DateConstants.ONE_DAY_SECONDS; - public class StopTimesHelper { /** * Fetch upcoming vehicle departures from a stop. @@ -72,7 +71,7 @@ public static List<StopTimesInPattern> stopTimesForStop( dates.add(new ServiceDate(date).shift(i)); } - ServiceDate[] serviceDates = dates.toArray(new ServiceDate[dates.size()]); + ServiceDate[] serviceDates = dates.toArray(new ServiceDate[0]); // Fetch all patterns, including those from realtime sources Collection<TripPattern> patterns = routingService.getPatternsForStop(stop, timetableSnapshot); @@ -141,7 +140,7 @@ public static List<StopTimesInPattern> stopTimesForStop( .getRoute() .getAgency().getId()); int sidx = 0; - for (var currStop : pattern.getStopPattern().getStops()) { + for (var currStop : pattern.getStops()) { if (currStop == stop) { if(skipByPickUpDropOff(pattern, arrivalDeparture, sidx)) continue; for (TripTimes t : tt.getTripTimes()) { @@ -246,7 +245,7 @@ private static Queue<TripTimeOnDate> listTripTimeShortsForPatternAtStop( int secondsSinceMidnight = sd.secondsSinceMidnight(startTime); int stopIndex = 0; - for (var currStop : pattern.getStopPattern().getStops()) { + for (var currStop : pattern.getStops()) { if (currStop == stop) { if (skipByPickUpDropOff(pattern, arrivalDeparture, stopIndex)) { continue; } @@ -256,7 +255,7 @@ private static Queue<TripTimeOnDate> listTripTimeShortsForPatternAtStop( if (!sd.serviceRunning(tripTimes.getServiceCode())) { continue; } if (skipByTripCancellation(tripTimes, includeCancellations)) { continue; } if ( - !includeReplaced && + !includeReplaced && isReplacedByAnotherPattern( tripTimes.getTrip(), serviceDate, pattern, timetableSnapshot ) @@ -309,23 +308,20 @@ private static boolean skipByTripCancellation(TripTimes tripTimes, boolean inclu private static boolean skipByPickUpDropOff( TripPattern pattern, ArrivalDeparture arrivalDeparture, int stopIndex ) { - boolean pickup = pattern.getStopPattern().getPickup(stopIndex) != PickDrop.NONE; - boolean dropoff = pattern.getStopPattern().getDropoff(stopIndex) != PickDrop.NONE; - - if (!pickup && !dropoff) - return true; - if (!pickup && arrivalDeparture == DEPARTURES) - return true; - if (!dropoff && arrivalDeparture == ARRIVALS) - return true; + boolean noPickup = pattern.getBoardType(stopIndex).is(PickDrop.NONE); + boolean noDropoff = pattern.getAlightType(stopIndex).is(PickDrop.NONE); + + if (noPickup && noDropoff) { return true; } + if (noPickup && arrivalDeparture == DEPARTURES) { return true; } + if (noDropoff && arrivalDeparture == ARRIVALS) { return true; } return false; } private static boolean skipByStopCancellation( TripPattern pattern, boolean includeCancelledTrips, int stopIndex ) { - boolean pickupCancelled = pattern.getStopPattern().getPickup(stopIndex) == PickDrop.CANCELLED; - boolean dropOffCancelled = pattern.getStopPattern().getDropoff(stopIndex) == PickDrop.CANCELLED; + boolean pickupCancelled = pattern.getBoardType(stopIndex).is(PickDrop.CANCELLED); + boolean dropOffCancelled = pattern.getAlightType(stopIndex).is(PickDrop.CANCELLED); return (pickupCancelled || dropOffCancelled) && !includeCancelledTrips; } diff --git a/src/main/java/org/opentripplanner/transit/raptor/api/path/PathBuilderLeg.java b/src/main/java/org/opentripplanner/transit/raptor/api/path/PathBuilderLeg.java index 5a01e7d5d01..846c9c3cf2d 100644 --- a/src/main/java/org/opentripplanner/transit/raptor/api/path/PathBuilderLeg.java +++ b/src/main/java/org/opentripplanner/transit/raptor/api/path/PathBuilderLeg.java @@ -8,6 +8,7 @@ import org.opentripplanner.transit.raptor.api.transit.RaptorConstrainedTransfer; import org.opentripplanner.transit.raptor.api.transit.RaptorSlackProvider; import org.opentripplanner.transit.raptor.api.transit.RaptorTransfer; +import org.opentripplanner.transit.raptor.api.transit.RaptorTransferConstraint; import org.opentripplanner.transit.raptor.api.transit.RaptorTripSchedule; import org.opentripplanner.transit.raptor.api.view.BoardAndAlightTime; import org.opentripplanner.transit.raptor.util.PathStringBuilder; @@ -426,7 +427,9 @@ private int transitCost(CostCalculator costCalculator, RaptorSlackProvider slack var txBeforeLeg = prevTransit == null ? null : prevTransit.constrainedTransferAfterLeg(); - var transferConstraint = txBeforeLeg == null ? null : txBeforeLeg.getTransferConstraint(); + var transferConstraint = txBeforeLeg == null + ? RaptorTransferConstraint.REGULAR_TRANSFER + : txBeforeLeg.getTransferConstraint(); boolean firstBoarding = prev != null && prev.isAccessWithoutRides(); int boardCost = costCalculator.boardingCost( diff --git a/src/main/java/org/opentripplanner/transit/raptor/api/transit/RaptorTransferConstraint.java b/src/main/java/org/opentripplanner/transit/raptor/api/transit/RaptorTransferConstraint.java index 84469bde812..7bf76bd5b31 100644 --- a/src/main/java/org/opentripplanner/transit/raptor/api/transit/RaptorTransferConstraint.java +++ b/src/main/java/org/opentripplanner/transit/raptor/api/transit/RaptorTransferConstraint.java @@ -6,5 +6,26 @@ * instance in a callback to the cost calculator. */ public interface RaptorTransferConstraint { - /* This is intentionally empty */ + + /** + * A regular transfer is a transfer with no constraints. + */ + RaptorTransferConstraint REGULAR_TRANSFER = new RaptorTransferConstraint() { + @Override public boolean isNotAllowed() { return false; } + @Override public boolean isRegularTransfer() { return true; } + }; + + + /** + * Return {@code true} if the constrained transfer is not allowed between the two routes. + * Note! If a constraint only apply to specific trips, then the + * {@link RaptorConstrainedTripScheduleBoardingSearch} is reponsible for NOT returning the + * NOT-ALLOWED transfer, and finding the next ALLOWED trip. + */ + boolean isNotAllowed(); + + /** + * Returns {@code true} if this is a regular transfer without any constrains. + */ + boolean isRegularTransfer(); } \ No newline at end of file diff --git a/src/main/java/org/opentripplanner/transit/raptor/api/transit/RaptorTripScheduleBoardOrAlightEvent.java b/src/main/java/org/opentripplanner/transit/raptor/api/transit/RaptorTripScheduleBoardOrAlightEvent.java index eb029364fcc..ca719e731b8 100644 --- a/src/main/java/org/opentripplanner/transit/raptor/api/transit/RaptorTripScheduleBoardOrAlightEvent.java +++ b/src/main/java/org/opentripplanner/transit/raptor/api/transit/RaptorTripScheduleBoardOrAlightEvent.java @@ -1,7 +1,7 @@ package org.opentripplanner.transit.raptor.api.transit; -import javax.annotation.Nullable; +import javax.validation.constraints.NotNull; /** * The purpose of the TripScheduleBoardAlight is to represent the board/alight for a @@ -50,8 +50,10 @@ default int getBoardStopIndex() { int getTime(); /** - * Return the transfer constrains for the transfer before this boarding, if it exists. + * Return the transfer constrains for the transfer before this boarding. + * If there are no transfer constraints assisiated with the boarding the + * {@link RaptorTransferConstraint#isRegularTransfer()} is {@code true}. */ - @Nullable + @NotNull RaptorTransferConstraint getTransferConstraint(); } diff --git a/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/RangeRaptorWorker.java b/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/RangeRaptorWorker.java index 6daa2e912ed..e753b2eec56 100644 --- a/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/RangeRaptorWorker.java +++ b/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/RangeRaptorWorker.java @@ -45,7 +45,7 @@ * <li>Multi-criteria pareto optimal Range Raptor (McRR) * <li>Reverse search in combination with R and RR * </ul> - * This version do NOT support the following features: + * This version does NOT support the following features: * <ul> * <li>Frequency routes, supported by the original code using Monte Carlo methods * (generating randomized schedules) @@ -214,7 +214,7 @@ private void findAllTransitForRound() { var route = routeIterator.next(); var pattern = route.pattern(); var tripSearch = createTripSearch(route.timetable()); - var txService = enableTransferConstraints + var txSearch = enableTransferConstraints ? calculator.transferConstraintsSearch(route) : null; int alightSlack = slackProvider.alightSlack(pattern); @@ -238,12 +238,12 @@ private void findAllTransitForRound() { // MC Raptor have many, while RR have one boarding transitWorker.forEachBoarding(stopIndex, (int prevArrivalTime) -> { - boolean ok = boardWithConstrainedTransfer( - txService, route.timetable(), stopIndex, stopPos + boolean handled = boardWithConstrainedTransfer( + txSearch, route.timetable(), stopIndex, stopPos ); // Find the best trip and board [no guaranteed transfer exist] - if(!ok) { + if(!handled) { boardWithRegularTransfer( tripSearch, stopPos, stopIndex, prevArrivalTime, boardSlack ); @@ -280,15 +280,19 @@ private void boardWithRegularTransfer( } } + /** + * @return {@code true} if a constrained transfer exist to prevent the normal + * trip search from execution. + */ private boolean boardWithConstrainedTransfer( - RaptorConstrainedTripScheduleBoardingSearch<T> txService, + RaptorConstrainedTripScheduleBoardingSearch<T> txSearch, RaptorTimeTable<T> targetTimetable, int targetStopIndex, int targetStopPos ) { if(!enableTransferConstraints) { return false; } - if(!txService.transferExist(targetStopPos)) { return false; } + if(!txSearch.transferExist(targetStopPos)) { return false; } // Get the previous transit stop arrival (transfer source) TransitArrival<T> sourceStopArrival = transitWorker.previousTransit(targetStopIndex); @@ -301,15 +305,19 @@ private boolean boardWithConstrainedTransfer( slackProvider.alightSlack(sourceStopArrival.trip().pattern()) ); - var result = txService.find( + var result = txSearch.find( targetTimetable, sourceStopArrival.trip(), sourceStopArrival.stop(), earliestBoardTime ); - if (result == null) { - return false; + if (result == null) { return false; } + + if (result.getTransferConstraint().isNotAllowed()) { + // We are blocking a normal trip search here by returning + // true without boarding the trip + return true; } this.earliestBoardTime = earliestBoardTime; diff --git a/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/standard/StopArrivalsState.java b/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/standard/StopArrivalsState.java index c27d5f5294e..e84bc71c41a 100644 --- a/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/standard/StopArrivalsState.java +++ b/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/standard/StopArrivalsState.java @@ -41,13 +41,7 @@ default void rejectNewBestTransitTime(int alightStop, int alightTime, T trip, in default void rejectNewBestTransferTime(int fromStop, int arrivalTime, RaptorTransfer transfer) {} @Nullable - default TransitArrival<T> previousTransit(int boardStopIndex) { - throw new IllegalStateException( - "The implementation of this interface is not compatible with the request" + - "configuration. For example the BestTimesOnlyStopArrivalsState can not be used " + - "with constrained transfers." - ); - } + TransitArrival<T> previousTransit(int boardStopIndex); default Collection<Path<T>> extractPaths() { return List.of(); } } \ No newline at end of file diff --git a/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/standard/besttimes/BestTimesOnlyStopArrivalsState.java b/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/standard/besttimes/BestTimesOnlyStopArrivalsState.java index b004a06b3ab..0cbda5d8872 100644 --- a/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/standard/besttimes/BestTimesOnlyStopArrivalsState.java +++ b/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/standard/besttimes/BestTimesOnlyStopArrivalsState.java @@ -3,6 +3,7 @@ import org.opentripplanner.transit.raptor.api.transit.RaptorTransfer; import org.opentripplanner.transit.raptor.api.transit.RaptorTripSchedule; +import org.opentripplanner.transit.raptor.api.transit.TransitArrival; import org.opentripplanner.transit.raptor.rangeraptor.standard.StopArrivalsState; /** @@ -60,4 +61,13 @@ public void setNewBestTransitTime(int stop, int alightTime, T trip, int boardSto public void setNewBestTransferTime(int fromStop, int arrivalTime, RaptorTransfer transfer) { bestNumberOfTransfers.arriveAtStop(transfer.stop()); } + + @Override + public TransitArrival<T> previousTransit(int boardStopIndex) { + throw new IllegalStateException( + "The implementation of this interface is not compatible with the request" + + "configuration. For example the BestTimesOnlyStopArrivalsState can not be used " + + "with constrained transfers." + ); + } } diff --git a/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/transit/TripScheduleAlightSearch.java b/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/transit/TripScheduleAlightSearch.java index 7513799fae9..88f888ae1e7 100644 --- a/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/transit/TripScheduleAlightSearch.java +++ b/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/transit/TripScheduleAlightSearch.java @@ -65,9 +65,10 @@ public int getStopPositionInPattern() { return stopPositionInPattern; } - @Nullable @Override - public RaptorTransferConstraint getTransferConstraint() { return null; } + public RaptorTransferConstraint getTransferConstraint() { + return RaptorTransferConstraint.REGULAR_TRANSFER; + } /* TripScheduleSearch implementation */ diff --git a/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/transit/TripScheduleBoardSearch.java b/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/transit/TripScheduleBoardSearch.java index 9b80e89da20..578ec14de04 100644 --- a/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/transit/TripScheduleBoardSearch.java +++ b/src/main/java/org/opentripplanner/transit/raptor/rangeraptor/transit/TripScheduleBoardSearch.java @@ -67,9 +67,10 @@ public int getStopPositionInPattern() { return stopPositionInPattern; } - @Nullable @Override - public RaptorTransferConstraint getTransferConstraint() { return null; } + public RaptorTransferConstraint getTransferConstraint() { + return RaptorTransferConstraint.REGULAR_TRANSFER; + } /* TripScheduleSearch implementation */ diff --git a/src/test/java/org/opentripplanner/common/model/T2Test.java b/src/test/java/org/opentripplanner/common/model/T2Test.java new file mode 100644 index 00000000000..7f5c8d0db83 --- /dev/null +++ b/src/test/java/org/opentripplanner/common/model/T2Test.java @@ -0,0 +1,32 @@ +package org.opentripplanner.common.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.api.Test; + +class T2Test { + + @Test + void testEquals() { + var subject = new T2<>("Alf", 1); + + assertEquals(new T2<>("Alf", 1), subject); + assertEquals(new T2<>("Alf", 1).hashCode(), subject.hashCode()); + + // first is different + assertNotEquals(new T2<>("Alfi", 1), subject); + + // second is different + assertNotEquals(new T2<>("Alf", 2), subject); + + // Different types, should not fail with exception + assertNotEquals(new T2<>(1, "Alf"), subject); + } + + @Test + void testToString() { + var subject = new T2<>("Alf", 1); + assertEquals("T2(Alf, 1)", subject.toString()); + } +} \ No newline at end of file diff --git a/src/test/java/org/opentripplanner/graph_builder/linking/LinkStopToPlatformTest.java b/src/test/java/org/opentripplanner/graph_builder/linking/LinkStopToPlatformTest.java index 323288ec050..4033d50c289 100644 --- a/src/test/java/org/opentripplanner/graph_builder/linking/LinkStopToPlatformTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/linking/LinkStopToPlatformTest.java @@ -1,6 +1,10 @@ package org.opentripplanner.graph_builder.linking; +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -23,14 +27,9 @@ import org.opentripplanner.util.I18NString; import org.opentripplanner.util.LocalizedString; -import java.util.ArrayList; -import java.util.List; - -import static org.junit.Assert.assertEquals; - public class LinkStopToPlatformTest { - private static GeometryFactory geometryFactory = GeometryUtils.getGeometryFactory(); + private static final GeometryFactory geometryFactory = GeometryUtils.getGeometryFactory(); private Graph graph; diff --git a/src/test/java/org/opentripplanner/model/TimetableTest.java b/src/test/java/org/opentripplanner/model/TimetableTest.java index 2c62c1323b3..d9a1c47ea2b 100644 --- a/src/test/java/org/opentripplanner/model/TimetableTest.java +++ b/src/test/java/org/opentripplanner/model/TimetableTest.java @@ -195,8 +195,8 @@ public void testUpdate() { // TODO This will not work since individual stops cannot be cancelled using GTFS updates // yet for (int i = 0; i < tripTimes.getNumStops(); i++) { - assertEquals(PickDrop.CANCELLED, pattern.getStopPattern().getPickup(i) ); - assertEquals(PickDrop.CANCELLED, pattern.getStopPattern().getDropoff(i) ); + assertEquals(PickDrop.CANCELLED, pattern.getBoardType(i)); + assertEquals(PickDrop.CANCELLED, pattern.getAlightType(i)); } //--- diff --git a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java index 7d6255fc072..2da6ee9cbe5 100644 --- a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java +++ b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java @@ -3,15 +3,15 @@ import static java.util.Comparator.comparing; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.gtfs.GtfsContextBuilder.contextBuilder; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.opentripplanner.ConstantsForTests; import org.opentripplanner.gtfs.GtfsContextBuilder; import org.opentripplanner.model.Agency; @@ -37,7 +37,7 @@ public class OtpTransitServiceImplTest { private static OtpTransitService subject; - @BeforeClass + @BeforeAll public static void setup() throws IOException { GtfsContextBuilder contextBuilder = contextBuilder(FEED_ID, ConstantsForTests.FAKE_GTFS); OtpTransitServiceBuilder builder = contextBuilder.getTransitBuilder(); @@ -106,16 +106,13 @@ public void testGetAllTransfers() { .collect(joining("\n")) ); - // There is 9 transfers, but because of the route to trip we get more - // TODO TGR - Support Route to trip expansion assertEquals( - //"Transfer{from: (route: 2, trip: 2.1, stopPos: 2), to: (route: 5, trip: 5.1, stopPos: 0), constraint: {guaranteed}}\n" - //+ "Transfer{from: (route: 2, trip: 2.2, stopPos: 2), to: (route: 5, trip: 5.1, stopPos: 0), constraint: {guaranteed}}\n" - "ConstrainedTransfer{from: (stop: K), to: (stop: L), constraint: {priority: RECOMMENDED}}\n" - + "ConstrainedTransfer{from: (stop: K), to: (stop: M), constraint: {priority: NOT_ALLOWED}}\n" - + "ConstrainedTransfer{from: (stop: L), to: (stop: K), constraint: {priority: RECOMMENDED}}\n" - + "ConstrainedTransfer{from: (stop: M), to: (stop: K), constraint: {priority: NOT_ALLOWED}}\n" - + "ConstrainedTransfer{from: (trip: 1.1, stopPos: 1), to: (trip: 2.2, stopPos: 0), constraint: {guaranteed}}", + "ConstrainedTransfer{from: <Route 2, stop D>, to: <Route 5, stop I>, constraint: {guaranteed}}\n" + + "ConstrainedTransfer{from: <Stop K>, to: <Stop L>, constraint: {priority: RECOMMENDED}}\n" + + "ConstrainedTransfer{from: <Stop K>, to: <Stop M>, constraint: {priority: NOT_ALLOWED}}\n" + + "ConstrainedTransfer{from: <Stop L>, to: <Stop K>, constraint: {priority: RECOMMENDED}}\n" + + "ConstrainedTransfer{from: <Stop M>, to: <Stop K>, constraint: {priority: NOT_ALLOWED}}\n" + + "ConstrainedTransfer{from: <Trip 1.1, stopPos 1>, to: <Trip 2.2, stopPos 0>, constraint: {guaranteed}}", result ); } diff --git a/src/test/java/org/opentripplanner/model/transfer/ConstrainedTransferTest.java b/src/test/java/org/opentripplanner/model/transfer/ConstrainedTransferTest.java index aaba82eeb26..a78d723a294 100644 --- a/src/test/java/org/opentripplanner/model/transfer/ConstrainedTransferTest.java +++ b/src/test/java/org/opentripplanner/model/transfer/ConstrainedTransferTest.java @@ -3,52 +3,112 @@ 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.transfer.TransferTestData.ROUTE_1; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_2; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_1A; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_1S; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_2B; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_2S; +import static org.opentripplanner.model.transfer.TransferTestData.STATION_POINT; +import static org.opentripplanner.model.transfer.TransferTestData.STOP_POINT_A; +import static org.opentripplanner.model.transfer.TransferTestData.STOP_POINT_B; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_11; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_21; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_POINT_11_1; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_POINT_21_3; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class ConstrainedTransferTest implements TransferTestData { +public class ConstrainedTransferTest { private static final TransferConstraint NO_CONSTRAINS = TransferConstraint.create().build(); private static final TransferConstraint GUARANTIED = TransferConstraint.create().guaranteed().build(); - private final ConstrainedTransfer TX_A_TO_B = new ConstrainedTransfer(null, STOP_POINT_A, STOP_POINT_B, NO_CONSTRAINS); - private final ConstrainedTransfer TX_A_TO_R22 = new ConstrainedTransfer(null, STOP_POINT_A, ROUTE_POINT_22, NO_CONSTRAINS); - private final ConstrainedTransfer TX_A_TO_T23 = new ConstrainedTransfer(null, STOP_POINT_A, TRIP_POINT_23, NO_CONSTRAINS); - private final ConstrainedTransfer TX_R11_TO_B = new ConstrainedTransfer(null, ROUTE_POINT_11, STOP_POINT_B, NO_CONSTRAINS); - private final ConstrainedTransfer TX_R11_TO_R22 = new ConstrainedTransfer(null, ROUTE_POINT_11, ROUTE_POINT_22, NO_CONSTRAINS); - private final ConstrainedTransfer TX_T11_TO_R22 = new ConstrainedTransfer(null, TRIP_POINT_11, ROUTE_POINT_22, NO_CONSTRAINS); - private final ConstrainedTransfer TX_T11_TO_T22 = new ConstrainedTransfer(null, TRIP_POINT_11, TRIP_POINT_23, NO_CONSTRAINS); + private final ConstrainedTransfer TX_STATION_TO_STATION = noConstTx(STATION_POINT, STATION_POINT); + private final ConstrainedTransfer TX_STATION_TO_B = noConstTx(STATION_POINT, STOP_POINT_B); + private final ConstrainedTransfer TX_STATION_TO_R2B = noConstTx(STATION_POINT, ROUTE_POINT_2B); + private final ConstrainedTransfer TX_STATION_TO_R2S = noConstTx(STATION_POINT, ROUTE_POINT_2S); + private final ConstrainedTransfer TX_STATION_TO_T23 = noConstTx(STATION_POINT, TRIP_POINT_21_3); - private final ConstrainedTransfer TX_NO_CONSTRAINS = new ConstrainedTransfer(null, STOP_POINT_A, STOP_POINT_B, NO_CONSTRAINS); - private final ConstrainedTransfer TX_GUARANTIED = new ConstrainedTransfer(null, TRIP_POINT_11, TRIP_POINT_23, GUARANTIED); + private final ConstrainedTransfer TX_A_TO_STATION = noConstTx(STOP_POINT_A, STATION_POINT); + private final ConstrainedTransfer TX_A_TO_B = noConstTx(STOP_POINT_A, STOP_POINT_B); + private final ConstrainedTransfer TX_A_TO_R2B = noConstTx(STOP_POINT_A, ROUTE_POINT_2B); + private final ConstrainedTransfer TX_A_TO_R2S = noConstTx(STOP_POINT_A, ROUTE_POINT_2S); + private final ConstrainedTransfer TX_A_TO_T23 = noConstTx(STOP_POINT_A, TRIP_POINT_21_3); + + private final ConstrainedTransfer TX_R1S_TO_STATION = noConstTx(ROUTE_POINT_1S, STATION_POINT); + private final ConstrainedTransfer TX_R1S_TO_B = noConstTx(ROUTE_POINT_1S, STOP_POINT_B); + private final ConstrainedTransfer TX_R1S_TO_R2B = noConstTx(ROUTE_POINT_1S, ROUTE_POINT_2B); + private final ConstrainedTransfer TX_R1S_TO_R2S = noConstTx(ROUTE_POINT_1S, ROUTE_POINT_2S); + private final ConstrainedTransfer TX_R1S_TO_T23 = noConstTx(ROUTE_POINT_1S, TRIP_POINT_21_3); + + private final ConstrainedTransfer TX_R1A_TO_STATION = noConstTx(ROUTE_POINT_1A, STATION_POINT); + private final ConstrainedTransfer TX_R1A_TO_B = noConstTx(ROUTE_POINT_1A, STOP_POINT_B); + private final ConstrainedTransfer TX_R1A_TO_R2B = noConstTx(ROUTE_POINT_1A, ROUTE_POINT_2B); + private final ConstrainedTransfer TX_R1A_TO_R2S = noConstTx(ROUTE_POINT_1A, ROUTE_POINT_2S); + private final ConstrainedTransfer TX_R1A_TO_T23 = noConstTx(ROUTE_POINT_1A, TRIP_POINT_21_3); + + private final ConstrainedTransfer TX_T11_TO_STATION = noConstTx(TRIP_POINT_11_1, STATION_POINT); + private final ConstrainedTransfer TX_T11_TO_B = noConstTx(TRIP_POINT_11_1, STOP_POINT_B); + private final ConstrainedTransfer TX_T11_TO_R2B = noConstTx(TRIP_POINT_11_1, ROUTE_POINT_2B); + private final ConstrainedTransfer TX_T11_TO_R2S = noConstTx(TRIP_POINT_11_1, ROUTE_POINT_2S); + private final ConstrainedTransfer TX_T11_TO_T23 = noConstTx(TRIP_POINT_11_1, TRIP_POINT_21_3); + + private final ConstrainedTransfer TX_NO_CONSTRAINS = noConstTx(STOP_POINT_A, STOP_POINT_B); + + private final ConstrainedTransfer TX_GUARANTIED = new ConstrainedTransfer( + null, TRIP_POINT_11_1, TRIP_POINT_21_3, GUARANTIED + ); @BeforeEach public void setup() { ROUTE_1.setShortName("L1"); ROUTE_2.setShortName("L2"); - TRIP_1.setRoute(ROUTE_1); - TRIP_2.setRoute(ROUTE_2); - TRIP_1.setRoute(ROUTE_1); - TRIP_2.setRoute(ROUTE_2); + TRIP_11.setRoute(ROUTE_1); + TRIP_21.setRoute(ROUTE_2); + TRIP_11.setRoute(ROUTE_1); + TRIP_21.setRoute(ROUTE_2); } @Test public void getSpecificityRanking() { - assertEquals(0, TX_A_TO_B.getSpecificityRanking()); - assertEquals(1, TX_R11_TO_B.getSpecificityRanking()); - assertEquals(1, TX_A_TO_R22.getSpecificityRanking()); - assertEquals(2, TX_R11_TO_R22.getSpecificityRanking()); - assertEquals(2, TX_A_TO_T23.getSpecificityRanking()); - assertEquals(3, TX_T11_TO_R22.getSpecificityRanking()); - assertEquals(4, TX_T11_TO_T22.getSpecificityRanking()); + assertEquals(0, TX_STATION_TO_STATION.getSpecificityRanking()); + assertEquals(10, TX_STATION_TO_B.getSpecificityRanking()); + assertEquals(20, TX_STATION_TO_R2S.getSpecificityRanking()); + assertEquals(30, TX_STATION_TO_R2B.getSpecificityRanking()); + assertEquals(40, TX_STATION_TO_T23.getSpecificityRanking()); + + assertEquals(11, TX_A_TO_STATION.getSpecificityRanking()); + assertEquals(21, TX_A_TO_B.getSpecificityRanking()); + assertEquals(31, TX_A_TO_R2S.getSpecificityRanking()); + assertEquals(41, TX_A_TO_R2B.getSpecificityRanking()); + assertEquals(51, TX_A_TO_T23.getSpecificityRanking()); + + assertEquals(22, TX_R1S_TO_STATION.getSpecificityRanking()); + assertEquals(32, TX_R1S_TO_B.getSpecificityRanking()); + assertEquals(42, TX_R1S_TO_R2S.getSpecificityRanking()); + assertEquals(52, TX_R1S_TO_R2B.getSpecificityRanking()); + assertEquals(62, TX_R1S_TO_T23.getSpecificityRanking()); + + assertEquals(33, TX_R1A_TO_STATION.getSpecificityRanking()); + assertEquals(43, TX_R1A_TO_B.getSpecificityRanking()); + assertEquals(53, TX_R1A_TO_R2S.getSpecificityRanking()); + assertEquals(63, TX_R1A_TO_R2B.getSpecificityRanking()); + assertEquals(73, TX_R1A_TO_T23.getSpecificityRanking()); + + assertEquals(44, TX_T11_TO_STATION.getSpecificityRanking()); + assertEquals(54, TX_T11_TO_B.getSpecificityRanking()); + assertEquals(64, TX_T11_TO_R2S.getSpecificityRanking()); + assertEquals(74, TX_T11_TO_R2B.getSpecificityRanking()); + assertEquals(84, TX_T11_TO_T23.getSpecificityRanking()); } @Test public void testOtherAccessors() { - assertEquals(STOP_POINT_A, TX_A_TO_R22.getFrom()); - assertEquals(ROUTE_POINT_22, TX_A_TO_R22.getTo()); + assertEquals(STOP_POINT_A, TX_A_TO_R2B.getFrom()); + assertEquals(ROUTE_POINT_2B, TX_A_TO_R2B.getTo()); } @Test @@ -60,8 +120,12 @@ public void noConstraints() { @Test public void testToString() { assertEquals( - "ConstrainedTransfer{from: (stop: F:A), to: (stop: F:B), constraint: {no constraints}}", + "ConstrainedTransfer{from: <Stop F:A>, to: <Stop F:B>, constraint: {no constraints}}", TX_A_TO_B.toString() ); } + + private static ConstrainedTransfer noConstTx(TransferPoint s, TransferPoint t) { + return new ConstrainedTransfer(null, s, t, NO_CONSTRAINS); + } } \ No newline at end of file diff --git a/src/test/java/org/opentripplanner/model/transfer/TransferConstraintTest.java b/src/test/java/org/opentripplanner/model/transfer/TransferConstraintTest.java index fd005c544f5..b2dbedfc8a0 100644 --- a/src/test/java/org/opentripplanner/model/transfer/TransferConstraintTest.java +++ b/src/test/java/org/opentripplanner/model/transfer/TransferConstraintTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.Test; import org.opentripplanner.util.time.DurationUtils; -public class TransferConstraintTest implements TransferTestData { +public class TransferConstraintTest { public static final int MAX_WAIT_TIME_ONE_HOUR = DurationUtils.duration("1h"); @@ -16,6 +16,7 @@ public class TransferConstraintTest implements TransferTestData { private final TransferConstraint RECOMMENDED = TransferConstraint.create().recommended().build(); private final TransferConstraint STAY_SEATED = TransferConstraint.create().staySeated().build(); private final TransferConstraint GUARANTIED = TransferConstraint.create().guaranteed().build(); + private final TransferConstraint NOT_ALLOWED = TransferConstraint.create().notAllowed().build(); private final TransferConstraint MAX_WAIT_TIME = TransferConstraint.create() .guaranteed().maxWaitTime(MAX_WAIT_TIME_ONE_HOUR).build(); private final TransferConstraint EVERYTHING = TransferConstraint.create() @@ -44,6 +45,22 @@ public void isFacilitated() { assertTrue(GUARANTIED.isFacilitated()); assertTrue(STAY_SEATED.isFacilitated()); assertFalse(NO_CONSTRAINS.isFacilitated()); + assertFalse(NOT_ALLOWED.isFacilitated()); + } + + @Test + public void useInRaptorRouting() { + assertTrue(GUARANTIED.useInRaptorRouting()); + assertTrue(STAY_SEATED.useInRaptorRouting()); + assertFalse(NO_CONSTRAINS.useInRaptorRouting()); + assertTrue(NOT_ALLOWED.useInRaptorRouting()); + } + + @Test + public void isNotAllowed() { + assertTrue(NOT_ALLOWED.isNotAllowed()); + assertFalse(GUARANTIED.isNotAllowed()); + assertFalse(NO_CONSTRAINS.isNotAllowed()); } @Test @@ -62,12 +79,12 @@ public void cost() { @Test public void noConstraints() { - assertTrue(NO_CONSTRAINS.noConstraints()); - assertFalse(STAY_SEATED.noConstraints()); - assertFalse(GUARANTIED.noConstraints()); - assertFalse(RECOMMENDED.noConstraints()); - assertFalse(MAX_WAIT_TIME.noConstraints()); - assertFalse(EVERYTHING.noConstraints()); + assertTrue(NO_CONSTRAINS.isRegularTransfer()); + assertFalse(STAY_SEATED.isRegularTransfer()); + assertFalse(GUARANTIED.isRegularTransfer()); + assertFalse(RECOMMENDED.isRegularTransfer()); + assertFalse(MAX_WAIT_TIME.isRegularTransfer()); + assertFalse(EVERYTHING.isRegularTransfer()); } @Test diff --git a/src/test/java/org/opentripplanner/model/transfer/TransferPointMapTest.java b/src/test/java/org/opentripplanner/model/transfer/TransferPointMapTest.java new file mode 100644 index 00000000000..49cfc826b24 --- /dev/null +++ b/src/test/java/org/opentripplanner/model/transfer/TransferPointMapTest.java @@ -0,0 +1,71 @@ +package org.opentripplanner.model.transfer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.model.transfer.TransferTestData.ANY_STOP; +import static org.opentripplanner.model.transfer.TransferTestData.POS_1; +import static org.opentripplanner.model.transfer.TransferTestData.POS_2; +import static org.opentripplanner.model.transfer.TransferTestData.POS_3; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_1A; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_1S; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_2B; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_2S; +import static org.opentripplanner.model.transfer.TransferTestData.STATION; +import static org.opentripplanner.model.transfer.TransferTestData.STATION_POINT; +import static org.opentripplanner.model.transfer.TransferTestData.STOP_A; +import static org.opentripplanner.model.transfer.TransferTestData.STOP_B; +import static org.opentripplanner.model.transfer.TransferTestData.STOP_POINT_A; +import static org.opentripplanner.model.transfer.TransferTestData.STOP_POINT_B; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_11; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_21; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_POINT_11_1; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_POINT_21_3; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class TransferPointMapTest { + private static final int ANY_STOP_POS = 999; + + final TransferPointMap<String> subject = new TransferPointMap<>(); + + @BeforeEach + void setup() { + STOP_A.setParentStation(STATION); + } + + @Test + void addAndGetEmptyMap() { + assertEquals(List.of(), subject.get(TRIP_11, STOP_A, POS_1)); + } + + @Test + void addAndGet() { + subject.put(TRIP_POINT_11_1, "A"); + subject.put(TRIP_POINT_21_3, "B"); + subject.put(ROUTE_POINT_1A, "C"); + subject.put(ROUTE_POINT_2B, "D"); + subject.put(ROUTE_POINT_1S, "E"); + subject.put(ROUTE_POINT_2S, "F"); + subject.put(STOP_POINT_A, "G"); + subject.put(STOP_POINT_B, "H"); + subject.put(STATION_POINT, "I"); + + assertEquals(List.of("A", "C", "E", "G", "I"), subject.get(TRIP_11, STOP_A, POS_1)); + assertEquals(List.of("F", "G", "I"), subject.get(TRIP_21, STOP_A, ANY_STOP_POS)); + assertEquals(List.of("D", "H"), subject.get(TRIP_21, STOP_B, POS_2)); + assertEquals(List.of("B"), subject.get(TRIP_21, ANY_STOP, POS_3)); + } + + @Test + void computeIfAbsent() { + assertEquals("A", subject.computeIfAbsent(TRIP_POINT_11_1, () -> "A")); + assertEquals("B", subject.computeIfAbsent(ROUTE_POINT_1A, () -> "B")); + assertEquals("C", subject.computeIfAbsent(STOP_POINT_B, () -> "C")); + assertEquals("D", subject.computeIfAbsent(STATION_POINT, () -> "D")); + assertEquals("E", subject.computeIfAbsent(ROUTE_POINT_1S, () -> "E")); + + assertEquals(List.of("A", "B", "E", "D"), subject.get(TRIP_11, STOP_A, POS_1)); + assertEquals(List.of("C"), subject.get(TRIP_21, STOP_B, POS_2)); + } +} \ No newline at end of file diff --git a/src/test/java/org/opentripplanner/model/transfer/TransferPointTest.java b/src/test/java/org/opentripplanner/model/transfer/TransferPointTest.java index 5f617f517cc..e5f5e654e8c 100644 --- a/src/test/java/org/opentripplanner/model/transfer/TransferPointTest.java +++ b/src/test/java/org/opentripplanner/model/transfer/TransferPointTest.java @@ -1,77 +1,97 @@ package org.opentripplanner.model.transfer; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNull; - -import org.junit.Test; - -public class TransferPointTest implements TransferTestData { - - public static final int STOP_POSITION_1 = 1; - public static final int STOP_POSITION_2 = 2; - - private final TransferPoint otherPoint = new RouteTransferPoint(ROUTE_2, TRIP_2, STOP_POSITION_2); +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.transfer.TransferTestData.POS_1; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_1; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_2; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_1A; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_1S; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_2B; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_2S; +import static org.opentripplanner.model.transfer.TransferTestData.STATION; +import static org.opentripplanner.model.transfer.TransferTestData.STATION_POINT; +import static org.opentripplanner.model.transfer.TransferTestData.STOP_A; +import static org.opentripplanner.model.transfer.TransferTestData.STOP_B; +import static org.opentripplanner.model.transfer.TransferTestData.STOP_POINT_A; +import static org.opentripplanner.model.transfer.TransferTestData.STOP_POINT_B; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_11; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_POINT_11_1; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_POINT_21_3; + +import java.util.List; +import org.junit.jupiter.api.Test; + +public class TransferPointTest { + @Test + public void getStation() { + assertEquals(STATION, STATION_POINT.asStationTransferPoint().getStation()); + assertEquals(STATION, ROUTE_POINT_1S.asRouteStationTransferPoint().getStation()); + } @Test public void getStop() { - assertEquals(STOP_A, STOP_POINT_A.getStop()); - assertNull(TRIP_POINT_11.getStop()); - assertNull(ROUTE_POINT_11.getStop()); + assertEquals(STOP_A, STOP_POINT_A.asStopTransferPoint().getStop()); + assertEquals(STOP_A, ROUTE_POINT_1A.asRouteStopTransferPoint().getStop()); + assertEquals(STOP_B, STOP_POINT_B.asStopTransferPoint().getStop()); + assertEquals(STOP_B, ROUTE_POINT_2B.asRouteStopTransferPoint().getStop()); + } + + @Test + public void getRoute() { + assertEquals(ROUTE_1, ROUTE_POINT_1S.asRouteStationTransferPoint().getRoute()); + assertEquals(ROUTE_1, ROUTE_POINT_1A.asRouteStopTransferPoint().getRoute()); + assertEquals(ROUTE_2, ROUTE_POINT_2S.asRouteStationTransferPoint().getRoute()); + assertEquals(ROUTE_2, ROUTE_POINT_2B.asRouteStopTransferPoint().getRoute()); } @Test public void getTrip() { - assertNull(STOP_POINT_A.getTrip()); - assertEquals(TRIP_1, TRIP_POINT_11.getTrip()); - assertEquals(TRIP_1, ROUTE_POINT_11.getTrip()); + assertEquals(TRIP_11, TRIP_POINT_11_1.asTripTransferPoint().getTrip()); } @Test public void getStopPosition() { - assertEquals(TransferPoint.NOT_AVAILABLE, STOP_POINT_A.getStopPosition()); - assertEquals(STOP_POSITION_1, TRIP_POINT_11.getStopPosition()); - assertEquals(STOP_POSITION_1, ROUTE_POINT_11.getStopPosition()); + assertEquals(POS_1, TRIP_POINT_11_1.asTripTransferPoint().getStopPositionInPattern()); } @Test public void getSpecificityRanking() { - assertEquals(0, STOP_POINT_A.getSpecificityRanking()); - assertEquals(1, ROUTE_POINT_11.getSpecificityRanking()); - assertEquals(2, TRIP_POINT_11.getSpecificityRanking()); + assertEquals(0, STATION_POINT.getSpecificityRanking()); + assertEquals(1, STOP_POINT_A.getSpecificityRanking()); + assertEquals(2, ROUTE_POINT_1S.getSpecificityRanking()); + assertEquals(3, ROUTE_POINT_1A.getSpecificityRanking()); + assertEquals(4, TRIP_POINT_11_1.getSpecificityRanking()); } @Test - public void equalsAndHashCode() { - // A STOP_POINT_A should never match a route or trip point - assertNotEquals(STOP_POINT_A, ROUTE_POINT_11); - assertNotEquals(STOP_POINT_A, TRIP_POINT_11); - assertNotEquals(ROUTE_POINT_11, STOP_POINT_A); - assertNotEquals(TRIP_POINT_11, STOP_POINT_A); - - assertNotEquals(STOP_POINT_A.hashCode(), ROUTE_POINT_11.hashCode()); - assertNotEquals(STOP_POINT_A.hashCode(), TRIP_POINT_11.hashCode()); - assertNotEquals(ROUTE_POINT_11.hashCode(), STOP_POINT_A.hashCode()); - assertNotEquals(TRIP_POINT_11.hashCode(), STOP_POINT_A.hashCode()); - - // If the trip and stopPosition is the same then trip and route point should match - assertEquals(TRIP_POINT_11, ROUTE_POINT_11); - assertEquals(ROUTE_POINT_11, TRIP_POINT_11); - - assertEquals(TRIP_POINT_11.hashCode(), ROUTE_POINT_11.hashCode()); - assertEquals(ROUTE_POINT_11.hashCode(), TRIP_POINT_11.hashCode()); + public void isNnnTransferPoint() { + List.of(STATION_POINT, STOP_POINT_A, ROUTE_POINT_1A, ROUTE_POINT_1S, TRIP_POINT_11_1).forEach( p -> { + assertEquals(p == STATION_POINT, p.isStationTransferPoint()); + assertEquals(p == STOP_POINT_A, p.isStopTransferPoint()); + assertEquals(p == ROUTE_POINT_1A, p.isRouteStopTransferPoint()); + assertEquals(p == ROUTE_POINT_1S, p.isRouteStationTransferPoint()); + assertEquals(p == TRIP_POINT_11_1, p.isTripTransferPoint()); + }); + } - assertNotEquals(TRIP_POINT_11, otherPoint); - assertNotEquals(ROUTE_POINT_11, otherPoint); - assertNotEquals(TRIP_POINT_11.hashCode(), otherPoint.hashCode()); - assertNotEquals(ROUTE_POINT_11.hashCode(), otherPoint.hashCode()); + @Test + public void applyToAllTrips() { + assertTrue(STATION_POINT.appliesToAllTrips()); + assertTrue(STOP_POINT_A.appliesToAllTrips()); + assertTrue(ROUTE_POINT_1A.appliesToAllTrips()); + assertTrue(ROUTE_POINT_1S.appliesToAllTrips()); + assertFalse(TRIP_POINT_11_1.appliesToAllTrips()); } @Test public void testToString() { - assertEquals("(stop: F:A)", STOP_POINT_A.toString()); - assertEquals("(route: R:1, trip: T:1, stopPos: 1)", ROUTE_POINT_11.toString()); - assertEquals("(trip: T:1, stopPos: 1)", TRIP_POINT_11.toString()); + assertEquals("<Station F:Central Station>", STATION_POINT.toString()); + assertEquals("<Stop F:A>", STOP_POINT_A.toString()); + assertEquals("<Route F:1, stop F:A>", ROUTE_POINT_1A.toString()); + assertEquals("<Route F:1, station F:Central Station>", ROUTE_POINT_1S.toString()); + assertEquals("<Trip F:21, stopPos 3>", TRIP_POINT_21_3.toString()); } } \ No newline at end of file diff --git a/src/test/java/org/opentripplanner/model/transfer/TransferServiceTest.java b/src/test/java/org/opentripplanner/model/transfer/TransferServiceTest.java index 9fd1ba7ffc8..e44b5f3008f 100644 --- a/src/test/java/org/opentripplanner/model/transfer/TransferServiceTest.java +++ b/src/test/java/org/opentripplanner/model/transfer/TransferServiceTest.java @@ -1,43 +1,85 @@ package org.opentripplanner.model.transfer; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.model.transfer.TransferTestData.ANY_POS; +import static org.opentripplanner.model.transfer.TransferTestData.ANY_TRIP; +import static org.opentripplanner.model.transfer.TransferTestData.POS_1; +import static org.opentripplanner.model.transfer.TransferTestData.POS_3; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_1A; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_1S; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_2B; +import static org.opentripplanner.model.transfer.TransferTestData.ROUTE_POINT_2S; +import static org.opentripplanner.model.transfer.TransferTestData.STATION; +import static org.opentripplanner.model.transfer.TransferTestData.STATION_POINT; +import static org.opentripplanner.model.transfer.TransferTestData.STOP_A; +import static org.opentripplanner.model.transfer.TransferTestData.STOP_B; +import static org.opentripplanner.model.transfer.TransferTestData.STOP_POINT_A; +import static org.opentripplanner.model.transfer.TransferTestData.STOP_POINT_B; +import static org.opentripplanner.model.transfer.TransferTestData.STOP_S; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_11; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_12; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_21; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_POINT_11_1; +import static org.opentripplanner.model.transfer.TransferTestData.TRIP_POINT_21_3; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -public class TransferServiceTest implements TransferTestData { +public class TransferServiceTest { private final TransferService subject = new TransferService(); + @BeforeEach + public void setup() { + STOP_A.setParentStation(STATION); + } @Test - public void addOneTransferForEachCombinationOfFromToTypesAndRetriveEachOfThem() { - // Given: - var A = transfer(STOP_POINT_A, STOP_POINT_B); - var B = transfer(STOP_POINT_A, TRIP_POINT_23); - var C = transfer(ROUTE_POINT_11, STOP_POINT_B); - var D = transfer(TRIP_POINT_11, ROUTE_POINT_22); - var E = transfer(TRIP_POINT_11, TRIP_POINT_23); + public void findTransfer() { + // Given: // Ranking + var A = transfer(TRIP_POINT_11_1, TRIP_POINT_21_3); // 84 + var B = transfer(TRIP_POINT_11_1, ROUTE_POINT_2B); // 74 + var C = transfer(TRIP_POINT_11_1, ROUTE_POINT_2S); // 64 + var D = transfer(STOP_POINT_A, TRIP_POINT_21_3); // 51 + var E = transfer(ROUTE_POINT_1A, STOP_POINT_B); // 43 + var F = transfer(ROUTE_POINT_1S, STOP_POINT_B); // 32 + var G = transfer(STATION_POINT, ROUTE_POINT_2B); // 30 + var H = transfer(STATION_POINT, ROUTE_POINT_2S); // 20 + var I = transfer(STATION_POINT, STATION_POINT); // 11 // When: all transfers is added to service - subject.addAll(List.of(A, B, C, D, E)); + subject.addAll(List.of(A, B, C, D, E, F, G, H, I)); + + // Then: + + // Find the most specific transfer TRIP to TRIP + // Trip and stop position must match, stops are ignored + assertEquals(A, subject.findTransfer(TRIP_11, POS_1, STOP_A, TRIP_21, POS_3, STOP_B)); + + // Find the TRIP to ROUTE+STOP transfer when TO stop position does not match + assertEquals(B, subject.findTransfer(TRIP_11, POS_1, STOP_A, TRIP_21, ANY_POS, STOP_B)); + + // Find the TRIP to ROUTE+STATION transfer when TO stop does not match + assertEquals(C, subject.findTransfer(TRIP_11, POS_1, STOP_A, TRIP_21, ANY_POS, STOP_S)); - /* THEN */ + // Find transfer: STOP to TRIP when FROM trip does not match + assertEquals(D, subject.findTransfer(TRIP_12, POS_1, STOP_A, TRIP_21, POS_3, STOP_B)); - // Find the most specific transfer, Trip and stop position match - stops is ignored - assertEquals(D, subject.findTransfer(STOP_A, STOP_B, TRIP_1, TRIP_2, 1, 2)); + // Find the ROUTE+STOP to STOP transfer when FROM trip and TO stopPos do not match + assertEquals(E, subject.findTransfer(TRIP_12, POS_1, STOP_A, TRIP_21, ANY_POS, STOP_B)); - // Find the another specific transfer with the stop position changed - assertEquals(E, subject.findTransfer(STOP_A, STOP_B, TRIP_1, TRIP_2, 1, 3)); + // Find the ROUTE+STATION to STOP transfer when FROM stop position does not match + assertEquals(F, subject.findTransfer(TRIP_11, ANY_POS, STOP_S, TRIP_21, POS_3, STOP_B)); - // Find the specific transfer: TRIP -> STOP when stop position do not match TO point - assertEquals(C, subject.findTransfer(STOP_A, STOP_B, TRIP_1, TRIP_2, 1, 7)); + // Find STOP to STOP transfer, when FROM trip and TO stop position do not match + assertEquals(G, subject.findTransfer(ANY_TRIP, POS_1, STOP_A, TRIP_21, ANY_POS, STOP_B)); - // Find the specific transfer: STOP -> TRIP when stop position do not match FROM point - assertEquals(B, subject.findTransfer(STOP_A, STOP_B, TRIP_1, TRIP_2, 7, 3)); + // Find STATION to ROUTE+STATION when FROM trip and route and TO Stop do not match + assertEquals(H, subject.findTransfer(ANY_TRIP, ANY_POS, STOP_S, TRIP_21, POS_3, STOP_S)); - // Stop position fall back to STOP -> STOP when stop position do not match - assertEquals(A, subject.findTransfer(STOP_A, STOP_B, TRIP_1, TRIP_2, 7, 7)); + // Find STATION to STATION when there are no match for FROM/TO trips and patterns + assertEquals(I, subject.findTransfer(ANY_TRIP, POS_1, STOP_S, ANY_TRIP, ANY_POS, STOP_S)); } @Test @@ -45,12 +87,26 @@ public void addSameTransferTwiceRetrieveFirstAdded() { var A = transfer(STOP_POINT_A, STOP_POINT_B); var A_EQ = transfer(STOP_POINT_A, STOP_POINT_B); - // Adding two transfers between the same stops, will result in only the first being kept + // Adding two transfers between the same stops + // should result in only the first being added subject.addAll(List.of(A, A_EQ)); - assertEquals(A, subject.findTransfer(STOP_A, STOP_B, TRIP_1, TRIP_2, 1, 2)); + assertEquals(List.of(A), subject.listAll()); } + @Test + public void listAll() { + // Given: + var A = transfer(STATION_POINT, ROUTE_POINT_1A); + var B = transfer(STOP_POINT_A, STOP_POINT_B); + var C = transfer(STOP_POINT_A, TRIP_POINT_21_3); + + // When: all transfers is added to service + subject.addAll(List.of(A, B, C)); + + // Then + assertEquals(List.of(A, B, C), subject.listAll()); + } ConstrainedTransfer transfer(TransferPoint from, TransferPoint to) { var c = TransferConstraint.create().build(); diff --git a/src/test/java/org/opentripplanner/model/transfer/TransferTestData.java b/src/test/java/org/opentripplanner/model/transfer/TransferTestData.java index 532a889ccf0..f8e198a0060 100644 --- a/src/test/java/org/opentripplanner/model/transfer/TransferTestData.java +++ b/src/test/java/org/opentripplanner/model/transfer/TransferTestData.java @@ -2,32 +2,69 @@ import org.opentripplanner.model.FeedScopedId; import org.opentripplanner.model.Route; +import org.opentripplanner.model.Station; import org.opentripplanner.model.Stop; import org.opentripplanner.model.Trip; -public interface TransferTestData { - Stop STOP_A = Stop.stopForTest("A", 60.0, 11.0); - Stop STOP_B = Stop.stopForTest("B", 60.0, 11.0); +public class TransferTestData { + static final String FEED_ID = "F"; - Route ROUTE_1 = new Route(new FeedScopedId("R", "1")); - Route ROUTE_2 = new Route(new FeedScopedId("R", "2")); + static final Station STATION = Station.stationForTest("Central Station", 60.0, 11.0); - Trip TRIP_1 = createTrip("1", ROUTE_1); - Trip TRIP_2 = createTrip("2", ROUTE_2); + static final int POS_1 = 1; + static final int POS_2 = 2; + static final int POS_3 = 3; + static final int ANY_POS = 999; - TransferPoint STOP_POINT_A = new StopTransferPoint(STOP_A); - TransferPoint STOP_POINT_B = new StopTransferPoint(STOP_B); + static final Stop STOP_A = Stop.stopForTest("A", 60.0, 11.0); + static final Stop STOP_B = Stop.stopForTest("B", 60.0, 11.0); + static final Stop STOP_S = Stop.stopForTest("S", 60.0, 11.0); + static final Stop ANY_STOP = Stop.stopForTest("any", 60.0, 11.0); - TransferPoint ROUTE_POINT_11 = new RouteTransferPoint(ROUTE_1, TRIP_1, 1); - TransferPoint ROUTE_POINT_22 = new RouteTransferPoint(ROUTE_2, TRIP_2, 2); + static final Route ROUTE_1 = createRoute(1, "L1"); + static final Route ROUTE_2 = createRoute(2, "L2"); + static final Route ANY_ROUTE = createRoute(999, "any"); - TransferPoint TRIP_POINT_11 = new TripTransferPoint(TRIP_1, 1); - TransferPoint TRIP_POINT_23 = new TripTransferPoint(TRIP_2, 3); + static final Trip TRIP_11 = createTrip(11, ROUTE_1); + static final Trip TRIP_12 = createTrip(12, ROUTE_1); + static final Trip TRIP_21 = createTrip(21, ROUTE_2); + static final Trip TRIP_22 = createTrip(22, ROUTE_2); + static final Trip ANY_TRIP = createTrip(999, ANY_ROUTE); + static final TransferPoint STATION_POINT = new StationTransferPoint(STATION); - private static Trip createTrip(String id, Route route) { - Trip t = new Trip(new FeedScopedId("T", id)); + static final TransferPoint STOP_POINT_A = new StopTransferPoint(STOP_A); + static final TransferPoint STOP_POINT_B = new StopTransferPoint(STOP_B); + + static final TransferPoint ROUTE_POINT_1S = new RouteStationTransferPoint(ROUTE_1, STATION); + static final TransferPoint ROUTE_POINT_2S = new RouteStationTransferPoint(ROUTE_2, STATION); + + static final TransferPoint ROUTE_POINT_1A = new RouteStopTransferPoint(ROUTE_1, STOP_A); + static final TransferPoint ROUTE_POINT_2B = new RouteStopTransferPoint(ROUTE_2, STOP_B); + + static final TransferPoint TRIP_POINT_11_1 = new TripTransferPoint(TRIP_11, POS_1); + static final TransferPoint TRIP_POINT_21_3 = new TripTransferPoint(TRIP_21, POS_3); + + static { + STATION.addChildStop(STOP_A); + STOP_A.setParentStation(STATION); + STATION.addChildStop(STOP_S); + STOP_S.setParentStation(STATION); + } + + private static Trip createTrip(int id, Route route) { + Trip t = new Trip(createId(id)); t.setRoute(route); return t; } + + private static Route createRoute(int id, String name) { + Route r = new Route(createId(id)); + r.setShortName(name); + return r; + } + + private static FeedScopedId createId(int id) { + return new FeedScopedId(FEED_ID, String.valueOf(id)); + } } diff --git a/src/test/java/org/opentripplanner/netex/mapping/StopTransferPriorityMapperTest.java b/src/test/java/org/opentripplanner/netex/mapping/StopTransferPriorityMapperTest.java index 0f91b91ea9b..d2345598b91 100644 --- a/src/test/java/org/opentripplanner/netex/mapping/StopTransferPriorityMapperTest.java +++ b/src/test/java/org/opentripplanner/netex/mapping/StopTransferPriorityMapperTest.java @@ -13,23 +13,23 @@ public class StopTransferPriorityMapperTest { @Test public void mapToDomain() { - assertNull(TransferPriorityMapper.mapToDomain(null)); + assertNull(StopTransferPriorityMapper.mapToDomain(null)); assertEquals( StopTransferPriority.DISCOURAGED, - TransferPriorityMapper.mapToDomain(InterchangeWeightingEnumeration.NO_INTERCHANGE) + StopTransferPriorityMapper.mapToDomain(InterchangeWeightingEnumeration.NO_INTERCHANGE) ); assertEquals( StopTransferPriority.ALLOWED, - TransferPriorityMapper.mapToDomain(InterchangeWeightingEnumeration.INTERCHANGE_ALLOWED) + StopTransferPriorityMapper.mapToDomain(InterchangeWeightingEnumeration.INTERCHANGE_ALLOWED) ); assertEquals( StopTransferPriority.PREFERRED, - TransferPriorityMapper.mapToDomain(InterchangeWeightingEnumeration.PREFERRED_INTERCHANGE) + StopTransferPriorityMapper.mapToDomain(InterchangeWeightingEnumeration.PREFERRED_INTERCHANGE) ); assertEquals( StopTransferPriority.RECOMMENDED, - TransferPriorityMapper.mapToDomain(InterchangeWeightingEnumeration.RECOMMENDED_INTERCHANGE) + StopTransferPriorityMapper.mapToDomain(InterchangeWeightingEnumeration.RECOMMENDED_INTERCHANGE) ); } } \ No newline at end of file diff --git a/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java b/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java index fca09a102b4..eb5e7486e9d 100644 --- a/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java +++ b/src/test/java/org/opentripplanner/netex/mapping/TripPatternMapperTest.java @@ -3,12 +3,10 @@ import static org.junit.Assert.assertEquals; import java.util.Collections; -import java.util.List; import java.util.Map; import org.junit.Test; import org.opentripplanner.graph_builder.DataImportIssueStore; import org.opentripplanner.model.FeedScopedId; -import org.opentripplanner.model.Stop; import org.opentripplanner.model.Trip; import org.opentripplanner.model.TripPattern; import org.opentripplanner.model.impl.EntityById; @@ -52,19 +50,18 @@ public void testMapTripPattern() { assertEquals(1, r.tripPatterns.size()); - TripPattern tripPattern = r.tripPatterns.get(0); + TripPattern tripPattern = r.tripPatterns.values().stream().findFirst().orElseThrow(); - assertEquals(4, tripPattern.getStops().size()); + assertEquals(4, tripPattern.numberOfStops()); assertEquals(1, tripPattern.getTrips().size()); - var stops = tripPattern.getStops(); Trip trip = tripPattern.getTrips().get(0); assertEquals("RUT:ServiceJourney:1", trip.getId().getId()); - assertEquals("NSR:Quay:1", stops.get(0).getId().getId()); - assertEquals("NSR:Quay:2", stops.get(1).getId().getId()); - assertEquals("NSR:Quay:3", stops.get(2).getId().getId()); - assertEquals("NSR:Quay:4", stops.get(3).getId().getId()); + assertEquals("NSR:Quay:1", tripPattern.getStop(0).getId().getId()); + assertEquals("NSR:Quay:2", tripPattern.getStop(1).getId().getId()); + assertEquals("NSR:Quay:3", tripPattern.getStop(2).getId().getId()); + assertEquals("NSR:Quay:4", tripPattern.getStop(3).getId().getId()); assertEquals(1, tripPattern.getScheduledTimetable().getTripTimes().size()); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/TransitSnapshotTest.snap b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/TransitSnapshotTest.snap index 6e07f7dd0db..4ff04b5c220 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/TransitSnapshotTest.snap +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/TransitSnapshotTest.snap @@ -2357,8 +2357,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "agencyUrl": "http://trimet.org", "arrivalDelay": 0, "departureDelay": 0, - "distance": 2347.5602438577907, - "endTime": "2009-11-17T18:52:12.000+00:00", + "distance": 2035.619484953424, + "endTime": "2009-11-17T18:51:01.000+00:00", "from": { "arrival": "2009-11-17T18:42:54.000+00:00", "departure": "2009-11-17T18:42:54.000+00:00", @@ -2372,7 +2372,7 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "vertexType": "TRANSIT", "zoneId": "1" }, - "generalizedCost": 1158, + "generalizedCost": 1087, "headsign": "Rose Qtr TC", "interlineWithPreviousLeg": false, "intermediateStops": [ @@ -2466,37 +2466,11 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "stopSequence": 39, "vertexType": "TRANSIT", "zoneId": "0" - }, - { - "arrival": "2009-11-17T18:51:01.000+00:00", - "departure": "2009-11-17T18:51:01.000+00:00", - "lat": 45.531569, - "lon": -122.659045, - "name": "NE Multnomah & 7th", - "stopCode": "4054", - "stopId": "prt:4054", - "stopIndex": 39, - "stopSequence": 40, - "vertexType": "TRANSIT", - "zoneId": "0" - }, - { - "arrival": "2009-11-17T18:51:27.000+00:00", - "departure": "2009-11-17T18:51:27.000+00:00", - "lat": 45.531586, - "lon": -122.660482, - "name": "NE Multnomah & Grand", - "stopCode": "4043", - "stopId": "prt:4043", - "stopIndex": 40, - "stopSequence": 41, - "vertexType": "TRANSIT", - "zoneId": "0" } ], "legGeometry": { - "length": 62, - "points": "s`ytGhxrkV[?mCAmC?wBA??W?mC?{BA??Q?oC?mC?kBAa@?w@???wA?mCAmC?oCA}C?sDC??aBAm@@k@AY?uABU@I@IBQFb@fC}@d@OFO@q@???Q?]?gGA??[??nJ???b@?vK?rA???tBCfD???^?nE?V@Z?PH\\Nb@`@~@Rf@" + "length": 49, + "points": "s`ytGhxrkV[?mCAmC?wBA??W?mC?{BA??Q?oC?mC?kBAa@?w@???wA?mCAmC?oCA}C?sDC??aBAm@@k@AY?uABU@I@IBQFb@fC}@d@OFO@q@???Q?]?gGA??[??nJ???b@?vK?rA" }, "mode": "BUS", "pathway": false, @@ -2509,15 +2483,15 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "startTime": "2009-11-17T18:42:54.000+00:00", "steps": [ ], "to": { - "arrival": "2009-11-17T18:52:12.000+00:00", - "departure": "2009-11-17T18:56:09.000+00:00", - "lat": 45.531159, - "lon": -122.66293, - "name": "NE Multnomah & 3rd", - "stopCode": "11492", - "stopId": "prt:11492", - "stopIndex": 41, - "stopSequence": 42, + "arrival": "2009-11-17T18:51:01.000+00:00", + "departure": "2009-11-17T18:54:29.000+00:00", + "lat": 45.531569, + "lon": -122.659045, + "name": "NE Multnomah & 7th", + "stopCode": "4054", + "stopId": "prt:4054", + "stopIndex": 39, + "stopSequence": 40, "vertexType": "TRANSIT", "zoneId": "0" }, @@ -2532,25 +2506,51 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "agencyUrl": "http://trimet.org", "arrivalDelay": 0, "departureDelay": 0, - "distance": 3248.2996826174576, + "distance": 3560.2404415218243, "endTime": "2009-11-17T19:07:55.000+00:00", "from": { - "arrival": "2009-11-17T18:52:12.000+00:00", - "departure": "2009-11-17T18:56:09.000+00:00", - "lat": 45.531159, - "lon": -122.66293, - "name": "NE Multnomah & 3rd", - "stopCode": "11492", - "stopId": "prt:11492", - "stopIndex": 83, - "stopSequence": 84, + "arrival": "2009-11-17T18:51:01.000+00:00", + "departure": "2009-11-17T18:54:29.000+00:00", + "lat": 45.531569, + "lon": -122.659045, + "name": "NE Multnomah & 7th", + "stopCode": "4054", + "stopId": "prt:4054", + "stopIndex": 81, + "stopSequence": 82, "vertexType": "TRANSIT", "zoneId": "0" }, - "generalizedCost": 1543, + "generalizedCost": 1614, "headsign": "Montgomery Park", "interlineWithPreviousLeg": false, "intermediateStops": [ + { + "arrival": "2009-11-17T18:55:05.000+00:00", + "departure": "2009-11-17T18:55:05.000+00:00", + "lat": 45.531586, + "lon": -122.660482, + "name": "NE Multnomah & Grand", + "stopCode": "4043", + "stopId": "prt:4043", + "stopIndex": 82, + "stopSequence": 83, + "vertexType": "TRANSIT", + "zoneId": "0" + }, + { + "arrival": "2009-11-17T18:56:09.000+00:00", + "departure": "2009-11-17T18:56:09.000+00:00", + "lat": 45.531159, + "lon": -122.66293, + "name": "NE Multnomah & 3rd", + "stopCode": "11492", + "stopId": "prt:11492", + "stopIndex": 83, + "stopSequence": 84, + "vertexType": "TRANSIT", + "zoneId": "0" + }, { "arrival": "2009-11-17T18:58:00.000+00:00", "departure": "2009-11-17T18:58:00.000+00:00", @@ -2657,8 +2657,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan } ], "legGeometry": { - "length": 91, - "points": "kx{tG~qtkV`@bANb@FV@R?P?pE?jA@h@AnAbBl@LFJN\\f@LT??NXJPPVJFf@Vf@Pp@Nd@NRLB@RNXZR\\vAhC@BhAhD`AhClAbDBrDCnG@n@@^@d@HdAP`CBjEDvD???LqCFmCDYBGDEBGJkAzAQR??KNa@b@MJuBBY?OHW@u@~@aD`EcBhBBrD@xC??@l@BlE@lD???XBjEBpD???VBlE?dA@t@?b@?h@BfEBrD???VBhEFtKDvJ??@\\DnJ" + "length": 104, + "points": "yz{tG`zskV?tBCfD???^?nE?V@Z?PH\\Nb@`@~@Rf@??`@bANb@FV@R?P?pE?jA@h@AnAbBl@LFJN\\f@LT??NXJPPVJFf@Vf@Pp@Nd@NRLB@RNXZR\\vAhC@BhAhD`AhClAbDBrDCnG@n@@^@d@HdAP`CBjEDvD???LqCFmCDYBGDEBGJkAzAQR??KNa@b@MJuBBY?OHW@u@~@aD`EcBhBBrD@xC??@l@BlE@lD???XBjEBpD???VBlE?dA@t@?b@?h@BfEBrD???VBhEFtKDvJ??@\\DnJ" }, "mode": "BUS", "pathway": false, @@ -2668,7 +2668,7 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "routeLongName": "Broadway/Halsey", "routeShortName": "77", "serviceDate": "2009-11-17", - "startTime": "2009-11-17T18:56:09.000+00:00", + "startTime": "2009-11-17T18:54:29.000+00:00", "steps": [ ], "to": { "arrival": "2009-11-17T19:07:55.000+00:00", @@ -2746,8 +2746,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "startTime": "2009-11-17T18:37:30.000+00:00", "tooSloped": false, "transfers": 1, - "transitTime": 1264, - "waitingTime": 237, + "transitTime": 1293, + "waitingTime": 208, "walkDistance": 438.42099999999994, "walkLimitExceeded": false, "walkTime": 348 @@ -4438,8 +4438,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "agencyUrl": "http://trimet.org", "arrivalDelay": 0, "departureDelay": 0, - "distance": 1667.1475209896525, - "endTime": "2009-11-17T18:52:12.000+00:00", + "distance": 1355.2067620852858, + "endTime": "2009-11-17T18:51:01.000+00:00", "from": { "arrival": "2009-11-17T18:46:00.000+00:00", "departure": "2009-11-17T18:46:00.000+00:00", @@ -4453,7 +4453,7 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "vertexType": "TRANSIT", "zoneId": "1" }, - "generalizedCost": 972, + "generalizedCost": 901, "headsign": "Rose Qtr TC", "interlineWithPreviousLeg": false, "intermediateStops": [ @@ -4508,37 +4508,11 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "stopSequence": 39, "vertexType": "TRANSIT", "zoneId": "0" - }, - { - "arrival": "2009-11-17T18:51:01.000+00:00", - "departure": "2009-11-17T18:51:01.000+00:00", - "lat": 45.531569, - "lon": -122.659045, - "name": "NE Multnomah & 7th", - "stopCode": "4054", - "stopId": "prt:4054", - "stopIndex": 39, - "stopSequence": 40, - "vertexType": "TRANSIT", - "zoneId": "0" - }, - { - "arrival": "2009-11-17T18:51:27.000+00:00", - "departure": "2009-11-17T18:51:27.000+00:00", - "lat": 45.531586, - "lon": -122.660482, - "name": "NE Multnomah & Grand", - "stopCode": "4043", - "stopId": "prt:4043", - "stopIndex": 40, - "stopSequence": 41, - "vertexType": "TRANSIT", - "zoneId": "0" } ], "legGeometry": { - "length": 46, - "points": "{fztG`xrkVwA?mCAmC?oCA}C?sDC??aBAm@@k@AY?uABU@I@IBQFb@fC}@d@OFO@q@???Q?]?gGA??[??nJ???b@?vK?rA???tBCfD???^?nE?V@Z?PH\\Nb@`@~@Rf@" + "length": 33, + "points": "{fztG`xrkVwA?mCAmC?oCA}C?sDC??aBAm@@k@AY?uABU@I@IBQFb@fC}@d@OFO@q@???Q?]?gGA??[??nJ???b@?vK?rA" }, "mode": "BUS", "pathway": false, @@ -4551,15 +4525,15 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "startTime": "2009-11-17T18:46:00.000+00:00", "steps": [ ], "to": { - "arrival": "2009-11-17T18:52:12.000+00:00", - "departure": "2009-11-17T18:56:09.000+00:00", - "lat": 45.531159, - "lon": -122.66293, - "name": "NE Multnomah & 3rd", - "stopCode": "11492", - "stopId": "prt:11492", - "stopIndex": 41, - "stopSequence": 42, + "arrival": "2009-11-17T18:51:01.000+00:00", + "departure": "2009-11-17T18:54:29.000+00:00", + "lat": 45.531569, + "lon": -122.659045, + "name": "NE Multnomah & 7th", + "stopCode": "4054", + "stopId": "prt:4054", + "stopIndex": 39, + "stopSequence": 40, "vertexType": "TRANSIT", "zoneId": "0" }, @@ -4574,25 +4548,51 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "agencyUrl": "http://trimet.org", "arrivalDelay": 0, "departureDelay": 0, - "distance": 3526.7741793792093, + "distance": 3838.714938283576, "endTime": "2009-11-17T19:08:50.000+00:00", "from": { - "arrival": "2009-11-17T18:52:12.000+00:00", - "departure": "2009-11-17T18:56:09.000+00:00", - "lat": 45.531159, - "lon": -122.66293, - "name": "NE Multnomah & 3rd", - "stopCode": "11492", - "stopId": "prt:11492", - "stopIndex": 83, - "stopSequence": 84, + "arrival": "2009-11-17T18:51:01.000+00:00", + "departure": "2009-11-17T18:54:29.000+00:00", + "lat": 45.531569, + "lon": -122.659045, + "name": "NE Multnomah & 7th", + "stopCode": "4054", + "stopId": "prt:4054", + "stopIndex": 81, + "stopSequence": 82, "vertexType": "TRANSIT", "zoneId": "0" }, - "generalizedCost": 1598, + "generalizedCost": 1669, "headsign": "Montgomery Park", "interlineWithPreviousLeg": false, "intermediateStops": [ + { + "arrival": "2009-11-17T18:55:05.000+00:00", + "departure": "2009-11-17T18:55:05.000+00:00", + "lat": 45.531586, + "lon": -122.660482, + "name": "NE Multnomah & Grand", + "stopCode": "4043", + "stopId": "prt:4043", + "stopIndex": 82, + "stopSequence": 83, + "vertexType": "TRANSIT", + "zoneId": "0" + }, + { + "arrival": "2009-11-17T18:56:09.000+00:00", + "departure": "2009-11-17T18:56:09.000+00:00", + "lat": 45.531159, + "lon": -122.66293, + "name": "NE Multnomah & 3rd", + "stopCode": "11492", + "stopId": "prt:11492", + "stopIndex": 83, + "stopSequence": 84, + "vertexType": "TRANSIT", + "zoneId": "0" + }, { "arrival": "2009-11-17T18:58:00.000+00:00", "departure": "2009-11-17T18:58:00.000+00:00", @@ -4712,8 +4712,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan } ], "legGeometry": { - "length": 96, - "points": "kx{tG~qtkV`@bANb@FV@R?P?pE?jA@h@AnAbBl@LFJN\\f@LT??NXJPPVJFf@Vf@Pp@Nd@NRLB@RNXZR\\vAhC@BhAhD`AhClAbDBrDCnG@n@@^@d@HdAP`CBjEDvD???LqCFmCDYBGDEBGJkAzAQR??KNa@b@MJuBBY?OHW@u@~@aD`EcBhBBrD@xC??@l@BlE@lD???XBjEBpD???VBlE?dA@t@?b@?h@BfEBrD???VBhEFtKDvJ??@\\DnJ???d@FtKmCBo@@" + "length": 109, + "points": "yz{tG`zskV?tBCfD???^?nE?V@Z?PH\\Nb@`@~@Rf@??`@bANb@FV@R?P?pE?jA@h@AnAbBl@LFJN\\f@LT??NXJPPVJFf@Vf@Pp@Nd@NRLB@RNXZR\\vAhC@BhAhD`AhClAbDBrDCnG@n@@^@d@HdAP`CBjEDvD???LqCFmCDYBGDEBGJkAzAQR??KNa@b@MJuBBY?OHW@u@~@aD`EcBhBBrD@xC??@l@BlE@lD???XBjEBpD???VBlE?dA@t@?b@?h@BfEBrD???VBhEFtKDvJ??@\\DnJ???d@FtKmCBo@@" }, "mode": "BUS", "pathway": false, @@ -4723,7 +4723,7 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "routeLongName": "Broadway/Halsey", "routeShortName": "77", "serviceDate": "2009-11-17", - "startTime": "2009-11-17T18:56:09.000+00:00", + "startTime": "2009-11-17T18:54:29.000+00:00", "steps": [ ], "to": { "arrival": "2009-11-17T19:08:50.000+00:00", @@ -4813,8 +4813,8 @@ org.opentripplanner.routing.algorithm.mapping.TransitSnapshotTest.test_trip_plan "startTime": "2009-11-17T18:45:44.000+00:00", "tooSloped": false, "transfers": 1, - "transitTime": 1133, - "waitingTime": 237, + "transitTime": 1162, + "waitingTime": 208, "walkDistance": 252.199, "walkLimitExceeded": false, "walkTime": 197 diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchTest.java new file mode 100644 index 00000000000..9c661c81a93 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchTest.java @@ -0,0 +1,349 @@ +package org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.model.transfer.TransferConstraint.REGULAR_TRANSFER; +import static org.opentripplanner.routing.algorithm.raptor.transit.request.TestTransitCaseData.RAPTOR_STOP_INDEX; +import static org.opentripplanner.routing.algorithm.raptor.transit.request.TestTransitCaseData.STATION_B; +import static org.opentripplanner.routing.algorithm.raptor.transit.request.TestTransitCaseData.STOP_A; +import static org.opentripplanner.routing.algorithm.raptor.transit.request.TestTransitCaseData.STOP_B; +import static org.opentripplanner.routing.algorithm.raptor.transit.request.TestTransitCaseData.STOP_C; +import static org.opentripplanner.routing.algorithm.raptor.transit.request.TestTransitCaseData.STOP_D; +import static org.opentripplanner.routing.algorithm.raptor.transit.request.TestTransitCaseData.id; +import static org.opentripplanner.routing.algorithm.raptor.transit.request.TestTransitCaseData.stopIndex; + +import java.util.Collection; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.opentripplanner.model.FeedScopedId; +import org.opentripplanner.model.Stop; +import org.opentripplanner.model.TransitMode; +import org.opentripplanner.model.transfer.ConstrainedTransfer; +import org.opentripplanner.model.transfer.RouteStationTransferPoint; +import org.opentripplanner.model.transfer.RouteStopTransferPoint; +import org.opentripplanner.model.transfer.StationTransferPoint; +import org.opentripplanner.model.transfer.StopTransferPoint; +import org.opentripplanner.model.transfer.TransferConstraint; +import org.opentripplanner.model.transfer.TripTransferPoint; +import org.opentripplanner.routing.algorithm.raptor.transit.StopIndexForRaptor; +import org.opentripplanner.routing.algorithm.raptor.transit.TransitTuningParameters; +import org.opentripplanner.routing.algorithm.raptor.transit.TripPatternWithRaptorStopIndexes; +import org.opentripplanner.routing.algorithm.raptor.transit.TripSchedule; +import org.opentripplanner.routing.algorithm.raptor.transit.request.TestRouteData; +import org.opentripplanner.transit.raptor.api.transit.RaptorTripScheduleBoardOrAlightEvent; + + +public class ConstrainedBoardingSearchTest { + + private static final FeedScopedId ID = id("ID"); + private static final TransferConstraint GUARANTEED_CONSTRAINT = + TransferConstraint.create().guaranteed().build(); + private static final TransferConstraint NOT_ALLOWED_CONSTRAINT = + TransferConstraint.create().notAllowed().build(); + private static final StopTransferPoint STOP_B_TX_POINT = new StopTransferPoint(STOP_B); + private static final StopTransferPoint STOP_C_TX_POINT = new StopTransferPoint(STOP_C); + + private static final int TRIP_1_INDEX = 0; + private static final int TRIP_2_INDEX = 1; + public static final StationTransferPoint STATION_B_TX_POINT = + new StationTransferPoint(STATION_B); + + private TestRouteData route1; + private TestRouteData route2; + private TripPatternWithRaptorStopIndexes pattern1; + private TripPatternWithRaptorStopIndexes pattern2; + private StopIndexForRaptor stopIndex; + + /** + * Create transit data with 2 routes with a trip each. + * <pre> + * STOPS + * A B C D + * Route R1 + * - Trip R1-1: 10:00 10:10 10:20 + * - Trip R1-2: 10:05 10:15 10:25 + * Route R2 + * - Trip R2-1: 10:15 10:30 10:40 + * - Trip R2-2: 10:20 10:35 10:45 + * - Trip R2-3: 10:25 10:40 10:50 + * - Trip R2-4: 10:30 10:45 10:55 + * - Trip R2-5: 10:35 10:50 11:00 + * - Trip R2-6: 10:40 10:55 11:05 + * </pre> + * <ul> + * <li> + * The transfer at stop B is tight between trip R1-2 and R2-1. There is no time between + * the arrival and departure, and it is only possible to transfer if the transfer is + * stay-seated or guaranteed. For other types of constrained transfers we should board + * the next trip 'R2-2'. + * </li> + * <li> + * The transfer at stop C allow regular transfers between trip R1-2 and R2-1. + * </li> + * <li> + * R1-1 is the fallback in the reverse search in the same way as R2-2 is the fallback + * int the forward search. + * </li> + * </ul> + * The + * + */ + @BeforeEach + void setup() { + route1 = new TestRouteData( + "R1", TransitMode.RAIL, + List.of(STOP_A, STOP_B, STOP_C), + "10:00 10:10 10:20", + "10:05 10:15 10:25" + ); + + route2 = new TestRouteData( + "R2", TransitMode.BUS, + List.of(STOP_B, STOP_C, STOP_D), + "10:15 10:30 10:40", + "10:20 10:35 10:45", + "10:25 10:40 10:50", + "10:30 10:45 10:55", + "10:35 10:50 11:00", + "10:40 10:55 11:05" + ); + + this.pattern1 = route1.getRaptorTripPattern(); + this.pattern2 = route2.getRaptorTripPattern(); + this.stopIndex = new StopIndexForRaptor( + List.of(RAPTOR_STOP_INDEX), + TransitTuningParameters.FOR_TEST + ); + } + + @Test + void transferExist() { + int fromStopPos = route1.stopPosition(STOP_C); + int toStopPos = route2.stopPosition(STOP_C); + + var txAllowed = new ConstrainedTransfer( + ID, STOP_C_TX_POINT, STOP_C_TX_POINT, GUARANTEED_CONSTRAINT + ); + generateTransfersForPatterns(List.of(txAllowed)); + + // Forward + var subject = route2.getRaptorTripPattern().constrainedTransferForwardSearch(); + assertTrue(subject.transferExist(toStopPos)); + + // Reverse + subject = route1.getRaptorTripPattern().constrainedTransferReverseSearch(); + assertTrue(subject.transferExist(fromStopPos)); + } + + @Test + void findGuaranteedTransferWithZeroConnectionTimeWithStation() { + var txGuaranteedTrip2Trip = new ConstrainedTransfer( + ID, STATION_B_TX_POINT, STATION_B_TX_POINT, GUARANTEED_CONSTRAINT + ); + findGuaranteedTransferWithZeroConnectionTime(List.of(txGuaranteedTrip2Trip)); + } + + @Test + void findGuaranteedTransferWithZeroConnectionTimeWithStop() { + var txGuaranteedTrip2Trip = new ConstrainedTransfer( + ID, STOP_B_TX_POINT, STOP_B_TX_POINT, GUARANTEED_CONSTRAINT + ); + findGuaranteedTransferWithZeroConnectionTime(List.of(txGuaranteedTrip2Trip)); + } + + @Test + void findGuaranteedTransferWithZeroConnectionTimeWithRouteAndStopTransfers() { + var route1TxPoint = new RouteStopTransferPoint(route1.getRoute(), STOP_B); + var route2TxPoint = new RouteStopTransferPoint(route2.getRoute(), STOP_B); + + var txGuaranteedTrip2Trip = new ConstrainedTransfer( + ID, route1TxPoint, route2TxPoint, GUARANTEED_CONSTRAINT + ); + findGuaranteedTransferWithZeroConnectionTime(List.of(txGuaranteedTrip2Trip)); + } + + @Test + void findGuaranteedTransferWithZeroConnectionTimeWithRouteAndStationTransfers() { + var route1TxPoint = new RouteStationTransferPoint(route1.getRoute(), STATION_B); + var route2TxPoint = new RouteStationTransferPoint(route2.getRoute(), STATION_B); + + var txGuaranteedTrip2Trip = new ConstrainedTransfer( + ID, route1TxPoint, route2TxPoint, GUARANTEED_CONSTRAINT + ); + findGuaranteedTransferWithZeroConnectionTime(List.of(txGuaranteedTrip2Trip)); + } + + @Test + void findGuaranteedTransferWithZeroConnectionTimeWithTripTransfers() { + int sourceStopPos = route1.stopPosition(STOP_B); + int targetStopPos = route2.stopPosition(STOP_B); + var trip1TxPoint = new TripTransferPoint(route1.lastTrip().trip(), sourceStopPos); + var trip2TxPoint = new TripTransferPoint(route2.firstTrip().trip(), targetStopPos); + + var txGuaranteedTrip2Trip = new ConstrainedTransfer( + ID, trip1TxPoint, trip2TxPoint, GUARANTEED_CONSTRAINT + ); + findGuaranteedTransferWithZeroConnectionTime(List.of(txGuaranteedTrip2Trip)); + } + + @Test + void findGuaranteedTransferWithMostSpecificTransfers() { + int sourceStopPos = route1.stopPosition(STOP_B); + int targetStopPos = route2.stopPosition(STOP_B); + var trip1TxPoint = new TripTransferPoint(route1.lastTrip().trip(), sourceStopPos); + var route1TxPoint = new RouteStopTransferPoint(route1.getRoute(), STOP_B); + var trip2TxPoint = new TripTransferPoint(route2.firstTrip().trip(), targetStopPos); + + + var transfers = List.of( + new ConstrainedTransfer(ID, STOP_B_TX_POINT, trip2TxPoint, NOT_ALLOWED_CONSTRAINT), + new ConstrainedTransfer(ID, trip1TxPoint, STOP_B_TX_POINT, GUARANTEED_CONSTRAINT), + new ConstrainedTransfer(ID, route1TxPoint, STOP_B_TX_POINT, NOT_ALLOWED_CONSTRAINT) + ); + findGuaranteedTransferWithZeroConnectionTime(transfers); + } + + @Test + void findNextTransferWhenFirstTransferIsNotAllowed() { + int sourceStopPos = route1.stopPosition(STOP_C); + int targetStopPos = route2.stopPosition(STOP_C); + var trip1TxPoint = new TripTransferPoint(route1.lastTrip().trip(), sourceStopPos); + var trip2TxPoint = new TripTransferPoint(route2.firstTrip().trip(), targetStopPos); + + var txNotAllowed = new ConstrainedTransfer( + ID, trip1TxPoint, trip2TxPoint, NOT_ALLOWED_CONSTRAINT + ); + + testTransferSearch( + STOP_C, List.of(txNotAllowed), TRIP_2_INDEX, TRIP_1_INDEX, REGULAR_TRANSFER + ); + } + + @Test + void blockTransferWhenNotAllowedApplyToAllTrips() { + ConstrainedTransfer transfer = new ConstrainedTransfer( + ID, STOP_C_TX_POINT, STOP_C_TX_POINT, NOT_ALLOWED_CONSTRAINT + ); + testTransferSearch( + STOP_C, List.of(transfer), TRIP_1_INDEX, TRIP_2_INDEX, NOT_ALLOWED_CONSTRAINT + ); + } + + @Test + void makeSureTheSearchIsAbortedAfter5NormalTripsAreFound() { + int sourceStopPos = route1.stopPosition(STOP_C); + int targetStopPos = route2.stopPosition(STOP_C); + var trip1TxPoint = new TripTransferPoint(route1.lastTrip().trip(), sourceStopPos); + var trip2TxPoint = new TripTransferPoint(route2.lastTrip().trip(), targetStopPos); + + var txGuaranteed = new ConstrainedTransfer( + ID, trip1TxPoint, trip2TxPoint, GUARANTEED_CONSTRAINT + ); + + testTransferSearch( + STOP_C, List.of(txGuaranteed), TRIP_2_INDEX, TRIP_1_INDEX, null + ); + } + + + /** + * The most specific transfer passed in should be a guaranteed transfer + * at stop B + */ + private void findGuaranteedTransferWithZeroConnectionTime( + List<ConstrainedTransfer> constrainedTransfers + ) { + testTransferSearch( + STOP_B, constrainedTransfers, TRIP_1_INDEX, TRIP_2_INDEX, GUARANTEED_CONSTRAINT + ); + } + + void testTransferSearch( + Stop transferStop, + List<ConstrainedTransfer> constraints, + int expTripIndexFwdSearch, + int expTripIndexRevSearch, + TransferConstraint expConstraint + ) { + testTransferSearchForward(transferStop, constraints, expTripIndexFwdSearch, expConstraint); + testTransferSearchReverse(transferStop, constraints, expTripIndexRevSearch, expConstraint); + } + + void testTransferSearchForward( + Stop transferStop, + List<ConstrainedTransfer> txList, + int expectedTripIndex, + TransferConstraint expectedConstraint + ) { + generateTransfersForPatterns(txList); + var subject = pattern2.constrainedTransferForwardSearch(); + + int targetStopPos = route2.stopPosition(transferStop); + int stopIndex = stopIndex(transferStop); + int sourceArrivalTime = route1.lastTrip().getStopTime(transferStop).getArrivalTime(); + + // Check that transfer exist + assertTrue(subject.transferExist(targetStopPos)); + + var boarding = subject.find( + route2.getTimetable(), + route1.lastTrip().getTripSchedule(), + stopIndex, + sourceArrivalTime + ); + assertBoarding(stopIndex, targetStopPos, expectedTripIndex, expectedConstraint, boarding); + } + + void testTransferSearchReverse( + Stop transferStop, + List<ConstrainedTransfer> txList, + int expectedTripIndex, + TransferConstraint expectedConstraint + ) { + generateTransfersForPatterns(txList); + var subject = pattern1.constrainedTransferReverseSearch(); + int targetStopPos = route1.stopPosition(transferStop); + + int stopIndex = stopIndex(transferStop); + int sourceArrivalTime = route2.firstTrip().getStopTime(transferStop).getDepartureTime(); + + // Check that transfer exist + assertTrue(subject.transferExist(targetStopPos)); + + var boarding = subject.find( + route1.getTimetable(), + route2.firstTrip().getTripSchedule(), + stopIndex, + sourceArrivalTime + ); + + assertBoarding(stopIndex, targetStopPos, expectedTripIndex, expectedConstraint, boarding); + } + + private void assertBoarding( + int stopIndex, + int targetStopPos, + int expectedTripIndex, + TransferConstraint expectedConstraint, + RaptorTripScheduleBoardOrAlightEvent<TripSchedule> boarding + ) { + if(expectedConstraint != null) { + assertNotNull(boarding); + assertEquals(expectedConstraint, boarding.getTransferConstraint()); + assertEquals(stopIndex, boarding.getBoardStopIndex()); + assertEquals(targetStopPos, boarding.getStopPositionInPattern()); + assertEquals(expectedTripIndex, boarding.getTripIndex()); + } + else { + assertNull(boarding); + } + } + + private void generateTransfersForPatterns(Collection<ConstrainedTransfer> txList) { + new TransferIndexGenerator(txList, List.of(pattern1, pattern2), stopIndex) + .generateTransfers(); + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/DateMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/DateMapperTest.java index c2bd0719639..616a5192bb9 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/DateMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/DateMapperTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.routing.algorithm.raptor.transit.mappers; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.routing.algorithm.raptor.transit.mappers.DateMapper.asStartOfService; import java.time.Instant; @@ -9,7 +9,7 @@ import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class DateMapperTest { private static final ZoneId ZONE_ID = ZoneId.of("Europe/Paris"); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/StopIndexForRaptorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/StopIndexForRaptorTest.java index 7bd18b24bba..e3437afeab7 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/StopIndexForRaptorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/StopIndexForRaptorTest.java @@ -1,21 +1,28 @@ package org.opentripplanner.routing.algorithm.raptor.transit.mappers; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Arrays; import java.util.List; -import org.junit.Test; +import java.util.stream.Collectors; +import lombok.val; +import org.junit.jupiter.api.Test; import org.opentripplanner.model.FeedScopedId; import org.opentripplanner.model.Station; import org.opentripplanner.model.Stop; import org.opentripplanner.model.StopLocation; +import org.opentripplanner.model.StopPattern; +import org.opentripplanner.model.StopTime; import org.opentripplanner.model.StopTransferPriority; +import org.opentripplanner.model.TripPattern; import org.opentripplanner.model.WgsCoordinate; import org.opentripplanner.routing.algorithm.raptor.transit.StopIndexForRaptor; import org.opentripplanner.routing.algorithm.raptor.transit.TransitTuningParameters; public class StopIndexForRaptorTest { + private final FeedScopedId ANY_ID = new FeedScopedId("F", "1"); + private final Stop STOP_0 = Stop.stopForTest("ID-" + 1, 0.0, 0.0); private final Stop STOP_1 = Stop.stopForTest("ID-" + 2, 0.0, 0.0); private final Stop STOP_2 = Stop.stopForTest("ID-" + 3, 0.0, 0.0); @@ -32,23 +39,23 @@ public class StopIndexForRaptorTest { @Test public void listStopIndexesForEmptyTripPattern() { StopIndexForRaptor stopIndex = new StopIndexForRaptor(STOPS, TransitTuningParameters.FOR_TEST); + val p = new TripPattern(ANY_ID, null, new StopPattern(List.of())); - int[] result = stopIndex.listStopIndexesForStops(new Stop[0]); + int[] result = stopIndex.listStopIndexesForPattern(p); assertEquals(result.length, 0); } @Test public void listStopIndexesForTripPattern() { - StopLocation[] input = new Stop[] { - STOP_0, - STOP_2, - STOP_4 - }; - - StopIndexForRaptor stopIndex = new StopIndexForRaptor(STOPS, TransitTuningParameters.FOR_TEST); + var stopIndex = new StopIndexForRaptor(STOPS, TransitTuningParameters.FOR_TEST); + var tripPattern = new TripPattern( + ANY_ID, + null, + new StopPattern(stopTimes(STOP_0, STOP_2, STOP_4)) + ); - int[] result = stopIndex.listStopIndexesForStops(input); + int[] result = stopIndex.listStopIndexesForPattern(tripPattern); assertEquals("[0, 2, 4]", Arrays.toString(result)); } @@ -68,6 +75,18 @@ public class StopIndexForRaptorTest { } Station createStation(String name, StopTransferPriority pri) { - return new Station(new FeedScopedId("F", name), name, new WgsCoordinate(0, 0), null, null, null, null, pri); + return new Station(ANY_ID, name, new WgsCoordinate(0, 0), null, null, null, null, pri); + } + + private static List<StopTime> stopTimes(Stop ... stops) { + return Arrays.stream(stops) + .map(StopIndexForRaptorTest::stopTime) + .collect(Collectors.toList()); + } + + private static StopTime stopTime(Stop stop) { + val st = new StopTime(); + st.setStop(stop); + return st; } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java index 3e7aa1c26ce..ae896989dce 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java @@ -1,7 +1,15 @@ package org.opentripplanner.routing.algorithm.raptor.transit.request; -import org.junit.Before; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.opentripplanner.model.FeedScopedId; import org.opentripplanner.model.Route; import org.opentripplanner.model.StopPattern; @@ -15,15 +23,6 @@ import org.opentripplanner.routing.trippattern.Deduplicator; import org.opentripplanner.routing.trippattern.TripTimes; -import java.time.LocalDate; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.junit.Assert.assertEquals; - public class RaptorRoutingRequestTransitDataCreatorTest { public static final FeedScopedId TP_ID_1 = new FeedScopedId("F", "1"); @@ -36,7 +35,7 @@ public class RaptorRoutingRequestTransitDataCreatorTest { new StopPattern(List.of()) ); - @Before + @BeforeEach public void setup() { TP.getRoute().setMode(TransitMode.BUS); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/RoutingRequestTransitDataProviderFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/RoutingRequestTransitDataProviderFilterTest.java index d8f5f7b809f..40240002006 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/RoutingRequestTransitDataProviderFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/RoutingRequestTransitDataProviderFilterTest.java @@ -1,22 +1,30 @@ package org.opentripplanner.routing.algorithm.raptor.transit.request; -import org.junit.Test; +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.time.LocalDate; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import org.opentripplanner.model.*; +import org.opentripplanner.model.BikeAccess; +import org.opentripplanner.model.FeedScopedId; +import org.opentripplanner.model.Route; +import org.opentripplanner.model.Stop; +import org.opentripplanner.model.StopPattern; +import org.opentripplanner.model.StopTime; +import org.opentripplanner.model.TransitMode; +import org.opentripplanner.model.Trip; +import org.opentripplanner.model.TripAlteration; +import org.opentripplanner.model.TripPattern; import org.opentripplanner.model.modes.AllowedTransitMode; import org.opentripplanner.routing.algorithm.raptor.transit.TripPatternForDate; import org.opentripplanner.routing.algorithm.raptor.transit.TripPatternWithRaptorStopIndexes; import org.opentripplanner.routing.trippattern.Deduplicator; import org.opentripplanner.routing.trippattern.TripTimes; -import java.time.LocalDate; -import java.util.List; -import java.util.Set; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - public class RoutingRequestTransitDataProviderFilterTest { private static final FeedScopedId TEST_ROUTE_ID = new FeedScopedId("TEST", "ROUTE"); @@ -135,7 +143,7 @@ private TripPatternForDate createTestTripPatternForDate() { TripPattern pattern = new TripPattern(null, route, stopPattern); TripPatternWithRaptorStopIndexes tripPattern = new TripPatternWithRaptorStopIndexes( - new int[0], pattern + pattern, new int[0] ); TripTimes tripTimes = Mockito.mock(TripTimes.class); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/TestRouteData.java b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/TestRouteData.java new file mode 100644 index 00000000000..507bb9d99a8 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/TestRouteData.java @@ -0,0 +1,158 @@ +package org.opentripplanner.routing.algorithm.raptor.transit.request; + +import static org.opentripplanner.routing.algorithm.raptor.transit.request.TestTransitCaseData.DATE; +import static org.opentripplanner.routing.algorithm.raptor.transit.request.TestTransitCaseData.OFFSET; +import static org.opentripplanner.routing.algorithm.raptor.transit.request.TestTransitCaseData.id; +import static org.opentripplanner.routing.algorithm.raptor.transit.request.TestTransitCaseData.stopIndex; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.opentripplanner.model.Route; +import org.opentripplanner.model.Stop; +import org.opentripplanner.model.StopLocation; +import org.opentripplanner.model.StopPattern; +import org.opentripplanner.model.StopTime; +import org.opentripplanner.model.TransitMode; +import org.opentripplanner.model.Trip; +import org.opentripplanner.model.TripPattern; +import org.opentripplanner.routing.algorithm.raptor.transit.TripPatternForDate; +import org.opentripplanner.routing.algorithm.raptor.transit.TripPatternWithRaptorStopIndexes; +import org.opentripplanner.routing.algorithm.raptor.transit.TripSchedule; +import org.opentripplanner.routing.trippattern.Deduplicator; +import org.opentripplanner.routing.trippattern.TripTimes; +import org.opentripplanner.transit.raptor.api.transit.RaptorTimeTable; +import org.opentripplanner.util.time.TimeUtils; + +public class TestRouteData { + + private final Route route; + private final List<Trip> trips; + private final Map<Trip, List<StopTime>> stopTimesByTrip = new HashMap<>(); + private final Map<Trip, TripTimes> tripTimesByTrip = new HashMap<>(); + private final Map<Trip, TripSchedule> tripSchedulesByTrip = new HashMap<>(); + private final RaptorTimeTable<TripSchedule> timetable; + private final TripPatternWithRaptorStopIndexes raptorTripPattern; + private Trip currentTrip; + + public TestRouteData(String route, TransitMode mode, List<Stop> stops, String ... times) { + final Deduplicator deduplicator = new Deduplicator(); + this.route = new Route(id(route)); + this.route.setMode(mode); + this.trips = Arrays.stream(times) + .map(it -> parseTripInfo(route, it, stops, deduplicator)) + .collect(Collectors.toList()); + + List<StopTime> stopTimesFistTrip = firstTrip().getStopTimes(); + // Get TripTimes in same order as the trips + List<TripTimes> tripTimes = trips.stream() + .map(tripTimesByTrip::get) + .collect(Collectors.toList()); + + raptorTripPattern = new TripPatternWithRaptorStopIndexes( + new TripPattern(id("TP:"+route), this.route, new StopPattern(stopTimesFistTrip)), + stopIndexes(stopTimesFistTrip) + ); + tripTimes.forEach(t -> raptorTripPattern.getPattern().add(t)); + + var listOfTripPatternForDates = List.of( + new TripPatternForDate(raptorTripPattern, tripTimes, DATE) + ); + + var patternForDates = new TripPatternForDates( + raptorTripPattern, listOfTripPatternForDates, List.of(OFFSET) + ); + for (Trip trip : trips) { + var tripSchedule = new TripScheduleWithOffset( + patternForDates, DATE, tripTimesByTrip.get(trip), OFFSET + ); + tripSchedulesByTrip.put(trip, tripSchedule); + } + + this.timetable = patternForDates; + } + + private Trip parseTripInfo(String route, String tripTimes, List<Stop> stops, Deduplicator deduplicator) { + var trip = new Trip(id(route + "-" + stopTimesByTrip.size()+1)); + trip.setRoute(this.route); + var stopTimes = stopTimes(trip, stops, tripTimes); + this.stopTimesByTrip.put(trip, stopTimes); + this.tripTimesByTrip.put(trip, new TripTimes(trip, stopTimes, deduplicator)); + return trip; + } + + public Route getRoute() { + return route; + } + + public Trip trip() { + return currentTrip; + } + + public TestRouteData firstTrip() { + this.currentTrip = trips.get(0); + return this; + } + + public TestRouteData lastTrip() { + this.currentTrip = trips.get(trips.size()-1); + return this; + } + + public StopTime getStopTime(Stop stop) { + return stopTimesByTrip.get(currentTrip).get(stopPosition(stop)); + } + + public RaptorTimeTable<TripSchedule> getTimetable() { + return timetable; + } + + public TripSchedule getTripSchedule() { + return tripSchedulesByTrip.get(currentTrip); + } + + public TripPatternWithRaptorStopIndexes getRaptorTripPattern() { + return raptorTripPattern; + } + + private List<StopTime> getStopTimes() { + return stopTimesByTrip.get(currentTrip); + } + + int[] stopIndexes(Collection<StopTime> times) { + return times.stream().mapToInt(it -> stopIndex(it.getStop())).toArray(); + } + + public int stopPosition(StopLocation stop) { + List<StopTime> times = firstTrip().getStopTimes(); + for (int i=0; i<times.size(); ++i) { + if(stop == times.get(i).getStop()) { return i; } + } + throw new IllegalArgumentException(); + } + + private List<StopTime> stopTimes(Trip trip, List<Stop> stops, String timesAsString) { + var times = TimeUtils.times(timesAsString); + var stopTimes = new ArrayList<StopTime>(); + for (int i = 0; i < stops.size(); i++) { + stopTimes.add(stopTime(trip, stops.get(i), times[i], i+1)); + } + return stopTimes; + } + + private StopTime stopTime(Trip trip, Stop stop, int time, int seq) { + var s = new StopTime(); + s.setTrip(trip); + s.setStop(stop); + s.setArrivalTime(time); + s.setDepartureTime(time); + s.setStopSequence(seq); + s.setStopHeadsign("NA"); + s.setRouteShortName("NA"); + return s; + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/TestTransitCaseData.java b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/TestTransitCaseData.java new file mode 100644 index 00000000000..69b6bc147cd --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/TestTransitCaseData.java @@ -0,0 +1,49 @@ +package org.opentripplanner.routing.algorithm.raptor.transit.request; + +import java.time.LocalDate; +import org.opentripplanner.model.FeedScopedId; +import org.opentripplanner.model.Station; +import org.opentripplanner.model.Stop; +import org.opentripplanner.model.StopLocation; + +public final class TestTransitCaseData { + public static final Station STATION_A = Station.stationForTest("A", 60.0, 11.1); + public static final Station STATION_B = Station.stationForTest("B", 61.0, 11.5); + + public static final Stop STOP_A = Stop.stopForTest("A", 60.0, 11.0, STATION_A); + public static final Stop STOP_B = Stop.stopForTest("B", 60.0, 11.2, STATION_B); + public static final Stop STOP_C = Stop.stopForTest("C", 61.0, 11.4); + public static final Stop STOP_D = Stop.stopForTest("D", 61.0, 11.6); + + // Random order stop indexes - should be different from stopPos in pattern to + // make sure code-under-test do not mix stopIndex and stopPosition + public static final Stop[] RAPTOR_STOP_INDEX = { STOP_D, STOP_A, STOP_C, STOP_B }; + + public static final LocalDate DATE = LocalDate.of(2021, 12, 24); + + public static final int OFFSET = 0; + + + public static FeedScopedId id(String id) { + return new FeedScopedId("F", id); + } + + public static int stopIndex(StopLocation stop) { + for (int i=0;i< RAPTOR_STOP_INDEX.length;++i) { + if(stop == RAPTOR_STOP_INDEX[i]) { return i; } + } + throw new IllegalArgumentException(); + } + + static { + setupStationStopRelationship(STATION_A, STOP_A); + setupStationStopRelationship(STATION_B, STOP_B); + } + + private static void setupStationStopRelationship(Station station, Stop ... stops) { + for (Stop stop : stops) { + station.addChildStop(stop); + stop.setParentStation(station); + } + } +} diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/TripPatternWithId.java b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/TripPatternWithId.java index 6cd8d0ecd85..a89401c9fd3 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/TripPatternWithId.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/TripPatternWithId.java @@ -4,14 +4,14 @@ import org.opentripplanner.routing.algorithm.raptor.transit.TripPatternWithRaptorStopIndexes; public class TripPatternWithId extends TripPatternWithRaptorStopIndexes { - private FeedScopedId id; + private final FeedScopedId id; public TripPatternWithId( FeedScopedId id, int[] stopIndexes, org.opentripplanner.model.TripPattern originalTripPattern ) { - super(stopIndexes, originalTripPattern); + super(originalTripPattern, stopIndexes); this.id = id; } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java index 00dffe9792f..5814df3340e 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/OptimizePathDomainServiceTest.java @@ -254,7 +254,7 @@ public void testConstrainedTransferIsPreferred() { ); // Verify the attached Transfer is exist and is valid assertEquals( - "ConstrainedTransfer{from: (trip: BUS T1:10:02, stopPos: 2), to: (trip: BUS T2:10:13, stopPos: 1), constraint: {guaranteed}}", + "ConstrainedTransfer{from: <Trip BUS T1:10:02, stopPos 2>, to: <Trip BUS T2:10:13, stopPos 1>, constraint: {guaranteed}}", it.accessLeg().nextLeg().asTransitLeg().getConstrainedTransferAfterLeg().toString() ); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorTest.java index fb69123fa4a..e2945b41376 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/transferoptimization/services/TransferGeneratorTest.java @@ -10,7 +10,6 @@ import java.util.List; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; -import org.opentripplanner.routing.algorithm.raptor.transit.SlackProvider; import org.opentripplanner.transit.raptor._data.RaptorTestConstants; import org.opentripplanner.transit.raptor._data.api.TestPathBuilder; import org.opentripplanner.transit.raptor._data.transit.TestRoute; @@ -330,6 +329,48 @@ void findTransferWithBoardingForbiddenAtDifferentStop() { ); } + @Test + void findTransferWithNotAllowedConstrainedTransfer() { + // given 3 possible expected transfers + var expBxB = "TripToTripTransfer{from: [2 10:10 BUS L1], to: [2 10:20 BUS L2]}"; + var expCxD = "TripToTripTransfer{from: [3 10:20 BUS L1], to: [4 10:30 BUS L2], transfer: On-Street 1m ~ 4}"; + var expExE = "TripToTripTransfer{from: [5 10:30 BUS L1], to: [5 10:40 BUS L2]}"; + + var l1 = route("L1", STOP_A, STOP_B, STOP_C, STOP_E) + .withTimetable(schedule("10:00 10:10 10:20 10:30")); + + var l2 = route("L2", STOP_B, STOP_D, STOP_E, STOP_F) + .withTimetable(schedule("10:20 10:30 10:40 10:50")); + + data.withRoutes(l1, l2).withTransfer(STOP_C, walk(STOP_D, D1m)); + + final Path<TestTripSchedule> path = pathBuilder + .access(ACCESS_START, ACCESS_DURATION, STOP_A) + .bus(l1.getTripSchedule(0), STOP_C) + .walk(D1m, STOP_D) + .bus(l2.getTripSchedule(0), STOP_F) + .egress(D1m); + + var transitLegs = path.transitLegs().collect(Collectors.toList()); + var subject = new TransferGenerator<>(TS_ADAPTOR, SLACK_PROVIDER, data); + + var tripA = l1.getTripSchedule(0); + var tripB = l2.getTripSchedule(0); + + data.withConstrainedTransfer(tripA, STOP_B, tripB, STOP_B, TestTransitData.TX_NOT_ALLOWED); + var result = subject.findAllPossibleTransfers(transitLegs); + + // The same stop transfer is no longer an option + assertEquals("[[" + expCxD + ", " + expExE + "]]", result.toString()); + + data.clearConstrainedTransfers(); + data.withConstrainedTransfer(tripA, STOP_C, tripB, STOP_D, TestTransitData.TX_NOT_ALLOWED); + result = subject.findAllPossibleTransfers(transitLegs); + + // The same stop transfer is no longer an option + assertEquals("[[" + expBxB + ", " + expExE + "]]", result.toString()); + } + @Test void findDependedTransfersForThreeRoutes() { TestRoute l1 = route("L1", STOP_A, STOP_B, STOP_C) diff --git a/src/test/java/org/opentripplanner/routing/graph/RoutingServiceTest.java b/src/test/java/org/opentripplanner/routing/graph/RoutingServiceTest.java index 765e8b4aacd..0660101729a 100644 --- a/src/test/java/org/opentripplanner/routing/graph/RoutingServiceTest.java +++ b/src/test/java/org/opentripplanner/routing/graph/RoutingServiceTest.java @@ -70,7 +70,8 @@ public void testPatternsCoherent() { } for (var stop : graph.index.getAllStops()) { for (TripPattern pattern : graph.index.getPatternsForStop(stop)) { - assertTrue(pattern.getStopPattern().containsStop(stop.getId().toString())); + int stopPos = pattern.findStopPosition(stop); + assertTrue("Stop position exist", stopPos >= 0); } } } diff --git a/src/test/java/org/opentripplanner/transit/raptor/_data/api/TestPathBuilderTest.java b/src/test/java/org/opentripplanner/transit/raptor/_data/api/TestPathBuilderTest.java index 4b8b19f19fa..14a5986c607 100644 --- a/src/test/java/org/opentripplanner/transit/raptor/_data/api/TestPathBuilderTest.java +++ b/src/test/java/org/opentripplanner/transit/raptor/_data/api/TestPathBuilderTest.java @@ -1,6 +1,7 @@ package org.opentripplanner.transit.raptor._data.api; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.model.transfer.TransferConstraint.REGULAR_TRANSFER; import static org.opentripplanner.transit.raptor._data.stoparrival.BasicPathTestCase.ACCESS_DURATION; import static org.opentripplanner.transit.raptor._data.stoparrival.BasicPathTestCase.ACCESS_START; import static org.opentripplanner.transit.raptor._data.stoparrival.BasicPathTestCase.BASIC_PATH_AS_DETAILED_STRING; @@ -46,7 +47,12 @@ public void testSimplePathWithOneTransit() { var transitLeg = path.accessLeg().nextLeg().asTransitLeg(); int boardCost = COST_CALCULATOR.boardingCost( - true, path.accessLeg().toTime(), STOP_A, transitLeg.fromTime(), transitLeg.trip(), null + true, + path.accessLeg().toTime(), + STOP_A, + transitLeg.fromTime(), + transitLeg.trip(), + REGULAR_TRANSFER ); int transitCost = COST_CALCULATOR.transitArrivalCost( diff --git a/src/test/java/org/opentripplanner/transit/raptor/_data/stoparrival/BasicPathTestCase.java b/src/test/java/org/opentripplanner/transit/raptor/_data/stoparrival/BasicPathTestCase.java index f7ef5dee1e0..cbd5e0a219e 100644 --- a/src/test/java/org/opentripplanner/transit/raptor/_data/stoparrival/BasicPathTestCase.java +++ b/src/test/java/org/opentripplanner/transit/raptor/_data/stoparrival/BasicPathTestCase.java @@ -1,6 +1,7 @@ package org.opentripplanner.transit.raptor._data.stoparrival; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.model.transfer.TransferConstraint.REGULAR_TRANSFER; import static org.opentripplanner.routing.algorithm.raptor.transit.cost.RaptorCostConverter.toRaptorCost; import static org.opentripplanner.transit.raptor._data.transit.TestTransfer.walk; import static org.opentripplanner.transit.raptor._data.transit.TestTripPattern.pattern; @@ -329,7 +330,7 @@ private static int transitArrivalCost( ) { boolean firstTransit = TRIP_1 == trip; int boardCost = COST_CALCULATOR.boardingCost( - firstTransit, prevArrivalTime, boardStop, boardTime, trip, null + firstTransit, prevArrivalTime, boardStop, boardTime, trip, REGULAR_TRANSFER ); return COST_CALCULATOR.transitArrivalCost( diff --git a/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestConstrainedBoardingSearch.java b/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestConstrainedBoardingSearch.java index 6ad8df996ab..75bb096ceff 100644 --- a/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestConstrainedBoardingSearch.java +++ b/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestConstrainedBoardingSearch.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.function.BiPredicate; import java.util.stream.Collectors; import javax.annotation.Nullable; import org.opentripplanner.model.base.ToStringBuilder; @@ -16,14 +17,17 @@ public class TestConstrainedBoardingSearch implements RaptorConstrainedTripScheduleBoardingSearch<TestTripSchedule> { - private static final TransferConstraint GUARANTEED = TransferConstraint.create().guaranteed().build(); - - /** Index of guaranteed transfers by fromStopPos */ private final TIntObjectMap<List<TestConstrainedTransferBoarding>> transfersByFromStopPos = new TIntObjectHashMap<>(); private int currentTargetStopPos; + private final BiPredicate<Integer, Integer> timeAfterOrEqual; + + TestConstrainedBoardingSearch(boolean forward) { + this.timeAfterOrEqual = forward ? (a,b) -> a >= b : (a,b) -> a <= b; + } + @Override public boolean transferExist(int targetStopPos) { this.currentTargetStopPos = targetStopPos; @@ -43,7 +47,8 @@ public RaptorTripScheduleBoardOrAlightEvent<TestTripSchedule> find( var trip = tx.getSourceTrip(); if(trip == sourceTrip) { int stopPos = trip.findDepartureStopPosition(sourceArrivalTime, sourceStopIndex); - if(tx.getSourceStopPos() == stopPos) { + boolean boardAlightPossible = timeAfterOrEqual.test(tx.getTime(), sourceArrivalTime); + if(tx.getSourceStopPos() == stopPos && boardAlightPossible) { return tx; } } @@ -65,13 +70,14 @@ public List<TestConstrainedTransferBoarding> constrainedBoardings() { * The the {@code source/target} is the trips in order of the search direction (forward or * reverse). For reverse search it is the opposite from {@code from/to} in the result path. */ - void addGuaranteedTransfers( + void addConstraintTransfers( TestTripSchedule sourceTrip, int sourceStopPos, TestTripSchedule targetTrip, int targetTripIndex, int targetStopPos, - int targetTime + int targetTime, + TransferConstraint constraint ) { List<TestConstrainedTransferBoarding> list = transfersByFromStopPos.get(targetStopPos); if(list == null) { @@ -79,7 +85,7 @@ void addGuaranteedTransfers( transfersByFromStopPos.put(targetStopPos, list); } list.add(new TestConstrainedTransferBoarding( - GUARANTEED, + constraint, sourceTrip, sourceStopPos, targetTrip, targetTripIndex, targetStopPos, targetTime )); @@ -93,4 +99,7 @@ public String toString() { .toString(); } + void clear() { + transfersByFromStopPos.clear(); + } } diff --git a/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestRoute.java b/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestRoute.java index f2e3ca75fc9..73bce82771d 100644 --- a/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestRoute.java +++ b/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestRoute.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.List; import org.opentripplanner.model.base.ToStringBuilder; +import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.transit.raptor.api.transit.RaptorConstrainedTripScheduleBoardingSearch; import org.opentripplanner.transit.raptor.api.transit.RaptorRoute; import org.opentripplanner.transit.raptor.api.transit.RaptorTimeTable; @@ -15,9 +16,9 @@ public class TestRoute implements RaptorRoute<TestTripSchedule>, RaptorTimeTable private final TestTripPattern pattern; private final List<TestTripSchedule> schedules = new ArrayList<>(); private final TestConstrainedBoardingSearch transferConstraintsForwardSearch = - new TestConstrainedBoardingSearch(); + new TestConstrainedBoardingSearch(true); private final TestConstrainedBoardingSearch transferConstraintsReverseSearch = - new TestConstrainedBoardingSearch(); + new TestConstrainedBoardingSearch(false); private TestRoute(TestTripPattern pattern) { @@ -87,35 +88,43 @@ public String toString() { .toString(); } - void addGuaranteedTxForwardSearch( - TestTripSchedule sourceTrip, - int sourceStopPos, - TestTripSchedule targetTrip, - int targetTripIndex, - int targetStopPos - ) { - final int targetTime = targetTrip.arrival(targetStopPos); - - this.transferConstraintsForwardSearch.addGuaranteedTransfers( - sourceTrip, sourceStopPos, targetTrip, targetTripIndex, targetStopPos, targetTime - ); + void clearTransferConstraints() { + transferConstraintsForwardSearch.clear(); + transferConstraintsReverseSearch.clear(); } /** - * Reverse search transfer, the {@code source/target} is the trips in order of the reverse - * search, which is opposite from {@code from/to} in the result path. + * Add a transfer constraint to the route by iterating over all trips and matching + * the provided {@code toTrip}(added to forward search) {@code fromTrip}(added to reverse + * search) with the rips in the route timetable. */ - void addGuaranteedTxReverseSearch( - TestTripSchedule sourceTrip, - int sourceStopPos, - TestTripSchedule targetTrip, - int targetTripIndex, - int targetStopPos + void addTransferConstraint( + TestTripSchedule fromTrip, + int fromStopPos, + TestTripSchedule toTrip, + int toStopPos, + TransferConstraint constraint ) { - final int targetTime = targetTrip.departure(targetStopPos); - // This is used in the revers search - this.transferConstraintsReverseSearch.addGuaranteedTransfers( - sourceTrip, sourceStopPos, targetTrip, targetTripIndex, targetStopPos, targetTime - ); + for (int i = 0; i < timetable().numberOfTripSchedules(); i++) { + var trip = timetable().getTripSchedule(i); + if(toTrip == trip) { + this.transferConstraintsForwardSearch.addConstraintTransfers( + fromTrip, fromStopPos, + trip, i, toStopPos, + trip.arrival(toStopPos), + constraint + ); + } + // Reverse search transfer, the {@code source/target} is the trips in order of the + // reverse search, which is opposite from {@code from/to} in the result path. + if(fromTrip == trip) { + this.transferConstraintsReverseSearch.addConstraintTransfers( + toTrip, toStopPos, + trip, i, fromStopPos, + trip.departure(fromStopPos), + constraint + ); + } + } } } diff --git a/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestTransferPoint.java b/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestTransferPoint.java index 4148251d191..a356cfe47af 100644 --- a/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestTransferPoint.java +++ b/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestTransferPoint.java @@ -6,13 +6,22 @@ public class TestTransferPoint implements TransferPoint { private final int stop; private final TestTripSchedule schedule; + private final boolean applyToAllTrips; public TestTransferPoint( int stop, - TestTripSchedule schedule + TestTripSchedule schedule, + boolean applyToAllTrips + ) { this.stop = stop; this.schedule = schedule; + this.applyToAllTrips = applyToAllTrips; + } + + @Override + public boolean appliesToAllTrips() { + return applyToAllTrips; } public int getStopPosition() { diff --git a/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestTransitData.java b/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestTransitData.java index 83e32fa5944..5dbcd1ecb98 100644 --- a/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestTransitData.java +++ b/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestTransitData.java @@ -31,14 +31,16 @@ @SuppressWarnings("UnusedReturnValue") public class TestTransitData implements RaptorTransitDataProvider<TestTripSchedule>, RaptorTestConstants { - private static final TransferConstraint GUARANTEED = TransferConstraint.create() - .guaranteed().build(); + public static final TransferConstraint TX_GUARANTEED = TransferConstraint.create().guaranteed() + .build(); + public static final TransferConstraint TX_NOT_ALLOWED = TransferConstraint.create().notAllowed() + .build(); private final List<List<RaptorTransfer>> transfersFromStop = new ArrayList<>(); private final List<List<RaptorTransfer>> transfersToStop = new ArrayList<>(); private final List<Set<TestRoute>> routesByStop = new ArrayList<>(); private final List<TestRoute> routes = new ArrayList<>(); - private final List<ConstrainedTransfer> guaranteedTransfers = new ArrayList<>(); + private final List<ConstrainedTransfer> constrainedTransfers = new ArrayList<>(); private final McCostParamsBuilder costParamsBuilder = new McCostParamsBuilder(); @Override @@ -174,27 +176,34 @@ public TestTransitData withTransfer(int fromStop, TestTransfer transfer) { public TestTransitData withGuaranteedTransfer( TestTripSchedule fromTrip, int fromStop, TestTripSchedule toTrip, int toStop + ) { + return withConstrainedTransfer(fromTrip, fromStop, toTrip, toStop, TX_GUARANTEED); + } + + public void clearConstrainedTransfers() { + constrainedTransfers.clear(); + for (TestRoute route : routes) { + route.clearTransferConstraints(); + } + } + + public TestTransitData withConstrainedTransfer( + TestTripSchedule fromTrip, int fromStop, + TestTripSchedule toTrip, int toStop, + TransferConstraint constraint ) { int fromStopPos = fromTrip.pattern().findStopPositionAfter(0, fromStop); int toStopPos = toTrip.pattern().findStopPositionAfter(0, toStop); for (TestRoute route : routes) { - for (int i = 0; i < route.timetable().numberOfTripSchedules(); i++) { - var trip = route.timetable().getTripSchedule(i); - if(toTrip == trip) { - route.addGuaranteedTxForwardSearch(fromTrip, fromStopPos, trip, i, toStopPos); - } - if(fromTrip == trip) { - route.addGuaranteedTxReverseSearch(toTrip, toStopPos, trip, i, fromStopPos); - } - } + route.addTransferConstraint(fromTrip, fromStopPos, toTrip, toStopPos, constraint); } - guaranteedTransfers.add( + constrainedTransfers.add( new ConstrainedTransfer( null, - new TestTransferPoint(fromStop, fromTrip), - new TestTransferPoint(toStop, toTrip), - GUARANTEED + new TestTransferPoint(fromStop, fromTrip, false), + new TestTransferPoint(toStop, toTrip, false), + constraint ) ); return this; @@ -204,13 +213,13 @@ public McCostParamsBuilder mcCostParamsBuilder() { return costParamsBuilder; } - public ConstrainedTransfer findGuaranteedTransfer( + public ConstrainedTransfer findConstrainedTransfer( TestTripSchedule fromTrip, int fromStop, TestTripSchedule toTrip, int toStop ) { - for (ConstrainedTransfer tx : guaranteedTransfers) { + for (ConstrainedTransfer tx : constrainedTransfers) { if( ((TestTransferPoint)tx.getFrom()).matches(fromTrip, fromStop) && ((TestTransferPoint)tx.getTo()).matches(toTrip, toStop) @@ -226,7 +235,7 @@ public TransferServiceAdaptor<TestTripSchedule> transferServiceAdaptor() { @Override protected ConstrainedTransfer findTransfer( TripStopTime<TestTripSchedule> from, TestTripSchedule toTrip, int toStop ) { - return findGuaranteedTransfer(from.trip(), from.stop(), toTrip, toStop); + return findConstrainedTransfer(from.trip(), from.stop(), toTrip, toStop); } }; } diff --git a/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestTripSchedule.java b/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestTripSchedule.java index 294024592f5..9d9734a04f1 100644 --- a/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestTripSchedule.java +++ b/src/test/java/org/opentripplanner/transit/raptor/_data/transit/TestTripSchedule.java @@ -18,7 +18,7 @@ public class TestTripSchedule implements RaptorTripSchedule { private final int transitReluctanceIndex; - private TestTripSchedule( + protected TestTripSchedule( TestTripPattern pattern, int[] arrivalTimes, int[] departureTimes, diff --git a/src/test/java/org/opentripplanner/transit/raptor/moduletests/E02_NotAllowedConstrainedTransferTest.java b/src/test/java/org/opentripplanner/transit/raptor/moduletests/E02_NotAllowedConstrainedTransferTest.java new file mode 100644 index 00000000000..c86b63899e1 --- /dev/null +++ b/src/test/java/org/opentripplanner/transit/raptor/moduletests/E02_NotAllowedConstrainedTransferTest.java @@ -0,0 +1,126 @@ +package org.opentripplanner.transit.raptor.moduletests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.transit.raptor._data.api.PathUtils.pathsToString; +import static org.opentripplanner.transit.raptor._data.transit.TestRoute.route; +import static org.opentripplanner.transit.raptor._data.transit.TestTransfer.walk; +import static org.opentripplanner.transit.raptor._data.transit.TestTripSchedule.schedule; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.opentripplanner.transit.raptor.RaptorService; +import org.opentripplanner.transit.raptor._data.RaptorTestConstants; +import org.opentripplanner.transit.raptor._data.transit.TestTransitData; +import org.opentripplanner.transit.raptor._data.transit.TestTripSchedule; +import org.opentripplanner.transit.raptor.api.request.Optimization; +import org.opentripplanner.transit.raptor.api.request.RaptorProfile; +import org.opentripplanner.transit.raptor.api.request.RaptorRequestBuilder; +import org.opentripplanner.transit.raptor.api.request.SearchDirection; +import org.opentripplanner.transit.raptor.rangeraptor.configure.RaptorConfig; + +/** + * FEATURE UNDER TEST + * <p> + * Raptor should NOT return a path with a NOT-ALLOWED transfer, instead it should try to find + * another option. + */ +public class E02_NotAllowedConstrainedTransferTest implements RaptorTestConstants { + + private final TestTransitData data = new TestTransitData(); + private final RaptorRequestBuilder<TestTripSchedule> requestBuilder = + new RaptorRequestBuilder<>(); + private final RaptorService<TestTripSchedule> raptorService = + new RaptorService<>(RaptorConfig.defaultConfigForTest()); + + private static final String EXP_PATH = "Walk 30s ~ A ~ BUS R1 0:02 0:05 ~ B " + + "~ BUS R3 0:15 0:20 ~ C ~ Walk 30s [0:01:30 0:20:30 19m"; + private static final String EXP_PATH_NO_COST = EXP_PATH + "]"; + private static final String EXP_PATH_WITH_COST = EXP_PATH + " $2500]"; + + /** + * Schedule: Stop: 1 2 3 R1: 00:02 - 00:05 R2: 00:05 - 00:10 + * <p> + * Access(stop 1) and egress(stop 3) is 30s. + */ + @BeforeEach + public void setup() { + var r1 = route("R1", STOP_A, STOP_B) + .withTimetable(schedule("0:02 0:05")); + var r2 = route("R2", STOP_B, STOP_C) + .withTimetable( + schedule("0:10 0:15"), + // Add another schedule - should not be used even if the not-allowed is + // attached to the first one - not-allowed in Raptor apply to the Route. + // The trip/timetable search should handle not-allowed on trip level. + schedule("0:12 0:17") + ); + var r3 = route("R3", STOP_B, STOP_C) + .withTimetable(schedule("0:15 0:20")); + + var tripA = r1.timetable().getTripSchedule(0); + var tripB = r2.timetable().getTripSchedule(0); + + data.withRoutes(r1, r2, r3); + + // Apply not-allowed on the first trip from R1 and R2 - when found this will apply to + // the second trip in R2 as well. This is of cause not a correct way to implement the + // transit model, a not-allowed transfer should apply to ALL trips if constraint is passed + // to raptor. + data.withConstrainedTransfer(tripA, STOP_B, tripB, STOP_B, TestTransitData.TX_NOT_ALLOWED); + data.mcCostParamsBuilder().transferCost(100); + + requestBuilder.searchParams() + .constrainedTransfersEnabled(true) + .addAccessPaths(walk(STOP_A, D30s)) + .addEgressPaths(walk(STOP_C, D30s)) + .earliestDepartureTime(T00_00) + .latestArrivalTime(T00_30) + .timetableEnabled(true); + + ModuleTestDebugLogging.setupDebugLogging(data, requestBuilder); + } + + @Test + public void standardOneIteration() { + var request = requestBuilder + .profile(RaptorProfile.STANDARD) + .searchParams().searchOneIterationOnly() + .build(); + var response = raptorService.route(request, data); + assertEquals(EXP_PATH_NO_COST, pathsToString(response)); + } + + @Test + public void standardDynamicSearchWindow() { + var request = requestBuilder + .profile(RaptorProfile.STANDARD) + .build(); + var response = raptorService.route(request, data); + assertEquals(EXP_PATH_NO_COST, pathsToString(response)); + } + + @Test + public void standardReverseOneIteration() { + var request = requestBuilder + .searchDirection(SearchDirection.REVERSE) + .profile(RaptorProfile.STANDARD) + .searchParams().searchOneIterationOnly() + .build(); + + var response = raptorService.route(request, data); + + assertEquals(EXP_PATH_NO_COST, pathsToString(response)); + } + + @Test + public void multiCriteria() { + requestBuilder.optimizations().add(Optimization.PARETO_CHECK_AGAINST_DESTINATION); + var request = requestBuilder + .profile(RaptorProfile.MULTI_CRITERIA) + .build(); + + var response = raptorService.route(request, data); + + assertEquals(EXP_PATH_WITH_COST, pathsToString(response)); + } +} \ No newline at end of file diff --git a/src/test/java/org/opentripplanner/updater/stoptime/TimetableSnapshotSourceTest.java b/src/test/java/org/opentripplanner/updater/stoptime/TimetableSnapshotSourceTest.java index c4589eb23c7..a5cebd54e41 100644 --- a/src/test/java/org/opentripplanner/updater/stoptime/TimetableSnapshotSourceTest.java +++ b/src/test/java/org/opentripplanner/updater/stoptime/TimetableSnapshotSourceTest.java @@ -1,10 +1,21 @@ package org.opentripplanner.updater.stoptime; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.opentripplanner.gtfs.GtfsContextBuilder.contextBuilder; + import com.google.protobuf.InvalidProtocolBufferException; import com.google.transit.realtime.GtfsRealtime.TripDescriptor; import com.google.transit.realtime.GtfsRealtime.TripUpdate; import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeEvent; import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate; +import java.text.ParseException; +import java.util.Arrays; +import java.util.Calendar; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; @@ -14,7 +25,6 @@ import org.opentripplanner.gtfs.GtfsContext; import org.opentripplanner.model.FeedScopedId; import org.opentripplanner.model.PickDrop; -import org.opentripplanner.model.Stop; import org.opentripplanner.model.Timetable; import org.opentripplanner.model.TimetableSnapshot; import org.opentripplanner.model.Trip; @@ -25,18 +35,6 @@ import org.opentripplanner.routing.trippattern.RealTimeState; import org.opentripplanner.routing.trippattern.TripTimes; -import java.text.ParseException; -import java.util.Arrays; -import java.util.Calendar; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.opentripplanner.gtfs.GtfsContextBuilder.contextBuilder; - /** * TODO OTP2 - Test is too close to the implementation and will need to be reimplemented. */ @@ -126,8 +124,8 @@ public void testHandleCanceledTrip() throws InvalidProtocolBufferException { final TripTimes tripTimes = forToday.getTripTimes(tripIndex); for (int i = 0; i < tripTimes.getNumStops(); i++) { - assertEquals(PickDrop.CANCELLED, pattern.getStopPattern().getPickup(i)); - assertEquals(PickDrop.CANCELLED, pattern.getStopPattern().getDropoff(i)); + assertEquals(PickDrop.CANCELLED, pattern.getBoardType(i)); + assertEquals(PickDrop.CANCELLED, pattern.getAlightType(i)); } assertEquals(RealTimeState.CANCELED, tripTimes.getRealTimeState()); }