/* 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.Serializable;
import java.util.Arrays;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import org.onebusaway.gtfs.model.Frequency;
import org.onebusaway.gtfs.model.Stop;
import org.onebusaway.gtfs.model.Trip;
import org.opentripplanner.common.model.T2;
public class FrequencyBasedTripPattern implements Serializable, TripPattern {
private static final long serialVersionUID = -5392197874345648815L;
private Trip exemplar;
// sorted by start time
@XmlElement
int[] timeRangeStart;
@XmlElement
int[] timeRangeEnd;
@XmlElement
int[] timeRangeFrequency;
/*
* All of the following arrays are indexed by stop sequence; the first departure time is always
* 00:00:00
*/
@XmlElement
int[] departureTimes;
int[] runningTimes;
@XmlElement
int[] arrivalTimes;
int[] dwellTimes;
private String[] headsigns;
@XmlElement
private String[] zones;
@XmlElement
private int tripFlags;
@XmlElement
private int[] perStopFlags;
private transient List<Stop> stops;
boolean exact;
private int serviceId;
public FrequencyBasedTripPattern(Trip trip, int size, int serviceId) {
this.exemplar = trip;
departureTimes = new int[size];
runningTimes = new int[size];
arrivalTimes = new int[size];
dwellTimes = new int[size];
headsigns = new String[size];
zones = new String[size];
perStopFlags = new int[size];
this.serviceId = serviceId;
}
public int getNextDepartureTime(int stopIndex, int afterTime, boolean wheelchairAccessible,
boolean bikesAllowed, boolean pickup) {
int mask = pickup ? TableTripPattern.MASK_PICKUP : TableTripPattern.MASK_DROPOFF;
int shift = pickup ? TableTripPattern.SHIFT_PICKUP : TableTripPattern.SHIFT_DROPOFF;
if ((perStopFlags[stopIndex] & mask) >> shift == TableTripPattern.NO_PICKUP) {
return -1;
}
if (wheelchairAccessible
&& (perStopFlags[stopIndex] & TableTripPattern.FLAG_WHEELCHAIR_ACCESSIBLE) == 0) {
return -1;
}
if (wheelchairAccessible || bikesAllowed) {
int flags = (bikesAllowed ? TableTripPattern.FLAG_BIKES_ALLOWED : 0)
| (wheelchairAccessible ? TableTripPattern.FLAG_WHEELCHAIR_ACCESSIBLE : 0);
if ((tripFlags & flags) == 0) {
return -1;
}
}
int stopDepartureTimeOffset = departureTimes[stopIndex];
int afterTimeAtStart = afterTime - stopDepartureTimeOffset;
int timeRange = Arrays.binarySearch(timeRangeStart, afterTimeAtStart);
if (timeRange < 0) {
timeRange = -timeRange - 1;
if (timeRange > 0)
timeRange -= 1;
}
int firstDepartureTimeInRange = stopDepartureTimeOffset + timeRangeStart[timeRange];
int frequency = timeRangeFrequency[timeRange];
int departureTime;
if (exact) {
if (afterTime < firstDepartureTimeInRange) {
departureTime = firstDepartureTimeInRange;
} else {
int offset = (afterTime - firstDepartureTimeInRange) % frequency;
if (offset == 0)
offset = frequency; // catch exact hits
departureTime = afterTime + frequency - offset;
}
} else {
departureTime = afterTime + frequency;
if (departureTime < firstDepartureTimeInRange) {
departureTime = firstDepartureTimeInRange;
}
}
if (departureTime - stopDepartureTimeOffset < timeRangeEnd[timeRange]) {
return departureTime;
}
timeRange++;
return -1;
}
public int getPreviousArrivalTime(int stopIndex, int beforeTime,
boolean wheelchairAccessible, boolean bikesAllowed, boolean pickup) {
int mask = pickup ? TableTripPattern.MASK_PICKUP : TableTripPattern.MASK_DROPOFF;
int shift = pickup ? TableTripPattern.SHIFT_PICKUP : TableTripPattern.SHIFT_DROPOFF;
if ((perStopFlags[stopIndex + 1] & mask) >> shift == TableTripPattern.NO_PICKUP) {
return -1;
}
if (wheelchairAccessible
&& (perStopFlags[stopIndex] & TableTripPattern.FLAG_WHEELCHAIR_ACCESSIBLE) == 0) {
return -1;
}
if (wheelchairAccessible || bikesAllowed) {
int flags = (bikesAllowed ? TableTripPattern.FLAG_BIKES_ALLOWED : 0)
| (wheelchairAccessible ? TableTripPattern.FLAG_WHEELCHAIR_ACCESSIBLE : 0);
if ((tripFlags & flags) == 0) {
return -1;
}
}
int stopArrivalTimeOffset = arrivalTimes[stopIndex];
int beforeTimeAtStart = beforeTime - stopArrivalTimeOffset;
int timeRange = Arrays.binarySearch(timeRangeEnd, beforeTimeAtStart);
if (timeRange < 0) {
timeRange = -timeRange - 1;
}
if (timeRange >= timeRangeStart.length) timeRange -= 1;
if (beforeTimeAtStart < timeRangeStart[timeRange]) timeRange -= 1;
if (timeRange < 0) return -1;
int frequency = timeRangeFrequency[timeRange];
int frequencyOffset = (timeRangeEnd[timeRange] - timeRangeStart[timeRange]) % frequency;
int lastArrivalTimeInRange = stopArrivalTimeOffset + timeRangeEnd[timeRange] - frequencyOffset;
int arrivalTime;
if (exact) {
if (beforeTime > lastArrivalTimeInRange) {
arrivalTime = lastArrivalTimeInRange;
} else {
int offset = (lastArrivalTimeInRange - beforeTime) % frequency;
if (offset == 0)
offset = frequency; // catch exact hits
arrivalTime = beforeTime - frequency + offset;
}
} else {
arrivalTime = beforeTime - frequency;
}
int arrivalTimeAtFirstStop = arrivalTime - stopArrivalTimeOffset;
if (arrivalTimeAtFirstStop < timeRangeEnd[timeRange] && arrivalTimeAtFirstStop >= timeRangeStart[timeRange]) {
return arrivalTime;
}
return -1;
}
public int getDwellTime(int stopIndex) {
return dwellTimes[stopIndex];
}
public int getRunningTime(int stopIndex) {
return runningTimes[stopIndex];
}
public boolean getWheelchairAccessible(int stopIndex) {
if ((perStopFlags[stopIndex] & TableTripPattern.FLAG_WHEELCHAIR_ACCESSIBLE) == 0) {
return false;
}
return true;
}
public boolean getBikesAllowed(int trip) {
return (tripFlags & TableTripPattern.FLAG_BIKES_ALLOWED) != 0;
}
public boolean canAlight(int stopIndex) {
return getAlightType(stopIndex) != TableTripPattern.NO_PICKUP;
}
public boolean canBoard(int stopIndex) {
return getBoardType(stopIndex) != TableTripPattern.NO_PICKUP;
}
public String getZone(int stopIndex) {
return zones[stopIndex];
}
public Object getTrip(int trip) {
return new T2<Trip, Integer>(exemplar, trip);
}
public String getHeadsign(int stopIndex) {
return headsigns[stopIndex];
}
@Override
public int getAlightType(int stopIndex) {
return (perStopFlags[stopIndex] & TableTripPattern.MASK_DROPOFF) >> TableTripPattern.SHIFT_DROPOFF;
}
@Override
public int getBoardType(int stopIndex) {
return (perStopFlags[stopIndex] & TableTripPattern.MASK_PICKUP) >> TableTripPattern.SHIFT_PICKUP;
}
public void createRanges(List<Frequency> frequencies) {
timeRangeStart = new int[frequencies.size()];
timeRangeEnd = new int[frequencies.size()];
timeRangeFrequency = new int[frequencies.size()];
for (int i = 0; i < frequencies.size(); ++i) {
Frequency frequency = frequencies.get(i);
timeRangeStart[i] = frequency.getStartTime();
timeRangeEnd[i] = frequency.getEndTime();
timeRangeFrequency[i] = frequency.getHeadwaySecs();
exact = frequency.getExactTimes() == 1; // FIXME: assumes all frequencies work the same
// way
}
}
public int getTripFlags() {
return tripFlags;
}
public void setTripFlags(int tripFlags) {
this.tripFlags = tripFlags;
}
public Trip getTrip() {
return exemplar;
}
public void addHop(int index, int departureTime, int runningTime, int arrivalTime,
int dwellTime, String headsign) {
departureTimes[index] = departureTime;
runningTimes[index] = runningTime;
arrivalTimes[index+1] = arrivalTime;
dwellTimes[index] = dwellTime;
headsigns[index] = headsign;
}
@Override
public List<Stop> getStops() {
return stops;
}
@Override
public int getHopCount() {
return stops.size() - 1;
}
public void setStops(List<Stop> stops) {
this.stops = stops;
}
public int getServiceId() {
return serviceId;
}
}