/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.opentripplanner.routing.edgetype; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlTransient; import lombok.Delegate; import org.onebusaway.gtfs.model.Stop; import org.onebusaway.gtfs.model.Trip; import org.opentripplanner.common.MavenVersion; import org.opentripplanner.routing.core.RoutingRequest; import org.opentripplanner.routing.core.ServiceDay; import org.opentripplanner.routing.core.State; import org.opentripplanner.routing.trippattern.TripTimes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents a class of trips distinguished by service id and list of stops. For each stop, there * is a list of departure times, running times, arrival times, dwell times, and wheelchair * accessibility information (one of each of these per trip per stop). An exemplar trip is also * included so that information such as route name can be found. Trips are assumed to be * non-overtaking, so that an earlier trip never arrives after a later trip. */ public class TableTripPattern implements TripPattern, Serializable { private static final Logger LOG = LoggerFactory.getLogger(TableTripPattern.class); private static final long serialVersionUID = MavenVersion.VERSION.getUID(); public static final int FLAG_WHEELCHAIR_ACCESSIBLE = 1; public static final int MASK_PICKUP = 2|4; public static final int SHIFT_PICKUP = 1; public static final int MASK_DROPOFF = 8|16; public static final int SHIFT_DROPOFF = 3; public static final int NO_PICKUP = 1; public static final int FLAG_BIKES_ALLOWED = 32; /** * An integer index uniquely identifying this pattern among all in the graph. * This additional level of indirection allows versioning of trip patterns, which is * necessary for real-time stop time updates. (Currently using a hashmap until that proves to * be too inefficient.) */ // public final int patternIndex; /** An arbitrary trip that uses this pattern. Maybe we should just store route, etc. directly. */ public final Trip exemplar; /** * This timetable holds the 'official' stop times from GTFS. If realtime stoptime updates are * applied, trips searches will be conducted using another timetable and this one will serve to * find early/late offsets, or as a fallback if the other timetable becomes corrupted or * expires. Via Lombok Delegate, calling timetable methods on a TableTripPattern will call * them on its scheduled timetable. */ @Delegate protected final Timetable scheduledTimetable = new Timetable(this); // redundant since tripTimes have a trip // however it's nice to have for order reference, since all timetables must have tripTimes // in this order, e.g. for interlining. // potential optimization: trip fields can be removed from TripTimes? /** * This pattern may have multiple Timetable objects, but they should all contain TripTimes * for the same trips, in the same order (that of the scheduled Timetable). An exception to * this rule may arise if unscheduled trips are added to a Timetable. For that case we need * to search for trips/TripIds in the Timetable rather than the enclosing TripPattern. */ final ArrayList<Trip> trips = new ArrayList<Trip>(); /** * An ordered list of related PatternHop. All trips in a pattern have the same stops and a * PatternHop apply to all those trips, so this array apply to every trip in every timetable in * this pattern. Please note that the array size is the number of stops minus 1. This also allow * to access the ordered list of stops. */ private PatternHop[] patternHops; /** Holds stop-specific information such as wheelchair accessibility and pickup/dropoff roles. */ @XmlElement int[] perStopFlags; /** Optimized serviceId code. All trips in a pattern are by definition on the same service. */ int serviceId; public TableTripPattern(Trip exemplar, ScheduledStopPattern stopPattern, int serviceId) { this.exemplar = exemplar; this.serviceId = serviceId; setStopsFromStopPattern(stopPattern); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // The serialized graph contains cyclic references TableTripPattern <--> Timetable. // The Timetable must be indexed from here (rather than in its own readObject method) // to ensure that the stops field it uses in TableTripPattern is already deserialized. this.scheduledTimetable.finish(); } private void setStopsFromStopPattern(ScheduledStopPattern stopPattern) { patternHops = new PatternHop[stopPattern.stops.size() - 1]; perStopFlags = new int[stopPattern.stops.size()]; int i = 0; for (Stop stop : stopPattern.stops) { if (stop.getWheelchairBoarding() == 1) { perStopFlags[i] |= FLAG_WHEELCHAIR_ACCESSIBLE; } perStopFlags[i] |= stopPattern.pickups.get(i) << SHIFT_PICKUP; perStopFlags[i] |= stopPattern.dropoffs.get(i) << SHIFT_DROPOFF; ++i; } } public Stop getStop(int stopIndex) { if (stopIndex == patternHops.length) return patternHops[stopIndex - 1].getEndStop(); else return patternHops[stopIndex].getStartStop(); } public List<Stop> getStops() { /* * Dynamically build the list from the PatternHop list. Not super efficient but this method * is not called very often. */ List<Stop> retval = new ArrayList<Stop>(patternHops.length + 1); for (int i = 0; i <= patternHops.length; i++) retval.add(getStop(i)); return retval; } public List<PatternHop> getPatternHops() { return Arrays.asList(patternHops); } /* package private */ void setPatternHop(int stopIndex, PatternHop patternHop) { patternHops[stopIndex] = patternHop; } @Override public int getHopCount() { return patternHops.length; } public Trip getTrip(int tripIndex) { return trips.get(tripIndex); } @XmlTransient public List<Trip> getTrips() { return trips; } public int getTripIndex(Trip trip) { return trips.indexOf(trip); } /** Returns whether passengers can alight at a given stop */ public boolean canAlight(int stopIndex) { return getAlightType(stopIndex) != NO_PICKUP; } /** Returns whether passengers can board at a given stop */ public boolean canBoard(int stopIndex) { return getBoardType(stopIndex) != NO_PICKUP; } /** Returns the zone of a given stop */ public String getZone(int stopIndex) { return getStop(stopIndex).getZoneId(); } /** Returns an arbitrary trip that uses this pattern */ public Trip getExemplar() { return exemplar; } @Override public int getAlightType(int stopIndex) { return (perStopFlags[stopIndex] & MASK_DROPOFF) >> SHIFT_DROPOFF; } @Override public int getBoardType(int stopIndex) { return (perStopFlags[stopIndex] & MASK_PICKUP) >> SHIFT_PICKUP; } /** * Gets the number of scheduled trips on this pattern. Note that when stop time updates are * being applied, there may be other Timetables for this pattern which contain a larger number * of trips. However, all trips with indexes from 0 through getNumTrips()-1 will always * correspond to the scheduled trips. */ public int getNumScheduledTrips () { return trips.size(); } // TODO: Lombokize all boilerplate... but lombok does not generate javadoc :/ public int getServiceId() { return serviceId; } /** * Find the next (or previous) departure on this pattern at or after (respectively before) the * specified time. This method will make use of any TimetableResolver present in the * RoutingContext to redirect departure lookups to the appropriate updated Timetable, and will * fall back on the scheduled timetable when no updates are available. * @param boarding true means find next departure, false means find previous arrival * @return a TripTimes object providing all the arrival and departure times on the best trip. */ public TripTimes getNextTrip(int stopIndex, int time, State state0, ServiceDay sd, boolean haveBicycle, boolean boarding) { RoutingRequest options = state0.getOptions(); Timetable timetable = scheduledTimetable; TimetableResolver snapshot = options.rctx.timetableSnapshot; if (snapshot != null) timetable = snapshot.resolve(this); // check that we can even board/alight the given stop on this pattern with these options int mask = boarding ? MASK_PICKUP : MASK_DROPOFF; int shift = boarding ? SHIFT_PICKUP : SHIFT_DROPOFF; int stopOffset = boarding ? 0 : 1; if ((perStopFlags[stopIndex + stopOffset] & mask) >> shift == NO_PICKUP) { return null; } if (options.wheelchairAccessible && (perStopFlags[stopIndex + stopOffset] & FLAG_WHEELCHAIR_ACCESSIBLE) == 0) { return null; } // so far so good, delegate to the timetable return timetable.getNextTrip(stopIndex, time, state0, sd, haveBicycle, boarding); } public Iterator<Integer> getScheduledDepartureTimes(int stopIndex) { return scheduledTimetable.getDepartureTimes(stopIndex); } }