package org.opentripplanner.analyst.scenario; import com.google.common.collect.Lists; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.io.WKTReader; import gnu.trove.iterator.TObjectIntIterator; import gnu.trove.map.TIntIntMap; import junit.framework.TestCase; import org.joda.time.LocalDate; import org.junit.Test; import org.opentripplanner.common.model.GenericLocation; import org.opentripplanner.profile.*; import org.opentripplanner.routing.algorithm.AStar; import org.opentripplanner.routing.core.RoutingRequest; import org.opentripplanner.routing.core.TraverseMode; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graph.Vertex; import org.opentripplanner.routing.impl.DefaultStreetVertexIndexFactory; import org.opentripplanner.routing.spt.ShortestPathTree; import java.time.DayOfWeek; import java.util.Arrays; import java.util.BitSet; import static org.opentripplanner.graph_builder.module.FakeGraph.*; /** * Test adding trip patterns. */ public class AddTripPatternTest extends TestCase { /** Make sure that stops are properly linked into the graph */ @Test public void testStopLinking () throws Exception { AddTripPattern atp = getAddTripPattern(RouteSelector.BROAD_HIGH); atp.timetables.add(getTimetable(true)); // get a graph Graph g = buildGraphNoTransit(); link(g); g.index(new DefaultStreetVertexIndexFactory()); // materialize the trip pattern atp.materialize(g); // there should be five stops because one point is not a stop assertEquals(5, atp.temporaryStops.length); // they should all be linked into the graph for (int i = 0; i < atp.temporaryStops.length; i++) { assertNotNull(atp.temporaryStops[i].sample); assertNotNull(atp.temporaryStops[i].sample.v0); assertNotNull(atp.temporaryStops[i].sample.v1); } // no services running: not needed for trips added on the fly. TimeWindow window = new TimeWindow(7 * 3600, 9 * 3600, new BitSet(), DayOfWeek.WEDNESDAY); Scenario scenario = new Scenario(0); scenario.modifications = Lists.newArrayList(atp); ProfileRequest req = new ProfileRequest(); req.scenario = scenario; req.boardingAssumption = RaptorWorkerTimetable.BoardingAssumption.WORST_CASE; RaptorWorkerData data = new RaptorWorkerData(g, window, req); assertEquals(5, data.nStops); // make sure we can find the stops AStar aStar = new AStar(); RoutingRequest rr = new RoutingRequest(TraverseMode.WALK); rr.from = new GenericLocation(39.963417, -82.980799); rr.batch = true; rr.setRoutingContext(g); rr.batch = true; ShortestPathTree spt = aStar.getShortestPathTree(rr); TIntIntMap stops = data.findStopsNear(spt, g, false, 1.3f); // we should have found stops assertFalse(stops.isEmpty()); // ensure that the times made it into the data // This assumes worst-case departure, and the first worst departure is 10:30 after the service // starts running (dwell + headway) assertEquals(4 * 3600 + 600 + 30, data.timetablesForPattern.get(0).getFrequencyDeparture(0, 0, 39 * 360, -1, null)); } /** Test adding trips with a timetable rather than frequencies */ @Test public void testTimetableTrips () throws Exception { AddTripPattern atp = getAddTripPattern(RouteSelector.BROAD_HIGH); atp.timetables.add(getTimetable(false)); // get a graph Graph g = buildGraphNoTransit(); link(g); g.index(new DefaultStreetVertexIndexFactory()); // materialize the trip pattern atp.materialize(g); // there should be five stops because one point is not a stop assertEquals(5, atp.temporaryStops.length); // they should all be linked into the graph for (int i = 0; i < atp.temporaryStops.length; i++) { assertNotNull(atp.temporaryStops[i].sample); assertNotNull(atp.temporaryStops[i].sample.v0); assertNotNull(atp.temporaryStops[i].sample.v1); } // no services running: not needed for trips added on the fly. TimeWindow window = new TimeWindow(7 * 3600, 9 * 3600, new BitSet(), DayOfWeek.WEDNESDAY); Scenario scenario = new Scenario(0); scenario.modifications = Lists.newArrayList(atp); ProfileRequest req = new ProfileRequest(); req.scenario = scenario; req.boardingAssumption = RaptorWorkerTimetable.BoardingAssumption.WORST_CASE; RaptorWorkerData data = new RaptorWorkerData(g, window, req); assertEquals(5, data.nStops); // make sure we can find the stops AStar aStar = new AStar(); RoutingRequest rr = new RoutingRequest(TraverseMode.WALK); rr.from = new GenericLocation(39.963417, -82.980799); rr.batch = true; rr.setRoutingContext(g); rr.batch = true; ShortestPathTree spt = aStar.getShortestPathTree(rr); TIntIntMap stops = data.findStopsNear(spt, g, false, 1.3f); // we should have found stops assertFalse(stops.isEmpty()); // ensure that the times made it into the data // This is after the first dwell time has been applied assertEquals(7 * 3600 + 30, data.timetablesForPattern.get(0).getDeparture(0, 0)); } /** Make sure that transfers work */ @Test public void testTransfers () throws Exception { AddTripPattern atp = getAddTripPattern(RouteSelector.BROAD_HIGH); atp.timetables.add(getTimetable(false)); AddTripPattern atp2 = getAddTripPattern(RouteSelector.BEXLEY_CMH); atp2.timetables.add(getTimetable(true)); // get a graph Graph g = buildGraphNoTransit(); addTransit(g); link(g); g.index(new DefaultStreetVertexIndexFactory()); // materialize the trip pattern atp.materialize(g); atp2.materialize(g); TimeWindow window = new TimeWindow(7 * 3600, 9 * 3600, g.index.servicesRunning(new LocalDate(2015, 6, 10)), DayOfWeek.WEDNESDAY); Scenario scenario = new Scenario(0); scenario.modifications = Lists.newArrayList(atp, atp2); ProfileRequest req = new ProfileRequest(); req.scenario = scenario; req.boardingAssumption = RaptorWorkerTimetable.BoardingAssumption.WORST_CASE; RaptorWorkerData data = new RaptorWorkerData(g, window, req); // make sure that we have transfers a) between the new lines b) from the new lines // to the existing lines c) from the existing lines to the new lines // stop IDs in the data will be 0 and 1 for existing stops, 2 - 6 for Broad/High and 7 - 11 for Bexley/CMH int[] txFromExisting = data.transfersForStop.get(0); if (txFromExisting.length == 0) txFromExisting = data.transfersForStop.get(1); // make sure there's a transfer to stop 4 (Broad/High) // the AddTripPattern instructions are processed in order // also recall that each transfer has two ints in the array as it's a jagged array of // dest_pattern, distance assertTrue(txFromExisting.length > 0); boolean foundTx = false; for (int i = 0; i < txFromExisting.length; i += 2) { if (txFromExisting[i] == 4) { foundTx = true; break; } } assertTrue("transfer from existing to new", foundTx); // Check that there are transfers from the new route to the existing route // This is the stop at Broad and High int[] txToExisting = data.transfersForStop.get(4); assertTrue(txToExisting.length > 0); foundTx = false; for (int i = 0; i < txToExisting.length; i += 2) { if (txToExisting[i] == 0 || txToExisting[i] == 1) { // stop from existing route foundTx = true; break; } } assertTrue("transfer from new to existing", foundTx); // Check that there are transfers between the new routes int[] txBetweenNew = data.transfersForStop.get(7); assertTrue(txBetweenNew.length > 0); foundTx = false; for (int i = 0; i < txBetweenNew.length; i += 2) { if (txBetweenNew[i] == 2) { foundTx = true; break; } } assertTrue(foundTx); } /** Test the full routing */ @Test public void integrationTest () throws Exception { Graph g = buildGraphNoTransit(); addTransit(g); link(g); ProfileRequest pr1 = new ProfileRequest(); pr1.date = new LocalDate(2015, 6, 10); pr1.fromTime = 7 * 3600; pr1.toTime = 9 * 3600; pr1.fromLat = pr1.toLat = 39.9621; pr1.fromLon = pr1.toLon = -83.0007; RepeatedRaptorProfileRouter rrpr1 = new RepeatedRaptorProfileRouter(g, pr1); rrpr1.route(); ProfileRequest pr2 = new ProfileRequest(); pr2.date = new LocalDate(2015, 6, 10); pr2.fromTime = 7 * 3600; pr2.toTime = 9 * 3600; pr2.fromLat = pr2.toLat = 39.9621; pr2.fromLon = pr2.toLon = -83.0007; AddTripPattern atp = getAddTripPattern(RouteSelector.BROAD_HIGH); atp.timetables.add(getTimetable(true)); pr2.scenario = new Scenario(0); pr2.scenario.modifications = Arrays.asList(atp); RepeatedRaptorProfileRouter rrpr2 = new RepeatedRaptorProfileRouter(g, pr2); rrpr2.route(); boolean foundDecrease = false; // make sure that travel time did not increase for (TObjectIntIterator<Vertex> vit = rrpr1.timeSurfaceRangeSet.min.times.iterator(); vit.hasNext();) { vit.advance(); int time2 = rrpr2.timeSurfaceRangeSet.min.getTime(vit.key()); assertTrue(time2 <= vit.value()); if (time2 < vit.value()) foundDecrease = true; } assertTrue("found decreases in travel time due to adding route", foundDecrease); } private AddTripPattern getAddTripPattern (RouteSelector sel) throws Exception { AddTripPattern atp = new AddTripPattern(); WKTReader wr = new WKTReader(); atp.geometry = sel.getGeometry(); atp.name = "Broad High Express"; atp.stops = new BitSet(); // everything is a stop except for (0-based) point 3, on High one block north of Broad // or on East Broad, depending on geometry chosen for (int i = 0; i < 6; i++) { if (i == 3) atp.stops.clear(i); else atp.stops.set(i); } atp.timetables = Lists.newArrayList(); return atp; } /** * Get a timetable. If frequency = true, run every 10 minutes from 4AM to 10PM local. * If frequency = false, one run at 7 AM. */ private AddTripPattern.PatternTimetable getTimetable (boolean frequency) { AddTripPattern.PatternTimetable tt = new AddTripPattern.PatternTimetable(); tt.days = new BitSet(); // wednesday service only tt.days.set(2); tt.dwellTimes = new int[] { 30, 30, 30, 30, 30 }; tt.hopTimes = new int[] { 90, 90, 90, 90 }; tt.frequency = frequency; if (frequency) { tt.startTime = 4 * 3600; tt.endTime = 22 * 3600; tt.headwaySecs = 600; } else tt.startTime = 7 * 3600; return tt; } /** Just a switch between two different route geometries */ private static enum RouteSelector { BROAD_HIGH, BEXLEY_CMH; public LineString getGeometry () throws Exception { WKTReader wr = new WKTReader(); switch (this) { // Running west on Broad Street from Bexley to High, and then north to the Short North, // in Columbus, OH case BROAD_HIGH: return (LineString) wr.read("LINESTRING(-82.93727602046642744 39.96934234865877045, -82.98058356730228979 39.96435660398918088, -82.99808255349556418 39.96259692939991481, -83.00091758477827852 39.96357452639394836, -83.00492573245382744 39.98283318717648882, -83.00639212794487776 39.99065396312879273)"); // Running east on Broad Street and then up Hamilton to Sawyer and the CMH airport case BEXLEY_CMH: return (LineString) wr.read("LINESTRING(-82.93296696 39.96964327, -82.91302398 39.97218502, -82.88408711 39.97492229, -82.8739201 39.97570437, -82.86864107 39.99603838, -82.89444963 39.99897118)"); default: // can't happen return null; } } } }