Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No deviation if near bus stop #304

Open
wants to merge 11 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
import org.opentripplanner.middleware.otp.response.OtpResponse;
import org.opentripplanner.middleware.otp.response.TripPlan;
import org.opentripplanner.middleware.persistence.Persistence;
import org.opentripplanner.middleware.triptracker.instruction.OnTrackInstruction;
import org.opentripplanner.middleware.triptracker.instruction.SelfLegInstruction;
import org.opentripplanner.middleware.triptracker.instruction.TripInstruction;
import org.opentripplanner.middleware.triptracker.instruction.WaitForTransitInstruction;
import org.opentripplanner.middleware.triptracker.interactions.TripActions;
import org.opentripplanner.middleware.triptracker.interactions.busnotifiers.BusOperatorActions;
import org.opentripplanner.middleware.triptracker.response.EndTrackingResponse;
Expand Down Expand Up @@ -126,6 +128,13 @@ private static TrackingResponse doUpdateTracking(
create || rerouted
);

if (isDeviatedWithOnTrackInstruction(tripStatus, instruction)) {
// Deem traveler on track (not deviated) if they are in the 'upcoming' range of a bus stop
// or the departure location.
// (If near a bus stop, applicable bus notifications would have already been triggered.)
tripStatus = TripStatus.getTimingStatus(travelerPosition);
}

// Perform interactions such as triggering traffic signals when approaching segments so configured.
// It is assumed to be ok to repeatedly perform the interaction.
if (instruction instanceof SelfLegInstruction && instruction.distance <= TRIP_INSTRUCTION_UPCOMING_RADIUS) {
Expand All @@ -148,6 +157,15 @@ private static TrackingResponse doUpdateTracking(
return null;
}

/**
* Detect if we are deeming a traveler deviated while giving out a non-deviated instruction
* (to prevent, for instance, being offered rerouting while near a bus stop).
*/
private static boolean isDeviatedWithOnTrackInstruction(TripStatus tripStatus, TripInstruction instruction) {
return tripStatus == TripStatus.DEVIATED &&
(instruction instanceof WaitForTransitInstruction || instruction instanceof OnTrackInstruction);
}

/**
* Update the tracking location information provided by the caller.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,11 @@ public static TripInstruction alignTravelerToTrip(
Locale locale = travelerPosition.locale;

if (isApproachingEndOfLeg(travelerPosition)) {
if (sendBusNotification(travelerPosition)) {
Leg transitLeg = travelerPosition.getTransitLegWithClosestUpcomingOrigin();
if (transitLeg != null && shouldSendBusNotification(transitLeg, travelerPosition.currentTime)) {
sendBusNotifications(travelerPosition, transitLeg);
// Regardless of whether the notification is sent or qualifies, provide a 'wait for bus' instruction.
return new WaitForTransitInstruction(travelerPosition.nextLeg, travelerPosition.currentTime, locale);
return new WaitForTransitInstruction(transitLeg, travelerPosition.currentTime, locale);
}
return new OnTrackInstruction(getDistanceToEndOfLeg(travelerPosition), travelerPosition.expectedLeg.to.name, locale);
}
Expand Down Expand Up @@ -246,17 +248,19 @@ private static Step getPreviousStep(List<Step> steps, Step nextStep) {
}

/**
* Send bus notification if the first leg is a bus leg or approaching a bus leg and within the notify window.
* Whether to send a bus notification if the first leg is a bus leg or approaching a bus leg and within the notify window.
*/
public static boolean sendBusNotification(TravelerPosition travelerPosition) {
Leg busLeg = atStartOfTransitTrip(travelerPosition) ? travelerPosition.expectedLeg : travelerPosition.nextLeg;
if (isBusLeg(busLeg) && isWithinOperationalNotifyWindow(travelerPosition.currentTime, busLeg)) {
BusOperatorActions
.getDefault()
.handleSendNotificationAction(travelerPosition, busLeg);
return true;
}
return false;
public static boolean shouldSendBusNotification(Leg leg, Instant currentTime) {
return (isBusLeg(leg) && isWithinOperationalNotifyWindow(currentTime, leg));
}

/**
* Send bus notification for the corresponding leg.
*/
private static void sendBusNotifications(TravelerPosition travelerPosition, Leg busLeg) {
BusOperatorActions
.getDefault()
.handleSendNotificationAction(travelerPosition, busLeg);
}

/**
Expand All @@ -279,9 +283,11 @@ public static TripInstruction alignTravelerToTransitTrip(TravelerPosition travel
Leg expectedLeg = travelerPosition.expectedLeg;
String finalStop = expectedLeg.to.name;

if (sendBusNotification(travelerPosition)) {
Leg transitLeg = travelerPosition.getTransitLegWithClosestUpcomingOrigin();
if (transitLeg != null && shouldSendBusNotification(transitLeg, travelerPosition.currentTime)) {
sendBusNotifications(travelerPosition, transitLeg);
// Regardless of whether the notification is sent or qualifies, provide a 'wait for bus' instruction.
return new WaitForTransitInstruction(expectedLeg, travelerPosition.currentTime, locale);
return new WaitForTransitInstruction(transitLeg, travelerPosition.currentTime, locale);
}

if (isApproachingEndOfLeg(travelerPosition)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import static org.opentripplanner.middleware.triptracker.ManageLegTraversal.getExpectedLeg;
import static org.opentripplanner.middleware.triptracker.ManageLegTraversal.getNextLeg;
import static org.opentripplanner.middleware.triptracker.ManageLegTraversal.getSegmentFromPosition;
import static org.opentripplanner.middleware.triptracker.instruction.TripInstruction.TRIP_INSTRUCTION_UPCOMING_RADIUS;
import static org.opentripplanner.middleware.utils.GeometryUtils.getDistance;
import static org.opentripplanner.middleware.utils.GeometryUtils.getDistanceFromLine;
import static org.opentripplanner.middleware.utils.ItineraryUtils.getFirstLeg;

Expand Down Expand Up @@ -82,6 +84,28 @@ public TravelerPosition(TrackedJourney trackedJourney, Itinerary itinerary, OtpU
firstLegOfTrip = getFirstLeg(itinerary);
}

/**
* Returns the closest transit leg in 'upcoming' radius eligible for a "Wait for transit" instruction.
*/
public Leg getTransitLegWithClosestUpcomingOrigin() {
double distancetoExpectedLeg = distanceToTransitLegOrigin(currentPosition, expectedLeg);
double distanceToNextLeg = distanceToTransitLegOrigin(currentPosition, nextLeg);

if (distancetoExpectedLeg <= TRIP_INSTRUCTION_UPCOMING_RADIUS && distancetoExpectedLeg < distanceToNextLeg) {
return expectedLeg;
} else if (distanceToNextLeg <= TRIP_INSTRUCTION_UPCOMING_RADIUS && distanceToNextLeg < distancetoExpectedLeg) {
return nextLeg;
} else {
return null;
}
}

private static double distanceToTransitLegOrigin(Coordinates position, Leg leg) {
return leg != null && leg.transitLeg && leg.from != null
? getDistance(position, leg.from.toCoordinates())
: Double.MAX_VALUE;
}

/** Computes the current deviation in meters from the expected itinerary. */
public double getDeviationMeters() {
return getDistanceFromLine(legSegmentFromPosition.start, legSegmentFromPosition.end, currentPosition);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import static org.opentripplanner.middleware.triptracker.TravelerLocator.isAtEndOfLeg;
import static org.opentripplanner.middleware.utils.ConfigUtils.getConfigPropertyAsInt;
import static org.opentripplanner.middleware.utils.GeometryUtils.getDistanceFromLine;


/**
Expand Down Expand Up @@ -71,23 +70,30 @@ public static TripStatus getTripStatus(TravelerPosition travelerPosition) {
travelerPosition.legSegmentFromPosition != null &&
isWithinModeRadius(travelerPosition)
) {
Instant segmentStartTime = getSegmentStartTime(travelerPosition);
Instant segmentEndTime = travelerPosition
.expectedLeg
.startTime
.toInstant()
.plusSeconds((long) travelerPosition.legSegmentFromPosition.cumulativeTime);
if (travelerPosition.currentTime.isBefore(segmentStartTime)) {
return TripStatus.AHEAD_OF_SCHEDULE;
} else if (travelerPosition.currentTime.isAfter(segmentEndTime)) {
return TripStatus.BEHIND_SCHEDULE;
} else {
return TripStatus.ON_SCHEDULE;
}
return getTimingStatus(travelerPosition);
}
return TripStatus.DEVIATED;
}

/**
* Get the timing status of the traveler.
*/
public static TripStatus getTimingStatus(TravelerPosition travelerPosition) {
Instant segmentStartTime = getSegmentStartTime(travelerPosition);
Instant segmentEndTime = travelerPosition
.expectedLeg
.startTime
.toInstant()
.plusSeconds((long) travelerPosition.legSegmentFromPosition.cumulativeTime);
if (travelerPosition.currentTime.isBefore(segmentStartTime)) {
return TripStatus.AHEAD_OF_SCHEDULE;
} else if (travelerPosition.currentTime.isAfter(segmentEndTime)) {
return TripStatus.BEHIND_SCHEDULE;
} else {
return TripStatus.ON_SCHEDULE;
}
}

public static double getLegSegmentStartTime(LegSegment legSegmentFromPosition) {
return legSegmentFromPosition.cumulativeTime - legSegmentFromPosition.timeInSegment;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,8 +348,8 @@ private static Stream<Arguments> createInstructionAndStatusCases() {
monitoredTrip,
createPoint(firstStepCoords, 4, NORTH_EAST_BEARING),
new OnTrackInstruction(4, adairAvenueNortheastStep, locale).build(),
TripStatus.DEVIATED,
"Coords deviated but near first step should produce relevant instruction"
TripStatus.ON_SCHEDULE,
"Coords in the 'upcoming' range of first step should produce relevant instruction and deemed not deviated."
),
Arguments.of(
monitoredTrip,
Expand Down Expand Up @@ -404,6 +404,30 @@ private static Stream<Arguments> createInstructionAndStatusCases() {
TripStatus.AHEAD_OF_SCHEDULE,
"Arriving ahead of schedule to a bus stop at the end of first leg."
),
// This position overlaps with the beginning of the transit trip,
// but it is still within the 'upcoming' radius of the stop, so display a "wait for transit" instruction.
Arguments.of(
multiLegMonitoredTrip,
createPoint(multiItinFirstLegDestCoords, 1.5, NORTH_EAST_BEARING),
new WaitForTransitInstruction(
multiItinBusLeg,
multiItinBusLeg.getScheduledStartTime().toInstant().minus(Duration.ofMinutes(6)),
locale)
.build(),
TripStatus.AHEAD_OF_SCHEDULE,
"Arriving ahead of schedule to a bus stop at the end of first leg should produce a non-trivial instruction."
),
Arguments.of(
multiLegMonitoredTrip,
createPoint(multiItinFirstLegDestCoords, 7, WEST_BEARING),
new WaitForTransitInstruction(
multiItinBusLeg,
multiItinBusLeg.getScheduledStartTime().toInstant().minus(Duration.ofMinutes(6)),
locale)
.build(),
TripStatus.AHEAD_OF_SCHEDULE,
"Arriving ahead of schedule near a bus stop (in 'upcoming' range) at the end of first leg."
),
Arguments.of(
multiLegMonitoredTrip,
createPoint(multiItinLastLegDestCoords, 1, NORTH_WEST_BEARING),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.opentripplanner.middleware.triptracker.instruction.DeviatedInstruction;
import org.opentripplanner.middleware.triptracker.instruction.OnTrackInstruction;
import org.opentripplanner.middleware.triptracker.instruction.TripInstruction;
import org.opentripplanner.middleware.triptracker.instruction.WaitForTransitInstruction;
import org.opentripplanner.middleware.utils.ConfigUtils;
import org.opentripplanner.middleware.utils.Coordinates;
import org.opentripplanner.middleware.utils.DateTimeUtils;
Expand Down Expand Up @@ -193,8 +194,10 @@ void canTrackTurnByTurn(Leg firstLeg, TraceData traceData) {
.setExpectedLeg(firstLeg)
.setCurrentPosition(traceData.position)
.setFirstLegOfTrip(firstLeg)
.setCurrentTime(firstLeg.startTime.toInstant().minus(4, ChronoUnit.MINUTES))
.setSpeed(0)
.build();
travelerPosition.locale = locale;
TripInstruction tripInstruction = TravelerLocator.getInstruction(traceData.tripStatus, travelerPosition, traceData.isStartOfTrip);
assertEquals(traceData.expectedInstruction, tripInstruction != null ? tripInstruction.build() : NO_INSTRUCTION, traceData.message);
}
Expand Down Expand Up @@ -255,6 +258,26 @@ private static Stream<Arguments> createTurnByTurnTrace() {
"Deviated from the start of a trip which starts with a bus leg. Suggest path to head towards."
)
),
Arguments.of(
firstBusLeg,
new TraceData(
TripStatus.ON_SCHEDULE,
createPoint(busStopCoords, 4, NORTH_WEST_BEARING),
new WaitForTransitInstruction(firstBusLeg, firstBusLeg.startTime.toInstant().minus(4, ChronoUnit.MINUTES), locale),
true,
"On-time and near the initial bus stop on trip which starts with a bus leg. Instructs to wait for bus."
)
),
Arguments.of(
firstBusLeg,
new TraceData(
TripStatus.ON_SCHEDULE,
new Coordinates(33.916779, -84.226556),
NO_INSTRUCTION,
true,
"On transit leg away from the boarding location (or walked past the bus stop). No specific instruction to give."
)
),
Arguments.of(
walkLeg,
new TraceData(
Expand Down Expand Up @@ -465,8 +488,12 @@ void canTrackTransitRide(TraceData traceData) {
TravelerPosition travelerPosition = new TravelerPosition.Builder()
.setExpectedLeg(transitLeg)
.setCurrentPosition(traceData.position)
// Unless specified in traceData, an instant corresponding to approx the trip start time should be provided
// for correct instructions to be produced.
.setCurrentTime(traceData.instant != null ? traceData.instant : transitLeg.startTime.toInstant())
.setSpeed(traceData.speed)
.build();
travelerPosition.locale = locale;
TripInstruction tripInstruction = TravelerLocator.getInstruction(traceData.tripStatus, travelerPosition, false);
assertEquals(traceData.expectedInstruction, tripInstruction != null ? tripInstruction.build() : NO_INSTRUCTION, traceData.message);
}
Expand All @@ -484,7 +511,15 @@ private static Stream<Arguments> createTransitRideTrace() {
new TraceData(
originCoords,
NO_INSTRUCTION,
"Just boarded the transit vehicle leg, there should not be an instruction."
Instant.now(),
"If present at the transit stop after the trip departure, there should not be an instruction."
)
),
Arguments.of(
new TraceData(
originCoords,
new WaitForTransitInstruction(transitLeg, transitLeg.startTime.toInstant(), locale).build(),
"At the bus stop, or just boarded the transit vehicle leg, it should still tell the user to board the bus."
)
),
// This instruction can be missed if the transit vehicle is in a slow/congested area
Expand Down Expand Up @@ -650,6 +685,7 @@ private static class TraceData {
String expectedInstruction;
boolean isStartOfTrip;
boolean dismissIntermediateStops;
Instant instant;
String message;

public TraceData(Coordinates position, String expectedInstruction, boolean isStartOfTrip, String message) {
Expand All @@ -667,6 +703,11 @@ public TraceData(Coordinates position, String expectedInstruction, String messag
this(position, expectedInstruction, false, message);
}

public TraceData(Coordinates position, String expectedInstruction, Instant instant, String message) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TraceData class could now benefit from being refactored to use the builder pattern and being moved into it's own class. Perhaps out of scope, will let you decide.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@br648 Yeah I think so, that class is becoming unwieldy.

this(position, expectedInstruction, false, message);
this.instant = instant;
}

public TraceData(Coordinates position, String expectedInstruction, String message, boolean dismissIntermediateStops) {
this(position, expectedInstruction, false, message);
this.dismissIntermediateStops = dismissIntermediateStops;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ private static Stream<Arguments> createWithinOperationalNotifyWindowTrace() {
@ParameterizedTest
@MethodSource("shouldSendBusNotificationAtStartOfTripTrace")
void shouldSendBusNotificationAtStartOfTrip(boolean expected, TravelerPosition travelerPosition, String message) {
assertEquals(expected, TravelerLocator.sendBusNotification(travelerPosition), message);
assertEquals(expected, TravelerLocator.shouldSendBusNotification(travelerPosition.nextLeg, travelerPosition.currentTime), message);
}

private static Stream<Arguments> shouldSendBusNotificationAtStartOfTripTrace() {
Expand Down
Loading
Loading