/* 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 org.onebusaway.gtfs.model.Stop;
import org.onebusaway.gtfs.model.Trip;
import org.opentripplanner.routing.core.RoutingContext;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.ServiceDay;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.core.StateEditor;
import org.opentripplanner.routing.core.StopTransfer;
import org.opentripplanner.routing.core.TransferTable;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.core.TraverseModeSet;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.vertextype.TransitVertex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vividsolutions.jts.geom.LineString;
public class FrequencyBoard extends Edge implements OnBoardForwardEdge, PatternEdge {
private static final long serialVersionUID = 7919511656529752927L;
private static final Logger LOG = LoggerFactory.getLogger(FrequencyBoard.class);
private int stopIndex;
private FrequencyBasedTripPattern pattern;
private int modeMask;
private int serviceId;
public FrequencyBoard(TransitVertex from, TransitVertex to,
FrequencyBasedTripPattern pattern, int stopIndex, TraverseMode mode, int serviceId) {
super(from, to);
this.pattern = pattern;
this.stopIndex = stopIndex;
this.modeMask = new TraverseModeSet(mode).getMask();
this.serviceId = serviceId;
}
@Override
public Trip getTrip() {
return pattern.getTrip();
}
public String getDirection() {
return pattern.getHeadsign(stopIndex);
}
public double getDistance() {
return 0;
}
public LineString getGeometry() {
return null;
}
public TraverseMode getMode() {
return TraverseMode.LEG_SWITCH;
}
public String getName() {
return "leave street network for transit network";
}
public State traverse(State state0) {
RoutingContext rctx = state0.getContext();
RoutingRequest options = state0.getOptions();
Trip trip = pattern.getTrip();
if (options.isArriveBy()) {
/* reverse traversal, not so much to do */
// do not alight immediately when arrive-depart dwell has been eliminated
// this affects multi-itinerary searches
if (state0.getBackEdge() instanceof TransitBoardAlight &&
!((TransitBoardAlight) state0.getBackEdge()).isBoarding()) {
return null;
}
StateEditor s1 = state0.edit(this);
int type = pattern.getBoardType(stopIndex);
if (TransitUtils.handleBoardAlightType(s1, type)) {
return null;
}
s1.setTripId(null);
s1.setLastAlightedTimeSeconds(state0.getTimeSeconds());
s1.setBackMode(TraverseMode.LEG_SWITCH);
s1.setPreviousStop(((TransitVertex) fromv).getStop());
return s1.makeState();
} else {
/* forward traversal: look for a transit trip on this pattern */
if (!options.getModes().get(modeMask)) {
return null;
}
/*
* Check transfer rules. This is possible here because the pattern always returns the
* same trip.
*/
// Current time is used to find the next trip
long currentTime = state0.getTimeSeconds();
int transferPenalty = 0;
if (state0.getNumBoardings() > 0) {
// This is not the first boarding, thus a transfer
TransferTable transferTable = options.getRoutingContext().transferTable;
// Get the current stop
Stop currentStop = ((TransitVertex) fromv).getStop();
// Get the transfer time
int transferTime = transferTable.getTransferTime(state0.getPreviousStop(),
currentStop, state0.getPreviousTrip(), trip, true);
if (transferTime > 0) {
// There is a minimum transfer time to make this transfer
// Increase current time if necessary
long tableBoardAfter = state0.getLastAlightedTimeSeconds() + transferTime;
if (tableBoardAfter > currentTime) {
currentTime = tableBoardAfter;
}
} else if (transferTime == StopTransfer.FORBIDDEN_TRANSFER) {
// This transfer is not allowed
return null;
}
// Determine transfer penalty
transferPenalty = transferTable.determineTransferPenalty(transferTime, options.nonpreferredTransferPenalty);
// Check whether back edge is TimedTransferEdge
if (state0.getBackEdge() instanceof TimedTransferEdge) {
// Transfer must be of type TIMED_TRANSFER
if (transferTime != StopTransfer.TIMED_TRANSFER) {
return null;
}
}
}
/* find next boarding time */
/*
* check lists of transit serviceIds running yesterday, today, and tomorrow (relative to
* initial state) if this pattern's serviceId is running look for the next boarding time
* choose the soonest boarding time among trips starting yesterday, today, or tomorrow
*/
int bestWait = -1;
TraverseMode mode = state0.getNonTransitMode();
if (options.bannedTrips.containsKey(trip.getId())) {
//see comment in FrequencyAlight for details
return null;
}
for (ServiceDay sd : rctx.serviceDays) {
int secondsSinceMidnight = sd.secondsSinceMidnight(currentTime);
// only check for service on days that are not in the future
// this avoids unnecessarily examining tomorrow's services
if (secondsSinceMidnight < 0)
continue;
if (sd.serviceIdRunning(serviceId)) {
int startTime = pattern.getNextDepartureTime(stopIndex, secondsSinceMidnight,
options.wheelchairAccessible, mode == TraverseMode.BICYCLE, true);
if (startTime >= 0) {
// a trip was found, wait will be non-negative
int wait = (int) (sd.time(startTime) - state0.getTimeSeconds());
if (wait < 0)
LOG.error("negative wait time on board");
if (bestWait < 0 || wait < bestWait) {
// track the soonest departure over all relevant schedules
bestWait = wait;
}
}
}
}
if (bestWait < 0) {
return null;
}
/* check if trip is banned for this plan */
if (options.tripIsBanned(trip))
return null;
/* check if route is preferred for this plan */
long preferences_penalty = options.preferencesPenaltyForTrip(trip);
StateEditor s1 = state0.edit(this);
int type = pattern.getBoardType(stopIndex);
if (TransitUtils.handleBoardAlightType(s1, type)) {
return null;
}
s1.incrementTimeInSeconds(bestWait);
s1.incrementNumBoardings();
s1.setTripId(trip.getId());
s1.setPreviousTrip(trip);
s1.setZone(pattern.getZone(stopIndex));
s1.setRoute(trip.getRoute().getId());
s1.setBackMode(TraverseMode.LEG_SWITCH);
long wait_cost = bestWait;
if (state0.getNumBoardings() == 0) {
wait_cost *= options.waitAtBeginningFactor;
} else {
wait_cost *= options.waitReluctance;
}
s1.incrementWeight(preferences_penalty);
s1.incrementWeight(transferPenalty);
s1.incrementWeight(wait_cost + options.getBoardCost(mode));
return s1.makeState();
}
}
public State optimisticTraverse(State state0) {
StateEditor s1 = state0.edit(this);
// no cost (see patternalight)
s1.setBackMode(TraverseMode.LEG_SWITCH);
return s1.makeState();
}
/* See weightLowerBound comment. */
public double timeLowerBound(RoutingContext rctx) {
if (rctx.opt.isArriveBy()) {
if (! rctx.opt.getModes().get(modeMask)) {
return Double.POSITIVE_INFINITY;
}
for (ServiceDay sd : rctx.serviceDays)
if (sd.serviceIdRunning(serviceId))
return 0;
return Double.POSITIVE_INFINITY;
} else {
return 0;
}
}
/*
* If the main search is proceeding backward, the lower bound search is proceeding forward.
* Check the mode or serviceIds of this pattern at board time to see whether this pattern is
* worth exploring. If the main search is proceeding forward, board cost is added at board
* edges. The lower bound search is proceeding backward, and if it has reached a board edge the
* pattern was already deemed useful.
*/
public double weightLowerBound(RoutingRequest options) {
if (options.isArriveBy())
return timeLowerBound(options);
else
return options.getBoardCostLowerBound();
}
public int getStopIndex() {
return stopIndex;
}
public String toString() {
return "FrequencyBoard(" + getFromVertex() + ", " + getToVertex() + ")";
}
public FrequencyBasedTripPattern getPattern() {
return pattern;
}
}