/* 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.core;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import junit.framework.TestCase;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.gtfs.model.Stop;
import org.onebusaway.gtfs.model.Trip;
import org.onebusaway.gtfs.model.calendar.CalendarServiceData;
import org.opentripplanner.ConstantsForTests;
import org.opentripplanner.gtfs.GtfsContext;
import org.opentripplanner.gtfs.GtfsLibrary;
import org.opentripplanner.routing.algorithm.GenericAStar;
import org.opentripplanner.routing.edgetype.FrequencyAlight;
import org.opentripplanner.routing.edgetype.FrequencyBoard;
import org.opentripplanner.routing.edgetype.SimpleTransfer;
import org.opentripplanner.routing.edgetype.TableTripPattern;
import org.opentripplanner.routing.edgetype.TimedTransferEdge;
import org.opentripplanner.routing.edgetype.factory.GTFSPatternHopFactory;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.spt.GraphPath;
import org.opentripplanner.routing.spt.ShortestPathTree;
import org.opentripplanner.routing.trippattern.Update;
import org.opentripplanner.routing.trippattern.UpdateBlock;
import org.opentripplanner.routing.trippattern.Update.Status;
import org.opentripplanner.routing.vertextype.PatternStopVertex;
import org.opentripplanner.routing.vertextype.TransitStop;
import org.opentripplanner.util.TestUtils;
/**
* This is a singleton class to hold graph data between test runs, since loading it is slow.
*/
class Context {
public Graph graph;
public GenericAStar aStar;
private static Context instance = null;
public static Context getInstance() throws IOException {
if (instance == null) {
instance = new Context();
}
return instance;
}
public Context() throws IOException {
// Create a star search
aStar = new GenericAStar();
// Create graph
GtfsContext context = GtfsLibrary.readGtfs(new File(ConstantsForTests.FAKE_GTFS));
graph = spy(new Graph());
GTFSPatternHopFactory factory = new GTFSPatternHopFactory(context);
factory.run(graph);
graph.putService(CalendarServiceData.class,
GtfsLibrary.createCalendarServiceData(context.getDao()));
// Add simple transfer to make transfer possible between N-K and F-H
createSimpleTransfer("agency_K", "agency_F", 100);
// Add simple transfer to make transfer possible between O-P and U-V
createSimpleTransfer("agency_P", "agency_U", 100);
// Add simple transfer to make transfer possible between U-V and I-J
createSimpleTransfer("agency_V", "agency_I", 100);
}
/**
* Create simple transfer edge between two vertices given their labels
* @param from is label of from vertex
* @param to is label of to vertex
* @param distance is distance of transfer
*/
@SuppressWarnings("deprecation")
private void createSimpleTransfer(String from, String to, int distance) {
TransitStop fromv = ((TransitStop) graph.getVertex(from));
TransitStop tov = ((TransitStop) graph.getVertex(to));
new SimpleTransfer(fromv, tov, distance);
}
}
/**
* Test transfers, mostly stop-to-stop transfers.
*/
public class TestTransfers extends TestCase {
private Graph graph;
private GenericAStar aStar;
public void setUp() throws Exception {
// Get graph and a star from singleton class
graph = Context.getInstance().graph;
aStar = Context.getInstance().aStar;
}
/**
* Plan journey without optimization and return list of states and edges
* @param options are options to use for planning the journey
* @return ordered list of states and edges in the journey
*/
private GraphPath planJourney(RoutingRequest options) {
return planJourney(options, false);
}
/**
* Plan journey and return list of states and edges
* @param options are options to use for planning the journey
* @param optimize is true when optimization should be used
* @return ordered list of states and edges in the journey
*/
private GraphPath planJourney(RoutingRequest options, boolean optimize) {
// Calculate route and convert to path
ShortestPathTree spt = aStar.getShortestPathTree(options);
GraphPath path = spt.getPath(options.rctx.target, optimize);
// Return list of states and edges in the journey
return path;
}
private List<Trip> extractTrips(GraphPath path) {
// Get all trips in order
List<Trip> trips = new ArrayList<Trip>();
if (path != null) {
for (State s : path.states) {
if (s.getBackMode() != null && s.getBackMode().isTransit()) {
Trip trip = s.getBackTrip();
if (trip != null && !trips.contains(trip)) {
trips.add(trip);
}
}
}
}
// Return trips
return trips;
}
/**
* Apply an update to a table trip pattern and check whether the update was applied correctly
*/
private void applyUpdateToTripPattern(TableTripPattern pattern, String tripId, String stopId,
int stopSeq, int arrive, int depart, Status prediction, int timestamp) {
Update update = new Update(new AgencyAndId("agency",tripId), stopId, stopSeq, arrive, depart, prediction, timestamp);
ArrayList<Update> updates = new ArrayList<Update>(Arrays.asList(update));
UpdateBlock block = UpdateBlock.splitByTrip(updates).get(0);
boolean success = pattern.update(block);
assertTrue(success);
}
public void testStopToStopTransfer() throws Exception {
// Replace the transfer table with an empty table
TransferTable table = new TransferTable();
when(graph.getTransferTable()).thenReturn(table);
// Compute a normal path between two stops
@SuppressWarnings("deprecation")
Vertex origin = graph.getVertex("agency_N");
@SuppressWarnings("deprecation")
Vertex destination = graph.getVertex("agency_H");
// Set options like time and routing context
RoutingRequest options = new RoutingRequest();
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 7, 11, 11, 11, 0);
options.setRoutingContext(graph, origin, destination);
// Plan journey
GraphPath path;
List<Trip> trips;
path = planJourney(options);
trips = extractTrips(path);
// Validate result
assertEquals("8.1", trips.get(0).getId().getId());
assertEquals("4.2", trips.get(1).getId().getId());
// Add transfer to table, transfer time was 27600 seconds
Stop stopK = new Stop();
stopK.setId(new AgencyAndId("agency", "K"));
Stop stopF = new Stop();
stopF.setId(new AgencyAndId("agency", "F"));
table.addTransferTime(stopK, stopF, null, null, null, null, 27601);
// Plan journey
path = planJourney(options);
trips = extractTrips(path);
// Check whether a later second trip was taken
assertEquals("8.1", trips.get(0).getId().getId());
assertEquals("4.3", trips.get(1).getId().getId());
// Revert the graph, thus using the original transfer table again
reset(graph);
}
public void testStopToStopTransferInReverse() throws Exception {
// Replace the transfer table with an empty table
TransferTable table = new TransferTable();
when(graph.getTransferTable()).thenReturn(table);
// Compute a normal path between two stops
@SuppressWarnings("deprecation")
Vertex origin = graph.getVertex("agency_N");
@SuppressWarnings("deprecation")
Vertex destination = graph.getVertex("agency_H");
// Set options like time and routing context
RoutingRequest options = new RoutingRequest();
options.setArriveBy(true);
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 7, 12, 1, 0, 0);
options.setRoutingContext(graph, origin, destination);
// Plan journey
GraphPath path;
List<Trip> trips;
path = planJourney(options, true);
trips = extractTrips(path);
// Validate result
assertEquals("8.1", trips.get(0).getId().getId());
assertEquals("4.2", trips.get(1).getId().getId());
// Add transfer to table, transfer time was 27600 seconds
Stop stopK = new Stop();
stopK.setId(new AgencyAndId("agency", "K"));
Stop stopF = new Stop();
stopF.setId(new AgencyAndId("agency", "F"));
table.addTransferTime(stopK, stopF, null, null, null, null, 27601);
// Plan journey
path = planJourney(options, true);
trips = extractTrips(path);
// Check whether a later second trip was taken
assertEquals("8.1", trips.get(0).getId().getId());
assertEquals("4.3", trips.get(1).getId().getId());
// Revert the graph, thus using the original transfer table again
reset(graph);
}
public void testStopToStopTransferWithFrequency() throws Exception {
// Replace the transfer table with an empty table
TransferTable table = new TransferTable();
when(graph.getTransferTable()).thenReturn(table);
// Compute a normal path between two stops
@SuppressWarnings("deprecation")
Vertex origin = graph.getVertex("agency_O");
@SuppressWarnings("deprecation")
Vertex destination = graph.getVertex("agency_V");
// Set options like time and routing context
RoutingRequest options = new RoutingRequest();
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 7, 11, 13, 11, 0);
options.setRoutingContext(graph, origin, destination);
// Plan journey
GraphPath path;
List<Trip> trips;
path = planJourney(options);
trips = extractTrips(path);
// Validate result
assertEquals("10.5", trips.get(0).getId().getId());
assertEquals("15.1", trips.get(1).getId().getId());
// Find state with FrequencyBoard back edge and save time of that state
long time = -1;
for (State s : path.states) {
if (s.getBackEdge() instanceof FrequencyBoard) {
time = s.getTimeSeconds();
break;
}
}
assertTrue(time >= 0);
// Add transfer to table such that the next trip will be chosen (there are 3600 seconds between trips),
// transfer time was 75 seconds
Stop stopP = new Stop();
stopP.setId(new AgencyAndId("agency", "P"));
Stop stopU = new Stop();
stopU.setId(new AgencyAndId("agency", "U"));
table.addTransferTime(stopP, stopU, null, null, null, null, 3675);
// Plan journey
path = planJourney(options);
trips = extractTrips(path);
// Check whether a later second trip was taken
assertEquals("10.5", trips.get(0).getId().getId());
assertEquals("15.1", trips.get(1).getId().getId());
// Find state with FrequencyBoard back edge and save time of that state
long newTime = -1;
for (State s : path.states) {
if (s.getBackEdge() instanceof FrequencyBoard) {
newTime = s.getTimeSeconds();
break;
}
}
assertTrue(newTime >= 0);
assertTrue(newTime > time);
assertEquals(3600, newTime - time);
// Revert the graph, thus using the original transfer table again
reset(graph);
}
public void testStopToStopTransferWithFrequencyInReverse() throws Exception {
// Replace the transfer table with an empty table
TransferTable table = new TransferTable();
when(graph.getTransferTable()).thenReturn(table);
// Compute a normal path between two stops
@SuppressWarnings("deprecation")
Vertex origin = graph.getVertex("agency_U");
@SuppressWarnings("deprecation")
Vertex destination = graph.getVertex("agency_J");
// Set options like time and routing context
RoutingRequest options = new RoutingRequest();
options.setArriveBy(true);
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 7, 11, 11, 11, 0);
options.setRoutingContext(graph, origin, destination);
// Plan journey
GraphPath path;
List<Trip> trips;
path = planJourney(options);
trips = extractTrips(path);
// Validate result
assertEquals("15.1", trips.get(0).getId().getId());
assertEquals("5.1", trips.get(1).getId().getId());
// Find state with FrequencyBoard back edge and save time of that state
long time = -1;
for (State s : path.states) {
if (s.getBackEdge() instanceof FrequencyAlight
&& s.getBackState() != null) {
time = s.getBackState().getTimeSeconds();
break;
}
}
assertTrue(time >= 0);
// Add transfer to table such that the next trip will be chosen (there are 3600 seconds between trips),
// transfer time was 75 seconds
Stop stopV = new Stop();
stopV.setId(new AgencyAndId("agency", "V"));
Stop stopI = new Stop();
stopI.setId(new AgencyAndId("agency", "I"));
table.addTransferTime(stopV, stopI, null, null, null, null, 3675);
// Plan journey
path = planJourney(options);
trips = extractTrips(path);
// Check whether a later second trip was taken
assertEquals("15.1", trips.get(0).getId().getId());
assertEquals("5.1", trips.get(1).getId().getId());
// Find state with FrequencyBoard back edge and save time of that state
long newTime = -1;
for (State s : path.states) {
if (s.getBackEdge() instanceof FrequencyAlight
&& s.getBackState() != null) {
newTime = s.getBackState().getTimeSeconds();
break;
}
}
assertTrue(newTime >= 0);
assertTrue(newTime < time);
assertEquals(3600, time - newTime);
// Revert the graph, thus using the original transfer table again
reset(graph);
}
public void testForbiddenStopToStopTransfer() throws Exception {
// Replace the transfer table with an empty table
TransferTable table = new TransferTable();
when(graph.getTransferTable()).thenReturn(table);
// Compute a normal path between two stops
@SuppressWarnings("deprecation")
Vertex origin = graph.getVertex("agency_N");
@SuppressWarnings("deprecation")
Vertex destination = graph.getVertex("agency_H");
// Set options like time and routing context
RoutingRequest options = new RoutingRequest();
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 7, 11, 11, 11, 0);
options.setRoutingContext(graph, origin, destination);
// Plan journey
GraphPath path;
List<Trip> trips;
path = planJourney(options);
trips = extractTrips(path);
// Validate result
assertEquals("8.1", trips.get(0).getId().getId());
assertEquals("4.2", trips.get(1).getId().getId());
// Add forbidden transfer to table
Stop stopK = new Stop();
stopK.setId(new AgencyAndId("agency", "K"));
Stop stopF = new Stop();
stopF.setId(new AgencyAndId("agency", "F"));
table.addTransferTime(stopK, stopF, null, null, null, null, StopTransfer.FORBIDDEN_TRANSFER);
// Plan journey
path = planJourney(options);
trips = extractTrips(path);
// Check that no trip was returned
assertEquals(0, trips.size());
// Revert the graph, thus using the original transfer table again
reset(graph);
}
public void testForbiddenStopToStopTransferWithFrequencyInReverse() throws Exception {
// Replace the transfer table with an empty table
TransferTable table = new TransferTable();
when(graph.getTransferTable()).thenReturn(table);
// Compute a normal path between two stops
@SuppressWarnings("deprecation")
Vertex origin = graph.getVertex("agency_U");
@SuppressWarnings("deprecation")
Vertex destination = graph.getVertex("agency_J");
// Set options like time and routing context
RoutingRequest options = new RoutingRequest();
options.setArriveBy(true);
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 7, 11, 11, 11, 0);
options.setRoutingContext(graph, origin, destination);
// Plan journey
GraphPath path;
List<Trip> trips;
path = planJourney(options);
trips = extractTrips(path);
// Validate result
assertEquals("15.1", trips.get(0).getId().getId());
assertEquals("5.1", trips.get(1).getId().getId());
// Add forbidden transfer to table
Stop stopV = new Stop();
stopV.setId(new AgencyAndId("agency", "V"));
Stop stopI = new Stop();
stopI.setId(new AgencyAndId("agency", "I"));
table.addTransferTime(stopV, stopI, null, null, null, null, StopTransfer.FORBIDDEN_TRANSFER);
// Plan journey
path = planJourney(options);
trips = extractTrips(path);
// Check that no trip was returned
assertEquals(0, trips.size());
// Revert the graph, thus using the original transfer table again
reset(graph);
}
public void testTimedStopToStopTransfer() throws Exception {
// Replace the transfer table with an empty table
TransferTable table = new TransferTable();
when(graph.getTransferTable()).thenReturn(table);
// Compute a normal path between two stops
@SuppressWarnings("deprecation")
Vertex origin = graph.getVertex("agency_N");
@SuppressWarnings("deprecation")
Vertex destination = graph.getVertex("agency_H");
// Set options like time and routing context
RoutingRequest options = new RoutingRequest();
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 7, 11, 11, 11, 0);
options.setRoutingContext(graph, origin, destination);
// Plan journey
GraphPath path;
List<Trip> trips;
path = planJourney(options);
trips = extractTrips(path);
// Validate result
assertEquals("8.1", trips.get(0).getId().getId());
assertEquals("4.2", trips.get(1).getId().getId());
// Add timed transfer to table
Stop stopK = new Stop();
stopK.setId(new AgencyAndId("agency", "K"));
Stop stopF = new Stop();
stopF.setId(new AgencyAndId("agency", "F"));
table.addTransferTime(stopK, stopF, null, null, null, null, StopTransfer.TIMED_TRANSFER);
// Don't forget to also add a TimedTransferEdge
@SuppressWarnings("deprecation")
Vertex fromVertex = graph.getVertex("agency_K_arrive");
@SuppressWarnings("deprecation")
Vertex toVertex = graph.getVertex("agency_F_depart");
TimedTransferEdge timedTransferEdge = new TimedTransferEdge(fromVertex, toVertex);
// Plan journey
path = planJourney(options);
trips = extractTrips(path);
// Check whether the trips are still the same
assertEquals("8.1", trips.get(0).getId().getId());
assertEquals("4.2", trips.get(1).getId().getId());
// Now apply a real-time update: let the to-trip be early by 27600 seconds, resulting in a transfer time of 0 seconds
@SuppressWarnings("deprecation")
TableTripPattern pattern = ((PatternStopVertex) graph.getVertex("agency_F_agency_4.3_1_D")).getTripPattern();
applyUpdateToTripPattern(pattern, "4.2", "F", 0, 55200, 55200, Update.Status.PREDICTION, 0);
// Plan journey
path = planJourney(options);
trips = extractTrips(path);
// Check whether the trips are still the same
assertEquals("8.1", trips.get(0).getId().getId());
assertEquals("4.2", trips.get(1).getId().getId());
// Now apply a real-time update: let the to-trip be early by 27601 seconds, resulting in a transfer time of -1 seconds
applyUpdateToTripPattern(pattern, "4.2", "F", 0, 55199, 55199, Update.Status.PREDICTION, 0);
// Plan journey
path = planJourney(options);
trips = extractTrips(path);
// Check whether a later second trip was taken
assertEquals("8.1", trips.get(0).getId().getId());
assertEquals("4.3", trips.get(1).getId().getId());
// "Revert" the real-time update
applyUpdateToTripPattern(pattern, "4.2", "F", 0, 82800, 82800, Update.Status.PREDICTION, 0);
// Remove the timed transfer from the graph
timedTransferEdge.detach();
// Revert the graph, thus using the original transfer table again
reset(graph);
}
}