
org.opentripplanner.routing.alternativelegs.AlternativeLegs Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of otp Show documentation
Show all versions of otp Show documentation
The OpenTripPlanner multimodal journey planning system
The newest version!
package org.opentripplanner.routing.alternativelegs;
import static org.opentripplanner.routing.stoptimes.StopTimesHelper.skipByTripCancellation;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import org.opentripplanner.framework.time.ServiceDateUtils;
import org.opentripplanner.model.Timetable;
import org.opentripplanner.model.TripTimeOnDate;
import org.opentripplanner.model.plan.Leg;
import org.opentripplanner.model.plan.ScheduledTransitLeg;
import org.opentripplanner.model.plan.ScheduledTransitLegBuilder;
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.site.Station;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.model.timetable.TripIdAndServiceDate;
import org.opentripplanner.transit.model.timetable.TripOnServiceDate;
import org.opentripplanner.transit.model.timetable.TripTimes;
import org.opentripplanner.transit.service.TransitService;
/**
* A helper class to fetch previous/next alternative legs for a scheduled transit leg.
* The replacement legs arrive/depart from/to the same station as the original leg, but uses other
* trips for the leg.
*
* Generalized cost and constrained transfers are not included in the alternative legs.
*/
public class AlternativeLegs {
public static final int ZERO_COST = 0;
public static List getAlternativeLegs(
Leg leg,
Integer numberLegs,
TransitService transitService,
boolean searchBackward,
AlternativeLegsFilter filter
) {
return getAlternativeLegs(
leg,
numberLegs,
transitService,
searchBackward,
filter,
false,
false
);
}
/**
* Searches for alternatives to a given leg prior to or later than the departure of the given leg
*
* @param leg The original leg to which alternatives are searched for
* @param numberLegs The number of alternative legs requested. If fewer legs are found,
* only the found legs are returned.
* @param transitService The transit service used for the search
* @param includeDepartBefore Boolean indicating whether the alternative legs should depart
* earlier or later than the original leg True if earlier, false if
* later.
* @param filter AlternativeLegsFilter indicating which properties of the original
* leg should not change in the alternative legs
* @param exactOriginStop Boolean indicating whether the exact departure stop of the original
* leg is used as departure stop for the alternatives. If false, all
* stops belonging to the same parent station are used.
* @param exactDestinationStop Boolean indicating whether the exact destination stop of the
* original leg is used as destination stop for the alternatives. If
* false, all stops belonging to the same parent station are used.
*/
public static List getAlternativeLegs(
Leg leg,
Integer numberLegs,
TransitService transitService,
boolean includeDepartBefore,
AlternativeLegsFilter filter,
boolean exactOriginStop,
boolean exactDestinationStop
) {
StopLocation fromStop = leg.getFrom().stop;
StopLocation toStop = leg.getTo().stop;
Station fromStation = fromStop.getParentStation();
Station toStation = toStop.getParentStation();
Collection origins = fromStation == null || exactOriginStop
? List.of(fromStop)
: fromStation.getChildStops();
Collection destinations = toStation == null || exactDestinationStop
? List.of(toStop)
: toStation.getChildStops();
Comparator legComparator = Comparator.comparing(
ScheduledTransitLeg::getStartTime
);
if (includeDepartBefore) {
legComparator = legComparator.reversed();
}
Predicate tripPatternPredicate = filter.getFilter(leg);
return origins
.stream()
.flatMap(stop -> transitService.getPatternsForStop(stop, true).stream())
.filter(tripPattern -> tripPattern.getStops().stream().anyMatch(destinations::contains))
.filter(tripPatternPredicate)
.distinct()
.flatMap(tripPattern -> withBoardingAlightingPositions(origins, destinations, tripPattern))
.flatMap(t ->
generateLegs(
transitService,
t,
leg.getStartTime(),
leg.getServiceDate(),
includeDepartBefore
)
)
.filter(Predicate.not(leg::isPartiallySameTransitLeg))
.sorted(legComparator)
.limit(numberLegs)
.collect(Collectors.toList());
}
/**
* This has been copied and slightly modified from StopTimesHelper.
* TODO: Adapt after new transit model is in place
*/
@Nonnull
private static Stream generateLegs(
TransitService transitService,
TripPatternBetweenStops tripPatternBetweenStops,
ZonedDateTime departureTime,
LocalDate originalDate,
boolean includeDepartBefore
) {
TripPattern pattern = tripPatternBetweenStops.tripPattern;
int boardingPosition = tripPatternBetweenStops.positions.boardingPosition;
int alightingPosition = tripPatternBetweenStops.positions.alightingPosition;
// TODO: What should we have here
ZoneId timeZone = transitService.getTimeZone();
Comparator comparator = Comparator.comparing((TripTimeOnDate tts) ->
tts.getServiceDayMidnight() + tts.getRealtimeDeparture()
);
if (includeDepartBefore) {
comparator = comparator.reversed();
}
Queue pq = new PriorityQueue<>(comparator);
// Loop through all possible days
var serviceDates = List.of(originalDate.minusDays(1), originalDate, originalDate.plusDays(1));
for (LocalDate serviceDate : serviceDates) {
Timetable timetable = transitService.getTimetableForTripPattern(pattern, serviceDate);
ZonedDateTime midnight = ServiceDateUtils.asStartOfService(
serviceDate,
transitService.getTimeZone()
);
int secondsSinceMidnight = ServiceDateUtils.secondsSinceStartOfService(
midnight,
departureTime
);
var servicesRunning = transitService.getServiceCodesRunningForDate(serviceDate);
for (TripTimes tripTimes : timetable.getTripTimes()) {
if (!servicesRunning.contains(tripTimes.getServiceCode())) {
continue;
}
if (skipByTripCancellation(tripTimes, false)) {
continue;
}
boolean departureTimeInRange = includeDepartBefore
? tripTimes.getDepartureTime(boardingPosition) <= secondsSinceMidnight
: tripTimes.getDepartureTime(boardingPosition) >= secondsSinceMidnight;
if (departureTimeInRange) {
pq.add(
new TripTimeOnDate(
tripTimes,
boardingPosition,
pattern,
serviceDate,
midnight.toInstant()
)
);
}
}
}
List res = new ArrayList<>();
while (!pq.isEmpty()) {
TripTimeOnDate tripTimeOnDate = pq.poll();
res.add(
mapToLeg(
timeZone,
pattern,
boardingPosition,
alightingPosition,
tripTimeOnDate,
transitService
)
);
}
return res.stream();
}
@Nonnull
private static ScheduledTransitLeg mapToLeg(
ZoneId timeZone,
TripPattern pattern,
int boardingPosition,
int alightingPosition,
TripTimeOnDate tripTimeOnDate,
TransitService transitService
) {
LocalDate serviceDay = tripTimeOnDate.getServiceDay();
TripTimes tripTimes = tripTimeOnDate.getTripTimes();
ZonedDateTime boardingTime = ServiceDateUtils.toZonedDateTime(
serviceDay,
timeZone,
tripTimeOnDate.getRealtimeDeparture()
);
ZonedDateTime alightingTime = ServiceDateUtils.toZonedDateTime(
serviceDay,
timeZone,
tripTimes.getArrivalTime(alightingPosition)
);
TripOnServiceDate tripOnServiceDate = transitService.getTripOnServiceDateForTripAndDay(
new TripIdAndServiceDate(tripTimeOnDate.getTrip().getId(), tripTimeOnDate.getServiceDay())
);
return new ScheduledTransitLegBuilder<>()
.withTripTimes(tripTimes)
.withTripPattern(pattern)
.withBoardStopIndexInPattern(boardingPosition)
.withAlightStopIndexInPattern(alightingPosition)
.withStartTime(boardingTime)
.withEndTime(alightingTime)
.withServiceDate(serviceDay)
.withZoneId(timeZone)
.withTripOnServiceDate(tripOnServiceDate)
.build();
}
@Nonnull
private static Stream withBoardingAlightingPositions(
Collection origins,
Collection destinations,
TripPattern tripPattern
) {
List stops = tripPattern.getStops();
// Find out all alighting positions
var alightingPositions = IntStream
.iterate(stops.size() - 1, i -> i - 1)
.limit(stops.size())
.filter(i -> destinations.contains(stops.get(i)) && tripPattern.canAlight(i))
.toArray();
// Find out all boarding positions
return IntStream
.range(0, stops.size())
.filter(i -> origins.contains(stops.get(i)) && tripPattern.canBoard(i))
.boxed()
.flatMap(boardingPosition ->
Arrays
.stream(alightingPositions)
// Filter out the impossible combinations
.filter(alightingPosition -> boardingPosition < alightingPosition)
.min()
.stream()
.mapToObj(alightingPosition ->
new BoardingAlightingPositions(boardingPosition, alightingPosition)
)
)
// Group by alighting position
.collect(Collectors.groupingBy(pair -> pair.alightingPosition))
.values()
.stream()
// Find the shortest leg in each group
.flatMap(legGroup ->
legGroup
.stream()
.min(Comparator.comparing(ba -> ba.alightingPosition - ba.boardingPosition))
.stream()
)
.map(pair -> new TripPatternBetweenStops(tripPattern, pair));
}
private record BoardingAlightingPositions(int boardingPosition, int alightingPosition) {}
private record TripPatternBetweenStops(
TripPattern tripPattern,
BoardingAlightingPositions positions
) {}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy