Skip to content

Commit

Permalink
Fix test case for intermediate places
Browse files Browse the repository at this point in the history
Note that in LegSwitchingEdge I had to clear the "last pattern" of the state
editor because otherwise the TransitBoardAlight.traverse() method would 
choke on taking the same pattern (route) again - and return null. However, if
you travel from A to C via B, then there is nothing wrong with taking the
same bus in B that you came with.
  • Loading branch information
pieterbuzing committed Apr 25, 2016
1 parent 0d91f22 commit a822b70
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 24 deletions.
Expand Up @@ -38,6 +38,8 @@ public LegSwitchingEdge(Vertex v1, Vertex v2) {
public State traverse(State s0) { public State traverse(State s0) {
StateEditor editor = s0.edit(this); StateEditor editor = s0.edit(this);
editor.setBackMode(TraverseMode.LEG_SWITCH); editor.setBackMode(TraverseMode.LEG_SWITCH);
//Forget the last pattern to allow taking the same route from an intermediate place
editor.setLastPattern(null);
return editor.makeState(); return editor.makeState();
} }


Expand Down
Expand Up @@ -283,6 +283,10 @@ private static GraphPath joinPaths(List<GraphPath> paths) {
State lastState = paths.get(0).states.getLast(); State lastState = paths.get(0).states.getLast();
GraphPath newPath = new GraphPath(lastState, false); GraphPath newPath = new GraphPath(lastState, false);
Vertex lastVertex = lastState.getVertex(); Vertex lastVertex = lastState.getVertex();

//With more paths we should allow more transfers
lastState.getOptions().maxTransfers *= paths.size();

for (GraphPath path : paths.subList(1, paths.size())) { for (GraphPath path : paths.subList(1, paths.size())) {
lastState = newPath.states.getLast(); lastState = newPath.states.getLast();
// add a leg-switching state // add a leg-switching state
Expand Down
117 changes: 117 additions & 0 deletions src/test/java/org/opentripplanner/graph_builder/module/FakeGraph.java
Expand Up @@ -202,6 +202,123 @@ public static void addTransitMultipleLines (Graph g) throws Exception {
gtfs.buildGraph(g, new HashMap<>()); gtfs.buildGraph(g, new HashMap<>());
} }


public static void addPerpendicularRoutes(Graph graph) throws Exception {
GTFSFeed feed = new GTFSFeed();
Agency agencyA = createDummyAgency("agencyA", "Agency A", "America/New_York");
feed.agency.put("agencyA", agencyA);
Agency agencyB = createDummyAgency("agencyB", "Agency B", "America/New_York");
feed.agency.put("agencyB", agencyB);
Service s = createDummyService();
feed.services.put(s.service_id, s);

int stopIdX = 0;
int stopIdY = 0;
for (double lat = 39.9058; lat < 40.0281; lat += 0.005) {
stopIdX = 0;
for (double lon = -83.1341; lon < -82.8646; lon += 0.005) {
com.conveyal.gtfs.model.Stop stop = new com.conveyal.gtfs.model.Stop();
stop.stop_id = stop.stop_name = String.format("s-%d-%d", stopIdX, stopIdY);
stop.stop_lat = lat;
stop.stop_lon = lon;
feed.stops.put(stop.stop_id, stop);
stopIdX++;
}
stopIdY++;
}

for (int i = 0; i < stopIdY; i++) {
Route route = new Route();
route.route_short_name = "hr" + i;
route.route_long_name = i + "th Horizontal Street";
route.route_type = Route.BUS;
route.agency = agencyA;
route.route_id = "horizontalroute" + i;
feed.routes.put(route.route_id, route);
}
for (int i = 0; i < stopIdX; i++) {
Route route = new Route();
route.route_short_name = "vr" + i;
route.route_long_name = i + "th Vertical Street";
route.route_type = Route.TRAM;
route.agency = agencyB;
route.route_id = "verticalroute" + i;
feed.routes.put(route.route_id, route);
}

Map<String, Route> routes = feed.routes;
com.conveyal.gtfs.model.Stop stop;
for (Route route : routes.values()) {
int routeId = Integer.parseInt(route.route_short_name.substring(2));
int x, y;
boolean isHorizontal = route.route_short_name.startsWith("hr");
for (int departure = 7 * 3600; departure < 20 * 3600; departure += FREQUENCY) {
Trip t = new Trip();
t.trip_id = "trip:" + route.route_id + ":" + departure;
t.service = s;
t.route = route;
feed.trips.put(t.trip_id, t);

int departureTime = departure;
int nrOfStops = (isHorizontal ? stopIdX : stopIdY);
for (int stopSequenceNr = 0; stopSequenceNr < nrOfStops; stopSequenceNr++) {
x = (isHorizontal ? stopSequenceNr : routeId);
y = (isHorizontal ? routeId : stopSequenceNr);
stop = feed.stops.get(String.format("s-%d-%d", x, y));
StopTime st1 = new StopTime();
st1.trip_id = t.trip_id;
st1.arrival_time = departureTime;
st1.departure_time = departureTime;
st1.stop_id = stop.stop_id;
st1.stop_sequence = stopSequenceNr + 1;
feed.stop_times.put(new Fun.Tuple2(st1.trip_id, st1.stop_sequence), st1);
//connect last stop to first so graph is fully reachable
if (stopSequenceNr == 0) {
StopTime stopTime = new StopTime();
stopTime.trip_id = t.trip_id;
stopTime.arrival_time = departureTime + nrOfStops * 120 + 30 * 60;
stopTime.departure_time = departureTime + nrOfStops * 120 + 30 * 60;
stopTime.stop_id = stop.stop_id;
stopTime.stop_sequence = stopSequenceNr + 1 + nrOfStops;
feed.stop_times.put(new Fun.Tuple2(stopTime.trip_id, stopTime.stop_sequence), stopTime);
}
departureTime += 120;
}
}
}
File tempFile = File.createTempFile("gtfs", ".zip");
feed.toFile(tempFile.getAbsolutePath());

// phew. load it into the graph.
GtfsModule gtfs = new GtfsModule(Arrays.asList(new GtfsBundle(tempFile)));
gtfs.buildGraph(graph, new HashMap<>());
}

private static Service createDummyService() {
Service s = new Service("service");
s.calendar = new Calendar();
s.calendar.service = s;
s.calendar.monday = s.calendar.tuesday = s.calendar.wednesday = s.calendar.thursday = s.calendar.friday =
s.calendar.saturday = s.calendar.sunday = 1;
s.calendar.start_date = 19991231;
s.calendar.end_date = 21001231;
return s;
}

private static Agency createDummyAgency(String id, String name, String timeZone) {
Agency a = new Agency();
a.agency_id = id;
a.agency_name = name;
a.agency_timezone = timeZone;
try {
a.agency_url = new URL("http://www.example.com/" + id);
} catch (MalformedURLException e) {
// This really can't happen
assert false;
a.agency_url = null;
}
return a;
}

/** Add a transit line with multiple patterns to a Columbus graph. Most trips serve stops s1, s2, s3 but some serve only s1, s3 */ /** Add a transit line with multiple patterns to a Columbus graph. Most trips serve stops s1, s2, s3 but some serve only s1, s3 */
public static void addMultiplePatterns (Graph gg) throws Exception { public static void addMultiplePatterns (Graph gg) throws Exception {
// using conveyal GTFS lib to build GTFS so a lot of code does not have to be rewritten later // using conveyal GTFS lib to build GTFS so a lot of code does not have to be rewritten later
Expand Down
Expand Up @@ -8,6 +8,7 @@
import org.opentripplanner.api.model.TripPlan; import org.opentripplanner.api.model.TripPlan;
import org.opentripplanner.api.resource.GraphPathToTripPlanConverter; import org.opentripplanner.api.resource.GraphPathToTripPlanConverter;
import org.opentripplanner.common.model.GenericLocation; import org.opentripplanner.common.model.GenericLocation;
import org.opentripplanner.graph_builder.module.FakeGraph;
import org.opentripplanner.routing.core.RoutingRequest; import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.impl.DefaultStreetVertexIndexFactory; import org.opentripplanner.routing.impl.DefaultStreetVertexIndexFactory;
Expand All @@ -25,24 +26,26 @@
import java.util.TimeZone; import java.util.TimeZone;


import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.opentripplanner.graph_builder.module.FakeGraph.*;


/** /**
* Tests for planning with intermediate places * Tests for planning with intermediate places
*/ */
public class TestIntermediatePlaces { public class TestIntermediatePlaces {


public static final double DELTA = 0.001; /**
* The spacial deviation that we allow in degrees
*/
public static final double DELTA = 0.005;


private static TimeZone timeZone; private static TimeZone timeZone;


private static GraphPathFinder graphPathFinder; private static GraphPathFinder graphPathFinder;


@BeforeClass public static void setUp() { @BeforeClass public static void setUp() {
try { try {
Graph graph = buildGraphNoTransit(); Graph graph = FakeGraph.buildGraphNoTransit();
addTransit(graph); FakeGraph.addPerpendicularRoutes(graph);
link(graph); FakeGraph.link(graph);
graph.index(new DefaultStreetVertexIndexFactory()); graph.index(new DefaultStreetVertexIndexFactory());


OTPServer otpServer = new OTPServer(new CommandLineParameters(), new GraphService()); OTPServer otpServer = new OTPServer(new CommandLineParameters(), new GraphService());
Expand All @@ -65,15 +68,15 @@ public class TestIntermediatePlaces {
GenericLocation toLocation = new GenericLocation(39.96383, -82.96291); GenericLocation toLocation = new GenericLocation(39.96383, -82.96291);
GenericLocation[] intermediateLocations = {}; GenericLocation[] intermediateLocations = {};


handleRequest(fromLocation, toLocation, intermediateLocations); handleRequest(fromLocation, toLocation, intermediateLocations, "WALK", false);
} }


@Test public void testOneIntermediatePlace() { @Test public void testOneIntermediatePlace() {
GenericLocation fromLocation = new GenericLocation(39.93080, -82.98522); GenericLocation fromLocation = new GenericLocation(39.93080, -82.98522);
GenericLocation toLocation = new GenericLocation(39.96383, -82.96291); GenericLocation toLocation = new GenericLocation(39.96383, -82.96291);
GenericLocation[] intermediateLocations = { new GenericLocation(39.92099, -82.95570) }; GenericLocation[] intermediateLocations = { new GenericLocation(39.92099, -82.95570) };


handleRequest(fromLocation, toLocation, intermediateLocations); handleRequest(fromLocation, toLocation, intermediateLocations, "WALK", false);
} }


@Test public void testTwoIntermediatePlaces() { @Test public void testTwoIntermediatePlaces() {
Expand All @@ -83,11 +86,48 @@ public class TestIntermediatePlaces {
intermediateLocations[0] = new GenericLocation(39.92099, -82.95570); intermediateLocations[0] = new GenericLocation(39.92099, -82.95570);
intermediateLocations[1] = new GenericLocation(39.96146, -82.99552); intermediateLocations[1] = new GenericLocation(39.96146, -82.99552);


handleRequest(fromLocation, toLocation, intermediateLocations); handleRequest(fromLocation, toLocation, intermediateLocations, "CAR", false);
} }


private void handleRequest(GenericLocation from, GenericLocation to, GenericLocation[] via) { @Test public void testTransitWithoutIntermediatePlaces() {
RoutingRequest request = new RoutingRequest("WALK"); GenericLocation fromLocation = new GenericLocation(39.9308, -83.0118);
GenericLocation toLocation = new GenericLocation(39.9998, -83.0198);
GenericLocation[] intermediateLocations = {};

handleRequest(fromLocation, toLocation, intermediateLocations, "TRANSIT,WALK", false);
}

@Test public void testThreeBusStopPlaces() {
GenericLocation fromLocation = new GenericLocation(39.9058, -83.1341);
GenericLocation toLocation = new GenericLocation(39.9058, -82.8841);
GenericLocation[] intermediateLocations = { new GenericLocation(39.9058, -82.9841) };

handleRequest(fromLocation, toLocation, intermediateLocations, "TRANSIT", false);
}

@Test public void testTransitOneIntermediatePlace() {
GenericLocation fromLocation = new GenericLocation(39.9108, -83.0118);
GenericLocation toLocation = new GenericLocation(39.9698, -83.0198);
GenericLocation[] intermediateLocations = { new GenericLocation(39.9948, -83.0148) };

handleRequest(fromLocation, toLocation, intermediateLocations, "TRANSIT,WALK", false);
}

@Test public void testTransitTwoIntermediatePlaces() {
GenericLocation fromLocation = new GenericLocation(39.9908, -83.0118);
GenericLocation toLocation = new GenericLocation(39.9998, -83.0198);
GenericLocation[] intermediateLocations = new GenericLocation[2];
intermediateLocations[0] = new GenericLocation(40.0000, -82.900);
intermediateLocations[1] = new GenericLocation(39.9100, -83.100);

handleRequest(fromLocation, toLocation, intermediateLocations, "TRANSIT,WALK", false);
}

private void handleRequest(GenericLocation from, GenericLocation to, GenericLocation[] via,
String modes, boolean arriveBy) {
RoutingRequest request = new RoutingRequest(modes);
request.setDateTime("2016-04-20", "13:00", timeZone);
request.setArriveBy(arriveBy);
request.from = from; request.from = from;
request.to = to; request.to = to;
for (GenericLocation intermediateLocation : via) { for (GenericLocation intermediateLocation : via) {
Expand All @@ -101,31 +141,54 @@ private void handleRequest(GenericLocation from, GenericLocation to, GenericLoca
TripPlan plan = GraphPathToTripPlanConverter.generatePlan(pathList, request); TripPlan plan = GraphPathToTripPlanConverter.generatePlan(pathList, request);
assertLocationIsVeryCloseToPlace(from, plan.from); assertLocationIsVeryCloseToPlace(from, plan.from);
assertLocationIsVeryCloseToPlace(to, plan.to); assertLocationIsVeryCloseToPlace(to, plan.to);
assertEquals(1, plan.itinerary.size()); assertTrue(1 <= plan.itinerary.size());
Itinerary itinerary = plan.itinerary.get(0); for (Itinerary itinerary : plan.itinerary) {
assertEquals(1 + via.length, itinerary.legs.size()); validateIntermediatePlacesVisited(itinerary, via);
validateLegsTemporally(request, itinerary); assertTrue(via.length < itinerary.legs.size());
validateLegsSpatially(plan, itinerary); validateLegsTemporally(request, itinerary);
validateLegsSpatially(plan, itinerary);
if (modes.contains("TRANSIT")) {
assert itinerary.transitTime > 0;
}
}
} }


// Check that every via location is visited in the right order
private void validateIntermediatePlacesVisited(Itinerary itinerary, GenericLocation[] via) {
int legIndex = 0;

for (GenericLocation location : via) {
Leg leg;
do {
assertTrue("Intermediate location was not an endpoint of any leg",
legIndex < itinerary.legs.size());
leg = itinerary.legs.get(legIndex);
legIndex++;
} while (Math.abs(leg.to.lat - location.lat) > DELTA
|| Math.abs(leg.to.lon - location.lng) > DELTA);
}
}

// Check that the end point of a leg is also the start point of the next leg
private void validateLegsSpatially(TripPlan plan, Itinerary itinerary) { private void validateLegsSpatially(TripPlan plan, Itinerary itinerary) {
Place place = plan.from; Place place = plan.from;
for (Leg leg : itinerary.legs) { for (Leg leg : itinerary.legs) {
assertPlacesAreEqual(place, leg.from); assertPlacesAreVeryClose(place, leg.from);
place = leg.to; place = leg.to;
} }
assertPlacesAreEqual(place, plan.to); assertPlacesAreVeryClose(place, plan.to);
} }


// Check that the start time and end time of each leg are consistent
private void validateLegsTemporally(RoutingRequest request, Itinerary itinerary) { private void validateLegsTemporally(RoutingRequest request, Itinerary itinerary) {
Calendar departTime = Calendar.getInstance(timeZone); Calendar departTime = Calendar.getInstance(timeZone);
Calendar arriveTime = Calendar.getInstance(timeZone); Calendar arriveTime = Calendar.getInstance(timeZone);
if (request.arriveBy) { if (request.arriveBy) {
departTime.setTimeInMillis(0); departTime = itinerary.legs.get(0).from.departure;
arriveTime.setTimeInMillis(request.dateTime); arriveTime.setTimeInMillis(request.dateTime * 1000);
} else { } else {
departTime.setTimeInMillis(request.dateTime); departTime.setTimeInMillis(request.dateTime * 1000);
arriveTime.setTimeInMillis(Long.MAX_VALUE); arriveTime = itinerary.legs.get(itinerary.legs.size() - 1).to.arrival;
} }
long sumOfDuration = 0; long sumOfDuration = 0;
for (Leg leg : itinerary.legs) { for (Leg leg : itinerary.legs) {
Expand All @@ -137,17 +200,21 @@ private void validateLegsTemporally(RoutingRequest request, Itinerary itinerary)
departTime = leg.to.arrival; departTime = leg.to.arrival;
sumOfDuration += leg.getDuration(); sumOfDuration += leg.getDuration();
} }
sumOfDuration += itinerary.waitingTime;

assertFalse(departTime.after(arriveTime)); assertFalse(departTime.after(arriveTime));
assertEquals(sumOfDuration, itinerary.duration.longValue());
// Check the total duration of the legs,
int accuracy = itinerary.legs.size(); // allow 1 second per leg for rounding errors
assertEquals(sumOfDuration, itinerary.duration.doubleValue(), accuracy);
} }


private void assertLocationIsVeryCloseToPlace(GenericLocation location, Place place) { private void assertLocationIsVeryCloseToPlace(GenericLocation location, Place place) {
assertEquals(location.lat, place.lat, DELTA); assertEquals(location.lat, place.lat, DELTA);
assertEquals(location.lng, place.lon, DELTA); assertEquals(location.lng, place.lon, DELTA);
} }


private void assertPlacesAreEqual(Place a, Place b) { private void assertPlacesAreVeryClose(Place a, Place b) {
assertEquals(a.name, b.name);
assertEquals(a.lat, b.lat, DELTA); assertEquals(a.lat, b.lat, DELTA);
assertEquals(a.lon, b.lon, DELTA); assertEquals(a.lon, b.lon, DELTA);
} }
Expand Down

0 comments on commit a822b70

Please sign in to comment.