From ca028ac1a05bef25b07b3eeb9292988e7399763a Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Sat, 18 Dec 2021 02:35:48 +0100 Subject: [PATCH] feat: Support for GTFS Trip, Route, Stop & Station Transfers - The Transfer report is improved --- .../ext/reportapi/model/CsvReportBuilder.java | 6 +- .../ext/reportapi/model/TransfersReport.java | 124 +++++--- .../model/timetable/InterchangeType.java | 49 ++- .../gtfs/mapping/TransferMapper.java | 206 ++++++------ .../org/opentripplanner/model/Station.java | 18 ++ .../java/org/opentripplanner/model/Stop.java | 18 +- .../model/impl/OtpTransitServiceBuilder.java | 19 +- .../model/transfer/ConstrainedTransfer.java | 38 ++- .../model/transfer/RouteTransferPoint.java | 42 ++- .../model/transfer/StationTransferPoint.java | 42 +++ .../model/transfer/StopTransferPoint.java | 20 +- .../model/transfer/TransferConstraint.java | 25 +- .../model/transfer/TransferPoint.java | 100 +++--- .../model/transfer/TransferPointMap.java | 89 ++++++ .../model/transfer/TransferService.java | 164 ++-------- .../model/transfer/TransferType.java | 41 --- .../model/transfer/TripTransferPoint.java | 54 ++-- .../raptor/transit/StopIndexForRaptor.java | 1 - .../TripPatternWithRaptorStopIndexes.java | 84 ++--- .../ConstrainedBoardingSearch.java | 86 +++-- .../ConstrainedBoardingSearchForward.java | 26 ++ .../ConstrainedBoardingSearchReverse.java | 25 ++ .../ConstrainedBoardingSearchStrategy.java | 38 +++ .../ConstrainedTransferBoarding.java | 16 +- .../TransferForPattern.java | 71 +++++ .../TransferForPatternByStopPos.java | 37 +++ .../TransferIndexGenerator.java | 229 ++++++++++++++ .../TransferPointForPatternFactory.java | 109 +++++++ .../TransferPointMatcher.java | 10 + .../mappers/TransferIndexGenerator.java | 62 ---- .../transit/mappers/TransitLayerMapper.java | 10 +- .../transit/mappers/TripPatternMapper.java | 11 +- .../ConstrainedBoardingSearchForward.java | 66 ---- .../ConstrainedBoardingSearchReverse.java | 66 ---- .../ConstrainedBoardingSearchStrategy.java | 29 -- .../standard/StopArrivalsState.java | 8 +- .../BestTimesOnlyStopArrivalsState.java | 11 + .../linking/LinkStopToPlatformTest.java | 2 +- .../model/impl/OtpTransitServiceImplTest.java | 7 +- .../transfer/ConstrainedTransferTest.java | 20 +- .../transfer/TransferConstraintTest.java | 8 + .../model/transfer/TransferPointMapTest.java | 48 +++ .../model/transfer/TransferPointTest.java | 82 ++--- .../model/transfer/TransferServiceTest.java | 54 +++- .../model/transfer/TransferTestData.java | 31 +- .../ConstrainedBoardingSearchTest.java | 295 ++++++++++++++++++ .../transit/mappers/DateMapperTest.java | 8 +- .../mappers/StopIndexForRaptorTest.java | 4 +- .../ConstrainedBoardingSearchTest.java | 228 -------------- ...rRoutingRequestTransitDataCreatorTest.java | 23 +- ...gRequestTransitDataProviderFilterTest.java | 30 +- .../raptor/transit/request/TestRouteData.java | 58 ++-- .../transit/request/TestTransitCaseData.java | 17 +- .../transit/request/TripPatternWithId.java | 4 +- 54 files changed, 1802 insertions(+), 1167 deletions(-) create mode 100644 src/main/java/org/opentripplanner/model/transfer/StationTransferPoint.java create mode 100644 src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java delete mode 100644 src/main/java/org/opentripplanner/model/transfer/TransferType.java rename src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/{request => constrainedtransfer}/ConstrainedBoardingSearch.java (53%) create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchForward.java create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchReverse.java create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchStrategy.java rename src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/{request => constrainedtransfer}/ConstrainedTransferBoarding.java (72%) create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferForPattern.java create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferForPatternByStopPos.java create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferIndexGenerator.java create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferPointForPatternFactory.java create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferPointMatcher.java delete mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TransferIndexGenerator.java delete mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchForward.java delete mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchReverse.java delete mode 100644 src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchStrategy.java create mode 100644 src/test/java/org/opentripplanner/model/transfer/TransferPointMapTest.java create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchTest.java delete mode 100644 src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchTest.java 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. *

- * 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 6a6652dad56..2bb48f1eb68 100644 --- a/src/ext/java/org/opentripplanner/ext/reportapi/model/TransfersReport.java +++ b/src/ext/java/org/opentripplanner/ext/reportapi/model/TransfersReport.java @@ -6,17 +6,23 @@ import java.util.List; import org.locationtech.jts.geom.Coordinate; import org.opentripplanner.common.geometry.SphericalDistanceLibrary; +import org.opentripplanner.model.Station; import org.opentripplanner.model.Stop; +import org.opentripplanner.model.Trip; +import org.opentripplanner.model.TripPattern; import org.opentripplanner.model.transfer.ConstrainedTransfer; +import org.opentripplanner.model.transfer.RouteTransferPoint; +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 { @@ -41,32 +47,39 @@ public static String export(List 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 dist = (from.coordinate == null || to.coordinate == null) ? "" : String.format( "%.0fm", - SphericalDistanceLibrary.fastDistance(from.c, to.c) + SphericalDistanceLibrary.fastDistance(from.coordinate, to.coordinate) ); 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.addNumber(from.specificity); + buf.addText(to.type); + buf.addText(to.entityId); + buf.addText(to.route); buf.addText(to.trip); buf.addText(to.loc); + buf.addNumber(to.specificity); buf.addTime(from.time, NOT_SET); buf.addTime(to.time, NOT_SET); buf.addText(duration); @@ -85,42 +98,83 @@ private TxPoint pointInfo( boolean arrival ) { 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(); + addLocation(r, ptn, tp.getStopPositionInPattern(), trip, arrival); + } + else if(p instanceof RouteTransferPoint) { + var rp = (RouteTransferPoint)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.getStopPositionInPattern(), null, arrival); + } + else if(p instanceof StopTransferPoint) { + var sp = (StopTransferPoint)p; + Stop stop = sp.getStop(); + r.type = "Stop"; + r.entityId = stop.getId().getId(); + r.loc = stop.getName(); + r.coordinate = stop.getCoordinate().asJtsCoordinate(); + } + 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().asJtsCoordinate(); + } + r.specificity = p.getSpecificityRanking(); + r.coordinate = null; + return r; + } - if (ptn.getStops().size() > p.getStopPosition()) { - int pos = p.getStopPosition(); - Stop 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(); + private static void addLocation( + TxPoint r, + TripPattern pattern, + int stopPosition, + Trip trip, + boolean arrival + ) { + if(pattern == null || stopPosition >= pattern.getStopPattern().getSize()) { + r.loc += "[Stop position not found: " + stopPosition + "]"; + return; } - else { - r.loc += "[Stop index not found: " + p.getStopPosition() + "]"; + Stop stop = pattern.getStops().get(stopPosition); + r.loc += stop.getName() + " [" + stopPosition + "]" + " " + stop.getCoordinate(); + r.coordinate = stop.getCoordinate().asJtsCoordinate(); + + if(trip != null) { + var tt = pattern.getScheduledTimetable().getTripTimes(trip); + r.time = arrival + ? tt.getScheduledArrivalTime(stopPosition) + : tt.getScheduledDepartureTime(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 Coordinate coordinate = null; private int time = NOT_SET; } } 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 fromTo + ) { + return fromTo.apply(transfer(environment)); + } + + private static Trip transferTrip( + DataFetchingEnvironment environment, + Function fromTo + ) { + return TransferPoint.getTrip(transferPoint(environment, fromTo)); + } + + private static Route transferRoute( + DataFetchingEnvironment environment, + Function fromTo + ) { + return TransferPoint.getRoute(transferPoint(environment, fromTo)); + } + private static TransferConstraint constraint(DataFetchingEnvironment environment) { return transfer(environment).getTransferConstraint(); } diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/TransferMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/TransferMapper.java index cc02fa75043..20a3b07a491 100644 --- a/src/main/java/org/opentripplanner/gtfs/mapping/TransferMapper.java +++ b/src/main/java/org/opentripplanner/gtfs/mapping/TransferMapper.java @@ -1,20 +1,23 @@ 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.RouteTransferPoint; +import org.opentripplanner.model.transfer.StationTransferPoint; import org.opentripplanner.model.transfer.StopTransferPoint; import org.opentripplanner.model.transfer.TransferConstraint; import org.opentripplanner.model.transfer.TransferPoint; @@ -69,6 +72,9 @@ class TransferMapper { private final TripStopTimes stopTimesByTrip; + private final Multimap tripsByRoute = ArrayListMultimap.create(); + + TransferMapper( RouteMapper routeMapper, StationMapper stationMapper, @@ -97,27 +103,17 @@ static TransferPriority mapTypeToPriority(int type) { } Collection map(Collection allTransfers) { - List 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 map(org.onebusaway.gtfs.model.Transfer original) { - return original == null ? List.of() : doMap(original); - } - - private Collection 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 @@ -131,35 +127,21 @@ private Collection doMap(org.onebusaway.gtfs.model.Transfer 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. - - Collection fromStops = getStopOrChildStops(rhs.getFromStop()); - Collection toStops = getStopOrChildStops(rhs.getToStop()); - - Collection fromPoints = mapTransferPoints(fromStops, fromTrip, fromRoute); - Collection toPoints = mapTransferPoints(toStops, toTrip, toRoute); - - Collection 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.mappedTrips()) { + tripsByRoute.put(trip.getRoute(), trip); } - return result; } private TransferConstraint mapConstraint(Transfer rhs, Trip fromTrip, Trip toTrip) { @@ -172,64 +154,90 @@ private TransferConstraint mapConstraint(Transfer rhs, Trip fromTrip, Trip toTri return builder.build(); } - private Collection mapTransferPoints( - Collection 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 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 (Stop stop : stops) { - result.add(new StopTransferPoint(stop)); - } + station = stationMapper.map(rhsStopOrStation); + } + if(trip != null) { + int stopPositionInPattern = stopPosition(trip, stop, station, boardTrip); + return stopPositionInPattern < 0 ? null : new TripTransferPoint(trip, stopPositionInPattern); + } + else if(route != null) { + var trips = tripsByRoute.get(route); + if(trips.isEmpty()) { throw new IllegalStateException("No trips found fr route: " + route); } + int stopPositionInPattern = stopPosition(route, stop, station, boardTrip); + return new RouteTransferPoint(route, stopPositionInPattern); + } + else if(stop != null) { + return new StopTransferPoint(stop); + } + else if(station != null) { + return new StationTransferPoint(station); } - return result; + + throw new IllegalStateException("Should not get here!"); } - private Collection createTransferPointForTrip( - Collection stops, - Trip trip, - BiFunction createPoint - ) { - Collection result = new ArrayList<>(); + + private int stopPosition(Route route, Stop stop, Station station, boolean boardTrip) { + var stopPosList = tripsByRoute.get(route).stream() + .map(t -> stopPosition(t, stop, station, boardTrip)) + .distinct() + .collect(Collectors.toList()); + + if(stopPosList.size() == 1) { return stopPosList.get(0); } + + LOG.error( + "In GTFS 'transfers.txt' a transfer-point can be a combination of route and stop/station!" + + "OTP only support this case, if the stop/station have the same stop point in trip-" + + "pattern for all trips in the route. Route: " + route + ); + return -1; + } + + + private int stopPosition(Trip trip, Stop stop, Station station, boolean boardTrip) { List 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 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 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) { @@ -238,16 +246,4 @@ private boolean sameBlockId(Trip a, Trip b) { } return a.getBlockId() != null && a.getBlockId().equals(b.getBlockId()); } - - @Nullable - private Map> createTripsByRouteMapIfRouteTransfersExist( - Collection trips, - Collection 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/model/Station.java b/src/main/java/org/opentripplanner/model/Station.java index 912c3089d87..e554959021d 100644 --- a/src/main/java/org/opentripplanner/model/Station.java +++ b/src/main/java/org/opentripplanner/model/Station.java @@ -58,6 +58,24 @@ public Station( this.priority = priority == null ? DEFAULT_PRIORITY : priority; } + /** + * 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); } diff --git a/src/main/java/org/opentripplanner/model/Stop.java b/src/main/java/org/opentripplanner/model/Stop.java index 0bf945137cf..b83fdf061b5 100644 --- a/src/main/java/org/opentripplanner/model/Stop.java +++ b/src/main/java/org/opentripplanner/model/Stop.java @@ -60,12 +60,17 @@ public Stop( this.vehicleType = vehicleType; } - /** - * 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, @@ -79,9 +84,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/impl/OtpTransitServiceBuilder.java b/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java index d20401aeeb7..1f750f2c566 100644 --- a/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java +++ b/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java @@ -332,19 +332,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 5a6be6d9d15..44569e5aa72 100644 --- a/src/main/java/org/opentripplanner/model/transfer/ConstrainedTransfer.java +++ b/src/main/java/org/opentripplanner/model/transfer/ConstrainedTransfer.java @@ -18,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; @@ -67,17 +69,43 @@ public boolean noConstraints() { return constraint.isRegularTransfer(); } - public boolean matchesStopPos(int fromStopPos, int toStopPos) { - return from.getStopPosition() == fromStopPos && to.getStopPosition() == toStopPos; - } - /** * * Specificity of a transfer * + * + * The ranking implemented here is slightly modified: + *

*/ 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/RouteTransferPoint.java b/src/main/java/org/opentripplanner/model/transfer/RouteTransferPoint.java index 6faa133cf00..2f16db439af 100644 --- a/src/main/java/org/opentripplanner/model/transfer/RouteTransferPoint.java +++ b/src/main/java/org/opentripplanner/model/transfer/RouteTransferPoint.java @@ -2,25 +2,26 @@ 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 - *

- * 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 { +import org.opentripplanner.model.base.ValueObjectToStringBuilder; + +public final class RouteTransferPoint implements TransferPoint, Serializable { private static final long serialVersionUID = 1L; private final Route route; + private final int stopPositionInPattern; - public RouteTransferPoint(Route route, Trip trip, int stopPosition) { - super(trip, stopPosition); + public RouteTransferPoint(Route route, int stopPositionInPattern) { this.route = route; + this.stopPositionInPattern = stopPositionInPattern; + } + + public Route getRoute() { + return route; + } + + public int getStopPositionInPattern() { + return stopPositionInPattern; } @Override @@ -29,12 +30,19 @@ public boolean applyToAllTrips() { } @Override - public int getSpecificityRanking() { return 1; } + public int getSpecificityRanking() { return 2; } + + @Override + public boolean isRouteTransferPoint() { return true; } @Override public String toString() { - return ""; + return ValueObjectToStringBuilder.of() + .addText("") + .toString(); } } 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..dece0b8886d --- /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 applyToAllTrips() { + return true; + } + + @Override + public int getSpecificityRanking() { + return 0; + } + + @Override + public boolean isStationTransferPoint() { return true; } + + public String toString() { + return ValueObjectToStringBuilder.of() + .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 9a5e5bd30aa..b0d9217f620 100644 --- a/src/main/java/org/opentripplanner/model/transfer/StopTransferPoint.java +++ b/src/main/java/org/opentripplanner/model/transfer/StopTransferPoint.java @@ -1,7 +1,6 @@ package org.opentripplanner.model.transfer; import java.io.Serializable; -import java.util.Objects; import org.opentripplanner.model.Stop; public class StopTransferPoint implements TransferPoint, Serializable { @@ -10,11 +9,11 @@ public class StopTransferPoint implements TransferPoint, Serializable { private final Stop stop; + public StopTransferPoint(Stop stop) { this.stop = stop; } - @Override public Stop getStop() { return stop; } @@ -26,24 +25,13 @@ public boolean applyToAllTrips() { @Override public int getSpecificityRanking() { - return 0; + return 1; } @Override + public boolean isStopTransferPoint() { return true; } + public String toString() { return ""; } - - @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()); - } - - @Override - public int hashCode() { - return Objects.hash(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 6e0f276c51d..faa70764ab0 100644 --- a/src/main/java/org/opentripplanner/model/transfer/TransferConstraint.java +++ b/src/main/java/org/opentripplanner/model/transfer/TransferConstraint.java @@ -97,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; } @@ -110,12 +113,21 @@ public boolean isGuaranteed() { * if the alight-slack or board-slack is too tight. We ignore slack for facilitated transfers. *

* 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; @@ -131,6 +143,10 @@ public boolean isRegularTransfer() { /** * Maximum time after scheduled departure time the connecting transport is guarantied to wait * for the delayed trip. + *

+ * THIS IS NOT CONSIDERED IN RAPTOR. Otp relay 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; @@ -195,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 ec20c5bd54b..31b5fdadc08 100644 --- a/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java +++ b/src/main/java/org/opentripplanner/model/transfer/TransferPoint.java @@ -1,13 +1,15 @@ package org.opentripplanner.model.transfer; +import javax.annotation.Nullable; +import org.opentripplanner.model.Route; +import org.opentripplanner.model.Station; import org.opentripplanner.model.Stop; import org.opentripplanner.model.Trip; - /** * This interface is used to represent a point or location where a transfer start from or end. * - *

There are 3 different Transfer points: + *

There are 4 different Transfer points: *

    *
  1. * {@link StopTransferPoint} This apply to all trip stopping at the given stop. @@ -15,57 +17,39 @@ *

    This is the least specific type, and is overridden if a more specific type exist. *

  2. *
  3. - * 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 apply to all trip stopping at a stop part of the given + * station. + *

    The specificity-ranking is above {@link StationTransferPoint}s and less than + * {@link RouteTransferPoint}. + *

  4. + *
  5. + * A {@link RouteTransferPoint} is a from/to point for a Route at the given stop/station. This + * only exist in GTFS, not in the Nordic NeTex profile. * *

    The specificity-ranking is above {@link StopTransferPoint}s and less than * {@link TripTransferPoint}. *

  6. *
  7. * {@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. * *

    This is the most specific point type, and will override both {@link RouteTransferPoint} * and {@link StopTransferPoint} if more than one match exist. *

  8. *
+ *

+ * The GTFS Transfers may specify a transfer from/to a route/trip and stop/station. But in OTP we + * map the stop to a stop position in pattern. The OTP model {@link RouteTransferPoint} and + * {@link TripTransferPoint} do NOT reference the stop/station, but the + * {@code stopPositionInPattern} instead. The reason is that real-time updates could invalidate a + * (route+stop) transfer-point, since the stop could change to another platform(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). */ public interface TransferPoint { - int NOT_AVAILABLE = -1; - - default Stop getStop() { - return null; - } - - default Trip getTrip() { - return null; - } - /** Return {@code true} if this transfer point apply to all trips in pattern */ boolean applyToAllTrips(); - /** - * 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}. - */ - default int getStopPosition() { - return NOT_AVAILABLE; - } - /** * * Specificity of a transfer @@ -73,8 +57,46 @@ default int getStopPosition() { */ 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; + /** 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 isRouteTransferPoint() { return false; } + + default RouteTransferPoint asRouteTransferPoint() { return (RouteTransferPoint) 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; } + + + /** + * Utility method witch can be used in APIs to get the trip, if it exists, from a transfer point. + */ + @Nullable + static Trip getTrip(TransferPoint point) { + return point.isTripTransferPoint() ? point.asTripTransferPoint().getTrip() : null; + } + + /** + * Utility method witch can be used in APIs to get the route, if it exists, from a transfer point. + */ + @Nullable + static Route getRoute(TransferPoint point) { + if(point.isTripTransferPoint()) { + return point.asTripTransferPoint().getTrip().getRoute(); + } + if(point.isRouteTransferPoint()) { + return point.asRouteTransferPoint().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..12de78096d7 --- /dev/null +++ b/src/main/java/org/opentripplanner/model/transfer/TransferPointMap.java @@ -0,0 +1,89 @@ +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.Stop; +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 { + private final Map, E> tripMap = new HashMap<>(); + private final Map, E> routeMap = new HashMap<>(); + private final Map stopMap = new HashMap<>(); + private final Map 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.isRouteTransferPoint()) { + var rp = point.asRouteTransferPoint(); + routeMap.put(routeKey(rp.getRoute(), rp.getStopPositionInPattern()), 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 creator) { + if(point.isTripTransferPoint()) { + var tp = point.asTripTransferPoint(); + return tripMap.computeIfAbsent(tripKey(tp.getTrip(), tp.getStopPositionInPattern()), k -> creator.get()); + } + else if(point.isRouteTransferPoint()) { + var rp = point.asRouteTransferPoint(); + return routeMap.computeIfAbsent(routeKey(rp.getRoute(), rp.getStopPositionInPattern()), 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 get(Trip trip, Stop stop, int stopPointInPattern) { + return Stream.of( + tripMap.get(tripKey(trip, stopPointInPattern)), + routeMap.get(routeKey(trip.getRoute(), stopPointInPattern)), + stopMap.get(stop), + stationMap.get(stop.getParentStation()) + ) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private static T2 tripKey(Trip trip, int stopPositionInPattern) { + return new T2<>(trip, stopPositionInPattern); + } + + private static T2 routeKey(Route route, int stopPositionInPattern) { + return new T2<>(route, stopPositionInPattern); + } +} diff --git a/src/main/java/org/opentripplanner/model/transfer/TransferService.java b/src/main/java/org/opentripplanner/model/transfer/TransferService.java index b9ecc975d8f..26cccfb32c0 100644 --- a/src/main/java/org/opentripplanner/model/transfer/TransferService.java +++ b/src/main/java/org/opentripplanner/model/transfer/TransferService.java @@ -1,20 +1,14 @@ 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.List; -import java.util.Map; 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.Trip; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * This class represents all transfer information in the graph. Transfers are grouped by @@ -25,37 +19,19 @@ */ public class TransferService implements Serializable { - private static final Logger LOG = LoggerFactory.getLogger(TransferService.class); - - /** Index of constrained transfers with an effect on routing by the to/destination point. */ - private final Multimap constrainedTransferByToPoint; - - /** - * Table which contains transfers between two trips/routes - */ - private final Map, ConstrainedTransfer> trip2tripTransfers; + private final List transfersList; /** - * Table which contains transfers between a trip/route and a 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, ConstrainedTransfer> trip2StopTransfers; - - /** - * Table which contains transfers between a stop and a trip/route - */ - private final Map, ConstrainedTransfer> stop2TripTransfers; - - /** - * Table which contains transfers between two stops - */ - private final Map, ConstrainedTransfer> stop2StopTransfers; + private final TransferPointMap> 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 transfers) { @@ -65,16 +41,7 @@ public void addAll(Collection transfers) { } public List listAll() { - var list = new ArrayList(); - list.addAll(trip2tripTransfers.values()); - list.addAll(trip2StopTransfers.values()); - list.addAll(stop2TripTransfers.values()); - list.addAll(stop2StopTransfers.values()); - return list; - } - - public Collection listConstrainedTransfersTo(Trip toTrip, int toStopIndex) { - return constrainedTransferByToPoint.get(new TripTransferPoint(toTrip, toStopIndex)); + return transfersList; } @Nullable @@ -86,105 +53,20 @@ public ConstrainedTransfer findTransfer( 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): - *

    - *
  1. trip -> trip - *
  2. trip -> stop - *
  3. stop -> trip - *
  4. stop -> stop - *
- * 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 - ) { - 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)) + .stream() + .findFirst() + .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 12bde682375..8df22d712a6 100644 --- a/src/main/java/org/opentripplanner/model/transfer/TripTransferPoint.java +++ b/src/main/java/org/opentripplanner/model/transfer/TripTransferPoint.java @@ -1,30 +1,28 @@ 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; } @Override @@ -32,36 +30,20 @@ public boolean applyToAllTrips() { return false; } - /** - * - * GTFS Specificity of a transfer - * - * {@link #equals(Object)} - */ @Override - public int getSpecificityRanking() { return 2; } + public int getSpecificityRanking() { return 3; } @Override - public String toString() { - return ""; - } + public boolean isTripTransferPoint() { return true; } - /** - * 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; } - - TripTransferPoint that = (TripTransferPoint) o; - return stopPosition == that.stopPosition && trip.getId().equals(that.trip.getId()); - } - - @Override - public final int hashCode() { - return Objects.hash(trip.getId(), stopPosition); + public String toString() { + return ValueObjectToStringBuilder.of() + .addText("") + .toString(); } } 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 d788e7345da..35313455dbf 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,6 +1,5 @@ package org.opentripplanner.routing.algorithm.raptor.transit; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; 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 af812371686..4f2d2b3366e 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> 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> 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(); @@ -70,20 +67,19 @@ public RaptorConstrainedTripScheduleBoardingSearch 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{" + @@ -92,26 +88,32 @@ public String toString() { '}'; } - /** This is 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); } - /** This is 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 void add(TIntObjectMap> 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/request/ConstrainedBoardingSearch.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearch.java similarity index 53% rename from src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearch.java rename to src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearch.java index 9c9d5b7527b..1beff15f5bd 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearch.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearch.java @@ -1,15 +1,14 @@ -package org.opentripplanner.routing.algorithm.raptor.transit.request; +package org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer; -import gnu.trove.map.TIntObjectMap; -import java.util.Collection; 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.ConstrainedTransfer; 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; @@ -26,22 +25,23 @@ public final class ConstrainedBoardingSearch 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 TIntObjectMap> transfers; + private final TransferForPatternByStopPos transfers; - private List currentTransfers; + private List currentTransfers; private int currentTargetStopPos; public ConstrainedBoardingSearch( boolean forwardSearch, - TIntObjectMap> transfers + TransferForPatternByStopPos transfers ) { - this.translator = forwardSearch ? FORWARD_STRATEGY : REVERSE_STRATEGY; this.transfers = transfers; + this.translator = forwardSearch ? FORWARD_STRATEGY : REVERSE_STRATEGY; } @Override @@ -61,18 +61,13 @@ public RaptorTripScheduleBoardOrAlightEvent find( int sourceStopIndex, int sourceArrivalTime ) { - final Trip sourceTrip = sourceTripSchedule.getOriginalTripTimes().getTrip(); - final int sourceStopPos = translator.findSourceStopPosition( - sourceTripSchedule, sourceArrivalTime, sourceStopIndex - ); + var transfers = findMatchingTransfers(sourceTripSchedule, sourceStopIndex); - var list = findMatchingTransfers(sourceTrip, sourceStopPos); + if(transfers.isEmpty()) { return null; } - if(list.isEmpty()) { return null; } - - var tripInfo = translator.findTimetableTripInfo( + T2 tripInfo = findTimetableTripInfo( timetable, - list, + transfers, currentTargetStopPos, sourceArrivalTime ); @@ -80,7 +75,7 @@ public RaptorTripScheduleBoardOrAlightEvent find( if(tripInfo == null) { return null; } final int tripIndex = tripInfo.first; - final TransferConstraint transferConstraint = tripInfo.second; + final var transferConstraint = tripInfo.second; var trip = timetable.getTripSchedule(tripIndex); int departureTime = translator.time(trip, currentTargetStopPos); @@ -90,12 +85,59 @@ public RaptorTripScheduleBoardOrAlightEvent find( ); } - private Collection findMatchingTransfers( - Trip sourceTrip, - int sourceStopPos + private List findMatchingTransfers( + TripSchedule tripSchedule, + int stopIndex ) { + final Trip trip = tripSchedule.getOriginalTripTimes().getTrip(); return currentTransfers.stream() - .filter(tx -> translator.source(tx).matches(sourceTrip, sourceStopPos)) + .filter(t -> t.matchesSourcePoint(stopIndex, trip)) .collect(Collectors.toList()); } + + /** + * Find the trip to board (trip index) and the transfer constraint + */ + public T2 findTimetableTripInfo( + RaptorTimeTable timetable, + List transfers, + int stopPos, + int sourceTime + ) { + // Abort after 6 hours + 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; } + + 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); + } + } + 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 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 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 { + + /** + *
    + *
  1. In a forward search return the DEPARTURE time. + *
  2. In a reverse search return the ARRIVAL time. + *
+ */ + int time(RaptorTripSchedule schedule, int stopPos); + + /** + *
    + *
  1. In a forward search the time is before another time if it is in the PAST. + *
  2. In a reverse search the time is before another time if it is in the FUTURE. + *
+ */ + boolean timeIsBefore(int time0, int time1); + + /** + *
    + *
  1. In a forward search iterate in departure order. + *
  2. In a reverse search iterate in reverse departure order, + * starting with the last trip in the schedule. + *
+ */ + IntIterator scheduleIndexIterator(RaptorTimeTable 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 72% 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 a205e30242f..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,22 +1,24 @@ -package org.opentripplanner.routing.algorithm.raptor.transit.request; +package org.opentripplanner.routing.algorithm.raptor.transit.constrainedtransfer; import javax.validation.constraints.NotNull; -import org.opentripplanner.model.transfer.TransferConstraint; +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 implements RaptorTripScheduleBoardOrAlightEvent { - 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( - @NotNull TransferConstraint constraint, + @NotNull RaptorTransferConstraint constraint, int tripIndex, @NotNull T trip, int stopPositionInPattern, @@ -44,7 +46,5 @@ public class ConstrainedTransferBoarding @Override @NotNull - public TransferConstraint getTransferConstraint() { - return constraint; - } + 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 { + + /** + * 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> 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 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..f20bed967d9 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/TransferIndexGenerator.java @@ -0,0 +1,229 @@ +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.stream.Collectors; +import org.opentripplanner.model.Route; +import org.opentripplanner.model.Station; +import org.opentripplanner.model.Stop; +import org.opentripplanner.model.Trip; +import org.opentripplanner.model.TripPattern; +import org.opentripplanner.model.transfer.ConstrainedTransfer; +import org.opentripplanner.model.transfer.RouteTransferPoint; +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 final Collection constrainedTransfers; + private final Map> patternsByStation = new HashMap<>(); + private final Map> patternsByStop = new HashMap<>(); + private final Map> patternsByRoute = new HashMap<>(); + private final Map> patternsByTrip = new HashMap<>(); + private final StopIndexForRaptor stopIndex; + + public TransferIndexGenerator( + Collection constrainedTransfers, + Collection 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; } + + for (var fromPoint : findTPoints(tx.getFrom())) { + if (fromPoint.canAlight()) { + for (var toPoint : findTPoints(tx.getTo())) { + 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 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 (Stop 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 findTPoints(TransferPoint txPoint) { + if (txPoint.isStationTransferPoint()) { + return findTPoints(txPoint.asStationTransferPoint()); + } + else if (txPoint.isStopTransferPoint()) { + return findTPoints(txPoint.asStopTransferPoint()); + } + else if (txPoint.isRouteTransferPoint()) { + return findTPoint(txPoint.asRouteTransferPoint()); + } + else { + return findTPoints(txPoint.asTripTransferPoint()); + } + } + + private List findTPoints(StationTransferPoint point) { + var station = point.getStation(); + var patterns = patternsByStation.get(station); + var sourcePoint = createTransferPointForPattern(station, stopIndex); + var result = new ArrayList(); + + for (TripPatternWithRaptorStopIndexes pattern : patterns) { + Stop[] stops = pattern.getPattern().getStopPattern().getStops(); + for (int pos = 0; pos < stops.length; ++pos) { + if (point.getStation() == stops[pos].getParentStation()) { + result.add(new TPoint(pattern, sourcePoint, null, pos)); + } + } + } + return result; + } + + private List findTPoints(StopTransferPoint point) { + var stop = point.asStopTransferPoint().getStop(); + var patterns = patternsByStop.get(stop); + var sourcePoint = createTransferPointForPattern(stopIndex.indexOf(stop)); + + var result = new ArrayList(); + for (TripPatternWithRaptorStopIndexes pattern : patterns) { + Stop[] stops = pattern.getPattern().getStopPattern().getStops(); + for (int pos = 0; pos < stops.length; ++pos) { + if (point.getStop() == stops[pos]) { + result.add(new TPoint(pattern, sourcePoint, null, pos)); + } + } + } + return result; + } + + private List findTPoint(RouteTransferPoint point) { + var route = point.getRoute(); + var patterns = patternsByRoute.get(route); + int stopPosInPattern = point.getStopPositionInPattern(); + int stopIndex = patterns.get(0).stopIndex(stopPosInPattern); + var sourcePoint = createTransferPointForPattern(route, stopIndex); + return patterns.stream() + .map(p -> new TPoint(p, sourcePoint, null, stopPosInPattern)) + .collect(Collectors.toList()); + } + + private List findTPoints(TripTransferPoint point) { + var trip = point.getTrip(); + 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().getStopPattern().getSize() - 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..06f8761f76f --- /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.Stop; +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 toStop; + private final Station station; + + private StationSP(IntFunction 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/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 patternByTrip = new HashMap<>(); - - private TransferIndexGenerator(TransferService transferService) { - this.transferService = transferService; - } - - public static void generateTransfers( - TransferService transferService, - Collection tripPatterns - ) { - var generator = new TransferIndexGenerator(transferService); - - generator.setupPatternByTripIndex(tripPatterns); - - for (TripPatternWithRaptorStopIndexes pattern : tripPatterns) { - generator.generateTransfers(pattern); - } - } - - private void setupPatternByTripIndex(Collection 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/TransitLayerMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/mappers/TransitLayerMapper.java index 7eb269f352f..77362128a66 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..fbd10852057 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 mapOldTripPatternToRap for (TripPattern oldTripPattern : oldTripPatterns) { TripPatternWithRaptorStopIndexes newTripPattern = new TripPatternWithRaptorStopIndexes( - stopIndex.listStopIndexesForStops(oldTripPattern.getStopPattern().getStops()), - oldTripPattern + oldTripPattern, + stopIndex.listStopIndexesForStops(oldTripPattern.getStopPattern().getStops()) ); newTripPatternForOld.put(oldTripPattern, newTripPattern); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchForward.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchForward.java deleted file mode 100644 index 98c497c17b2..00000000000 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchForward.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.opentripplanner.routing.algorithm.raptor.transit.request; - -import java.util.Collection; -import org.opentripplanner.common.model.T2; -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.RaptorTimeTable; -import org.opentripplanner.transit.raptor.api.transit.RaptorTripSchedule; - -class ConstrainedBoardingSearchForward - implements ConstrainedBoardingSearchStrategy { - - @Override - public TransferPoint source(ConstrainedTransfer tx) {return tx.getFrom();} - - @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 findTimetableTripInfo( - RaptorTimeTable timetable, - Collection transfers, - int stopPos, - int sourceArrivalTime - ) { - // Abort after 6 hours - boolean boardNextNormalTrip = false; - - outer: - for (int i = 0; i < timetable.numberOfTripSchedules(); ++i) { - var it = timetable.getTripSchedule(i); - int departureTime = it.departure(stopPos); - if (departureTime < sourceArrivalTime) { continue; } - - var targetTrip = it.getOriginalTripTimes().getTrip(); - - for (ConstrainedTransfer tx : transfers) { - if (tx.getTo().applyToAllTrips()) { - return new T2<>(i, tx.getTransferConstraint()); - } - if (targetTrip == tx.getTo().getTrip()) { - if (tx.getTransferConstraint().isNotAllowed()) { - boardNextNormalTrip = true; - continue outer; - } - else { - return new T2<>(i, tx.getTransferConstraint()); - } - } - } - if (boardNextNormalTrip) { - return new T2<>(i, TransferConstraint.REGULAR_TRANSFER); - } - } - return null; - } -} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchReverse.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchReverse.java deleted file mode 100644 index dc2c004fef9..00000000000 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchReverse.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.opentripplanner.routing.algorithm.raptor.transit.request; - -import java.util.Collection; -import org.opentripplanner.common.model.T2; -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.RaptorTimeTable; -import org.opentripplanner.transit.raptor.api.transit.RaptorTripSchedule; - -class ConstrainedBoardingSearchReverse - implements ConstrainedBoardingSearchStrategy { - - @Override - public TransferPoint source(ConstrainedTransfer tx) {return tx.getTo();} - - @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 findTimetableTripInfo( - RaptorTimeTable timetable, - Collection transfers, - int stopPos, - int sourceDepartureTime - ) { - // Abort after 6 hours - boolean alightPrevNormalTrip = false; - - outer: - for (int i = timetable.numberOfTripSchedules()-1; i >= 0; --i) { - var it = timetable.getTripSchedule(i); - int arrivalTime = it.arrival(stopPos); - if (arrivalTime > sourceDepartureTime) { continue; } - - var targetTrip = it.getOriginalTripTimes().getTrip(); - - for (ConstrainedTransfer tx : transfers) { - if (tx.getFrom().applyToAllTrips()) { - return new T2<>(i, tx.getTransferConstraint()); - } - if (targetTrip == tx.getFrom().getTrip()) { - if (tx.getTransferConstraint().isNotAllowed()) { - alightPrevNormalTrip = true; - continue outer; - } - else { - return new T2<>(i, tx.getTransferConstraint()); - } - } - } - if (alightPrevNormalTrip) { - return new T2<>(i, TransferConstraint.REGULAR_TRANSFER); - } - } - return null; - } -} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchStrategy.java b/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchStrategy.java deleted file mode 100644 index e4c0ff29d1b..00000000000 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchStrategy.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.opentripplanner.routing.algorithm.raptor.transit.request; - -import java.util.Collection; -import org.opentripplanner.common.model.T2; -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.RaptorTimeTable; -import org.opentripplanner.transit.raptor.api.transit.RaptorTripSchedule; - -interface ConstrainedBoardingSearchStrategy { - - TransferPoint source(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 findTimetableTripInfo( - RaptorTimeTable timetable, - Collection transfers, - int stopPos, - int sourceTime - ); -} 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 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 previousTransit(int boardStopIndex); default Collection> 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..aa712024ee2 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 @@ -1,8 +1,10 @@ package org.opentripplanner.transit.raptor.rangeraptor.standard.besttimes; +import javax.annotation.Nullable; 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 +62,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 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/test/java/org/opentripplanner/graph_builder/linking/LinkStopToPlatformTest.java b/src/test/java/org/opentripplanner/graph_builder/linking/LinkStopToPlatformTest.java index 4ac8b67ca5f..1bcd82b722e 100644 --- a/src/test/java/org/opentripplanner/graph_builder/linking/LinkStopToPlatformTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/linking/LinkStopToPlatformTest.java @@ -30,7 +30,7 @@ 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/impl/OtpTransitServiceImplTest.java b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java index 250a7a90ce4..4320ed95500 100644 --- a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java +++ b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java @@ -105,12 +105,9 @@ 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: , to: , constraint: {priority: RECOMMENDED}}\n" + "ConstrainedTransfer{from: , to: , constraint: {guaranteed}}\n" + + "ConstrainedTransfer{from: , to: , constraint: {priority: RECOMMENDED}}\n" + "ConstrainedTransfer{from: , to: , constraint: {priority: NOT_ALLOWED}}\n" + "ConstrainedTransfer{from: , to: , constraint: {priority: RECOMMENDED}}\n" + "ConstrainedTransfer{from: , to: , constraint: {priority: NOT_ALLOWED}}\n" diff --git a/src/test/java/org/opentripplanner/model/transfer/ConstrainedTransferTest.java b/src/test/java/org/opentripplanner/model/transfer/ConstrainedTransferTest.java index e741ff15a1f..d7c85919dcf 100644 --- a/src/test/java/org/opentripplanner/model/transfer/ConstrainedTransferTest.java +++ b/src/test/java/org/opentripplanner/model/transfer/ConstrainedTransferTest.java @@ -13,6 +13,9 @@ public class ConstrainedTransferTest implements TransferTestData { private static final TransferConstraint NO_CONSTRAINS = TransferConstraint.create().build(); private static final TransferConstraint GUARANTIED = TransferConstraint.create().guaranteed().build(); + private final ConstrainedTransfer TX_STATIONS = new ConstrainedTransfer(null, STATION_POINT, STATION_POINT, NO_CONSTRAINS); + private final ConstrainedTransfer TX_STATION_TO_A = new ConstrainedTransfer(null, STATION_POINT, STOP_POINT_A, NO_CONSTRAINS); + private final ConstrainedTransfer TX_T11_STATION = new ConstrainedTransfer(null, TRIP_POINT_11, STATION_POINT, NO_CONSTRAINS); 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); @@ -36,13 +39,16 @@ public void setup() { @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_STATIONS.getSpecificityRanking()); + assertEquals(10, TX_STATION_TO_A.getSpecificityRanking()); + assertEquals(21, TX_A_TO_B.getSpecificityRanking()); + assertEquals(31, TX_A_TO_R22.getSpecificityRanking()); + assertEquals(32, TX_R11_TO_B.getSpecificityRanking()); + assertEquals(33, TX_T11_STATION.getSpecificityRanking()); + assertEquals(41, TX_A_TO_T23.getSpecificityRanking()); + assertEquals(42, TX_R11_TO_R22.getSpecificityRanking()); + assertEquals(53, TX_T11_TO_R22.getSpecificityRanking()); + assertEquals(63, TX_T11_TO_T22.getSpecificityRanking()); } @Test diff --git a/src/test/java/org/opentripplanner/model/transfer/TransferConstraintTest.java b/src/test/java/org/opentripplanner/model/transfer/TransferConstraintTest.java index 14a421b82fb..c51bce15d62 100644 --- a/src/test/java/org/opentripplanner/model/transfer/TransferConstraintTest.java +++ b/src/test/java/org/opentripplanner/model/transfer/TransferConstraintTest.java @@ -48,6 +48,14 @@ public void 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()); 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..842fb9b0141 --- /dev/null +++ b/src/test/java/org/opentripplanner/model/transfer/TransferPointMapTest.java @@ -0,0 +1,48 @@ +package org.opentripplanner.model.transfer; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class TransferPointMapTest implements TransferTestData { + + final TransferPointMap subject = new TransferPointMap<>(); + + @BeforeEach + void setup() { + STOP_A.setParentStation(STATION); + } + + @Test + void addAndGetEmptyMap() { + assertEquals(List.of(), subject.get(TRIP_1, STOP_A, STOP_POSITION_1)); + } + + @Test + void addAndGet() { + subject.put(TRIP_POINT_11, "A"); + subject.put(TRIP_POINT_23, "B"); + subject.put(ROUTE_POINT_11, "C"); + subject.put(ROUTE_POINT_22, "D"); + subject.put(STOP_POINT_A, "E"); + subject.put(STOP_POINT_B, "F"); + subject.put(STATION_POINT, "G"); + + assertEquals(List.of("A", "C", "E", "G"), subject.get(TRIP_1, STOP_A, STOP_POSITION_1)); + assertEquals(List.of("D", "F"), subject.get(TRIP_2, STOP_B, STOP_POSITION_2)); + } + + @Test + void computeIfAbsent() { + assertEquals("A", subject.computeIfAbsent(TRIP_POINT_11, () -> "A")); + assertEquals("B", subject.computeIfAbsent(ROUTE_POINT_11, () -> "B")); + assertEquals("C", subject.computeIfAbsent(STOP_POINT_B, () -> "C")); + assertEquals("D", subject.computeIfAbsent(STATION_POINT, () -> "D")); + assertEquals("B", subject.computeIfAbsent(ROUTE_POINT_11, () -> "E")); + + assertEquals(List.of("A", "B", "D"), subject.get(TRIP_1, STOP_A, STOP_POSITION_1)); + assertEquals(List.of("C"), subject.get(TRIP_2, STOP_B, STOP_POSITION_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 8a5b4787c6d..3c8db6d6474 100644 --- a/src/test/java/org/opentripplanner/model/transfer/TransferPointTest.java +++ b/src/test/java/org/opentripplanner/model/transfer/TransferPointTest.java @@ -1,86 +1,66 @@ package org.opentripplanner.model.transfer; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +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 org.junit.Test; +import java.util.List; +import org.junit.jupiter.api.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); - + @Test + public void getStation() { + assertEquals(STATION, STATION_POINT.asStationTransferPoint().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()); } @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_1, TRIP_POINT_11.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(STOP_POSITION_1, TRIP_POINT_11.asTripTransferPoint().getStopPositionInPattern()); + assertEquals(STOP_POSITION_1, ROUTE_POINT_11.asRouteTransferPoint().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_11.getSpecificityRanking()); + assertEquals(3, TRIP_POINT_11.getSpecificityRanking()); + } + + @Test + public void isStationTransferPoint() { + List.of(STATION_POINT, STOP_POINT_A, ROUTE_POINT_11, TRIP_POINT_11).forEach( p -> { + assertEquals(p == STATION_POINT, p.isStationTransferPoint()); + assertEquals(p == STOP_POINT_A, p.isStopTransferPoint()); + assertEquals(p == ROUTE_POINT_11, p.isRouteTransferPoint()); + assertEquals(p == TRIP_POINT_11, p.isTripTransferPoint()); + }); } @Test public void applyToAllTrips() { + assertTrue(STATION_POINT.applyToAllTrips()); assertTrue(STOP_POINT_A.applyToAllTrips()); assertTrue(ROUTE_POINT_11.applyToAllTrips()); assertFalse(TRIP_POINT_11.applyToAllTrips()); } - @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()); - - 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 testToString() { + assertEquals("", STATION_POINT.toString()); assertEquals("", STOP_POINT_A.toString()); - assertEquals("", ROUTE_POINT_11.toString()); - assertEquals("", TRIP_POINT_11.toString()); + assertEquals("", ROUTE_POINT_11.toString()); + assertEquals("", TRIP_POINT_23.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..d66acff5e25 100644 --- a/src/test/java/org/opentripplanner/model/transfer/TransferServiceTest.java +++ b/src/test/java/org/opentripplanner/model/transfer/TransferServiceTest.java @@ -1,43 +1,54 @@ package org.opentripplanner.model.transfer; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.opentripplanner.model.Stop; public class TransferServiceTest implements TransferTestData { private final TransferService subject = new TransferService(); + @BeforeEach + public void setup() { + STOP_A.setParentStation(STATION); + } @Test - public void addOneTransferForEachCombinationOfFromToTypesAndRetriveEachOfThem() { + public void findTransfer() { // 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); + var ANY_STOP = Stop.stopForTest("ANY", 67.0, 11.0); + var A = transfer(STATION_POINT, ROUTE_POINT_11); + var B = transfer(STOP_POINT_A, STOP_POINT_B); + var C = transfer(STOP_POINT_A, TRIP_POINT_23); + var D = transfer(ROUTE_POINT_11, STOP_POINT_B); + var E = transfer(TRIP_POINT_11, ROUTE_POINT_22); + var F = transfer(TRIP_POINT_11, TRIP_POINT_23); // 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)); - /* THEN */ + // Then: // 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)); + assertEquals(E, subject.findTransfer(STOP_A, STOP_B, TRIP_1, TRIP_2, 1, 2)); // Find the another specific transfer with the stop position changed - assertEquals(E, subject.findTransfer(STOP_A, STOP_B, TRIP_1, TRIP_2, 1, 3)); + assertEquals(F, subject.findTransfer(STOP_A, STOP_B, TRIP_1, TRIP_2, 1, 3)); // 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)); + assertEquals(D, subject.findTransfer(STOP_A, STOP_B, TRIP_1, TRIP_2, 1, 7)); // 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)); + assertEquals(C, subject.findTransfer(STOP_A, STOP_B, TRIP_1, TRIP_2, 7, 3)); // 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)); + assertEquals(B, subject.findTransfer(STOP_A, STOP_B, TRIP_1, TRIP_2, 7, 7)); + + // + assertEquals(A, subject.findTransfer(STOP_A, ANY_STOP, TRIP_2, TRIP_1, 7, 1)); } @Test @@ -51,6 +62,19 @@ public void addSameTransferTwiceRetrieveFirstAdded() { assertEquals(A, subject.findTransfer(STOP_A, STOP_B, TRIP_1, TRIP_2, 1, 2)); } + @Test + public void listAll() { + // Given: + var A = transfer(STATION_POINT, ROUTE_POINT_11); + var B = transfer(STOP_POINT_A, STOP_POINT_B); + var C = transfer(STOP_POINT_A, TRIP_POINT_23); + + // 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 04f4427e350..32544291978 100644 --- a/src/test/java/org/opentripplanner/model/transfer/TransferTestData.java +++ b/src/test/java/org/opentripplanner/model/transfer/TransferTestData.java @@ -2,12 +2,25 @@ import org.opentripplanner.model.FeedScopedId; import org.opentripplanner.model.Route; +import org.opentripplanner.model.Station; import org.opentripplanner.model.Stop; +import org.opentripplanner.model.StopTransferPriority; import org.opentripplanner.model.Trip; public interface TransferTestData { String FEED_ID = "F"; + Station STATION = new Station( + createId(1), + "Central Station", + null, null, null, null, null, + StopTransferPriority.ALLOWED + ); + + int STOP_POSITION_1 = 1; + int STOP_POSITION_2 = 2; + int STOP_POSITION_3 = 3; + Stop STOP_A = Stop.stopForTest("A", 60.0, 11.0); Stop STOP_B = Stop.stopForTest("B", 60.0, 11.0); @@ -17,15 +30,16 @@ public interface TransferTestData { Trip TRIP_1 = createTrip(1, ROUTE_1); Trip TRIP_2 = createTrip(2, ROUTE_2); + TransferPoint STATION_POINT = new StationTransferPoint(STATION); + TransferPoint STOP_POINT_A = new StopTransferPoint(STOP_A); TransferPoint STOP_POINT_B = new StopTransferPoint(STOP_B); - TransferPoint ROUTE_POINT_11 = new RouteTransferPoint(ROUTE_1, TRIP_1, 1); - TransferPoint ROUTE_POINT_22 = new RouteTransferPoint(ROUTE_2, TRIP_2, 2); - - TransferPoint TRIP_POINT_11 = new TripTransferPoint(TRIP_1, 1); - TransferPoint TRIP_POINT_23 = new TripTransferPoint(TRIP_2, 3); + TransferPoint ROUTE_POINT_11 = new RouteTransferPoint(ROUTE_1, STOP_POSITION_1); + TransferPoint ROUTE_POINT_22 = new RouteTransferPoint(ROUTE_2, STOP_POSITION_2); + TransferPoint TRIP_POINT_11 = new TripTransferPoint(TRIP_1, STOP_POSITION_1); + TransferPoint TRIP_POINT_23 = new TripTransferPoint(TRIP_2, STOP_POSITION_3); private static Trip createTrip(int id, Route route) { Trip t = new Trip(createId(id)); @@ -34,11 +48,10 @@ private static Trip createTrip(int id, Route route) { } private static Route createRoute(int id, String name) { - Route route = new Route(createId(id)); - route.setShortName(name); - return route; + 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/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..51d9648d4ce --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/constrainedtransfer/ConstrainedBoardingSearchTest.java @@ -0,0 +1,295 @@ +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.assertTrue; +import static org.opentripplanner.model.transfer.TransferConstraint.REGULAR_TRANSFER; +import static org.opentripplanner.routing.algorithm.raptor.transit.request.TestTransitCaseData.id; + +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.RouteTransferPoint; +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.request.TestRouteData; +import org.opentripplanner.routing.algorithm.raptor.transit.request.TestTransitCaseData; + + +public class ConstrainedBoardingSearchTest implements TestTransitCaseData { + + 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. + *
+     *                              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
+     * 
+ *
    + *
  • + * 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'. + *
  • + *
  • + * The transfer at stop C allow regular transfers between trip R1-2 and R2-1. + *
  • + *
  • + * R1-1 is the fallback in the reverse search in the same way as R2-2 is the fallback + * int the forward search. + *
  • + *
+ * 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" + ); + + 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 findGuaranteedTransferWithZeroConnectionTimeWithRouteTransfers() { + int sourceStopPos = route1.stopPosition(STOP_B); + int targetStopPos = route2.stopPosition(STOP_B); + var route1TxPoint = new RouteTransferPoint(route1.getRoute(), sourceStopPos); + var route2TxPoint = new RouteTransferPoint(route2.getRoute(), targetStopPos); + + 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 RouteTransferPoint(route1.getRoute(), sourceStopPos); + 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 + ); + } + + /** + * The most specific transfer passed in should be a guaranteed transfer + * at stop B + */ + private void findGuaranteedTransferWithZeroConnectionTime( + List constrainedTransfers + ) { + testTransferSearch( + STOP_B, constrainedTransfers, TRIP_1_INDEX, TRIP_2_INDEX, GUARANTEED_CONSTRAINT + ); + } + + void testTransferSearch( + Stop transferStop, + List constraints, + int expTripIndexFwdSearch, + int expTripIndexRevSearch, + TransferConstraint expConstraint + ) { + testTransferSearchForward(transferStop, constraints, expTripIndexFwdSearch, expConstraint); + testTransferSearchReverse(transferStop, constraints, expTripIndexRevSearch, expConstraint); + } + + void testTransferSearchForward( + Stop transferStop, + List 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 + ); + + assertNotNull(boarding); + assertEquals(expectedConstraint, boarding.getTransferConstraint()); + assertEquals(stopIndex , boarding.getBoardStopIndex()); + assertEquals(targetStopPos, boarding.getStopPositionInPattern()); + assertEquals(expectedTripIndex, boarding.getTripIndex()); + } + + void testTransferSearchReverse( + Stop transferStop, + List 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 + ); + + assertNotNull(boarding); + assertEquals(expectedConstraint, boarding.getTransferConstraint()); + assertEquals(stopIndex , boarding.getBoardStopIndex()); + assertEquals(targetStopPos, boarding.getStopPositionInPattern()); + assertEquals(expectedTripIndex, boarding.getTripIndex()); + } + + private void generateTransfersForPatterns(Collection 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 9e70cad4790..f42dd1e3246 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,17 +1,15 @@ package org.opentripplanner.routing.algorithm.raptor.transit.mappers; -import org.junit.Assert; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.routing.algorithm.raptor.transit.mappers.DateMapper.asStartOfService; import java.time.Instant; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; - -import static org.junit.Assert.assertEquals; -import static org.opentripplanner.routing.algorithm.raptor.transit.mappers.DateMapper.asStartOfService; +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 c7aa83c41ce..3df449eca0a 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,10 +1,10 @@ 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 org.junit.jupiter.api.Test; import org.opentripplanner.model.FeedScopedId; import org.opentripplanner.model.Station; import org.opentripplanner.model.Stop; diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchTest.java deleted file mode 100644 index 91654ad57f4..00000000000 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptor/transit/request/ConstrainedBoardingSearchTest.java +++ /dev/null @@ -1,228 +0,0 @@ -package org.opentripplanner.routing.algorithm.raptor.transit.request; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.model.transfer.TransferConstraint.REGULAR_TRANSFER; - -import gnu.trove.map.hash.TIntObjectHashMap; -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.opentripplanner.model.Stop; -import org.opentripplanner.model.TransitMode; -import org.opentripplanner.model.transfer.ConstrainedTransfer; -import org.opentripplanner.model.transfer.StopTransferPoint; -import org.opentripplanner.model.transfer.TransferConstraint; -import org.opentripplanner.model.transfer.TripTransferPoint; - -public class ConstrainedBoardingSearchTest implements TestTransitCaseData { - - private static final boolean FORWARD = true; - private static final boolean REVERSE = false; - private static final int TRIP_1_INDEX = 0; - private static final int TRIP_2_INDEX = 1; - - - private TestRouteData route1; - private TestRouteData route2; - - /** - * Create transit data with 2 routes with a trip each. - *
-     *                              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
-     * 
- *
    - *
  • - * 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'. - *
  • - *
  • - * The transfer at stop C is allow regular transfers between trip R1-2 and R2-1. - *
  • - *
  • - * R1-1 is the fallback int the reverse search in the same way as R2-2 is the fallback - * int the forward search. - *
  • - *
- * 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" - ); - } - - @Test - void transferExist() { - var transfers = new TIntObjectHashMap>(); - int targetStopPos = 3; - transfers.put(targetStopPos, List.of()); - - // Forward - var subject = new ConstrainedBoardingSearch(true, transfers); - assertTrue(subject.transferExist(targetStopPos)); - - // Reverse - subject = new ConstrainedBoardingSearch(false, transfers); - assertTrue(subject.transferExist(targetStopPos)); - } - - @Test - void findGuaranteedTransferWithZeroConnectionTime() { - var transfers = new TIntObjectHashMap>(); - int sourceStopPos = route1.stopPosition(STOP_B); - int targetStopPos = route2.stopPosition(STOP_B); - - TransferConstraint constraint = TransferConstraint.create().guaranteed().build(); - ConstrainedTransfer txGuaranteed = new ConstrainedTransfer( - id("Guaranteed"), - new TripTransferPoint(route1.lastTrip().trip(), sourceStopPos), - new TripTransferPoint(route2.firstTrip().trip(), targetStopPos), - constraint - ); - List constrainedTransfers = List.of(txGuaranteed); - transfers.put(targetStopPos, constrainedTransfers); - - testTransferSearch(STOP_B, constrainedTransfers, TRIP_1_INDEX, constraint); - } - - @Test - void findNextTransferWhenFirstTripIsNoAllowed() { - var transfers = new TIntObjectHashMap>(); - int sourceStopPos = route1.stopPosition(STOP_C); - int targetStopPos = route2.stopPosition(STOP_C); - - TransferConstraint constraint = TransferConstraint.create().notAllowed().build(); - ConstrainedTransfer txGuaranteed = new ConstrainedTransfer( - id("NOT-ALLOWED"), - new TripTransferPoint(route1.lastTrip().trip(), sourceStopPos), - new TripTransferPoint(route2.firstTrip().trip(), targetStopPos), - constraint - ); - List constrainedTransfers = List.of(txGuaranteed); - transfers.put(targetStopPos, constrainedTransfers); - - testTransferSearch(STOP_C, constrainedTransfers, TRIP_2_INDEX, REGULAR_TRANSFER); - } - - @Test - @Disabled("This test fail, so we will try to fix the problem in the nex commit") - void blockTransferWhenNoAllowedMatchesAllTripsInRoute() { - var transfers = new TIntObjectHashMap>(); - int targetStopPos = route2.stopPosition(STOP_C); - - TransferConstraint constraint = TransferConstraint.create().notAllowed().build(); - ConstrainedTransfer txGuaranteed = new ConstrainedTransfer( - id("NOT-ALLOWED"), - new StopTransferPoint(STOP_C), - new StopTransferPoint(STOP_C), - constraint - ); - List constrainedTransfers = List.of(txGuaranteed); - transfers.put(targetStopPos, constrainedTransfers); - - testTransferSearch(STOP_C, constrainedTransfers, TRIP_1_INDEX, constraint); - } - - void testTransferSearch( - Stop transferStop, - List constraints, - int expTripIndex, - TransferConstraint expConstraint - ) { - testTransferSearchForward(transferStop, constraints, expTripIndex, expConstraint); - // Swap expected trip index for reverse search - int revExpTripIndex = expTripIndex == TRIP_1_INDEX ? TRIP_2_INDEX : TRIP_1_INDEX; - testTransferSearchReverse(transferStop, constraints, revExpTripIndex, expConstraint); - } - - void testTransferSearchForward( - Stop transferStop, - List constraints, - int expectedTripIndex, - TransferConstraint expectedConstraint - ) { - int targetStopPos = route2.stopPosition(transferStop); - var transfers = new TIntObjectHashMap>(); - int stopIndex = stopIndex(transferStop); - int sourceArrivalTime = route1.lastTrip().getStopTime(transferStop).getArrivalTime(); - - transfers.put(targetStopPos, constraints); - - // Forward - var subject = new ConstrainedBoardingSearch(FORWARD, transfers); - - // Check that transfer exist - assertTrue(subject.transferExist(targetStopPos)); - - var boarding = subject.find( - route2.getTimetable(), - route1.lastTrip().getTripSchedule(), - stopIndex, - sourceArrivalTime - ); - - assertNotNull(boarding); - assertEquals(expectedConstraint, boarding.getTransferConstraint()); - assertEquals(stopIndex , boarding.getBoardStopIndex()); - assertEquals(targetStopPos, boarding.getStopPositionInPattern()); - assertEquals(expectedTripIndex, boarding.getTripIndex()); - } - - - void testTransferSearchReverse( - Stop transferStop, - List constraints, - int expectedTripIndex, - TransferConstraint expectedConstraint - ) { - int targetStopPos = route1.stopPosition(transferStop); - var transfers = new TIntObjectHashMap>(); - int stopIndex = stopIndex(transferStop); - int sourceArrivalTime = route2.firstTrip().getStopTime(transferStop).getDepartureTime(); - - transfers.put(targetStopPos, constraints); - - // Forward - var subject = new ConstrainedBoardingSearch(REVERSE, transfers); - - // Check that transfer exist - assertTrue(subject.transferExist(targetStopPos)); - - var boarding = subject.find( - route1.getTimetable(), - route2.firstTrip().getTripSchedule(), - stopIndex, - sourceArrivalTime - ); - - assertNotNull(boarding); - assertEquals(expectedConstraint, boarding.getTransferConstraint()); - assertEquals(stopIndex , boarding.getBoardStopIndex()); - assertEquals(targetStopPos, boarding.getStopPositionInPattern()); - assertEquals(expectedTripIndex, boarding.getTripIndex()); - } -} 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 ad7a7f2f383..aac0fa65683 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,21 +1,29 @@ 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.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"); @@ -134,7 +142,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 index f14753d6fe0..55ad155e7e0 100644 --- 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 @@ -1,5 +1,7 @@ package org.opentripplanner.routing.algorithm.raptor.transit.request; +import static org.opentripplanner.routing.algorithm.raptor.transit.request.TestTransitCaseData.id; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -31,6 +33,7 @@ public class TestRouteData implements TestTransitCaseData { private final Map tripTimesByTrip = new HashMap<>(); private final Map tripSchedulesByTrip = new HashMap<>(); private final RaptorTimeTable timetable; + private final TripPatternWithRaptorStopIndexes raptorTripPattern; private Trip currentTrip; public TestRouteData(String route, TransitMode mode, List stops, String ... times) { @@ -47,15 +50,19 @@ public TestRouteData(String route, TransitMode mode, List stops, String .. .map(tripTimesByTrip::get) .collect(Collectors.toList()); - var tripPattern = new TripPatternWithRaptorStopIndexes( - stopIndexes(stopTimesFistTrip), - new TripPattern(id("TP1"), this.route, new StopPattern(stopTimesFistTrip)) + 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(tripPattern, tripTimes, DATE) + new TripPatternForDate(raptorTripPattern, tripTimes, DATE) ); - var patternForDates = new TripPatternForDates(tripPattern, listOfTripPatternForDates, List.of(OFFSET)); + var patternForDates = new TripPatternForDates( + raptorTripPattern, listOfTripPatternForDates, List.of(OFFSET) + ); for (Trip trip : trips) { var tripSchedule = new TripScheduleWithOffset( patternForDates, DATE, tripTimesByTrip.get(trip), OFFSET @@ -75,58 +82,49 @@ private Trip parseTripInfo(String route, String tripTimes, List stops, Ded return trip; } - Trip trip() { + public Route getRoute() { + return route; + } + + public Trip trip() { return currentTrip; } - TestRouteData firstTrip() { + public TestRouteData firstTrip() { this.currentTrip = trips.get(0); return this; } - TestRouteData lastTrip() { + public TestRouteData lastTrip() { this.currentTrip = trips.get(trips.size()-1); return this; } - StopTime getStopTime(Stop stop) { + public StopTime getStopTime(Stop stop) { return stopTimesByTrip.get(currentTrip).get(stopPosition(stop)); } - RaptorTimeTable getTimetable() { + public RaptorTimeTable getTimetable() { return timetable; } - TripSchedule getTripSchedule() { + public TripSchedule getTripSchedule() { return tripSchedulesByTrip.get(currentTrip); } + public TripPatternWithRaptorStopIndexes getRaptorTripPattern() { + return raptorTripPattern; + } private List getStopTimes() { return stopTimesByTrip.get(currentTrip); } - private Route getRoute() { - return route; - } - - private List getTrips() { - return trips; - } - - private Map> getStopTimesByTrip() { - return stopTimesByTrip; - } - - private Map getTripTimesByTrip() { - return tripTimesByTrip; - } - int[] stopIndexes(Collection times) { return times.stream().mapToInt(it -> stopIndex(it.getStop())).toArray(); } - int stopPosition(StopLocation stop) { + public int stopPosition(StopLocation stop) { List times = firstTrip().getStopTimes(); for (int i=0; i stopTimes(Trip trip, List stops, String timesAsStri return stopTimes; } - private StopTime findStopTime(StopLocation stop, List times) { - return times.stream().filter(it -> it.getStop() == stop).findFirst().orElseThrow(); - } - private StopTime stopTime(Trip trip, Stop stop, int time, int seq) { var s = new StopTime(); s.setTrip(trip); 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 index 5c70d1e11ae..dd1e8fc3f94 100644 --- 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 @@ -2,16 +2,19 @@ import java.time.LocalDate; import org.opentripplanner.model.FeedScopedId; +import org.opentripplanner.model.Station; import org.opentripplanner.model.Stop; import org.opentripplanner.model.StopLocation; +import org.opentripplanner.model.StopTransferPriority; public interface TestTransitCaseData { - String FEED_ID = "F"; + Station STATION_A = Station.stationForTest("A", 60.0, 11.1); + Station STATION_B = Station.stationForTest("B", 61.0, 11.5); - Stop STOP_A = Stop.stopForTest("A", 60.0, 11.0); - Stop STOP_B = Stop.stopForTest("B", 60.1, 11.1); - Stop STOP_C = Stop.stopForTest("C", 60.2, 11.2); - Stop STOP_D = Stop.stopForTest("D", 60.3, 11.3); + Stop STOP_A = Stop.stopForTest("A", 60.0, 11.0, STATION_A); + Stop STOP_B = Stop.stopForTest("B", 60.0, 11.2, STATION_B); + Stop STOP_C = Stop.stopForTest("C", 61.0, 11.4); + 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 @@ -22,8 +25,8 @@ public interface TestTransitCaseData { int OFFSET = 0; - default FeedScopedId id(String id) { - return new FeedScopedId(FEED_ID, id); + static FeedScopedId id(String id) { + return new FeedScopedId("F", id); } default int stopIndex(StopLocation stop) { 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; }