/* 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.loader;
import java.io.File;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import junit.framework.TestCase;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.gtfs.model.Route;
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.common.geometry.GeometryUtils;
import org.opentripplanner.graph_builder.annotation.GraphBuilderAnnotation;
import org.opentripplanner.graph_builder.annotation.NegativeHopTime;
import org.opentripplanner.gtfs.GtfsContext;
import org.opentripplanner.gtfs.GtfsLibrary;
import org.opentripplanner.routing.algorithm.GenericAStar;
import org.opentripplanner.routing.core.OptimizeType;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.core.StopTransfer;
import org.opentripplanner.routing.core.TransferTable;
import org.opentripplanner.routing.core.TraverseModeSet;
import org.opentripplanner.routing.edgetype.*;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.edgetype.factory.GTFSPatternHopFactory;
import org.opentripplanner.routing.graph.Edge;
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.vertextype.IntersectionVertex;
import org.opentripplanner.routing.vertextype.TransitStop;
import org.opentripplanner.routing.vertextype.TransitStopArrive;
import org.opentripplanner.routing.vertextype.TransitStopDepart;
import org.opentripplanner.util.TestUtils;
import com.vividsolutions.jts.geom.Geometry;
public class TestPatternHopFactory extends TestCase {
private Graph graph;
private GenericAStar aStar = new GenericAStar();
private GtfsContext context;
public void setUp() throws Exception {
context = GtfsLibrary.readGtfs(new File(ConstantsForTests.FAKE_GTFS));
graph = new Graph();
GTFSPatternHopFactory factory = new GTFSPatternHopFactory(context);
factory.run(graph);
graph.putService(CalendarServiceData.class, GtfsLibrary.createCalendarServiceData(context.getDao()));
String[] stops = {"agency:A", "agency:B", "agency:C", "agency:D", "agency:E"};
for (int i = 0; i < stops.length; ++i) {
TransitStop stop = (TransitStop) (graph.getVertex(stops[i]));
IntersectionVertex front = new IntersectionVertex(graph, "near_1_" + stop.getStopId(), stop.getX() + 0.0001, stop.getY() + 0.0001);
IntersectionVertex back = new IntersectionVertex(graph, "near_2_" + stop.getStopId(), stop.getX() - 0.0001, stop.getY() - 0.0001);
StreetEdge street1 = new StreetEdge(front, back, GeometryUtils.makeLineString(stop.getX() + 0.0001, stop.getY() + 0.0001, stop.getX() - 0.0001, stop.getY() - 0.0001), "street", 100, StreetTraversalPermission.ALL, false);
StreetEdge street2 = new StreetEdge(back, front, GeometryUtils.makeLineString(stop.getX() - 0.0001, stop.getY() - 0.0001, stop.getX() + 0.0001, stop.getY() + 0.0001), "street", 100, StreetTraversalPermission.ALL, true);
}
NetworkLinker nl = new NetworkLinker(graph);
nl.createLinkage();
}
public void testAnnotation() {
boolean found = false;
for (GraphBuilderAnnotation annotation : graph.getBuilderAnnotations()) {
if (annotation instanceof NegativeHopTime) {
NegativeHopTime nht = (NegativeHopTime) annotation;
assertTrue(nht.st0.getDepartureTime() > nht.st1.getArrivalTime());
found = true;
}
}
assertTrue(found);
}
public void testBoardAlight() throws Exception {
Vertex stop_a_depart = graph.getVertex("agency:A_depart");
Vertex stop_b_depart = graph.getVertex("agency:B_depart");
assertEquals(1, stop_a_depart.getDegreeOut());
assertEquals(3, stop_b_depart.getDegreeOut());
for (Edge e : stop_a_depart.getOutgoing()) {
assertEquals(TransitBoardAlight.class, e.getClass());
assertTrue(((TransitBoardAlight) e).boarding);
}
TransitBoardAlight pb = (TransitBoardAlight) stop_a_depart.getOutgoing().iterator().next();
Vertex journey_a_1 = pb.getToVertex();
assertEquals(1, journey_a_1.getDegreeIn());
for (Edge e : journey_a_1.getOutgoing()) {
if (e.getToVertex() instanceof TransitStop) {
assertEquals(TransitBoardAlight.class, e.getClass());
} else {
assertEquals(PatternHop.class, e.getClass());
}
}
}
public void testBoardAlightStopIndex() {
Vertex stop_b_arrive = graph.getVertex("agency:C_arrive");
Vertex stop_b_depart = graph.getVertex("agency:C_depart");
Map<TripPattern, Integer> stopIndex = new HashMap<TripPattern, Integer>();
for(Edge edge : stop_b_depart.getOutgoing()) {
TransitBoardAlight tba = (TransitBoardAlight) edge;
stopIndex.put(tba.getPattern(), tba.getStopIndex());
}
for(Edge edge : stop_b_arrive.getIncoming()) {
TransitBoardAlight tba = (TransitBoardAlight) edge;
if(stopIndex.containsKey(tba.getPattern()))
assertEquals((Integer) stopIndex.get(tba.getPattern()), new Integer(tba.getStopIndex()));
}
}
private List<TransitStop> extractStopVertices(GraphPath path) {
List<TransitStop> ret = Lists.newArrayList();
for (State state : path.states) {
if (state.getVertex() instanceof TransitStop) {
ret.add(((TransitStop)state.getVertex()));
}
}
return ret;
}
public void testRouting() throws Exception {
Vertex stop_a = graph.getVertex("agency:A");
Vertex stop_b = graph.getVertex("agency:B");
Vertex stop_c = graph.getVertex("agency:C");
Vertex stop_d = graph.getVertex("agency:D");
Vertex stop_e = graph.getVertex("agency:E");
RoutingRequest options = new RoutingRequest();
// test feed is designed for instantaneous transfers
options.transferSlack = (0);
long startTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 7, 0, 0, 0);
options.dateTime = startTime;
ShortestPathTree spt;
GraphPath path;
// A to B
options.setRoutingContext(graph, stop_a, stop_b);
spt = aStar.getShortestPathTree(options);
path = spt.getPath(stop_b, false);
assertNotNull(path);
assertEquals(6, path.states.size());
// A to C
options.setRoutingContext(graph, stop_a, stop_c);
spt = aStar.getShortestPathTree(options);
path = spt.getPath(stop_c, false);
assertNotNull(path);
assertEquals(8, path.states.size());
// A to D (change at C)
options.setRoutingContext(graph, stop_a, stop_d);
spt = aStar.getShortestPathTree(options);
path = spt.getPath(stop_d, false);
assertNotNull(path);
// there are two paths of different lengths
// both arrive at 40 minutes after midnight
List<TransitStop> stops = extractStopVertices(path);
assertEquals(stops.size(), 3);
assertEquals(stops.get(1), stop_c);
long endTime = startTime + 40 * 60;
assertEquals(endTime, path.getEndTime());
//A to E (change at C)
options.setRoutingContext(graph, stop_a, stop_e);
spt = aStar.getShortestPathTree(options);
path = spt.getPath(stop_e, false);
assertNotNull(path);
stops = extractStopVertices(path);
assertEquals(stops.size(), 3);
assertEquals(stops.get(1), stop_c);
endTime = startTime + 70 * 60;
assertEquals(endTime, path.getEndTime());
}
public void testRoutingOverMidnight() throws Exception {
// this route only runs on weekdays
Vertex stop_g = graph.getVertex("agency:G_depart");
Vertex stop_h = graph.getVertex("agency:H_arrive");
ShortestPathTree spt;
GraphPath path;
RoutingRequest options = new RoutingRequest();
// Friday evening
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 18, 23, 20, 0);
options.setRoutingContext(graph, stop_g, stop_h);
spt = aStar.getShortestPathTree(options);
path = spt.getPath(stop_h, false);
assertNotNull(path);
assertEquals(4, path.states.size());
// Saturday morning
long startTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 19, 0, 5, 0);
options.dateTime = startTime;
options.setRoutingContext(graph, stop_g.getLabel(), stop_h.getLabel());
spt = aStar.getShortestPathTree(options);
path = spt.getPath(stop_h, false);
assertNotNull(path);
assertEquals(4, path.states.size());
long endTime = path.getEndTime();
assertTrue(endTime < startTime + 60 * 60);
}
/* Somewhat hackish convenience method to grab a hop edge on a particular route leaving a particular stop. */
private PatternHop getHopEdge(String stopId, String routeId) {
Vertex stopDepartVertex = graph.getVertex("agency:" + stopId + "_depart");
for (Edge edge : stopDepartVertex.getOutgoing()) {
if (edge instanceof TransitBoardAlight) {
TransitBoardAlight tba = ((TransitBoardAlight) edge);
if (tba.boarding && tba.getPattern().route.getId().getId().equals(routeId)) {
for (Edge edge2: tba.getToVertex().getOutgoing()) {
if (edge2 instanceof PatternHop) {
return (PatternHop) edge2;
}
}
}
}
}
return null;
}
public void testShapeByLocation() throws Exception {
PatternHop hop;
Geometry geom;
// Only route 4 goes through stop G and route 4 should contain only one pattern.
hop = getHopEdge("G", "4");
geom = hop.getGeometry();
assertEquals(geom.getLength(), 1.16, 0.1);
// Only route 1 goes through stop A, and route 1 should contain only one pattern.
hop = getHopEdge("A", "1");
geom = hop.getGeometry();
assertEquals(geom.getLength(), 0.01, 0.005);
}
public void testShapeByDistance() throws Exception {
PatternHop hop;
Geometry geom;
// Both routes 5 and 6 go through stop I.
hop = getHopEdge("I", "5");
geom = hop.getGeometry();
assertEquals(geom.getLength(), 1.73, 0.1);
}
public void testPickupDropoff() throws Exception {
Vertex stop_o = graph.getVertex("agency:O_depart");
Vertex stop_p = graph.getVertex("agency:P");
assertEquals(2, stop_o.getOutgoing().size());
long startTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 19, 12, 0, 0);
RoutingRequest options = new RoutingRequest();
options.dateTime = startTime;
options.setRoutingContext(graph, stop_o, stop_p);
ShortestPathTree spt = aStar.getShortestPathTree(options);
GraphPath path = spt.getPath(stop_p, false);
assertNotNull(path);
long endTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 19, 12, 10, 0);
assertEquals(endTime, path.getEndTime());
startTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 19, 12, 0, 1);
options.dateTime = startTime;
options.setRoutingContext(graph, stop_o, stop_p);
spt = aStar.getShortestPathTree(options);
path = spt.getPath(stop_p, false);
assertNotNull(path);
endTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 19, 15, 10, 0);
assertEquals(endTime, path.getEndTime());
}
public void testTransfers() throws Exception {
TransferTable transferTable = graph.getTransferTable();
// create dummy routes and trips
Route fromRoute = new Route();
fromRoute.setId(new AgencyAndId("agency", "1"));
Trip fromTrip = new Trip();
fromTrip.setId(new AgencyAndId("agency", "1.1"));
fromTrip.setRoute(fromRoute);
Route toRoute = new Route();
toRoute.setId(new AgencyAndId("agency", "2"));
Trip toTrip = new Trip();
toTrip.setId(new AgencyAndId("agency", "2.1"));
toTrip.setRoute(toRoute);
Trip toTrip2 = new Trip();
toTrip2.setId(new AgencyAndId("agency", "2.2"));
toTrip2.setRoute(toRoute);
// find stops
Stop stopK = ((TransitStopArrive)graph.getVertex("agency:K_arrive")).getStop();
Stop stopN = ((TransitStopDepart)graph.getVertex("agency:N_depart")).getStop();
Stop stopM = ((TransitStopDepart)graph.getVertex("agency:M_depart")).getStop();
assertTrue(transferTable.hasPreferredTransfers());
assertEquals(StopTransfer.UNKNOWN_TRANSFER, transferTable.getTransferTime(stopN, stopM, fromTrip, toTrip, true));
assertEquals(StopTransfer.FORBIDDEN_TRANSFER, transferTable.getTransferTime(stopK, stopM, fromTrip, toTrip, true));
assertEquals(StopTransfer.PREFERRED_TRANSFER, transferTable.getTransferTime(stopN, stopK, toTrip, toTrip2, true));
assertEquals(StopTransfer.TIMED_TRANSFER, transferTable.getTransferTime(stopN, stopK, fromTrip, toTrip, true));
assertEquals(15, transferTable.getTransferTime(stopN, stopK, fromTrip, toTrip2, true));
TransitStop e_arrive = (TransitStop) graph.getVertex("agency:E");
TransitStop f_depart = (TransitStop) graph.getVertex("agency:F");
Edge edge = new TransferEdge(e_arrive, f_depart, 10000, 10000);
long startTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 18, 0, 50, 0);
Vertex stop_b = graph.getVertex("agency:B_depart");
Vertex stop_g = graph.getVertex("agency:G_arrive");
RoutingRequest options = new RoutingRequest();
options.dateTime = startTime;
options.setRoutingContext(graph, stop_b, stop_g);
ShortestPathTree spt = aStar.getShortestPathTree(options);
GraphPath path = spt.getPath(stop_g, false);
assertNotNull(path);
assertTrue("expected to use much later trip due to min transfer time", path.getEndTime() - startTime > 4.5 * 60 * 60);
/* cleanup */
e_arrive.removeOutgoing(edge);
f_depart.removeIncoming(edge);
}
public void testTraverseMode() throws Exception {
Vertex stop_a = graph.getVertex("agency:A_depart");
Vertex stop_b = graph.getVertex("agency:B_arrive");
ShortestPathTree spt;
RoutingRequest options = new RoutingRequest();
options.setModes(new TraverseModeSet("TRAINISH"));
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 0, 0, 0, 0);
options.setRoutingContext(graph, stop_a, stop_b);
spt = aStar.getShortestPathTree(options );
//a to b is bus only
assertNull(spt.getPath(stop_b, false));
options.setModes(new TraverseModeSet("TRAINISH,BUSISH"));
spt = aStar.getShortestPathTree(options);
assertNotNull(spt.getPath(stop_b, false));
}
public void testTimelessStops() throws Exception {
Vertex stop_d = graph.getVertex("agency:D");
Vertex stop_c = graph.getVertex("agency:C");
RoutingRequest options = new RoutingRequest();
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 1, 10, 0, 0);
options.setRoutingContext(graph, stop_d, stop_c);
ShortestPathTree spt = aStar.getShortestPathTree(options);
GraphPath path = spt.getPath(stop_c, false);
assertNotNull(path);
assertEquals(TestUtils.dateInSeconds("America/New_York", 2009, 8, 1, 11, 0, 0), path.getEndTime());
}
public void testTripBikesAllowed() throws Exception {
Vertex stop_a = graph.getVertex("agency:A");
Vertex stop_b = graph.getVertex("agency:B");
Vertex stop_c = graph.getVertex("agency:C");
Vertex stop_d = graph.getVertex("agency:D");
RoutingRequest options = new RoutingRequest();
options.modes.setWalk(false);
options.modes.setBicycle(true);
options.modes.setTransit(true);
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 18, 0, 0, 0);
options.setRoutingContext(graph, stop_a, stop_b);
ShortestPathTree spt;
GraphPath path;
// route: bikes allowed, trip: no value
spt = aStar.getShortestPathTree(options);
path = spt.getPath(stop_b, false);
assertNotNull(path);
// route: bikes allowed, trip: bikes not allowed
options.setRoutingContext(graph, stop_d, stop_c);
spt = aStar.getShortestPathTree(options);
path = spt.getPath(stop_c, false);
assertNull(path);
// route: bikes not allowed, trip: bikes allowed
options.setRoutingContext(graph, stop_c, stop_d);
spt = aStar.getShortestPathTree(options);
path = spt.getPath(stop_d, false);
assertNotNull(path);
}
public void testWheelchairAccessible() throws Exception {
Vertex near_a = graph.getVertex("near_1_agency_A");
Vertex near_b = graph.getVertex("near_1_agency_B");
Vertex near_c = graph.getVertex("near_1_agency_C");
Vertex near_e = graph.getVertex("near_1_agency_E");
Vertex stop_d = graph.getVertex("agency:D");
Vertex split_d = null;
for (StreetTransitLink e : Iterables.filter(stop_d.getOutgoing(), StreetTransitLink.class)) {
split_d = e.getToVertex();
}
RoutingRequest options = new RoutingRequest();
options.wheelchairAccessible = true;
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 18, 0, 0, 0);
ShortestPathTree spt;
GraphPath path;
// stop B is accessible, so there should be a path.
options.setRoutingContext(graph, near_a, near_b);
spt = aStar.getShortestPathTree(options);
path = spt.getPath(near_b, false);
assertNotNull(path);
// stop C is not accessible, so there should be no path.
options.setRoutingContext(graph, near_a, near_c);
spt = aStar.getShortestPathTree(options);
path = spt.getPath(near_c, false);
assertNull(path);
// stop E has no accessibility information, but we should still be able to route to it.
options.setRoutingContext(graph, near_a, near_e);
spt = aStar.getShortestPathTree(options);
path = spt.getPath(near_e, false);
assertNotNull(path);
// from stop A to stop D would normally be trip 1.1 to trip 2.1, arriving at 00:30. But trip
// 2 is not accessible, so we'll do 1.1 to 3.1, arriving at 01:00
GregorianCalendar time = new GregorianCalendar(2009, 8, 18, 0, 0, 0);
time.setTimeZone(TimeZone.getTimeZone("America/New_York"));
options.dateTime = TestUtils.toSeconds(time);
options.setRoutingContext(graph, near_a, split_d);
spt = aStar.getShortestPathTree(options);
time.add(Calendar.HOUR, 1);
time.add(Calendar.SECOND, 1); //for the StreetTransitLink
path = spt.getPath(split_d, false);
assertNotNull(path);
assertEquals(TestUtils.toSeconds(time), path.getEndTime());
}
public void testRunForTrain() {
/** This is the notorious Byrd bug: we're going from Q to T at 8:30.
* There's a trip from S to T at 8:50 and a second one at 9:50.
* To get to S by 8:50, we need to take trip 12.1 from Q to R, and 13.1
* from R to S. If we take the direct-but-slower 11.1, we'll miss
* the 8:50 and have to catch the 9:50.
*/
Vertex destination = graph.getVertex("agency:T");
RoutingRequest options = new RoutingRequest();
// test is designed such that transfers must be instantaneous
options.transferSlack = (0);
GregorianCalendar startTime = new GregorianCalendar(2009, 11, 2, 8, 30, 0);
startTime.setTimeZone(TimeZone.getTimeZone("America/New_York"));
options.dateTime = TestUtils.toSeconds(startTime);
options.setRoutingContext(graph, "agency:Q", destination.getLabel());
ShortestPathTree spt = aStar.getShortestPathTree(options);
GraphPath path = spt.getPath(destination, false);
long endTime = path.getEndTime();
Calendar c = new GregorianCalendar();
c.setTimeInMillis(endTime * 1000L);
assertTrue(endTime - TestUtils.toSeconds(startTime) < 7200);
}
public void testFrequencies() {
Vertex stop_u = graph.getVertex("agency:U_depart");
Vertex stop_v = graph.getVertex("agency:V_arrive");
ShortestPathTree spt;
GraphPath path;
RoutingRequest options = new RoutingRequest();
options.setModes(new TraverseModeSet("TRANSIT"));
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 7, 0, 0, 0);
options.setRoutingContext(graph, stop_u, stop_v);
// U to V - original stop times - shouldn't be used
spt = aStar.getShortestPathTree(options);
path = spt.getPath(stop_v, false);
assertNotNull(path);
assertEquals(4, path.states.size());
long endTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 7, 6, 40, 0);
assertEquals(endTime, path.getEndTime());
// U to V - first frequency
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 7, 7, 0, 0);
options.setRoutingContext(graph, stop_u, stop_v);
spt = aStar.getShortestPathTree(options);
path = spt.getPath(stop_v, false);
assertNotNull(path);
assertEquals(4, path.states.size());
endTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 7, 7, 40, 0);
assertEquals(endTime, path.getEndTime());
// U to V - second frequency
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 7, 14, 0, 0);
options.setRoutingContext(graph, stop_u, stop_v);
spt = aStar.getShortestPathTree(options);
path = spt.getPath(stop_v, false);
assertNotNull(path);
assertEquals(4, path.states.size());
endTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 7, 14, 40, 0);
assertEquals(endTime, path.getEndTime());
// TODO more detailed testing of frequencies
}
public void testFewestTransfers() {
Vertex stop_c = graph.getVertex("agency:C");
Vertex stop_d = graph.getVertex("agency:D");
RoutingRequest options = new RoutingRequest();
options.optimize = OptimizeType.QUICK;
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 1, 16, 0, 0);
options.setRoutingContext(graph, stop_c, stop_d);
ShortestPathTree spt = aStar.getShortestPathTree(options);
//when optimizing for speed, take the fast two-bus path
GraphPath path = spt.getPath(stop_d, false);
assertNotNull(path);
assertEquals(TestUtils.dateInSeconds("America/New_York", 2009, 8, 1, 16, 20, 0), path.getEndTime());
//when optimizing for fewest transfers, take the slow one-bus path
options.transferPenalty = 1800;
spt = aStar.getShortestPathTree(options);
path = spt.getPath(stop_d, false);
assertNotNull(path);
assertEquals(TestUtils.dateInSeconds("America/New_York", 2009, 8, 1, 16, 50, 0), path.getEndTime());
}
public void testPathways() throws Exception {
Vertex entrance = graph.getVertex("agency:entrance_a");
assertNotNull(entrance);
Vertex stop = graph.getVertex("agency:A");
assertNotNull(stop);
RoutingRequest options = new RoutingRequest();
options.dateTime = TestUtils.dateInSeconds("America/New_York", 2009, 8, 1, 16, 0, 0);
options.setRoutingContext(graph, entrance, stop);
ShortestPathTree spt = aStar.getShortestPathTree(options);
GraphPath path = spt.getPath(stop, false);
assertNotNull(path);
assertEquals(TestUtils.dateInSeconds("America/New_York", 2009, 8, 1, 16, 0, 34), path.getEndTime());
}
}