Skip to content

Commit

Permalink
Fix Call-And-Ride bug: call-and-ride should be fully within the defin…
Browse files Browse the repository at this point in the history
…ed time period. Includes better tests.
  • Loading branch information
sdjacobs committed Dec 27, 2018
1 parent b0629b2 commit 0891b8b
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 46 deletions.
Expand Up @@ -242,7 +242,7 @@ public TripTimes getNextCallNRideTrip(State s0, ServiceDay serviceDay, int stopI
int adjustedTime = adjustTimeForTransfer(s0, currentStop, tt.trip, boarding, serviceDay, time);
if (adjustedTime == -1) continue;
if (boarding) {
long depTime = tt.getCallAndRideBoardTime(stopIndex, adjustedTime, serviceDay, useClockTime, clockTime);
long depTime = tt.getCallAndRideBoardTime(stopIndex, adjustedTime, directTime, serviceDay, useClockTime, clockTime);
if (depTime >= adjustedTime && depTime < bestTime && inBounds(depTime)) {
bestTrip = tt;
bestTime = depTime;
Expand Down
Expand Up @@ -76,7 +76,7 @@ public int calculateWait(State s0, ServiceDay sd, TripTimes tripTimes) {
boolean useClockTime = !s0.getOptions().flexIgnoreDrtAdvanceBookMin;
long clockTime = s0.getOptions().clockTimeSec;
if (boarding) {
int scheduledTime = tripTimes.getCallAndRideBoardTime(getStopIndex(), currTime, sd, useClockTime, clockTime);
int scheduledTime = tripTimes.getCallAndRideBoardTime(getStopIndex(), currTime, (int) hop.timeLowerBound(s0.getOptions()), sd, useClockTime, clockTime);
if (scheduledTime < 0)
throw new IllegalArgumentException("Unexpected bad wait time");
return (int) (sd.time(scheduledTime) - s0.getTimeSeconds());
Expand Down
Expand Up @@ -312,13 +312,15 @@ public int getDepartureDelay(final int stop) {
return getDepartureTime(stop) - (scheduledDepartureTimes[stop] + timeShift);
}

public int getCallAndRideBoardTime(int stop, long currTime, ServiceDay sd, boolean useClockTime, long startClockTime) {
int ret = (int) Math.min(Math.max(currTime, getDepartureTime(stop)), getArrivalTime(stop + 1));
public int getCallAndRideBoardTime(int stop, long currTime, int directTime, ServiceDay sd, boolean useClockTime, long startClockTime) {
int travelTime = getDemandResponseMaxTime(directTime);
int minBoardTime = getArrivalTime(stop + 1) - travelTime;
int ret = (int) Math.min(Math.max(currTime, getDepartureTime(stop)), minBoardTime);
if (useClockTime) {
int clockTime = (int) (sd.secondsSinceMidnight(startClockTime) + Math.round(trip.getDrtAdvanceBookMin() * 60.0));
if (ret >= clockTime) {
return ret;
} else if (clockTime < getArrivalTime(stop + 1)) {
} else if (clockTime < minBoardTime) {
return clockTime;
} else {
return -1;
Expand All @@ -329,19 +331,21 @@ public int getCallAndRideBoardTime(int stop, long currTime, ServiceDay sd, boole

public int getCallAndRideAlightTime(int stop, long currTime, int directTime, ServiceDay sd, boolean useClockTime, long startClockTime) {
int travelTime = getDemandResponseMaxTime(directTime);
currTime -= travelTime;
int startOfService = (int) Math.min(Math.max(currTime, getDepartureTime(stop - 1)), getArrivalTime(stop));
int maxAlightTime = getDepartureTime(stop - 1) + travelTime;
int ret = (int) Math.max(Math.min(currTime, getArrivalTime(stop)), maxAlightTime);
if (useClockTime) {
int clockTime = (int) (sd.secondsSinceMidnight(startClockTime) + Math.round(trip.getDrtAdvanceBookMin() * 60));
if (startOfService >= clockTime) {
// do nothing
} else if (clockTime < getArrivalTime(stop)) {
startOfService = clockTime;
} else {
int clockTime = (int) (sd.secondsSinceMidnight(startClockTime) + Math.round(trip.getDrtAdvanceBookMin() * 60.0));
// boarding time must be > clockTime
int boardTime = ret - travelTime;
if (boardTime >= clockTime) {
return ret;
}
ret += (clockTime - boardTime);
if (ret >= maxAlightTime) {
return -1;
}
}
return startOfService + travelTime;
return ret;
}

public int getDemandResponseMaxTime(int directTime) {
Expand Down
Expand Up @@ -3,14 +3,18 @@
import org.junit.Test;
import org.opentripplanner.ConstantsForTests;
import org.opentripplanner.api.model.BoardAlightType;
import org.opentripplanner.routing.algorithm.AStar;
import org.opentripplanner.routing.core.Fare;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.impl.GraphPathFinder;
import org.opentripplanner.routing.services.FareService;
import org.opentripplanner.routing.spt.GraphPath;
import org.opentripplanner.standalone.Router;
import org.opentripplanner.util.DateUtils;

import java.util.Date;
import java.util.List;
import java.util.TimeZone;

import static org.junit.Assert.*;

Expand Down Expand Up @@ -40,7 +44,6 @@ public void testFlagStop() {
checkFare(path);
}


// Deviated Route on both ends
@Test
public void testCallAndRide() {
Expand All @@ -55,6 +58,52 @@ public void testCallAndRide() {
assertEquals(BoardAlightType.DEVIATED, ride.getBoardType());
assertEquals(BoardAlightType.DEVIATED, ride.getBoardType());
checkFare(path);

// Check that times are respected. DAR is available 1pm-3pm

// arriveBy=false Check start time respected. If request is for before 1pm, we should get a trip starting at 1pm.
path = getPathToDestination(
buildRequest("44.38485134435363,-72.05881118774415", "44.422379116722084,-72.0198440551758",
"2018-05-23", "12:50pm")
);
rides = Ride.createRides(path);
assertEquals(1, rides.size());
ride = rides.get(0);
assertEquals("7415", ride.getRoute().getId());
assertDateEquals(ride.getStartTime(), "2018-05-23", "1:00pm", graph.getTimeZone());

// arriveBy=false Check end time respected.
path = getPathToDestination(
buildRequest("44.38485134435363,-72.05881118774415", "44.422379116722084,-72.0198440551758",
"2018-05-23", "2:56pm")
);
rides = Ride.createRides(path);
assertEquals(1, rides.size());
ride = rides.get(0);
assertEquals("7415", ride.getRoute().getId());
assertDateEquals(ride.getStartTime(), "2018-05-24", "1:00pm", graph.getTimeZone());

// arriveBy=true Check start time respected.
path = getPathToDestination(
buildRequest("44.38485134435363,-72.05881118774415", "44.422379116722084,-72.0198440551758",
"2018-05-24", "1:05pm", true)
);
rides = Ride.createRides(path);
assertEquals(1, rides.size());
ride = rides.get(0);
assertEquals("7415", ride.getRoute().getId());
assertDateEquals(ride.getEndTime(), "2018-05-23", "3:00pm", graph.getTimeZone());

// arriveBy=true Check end time respected
path = getPathToDestination(
buildRequest("44.38485134435363,-72.05881118774415", "44.422379116722084,-72.0198440551758",
"2018-05-24", "3:05pm", true)
);
rides = Ride.createRides(path);
assertEquals(1, rides.size());
ride = rides.get(0);
assertEquals("7415", ride.getRoute().getId());
assertDateEquals(ride.getEndTime(), "2018-05-24", "3:00pm", graph.getTimeZone());
}

// Deviated Fixed Route at both ends
Expand Down Expand Up @@ -94,34 +143,24 @@ public void testFlagStopToRegularStopEndingInDeviatedFixedRoute() {
checkFare(path);
}

private RoutingRequest buildRequest(String from, String to, String date, String time)
{
RoutingRequest options = new RoutingRequest();
// defaults in vermont router-config.json
options.maxWalkDistance = MAX_WALK_DISTANCE;
options.flexCallAndRideReluctance = CALL_AND_RIDE_RELUCTANCE;
options.walkReluctance = WALK_RELUCTANCE;
options.waitAtBeginningFactor = WAIT_AT_BEGINNING_FACTOR;
options.transferPenalty = TRANSFER_PENALTY;
// for testing
options.flexIgnoreDrtAdvanceBookMin = IGNORE_DRT_ADVANCE_MIN_BOOKING;
options.setDateTime(date, time, graph.getTimeZone());
options.setFromString(from);
options.setToString(to);
options.setRoutingContext(graph);
private RoutingRequest buildRequest(String from, String to, String date, String time) {
return buildRequest(from, to, date, time, false);
}

return buildRoutingRequest(from, to, date, time, MAX_WALK_DISTANCE,
private RoutingRequest buildRequest(String from, String to, String date, String time, boolean arriveBy) {
return buildRoutingRequest(from, to, date, time, arriveBy, MAX_WALK_DISTANCE,
CALL_AND_RIDE_RELUCTANCE, WALK_RELUCTANCE, WAIT_AT_BEGINNING_FACTOR,
TRANSFER_PENALTY, IGNORE_DRT_ADVANCE_MIN_BOOKING);
}

private RoutingRequest buildRoutingRequest(String from, String to, String date, String time, int maxWalkDistance,
double callAndRideReluctance, double walkReluctance,
private RoutingRequest buildRoutingRequest(String from, String to, String date, String time, boolean arriveBy,
int maxWalkDistance, double callAndRideReluctance, double walkReluctance,
double waitAtBeginningFactor, int transferPenalty,
boolean ignoreDrtAdvanceMinBooking) {
RoutingRequest options = new RoutingRequest();
options.setArriveBy(arriveBy);
// defaults in vermont router-config.json
options.maxWalkDistance = maxWalkDistance;
options.setMaxWalkDistance(maxWalkDistance);
options.flexCallAndRideReluctance = callAndRideReluctance;
options.walkReluctance = walkReluctance;
options.waitAtBeginningFactor = waitAtBeginningFactor;
Expand All @@ -140,17 +179,8 @@ private RoutingRequest buildRoutingRequest(String from, String to, String date,


private GraphPath getPathToDestination(RoutingRequest options) {
// Simulate GraphPathFinder - run modifiers for graph based on request
FlagStopGraphModifier svc1 = new FlagStopGraphModifier(graph);
DeviatedRouteGraphModifier svc2 = new DeviatedRouteGraphModifier(graph);
svc1.createForwardHops(options);
svc2.createForwardHops(options);
svc1.createBackwardHops(options);
svc2.createBackwardHops(options);

AStar astar = new AStar();
astar.getShortestPathTree(options);
return astar.getPathsToTarget().iterator().next();
Router router = new Router("default", graph);
return new GraphPathFinder(router).getPaths(options).get(0);
}

private void checkFare(GraphPath path) {
Expand All @@ -160,4 +190,8 @@ private void checkFare(GraphPath path) {
assertNotNull(cost);
assertEquals("920", cost.getDetails(Fare.FareType.regular).iterator().next().fareId.getId());
}

private void assertDateEquals(long timeSeconds, String date, String time, TimeZone timeZone) {
assertEquals(DateUtils.toDate(date, time, timeZone), new Date(timeSeconds * 1000));
}
}
Expand Up @@ -9,19 +9,23 @@
import java.util.List;

import org.junit.Test;
import org.mockito.Matchers;
import org.opentripplanner.model.FeedScopedId;
import org.opentripplanner.model.Route;
import org.opentripplanner.model.Stop;
import org.opentripplanner.model.StopTime;
import org.opentripplanner.model.Trip;
import org.opentripplanner.gtfs.BikeAccess;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.ServiceDay;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.SimpleConcreteVertex;
import org.opentripplanner.routing.graph.Vertex;

import static org.mockito.Mockito.*;

public class TripTimesTest {
private static final FeedScopedId tripId = new FeedScopedId("agency", "testtrip");

Expand Down Expand Up @@ -229,4 +233,80 @@ public void testGetDwellTime() {
assertEquals(i, updatedTripTimes.getDwellTime(i));
}
}

@Test
public void testCallAndRideBoardTime() {
// times: 0, 60, 120

ServiceDay sd = mock(ServiceDay.class);
when(sd.secondsSinceMidnight(Matchers.anyLong())).thenCallRealMethod();
int time;

// time before interval
time = originalTripTimes.getCallAndRideBoardTime(1, 50, 20, sd, false, 0);
assertEquals(60, time);

// time in interval
time = originalTripTimes.getCallAndRideBoardTime(1, 70, 20, sd, false, 0);
assertEquals(70, time);

// time would overlap end of interval
time = originalTripTimes.getCallAndRideBoardTime(1, 105, 20, sd, false, 0);
assertTrue(time < 105);

// time after end of interval
time = originalTripTimes.getCallAndRideBoardTime(1, 125, 20, sd, false, 0);
assertTrue(time < 105);

// clock time before
time = originalTripTimes.getCallAndRideBoardTime(1, 50, 20, sd, true, 30);
assertEquals(60, time);

// clock time in interval
time = originalTripTimes.getCallAndRideBoardTime(1, 50, 20, sd, true, 70);
assertEquals(70, time);

// clock time after interval
time = originalTripTimes.getCallAndRideBoardTime(1, 50, 20, sd, true, 130);
assertTrue(time < 50);

// clock time would cause overlap
time = originalTripTimes.getCallAndRideBoardTime(1, 50, 20, sd, true, 105);
assertTrue(time < 50);
}

@Test
public void testCallAndRideAlightTime() {
ServiceDay sd = mock(ServiceDay.class);
when(sd.secondsSinceMidnight(Matchers.anyLong())).thenCallRealMethod();
int time;

// time after interval
time = originalTripTimes.getCallAndRideAlightTime(2, 130, 20, sd, false, 0);
assertEquals(120, time);

// time in interval
time = originalTripTimes.getCallAndRideAlightTime(2, 110, 20, sd, false, 0);
assertEquals(110, time);

// time in interval, would cause overlap
time = originalTripTimes.getCallAndRideAlightTime(2, 65, 20, sd, false, 0);
assertTrue(time == -1 || time > 65);

// time after interval
time = originalTripTimes.getCallAndRideAlightTime(2, 55, 20, sd, false, 0);
assertTrue(time == -1 || time > 65);

// clock time after interval
time = originalTripTimes.getCallAndRideAlightTime(2, 130, 20, sd, true, 130);
assertEquals(-1, time);

// clock time before board
time = originalTripTimes.getCallAndRideAlightTime(2, 110, 20, sd, true, 85);
assertEquals(110, time);

// clock time after board
time = originalTripTimes.getCallAndRideAlightTime(2, 110, 20, sd, true, 100);
assertEquals(-1, time);
}
}
Binary file modified src/test/resources/vermont/ruralcommunity-flex-vt-us.zip
Binary file not shown.

0 comments on commit 0891b8b

Please sign in to comment.