/* 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.spt;
import java.util.LinkedList;
import java.util.List;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.gtfs.model.Trip;
import org.opentripplanner.routing.core.RoutingContext;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Vertex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A shortest path on the graph.
*/
public class GraphPath {
private static final Logger LOG = LoggerFactory.getLogger(GraphPath.class);
public LinkedList<State> states;
public LinkedList<Edge> edges;
// needed to track repeat invocations of path-reversing methods
private boolean back;
private double walkDistance = 0;
// don't really need to save this (available through State) but why not
private RoutingContext rctx;
/**
* Construct a GraphPath based on the given state by following back-edge fields all the way back
* to the origin of the search. This constructs a proper Java list of states (allowing random
* access etc.) from the predecessor information left in states by the search algorithm.
*
* Optionally re-traverses all edges backward in order to remove excess waiting time from the
* final itinerary presented to the user.
*
* @param s
* - the state for which a path is requested
* @param optimize
* - whether excess waiting time should be removed
* @param options
* - the traverse options used to reach this state
*/
public GraphPath(State s, boolean optimize) {
this.rctx = s.getContext();
this.back = s.getOptions().isArriveBy();
if (s.getOptions().getStartingTransitTripId() != null) {
LOG.debug("Disable reverse-optimize for on-board depart");
optimize = false;
}
/* Put path in chronological order, and optimize as necessary */
State lastState;
walkDistance = s.getWalkDistance();
if (back) {
lastState = optimize ? s.optimize() : s.reverse();
} else {
lastState = optimize ? s.optimize().reverse() : s;
}
// DEBUG
// lastState = s;
/*
* Starting from latest (time-wise) state, copy states to the head of a list in reverse
* chronological order. List indices will thus increase forward in time, and backEdges will
* be chronologically 'back' relative to their state.
*/
this.states = new LinkedList<State>();
this.edges = new LinkedList<Edge>();
for (State cur = lastState; cur != null; cur = cur.getBackState()) {
states.addFirst(cur);
// Record the edge if it exists and this is not the first state in the path.
if (cur.getBackEdge() != null && cur.getBackState() != null) {
edges.addFirst(cur.getBackEdge());
}
}
//dump();
}
/**
* Returns the start time of the trip in seconds since the epoch.
* @return
*/
public long getStartTime() {
return states.getFirst().getTimeSeconds();
}
/**
* Returns the end time of the trip in seconds since the epoch.
* @return
*/
public long getEndTime() {
return states.getLast().getTimeSeconds();
}
/**
* Returns the duration of the trip in seconds.
* @return
*/
public int getDuration() {
// test to see if it is the same as getStartTime - getEndTime;
return (int) states.getLast().getElapsedTimeSeconds();
}
public double getWeight() {
return states.getLast().getWeight();
}
public Vertex getStartVertex() {
return states.getFirst().getVertex();
}
public Vertex getEndVertex() {
return states.getLast().getVertex();
}
/** @return A list containing one AgencyAndId (trip_id) for each vehicle boarded in this path,
* in the chronological order they are boarded. */
public List<AgencyAndId> getTrips() {
List<AgencyAndId> ret = new LinkedList<AgencyAndId>();
Trip lastTrip = null;
for (State s : states) {
if (s.getBackEdge() != null) {
Trip trip = s.getBackTrip();
if (trip != null && trip != lastTrip) {
ret.add(trip.getId());
lastTrip = trip;
}
}
}
return ret;
}
public String toString() {
return "GraphPath(" + states.toString() + ")";
}
/**
* Two paths are equal if they use the same ordered list of trips
*/
public boolean equals(Object o) {
if (o instanceof GraphPath) {
GraphPath go = (GraphPath) o;
return go.getTrips().equals(getTrips());
}
return false;
}
// must compare edges, not states, since states are different at each search
public int hashCode() {
return this.edges.hashCode();
}
/****
* Private Methods
****/
public void dump() {
System.out.println(" --- BEGIN GRAPHPATH DUMP ---");
System.out.println(this.toString());
for (State s : states)
System.out.println(s + " via " + s.getBackEdge());
System.out.println(" --- END GRAPHPATH DUMP ---");
System.out.println("Total meters walked in this graphpath: " +
states.getLast().getWalkDistance());
}
public void dumpPathParser() {
System.out.println(" --- BEGIN GRAPHPATH DUMP ---");
System.out.println(this.toString());
for (State s : states)
System.out.println(s.getPathParserStates() + s + " via " + s.getBackEdge());
System.out.println(" --- END GRAPHPATH DUMP ---");
}
public double getWalkDistance() {
return walkDistance;
}
public RoutingContext getRoutingContext() {
return rctx;
}
}