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

org.opentripplanner.transit.service.StopTimesHelper Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.transit.service;

import static org.opentripplanner.transit.service.ArrivalDeparture.ARRIVALS;
import static org.opentripplanner.transit.service.ArrivalDeparture.DEPARTURES;

import com.google.common.collect.MinMaxPriorityQueue;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Queue;
import org.opentripplanner.model.PickDrop;
import org.opentripplanner.model.StopTimesInPattern;
import org.opentripplanner.model.Timetable;
import org.opentripplanner.model.TripTimeOnDate;
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.model.timetable.Trip;
import org.opentripplanner.transit.model.timetable.TripTimes;
import org.opentripplanner.utils.time.ServiceDateUtils;

class StopTimesHelper {

  private final TransitService transitService;

  StopTimesHelper(TransitService transitService) {
    this.transitService = transitService;
  }

  /**
   * Fetch upcoming vehicle departures from a stop. It goes though all patterns passing the stop for
   * the previous, current and next service date. It uses a priority queue to keep track of the next
   * departures. The queue is shared between all dates, as services from the previous service date
   * can visit the stop later than the current service date's services. This happens eg. with
   * sleeper trains.
   * 

* TODO: Add frequency based trips * * @param stop Stop object to perform the search for * @param startTime Start time for the search. * @param timeRange Searches forward for timeRange from startTime * @param numberOfDepartures Number of departures to fetch per pattern * @param arrivalDeparture Filter by arrivals, departures, or both * @param includeCancelledTrips If true, cancelled trips will also be included in result */ List stopTimesForStop( StopLocation stop, Instant startTime, Duration timeRange, int numberOfDepartures, ArrivalDeparture arrivalDeparture, boolean includeCancelledTrips ) { if (numberOfDepartures <= 0) { return List.of(); } List result = new ArrayList<>(); // Fetch all patterns, including those from realtime sources Collection patterns = transitService.findPatterns(stop, true); for (TripPattern pattern : patterns) { Queue pq = listTripTimeOnDatesForPatternAtStop( stop, pattern, startTime, timeRange, numberOfDepartures, arrivalDeparture, includeCancelledTrips, false ); result.addAll(getStopTimesInPattern(pattern, pq)); } return result; } /** * Get a list of all trips that pass through a stop during a single ServiceDate. Useful when * creating complete stop timetables for a single day. * * @param stop Stop object to perform the search for * @param serviceDate Return all departures for the specified date */ List stopTimesForStop( StopLocation stop, LocalDate serviceDate, ArrivalDeparture arrivalDeparture, boolean includeCancellations ) { List ret = new ArrayList<>(); var servicesRunning = transitService.getServiceCodesRunningForDate(serviceDate); Instant midnight = ServiceDateUtils.asStartOfService( serviceDate, transitService.getTimeZone() ).toInstant(); for (TripPattern pattern : transitService.findPatterns(stop, true)) { StopTimesInPattern stopTimes = new StopTimesInPattern(pattern); Timetable tt = transitService.findTimetable(pattern, serviceDate); List stops = pattern.getStops(); for (int i = 0; i < stops.size(); i++) { StopLocation currStop = stops.get(i); if (currStop == stop) { if (skipByPickUpDropOff(pattern, arrivalDeparture, i)) { continue; } if (skipByStopCancellation(pattern, includeCancellations, i)) { continue; } for (TripTimes t : tt.getTripTimes()) { if (TripTimesHelper.skipByTripCancellation(t, includeCancellations)) { continue; } if (servicesRunning.contains(t.getServiceCode())) { stopTimes.times.add(new TripTimeOnDate(t, i, pattern, serviceDate, midnight)); } } } } ret.add(stopTimes); } return ret; } /** * Fetch upcoming vehicle departures from a stop for a single pattern, passing the stop for the * previous, current and next service date. It uses a priority queue to keep track of the next * departures. The queue is shared between all dates, as services from the previous service date * can visit the stop later than the current service date's services. *

* TODO: Add frequency based trips * * @param stop Stop object to perform the search for * @param pattern Pattern object to perform the search for * @param startTime Start time for the search. * @param timeRange Searches forward for timeRange from startTime * @param numberOfDepartures Number of departures to fetch per pattern * @param arrivalDeparture Filter by arrivals, departures, or both. * @param includeCancellations If the result should include those trip times where either the entire * trip or the stop at the given stop location has been cancelled. * Deleted trips are never returned no matter the value of this parameter. */ List stopTimesForPatternAtStop( StopLocation stop, TripPattern pattern, Instant startTime, Duration timeRange, int numberOfDepartures, ArrivalDeparture arrivalDeparture, boolean includeCancellations ) { Queue pq = listTripTimeOnDatesForPatternAtStop( stop, pattern, startTime, timeRange, numberOfDepartures, arrivalDeparture, includeCancellations, true ); return new ArrayList<>(pq); } private static List getStopTimesInPattern( TripPattern pattern, Queue pq ) { List result = new ArrayList<>(); if (!pq.isEmpty()) { StopTimesInPattern stopTimes = new StopTimesInPattern(pattern); while (!pq.isEmpty()) { stopTimes.times.add(0, pq.poll()); } result.add(stopTimes); } return result; } private Queue listTripTimeOnDatesForPatternAtStop( StopLocation stop, TripPattern pattern, Instant startTime, Duration timeRange, int numberOfDepartures, ArrivalDeparture arrivalDeparture, boolean includeCancellations, boolean includeReplaced ) { ZoneId zoneId = transitService.getTimeZone(); LocalDate startDate = startTime.atZone(zoneId).toLocalDate().minusDays(1); LocalDate endDate = startTime.plus(timeRange).atZone(zoneId).toLocalDate(); // datesUntil is exclusive in the end, so need to add one day List serviceDates = startDate.datesUntil(endDate.plusDays(1)).toList(); // The bounded priority Q is used to keep a sorted short list of trip times. We can not // rely on the trip times to be in order because of real-time updates. This code can // probably be optimized, and the trip search in the Raptor search does almost the same // thing. This is no part of a routing request, but is a used frequently in some // operation like Entur for "departure boards" (apps, widgets, screens on platforms, and // hotel lobbies). Setting the numberOfDepartures and timeRange to a big number for a // transit hub could result in a DOS attack, but there are probably other more effective // ways to do it. // // The {@link MinMaxPriorityQueue} is marked beta, but we do not have a god alternative. MinMaxPriorityQueue pq = MinMaxPriorityQueue.orderedBy( Comparator.comparing( (TripTimeOnDate tts) -> tts.getServiceDayMidnight() + tts.getRealtimeDeparture() ) ) .maximumSize(numberOfDepartures) .create(); int timeRangeSeconds = (int) timeRange.toSeconds(); // Loop through all possible days for (LocalDate serviceDate : serviceDates) { Timetable timetable = transitService.findTimetable(pattern, serviceDate); ZonedDateTime midnight = ServiceDateUtils.asStartOfService(serviceDate, zoneId); int secondsSinceMidnight = ServiceDateUtils.secondsSinceStartOfService( midnight, ZonedDateTime.ofInstant(startTime, zoneId) ); var servicesRunning = transitService.getServiceCodesRunningForDate(serviceDate); List stops = pattern.getStops(); for (int stopIndex = 0; stopIndex < stops.size(); stopIndex++) { StopLocation currStop = stops.get(stopIndex); if (currStop == stop) { if (skipByPickUpDropOff(pattern, arrivalDeparture, stopIndex)) { continue; } if (skipByStopCancellation(pattern, includeCancellations, stopIndex)) { continue; } for (TripTimes tripTimes : timetable.getTripTimes()) { if (!servicesRunning.contains(tripTimes.getServiceCode())) { continue; } if (TripTimesHelper.skipByTripCancellation(tripTimes, includeCancellations)) { continue; } if ( !includeReplaced && isReplacedByAnotherPattern(tripTimes.getTrip(), serviceDate, pattern, transitService) ) { continue; } boolean departureTimeInRange = tripTimes.getDepartureTime(stopIndex) >= secondsSinceMidnight && tripTimes.getDepartureTime(stopIndex) <= secondsSinceMidnight + timeRangeSeconds; boolean arrivalTimeInRange = tripTimes.getArrivalTime(stopIndex) >= secondsSinceMidnight && tripTimes.getArrivalTime(stopIndex) <= secondsSinceMidnight + timeRangeSeconds; // ARRIVAL: Arrival time has to be within range // DEPARTURES: Departure time has to be within range // BOTH: Either arrival time or departure time has to be within range if ( (arrivalDeparture != ARRIVALS && departureTimeInRange) || (arrivalDeparture != DEPARTURES && arrivalTimeInRange) ) { pq.add( new TripTimeOnDate(tripTimes, stopIndex, pattern, serviceDate, midnight.toInstant()) ); } } // TODO Add back support for frequency entries } } } return pq; } private static boolean isReplacedByAnotherPattern( Trip trip, LocalDate serviceDate, TripPattern pattern, TransitService transitService ) { final TripPattern replacement = transitService.findNewTripPatternForModifiedTrip( trip.getId(), serviceDate ); return replacement != null && !replacement.equals(pattern); } private static boolean skipByPickUpDropOff( TripPattern pattern, ArrivalDeparture arrivalDeparture, int stopIndex ) { boolean noPickup = pattern.getBoardType(stopIndex).is(PickDrop.NONE); boolean noDropoff = pattern.getAlightType(stopIndex).is(PickDrop.NONE); if (noPickup && noDropoff) { return true; } if (noPickup && arrivalDeparture == DEPARTURES) { return true; } if (noDropoff && arrivalDeparture == ARRIVALS) { return true; } return false; } private static boolean skipByStopCancellation( TripPattern pattern, boolean includeCancelled, int stopIndex ) { boolean pickupCancelled = pattern.getBoardType(stopIndex).is(PickDrop.CANCELLED); boolean dropOffCancelled = pattern.getAlightType(stopIndex).is(PickDrop.CANCELLED); return (pickupCancelled || dropOffCancelled) && !includeCancelled; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy