All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.routing.alternativelegs.AlternativeLegs Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show 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.model.Timetable;
import org.opentripplanner.model.TripTimeOnDate;
import org.opentripplanner.model.plan.Leg;
import org.opentripplanner.model.plan.ScheduledTransitLeg;
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.TripTimes;
import org.opentripplanner.transit.service.TransitService;
import org.opentripplanner.util.time.ServiceDateUtils;

/**
 * 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
  ) {
    StopLocation fromStop = leg.getFrom().stop;
    StopLocation toStop = leg.getTo().stop;

    Station fromStation = fromStop.getParentStation();
    Station toStation = toStop.getParentStation();

    Collection origins = fromStation == null
      ? List.of(fromStop)
      : fromStation.getChildStops();

    Collection destinations = toStation == null
      ? List.of(toStop)
      : toStation.getChildStops();

    Comparator legComparator = Comparator.comparing(
      ScheduledTransitLeg::getStartTime
    );

    if (searchBackward) {
      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)
      .flatMap(tripPattern -> withBoardingAlightingPositions(origins, destinations, tripPattern))
      .flatMap(t ->
        generateLegs(transitService, t, leg.getStartTime(), leg.getServiceDate(), searchBackward)
      )
      .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 searchBackward
  ) {
    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 (searchBackward) {
      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 = searchBackward
          ? 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));
    }

    return res.stream();
  }

  @Nonnull
  private static ScheduledTransitLeg mapToLeg(
    ZoneId timeZone,
    TripPattern pattern,
    int boardingPosition,
    int alightingPosition,
    TripTimeOnDate tripTimeOnDate
  ) {
    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)
    );

    return new ScheduledTransitLeg(
      tripTimes,
      pattern,
      boardingPosition,
      alightingPosition,
      boardingTime,
      alightingTime,
      serviceDay,
      timeZone,
      null,
      null,
      ZERO_COST,
      null
    );
  }

  @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()
      // Create a cartesian product of the pairs
      .flatMap(boardingPosition ->
        Arrays
          .stream(alightingPositions)
          // Filter out the impossible combinations
          .filter(alightingPosition -> boardingPosition < alightingPosition)
          .mapToObj(alightingPosition ->
            new BoardingAlightingPositions(boardingPosition, alightingPosition)
          )
      )
      .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