Skip to content

Commit

Permalink
refactor(Initial work to provide traveler with continue instruction):
Browse files Browse the repository at this point in the history
  • Loading branch information
br648 committed Nov 13, 2024
1 parent 58d3417 commit aab57b5
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 23 deletions.
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@
<version>2.7</version>
</dependency>

<!-- Use for better object state checking. -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.17.0</version>
</dependency>

<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.opentripplanner.middleware.otp.response.Leg;
import org.opentripplanner.middleware.otp.response.Place;
import org.opentripplanner.middleware.otp.response.Step;
import org.opentripplanner.middleware.triptracker.instruction.ContinueInstruction;
import org.opentripplanner.middleware.triptracker.instruction.DeviatedInstruction;
import org.opentripplanner.middleware.triptracker.instruction.GetOffHereTransitInstruction;
import org.opentripplanner.middleware.triptracker.instruction.GetOffNextStopTransitInstruction;
Expand All @@ -27,7 +28,9 @@
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;
import static org.opentripplanner.middleware.triptracker.instruction.TripInstruction.NO_INSTRUCTION;
import static org.opentripplanner.middleware.triptracker.instruction.TripInstruction.TRIP_INSTRUCTION_IMMEDIATE_RADIUS;
import static org.opentripplanner.middleware.triptracker.instruction.TripInstruction.TRIP_INSTRUCTION_UPCOMING_RADIUS;
Expand Down Expand Up @@ -59,7 +62,7 @@ public static String getInstruction(
) {
if (hasRequiredWalkLeg(travelerPosition)) {
if (hasRequiredTripStatus(tripStatus)) {
TripInstruction tripInstruction = alignTravelerToTrip(travelerPosition, isStartOfTrip);
TripInstruction tripInstruction = alignTravelerToTrip(travelerPosition, isStartOfTrip, false);
if (tripInstruction != null) {
return tripInstruction.build();
}
Expand Down Expand Up @@ -124,7 +127,7 @@ private static TripInstruction getBackOnTrack(
TravelerPosition travelerPosition,
boolean isStartOfTrip
) {
TripInstruction instruction = alignTravelerToTrip(travelerPosition, isStartOfTrip);
TripInstruction instruction = alignTravelerToTrip(travelerPosition, isStartOfTrip, true);
if (instruction != null && instruction.hasInstruction()) {
return instruction;
}
Expand Down Expand Up @@ -169,7 +172,8 @@ private static String getBusStopName(Leg busLeg) {
@Nullable
public static TripInstruction alignTravelerToTrip(
TravelerPosition travelerPosition,
boolean isStartOfTrip
boolean isStartOfTrip,
boolean travelerHasDeviated
) {
Locale locale = travelerPosition.locale;

Expand All @@ -182,16 +186,55 @@ public static TripInstruction alignTravelerToTrip(
}

Step nextStep = snapToWaypoint(travelerPosition, travelerPosition.expectedLeg.steps);
TripInstruction tripInstruction = null;
if (nextStep != null && (!isPositionPastStep(travelerPosition, nextStep) || isStartOfTrip)) {
return new OnTrackInstruction(
tripInstruction = new OnTrackInstruction(
getDistance(travelerPosition.currentPosition, new Coordinates(nextStep)),
nextStep,
locale
);
}
return (travelerHasDeviated || (isNotEmpty(tripInstruction) && tripInstruction.hasInstruction()))
? tripInstruction
: getContinueInstruction(travelerPosition, nextStep, locale);
}

/**
* Traveler is on track, but no immediate instruction is available. Provide a "continue on street" reassurance
* instruction providing they are on a walk leg. This will be based on the current or previous step depending on the
* traveler's relative position to the next leg.
*/
private static ContinueInstruction getContinueInstruction(
TravelerPosition travelerPosition,
Step nextStep,
Locale locale
) {
if (!travelerPosition.expectedLeg.transitLeg && nextStep != null) {
Step currentStep = isPositionPastStep(travelerPosition, nextStep)
? nextStep :
getPreviousStep(travelerPosition.expectedLeg.steps, nextStep);
if (currentStep != null) {
return new ContinueInstruction(currentStep, locale);
}
}
return null;
}

/**
* Get the step prior to the next step provided.
*/
private static Step getPreviousStep(List<Step> steps, Step nextStep) {
if (steps.get(0).equals(nextStep)) {
return null;
}
Optional<Step> previousStep = IntStream
.range(0, steps.size())
.filter(i -> steps.get(i).equals(nextStep))
.mapToObj(i -> steps.get(i - 1))
.findFirst();
return previousStep.orElse(null);
}

/**
* Send bus notification if the first leg is a bus leg or approaching a bus leg and within the notify window.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.opentripplanner.middleware.triptracker.instruction;

import org.opentripplanner.middleware.otp.response.Step;

import java.util.Locale;

import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;

public class ContinueInstruction extends SelfLegInstruction {
public ContinueInstruction(Step legStep, Locale locale) {
this.legStep = legStep;
this.locale = locale;
}

@Override
public String build() {
if (isNotEmpty(legStep) && isNotEmpty(legStep.streetName)) {
// TODO: i18n
return String.format("Continue on %s", legStep.streetName);
}
return NO_INSTRUCTION;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.opentripplanner.middleware.models.TrackedJourney;
import org.opentripplanner.middleware.otp.response.Itinerary;
import org.opentripplanner.middleware.otp.response.Leg;
import org.opentripplanner.middleware.otp.response.Step;
import org.opentripplanner.middleware.persistence.Persistence;
import org.opentripplanner.middleware.testutils.ApiTestUtils;
import org.opentripplanner.middleware.testutils.CommonTestUtils;
Expand All @@ -27,6 +28,10 @@
import org.opentripplanner.middleware.triptracker.TrackingLocation;
import org.opentripplanner.middleware.triptracker.TripStatus;
import org.opentripplanner.middleware.triptracker.TripTrackingData;
import org.opentripplanner.middleware.triptracker.instruction.ContinueInstruction;
import org.opentripplanner.middleware.triptracker.instruction.DeviatedInstruction;
import org.opentripplanner.middleware.triptracker.instruction.OnTrackInstruction;
import org.opentripplanner.middleware.triptracker.instruction.WaitForTransitInstruction;
import org.opentripplanner.middleware.triptracker.payload.EndTrackingPayload;
import org.opentripplanner.middleware.triptracker.payload.ForceEndTrackingPayload;
import org.opentripplanner.middleware.triptracker.payload.StartTrackingPayload;
Expand All @@ -39,10 +44,12 @@
import org.opentripplanner.middleware.utils.HttpResponseValues;
import org.opentripplanner.middleware.utils.JsonUtils;

import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down Expand Up @@ -288,88 +295,105 @@ private static Stream<Arguments> createInstructionAndStatusCases() {
final int NORTH_WEST_BEARING = 315;
final int NORTH_EAST_BEARING = 45;
final int WEST_BEARING = 270;
final Locale locale = Locale.US;

Leg firstLeg = itinerary.legs.get(0);
Coordinates firstStepCoords = new Coordinates(firstLeg.steps.get(0));
Coordinates thirdStepCoords = new Coordinates(firstLeg.steps.get(2));
Step adairAvenueNortheastStep = firstLeg.steps.get(0);
Step virginiaCircleNortheastStep = firstLeg.steps.get(1);
Step ponceDeLeonPlaceNortheastStep = firstLeg.steps.get(2);
Coordinates firstStepCoords = new Coordinates(adairAvenueNortheastStep);
Coordinates thirdStepCoords = new Coordinates(ponceDeLeonPlaceNortheastStep);
Coordinates destinationCoords = new Coordinates(firstLeg.to);
String monroeDrDestinationName = firstLeg.to.name;

Leg multiItinFirstLeg = multiLegItinerary.legs.get(0);
Coordinates multiItinFirstLegDestCoords = new Coordinates(multiItinFirstLeg.to);
Leg multiItinLastLeg = multiLegItinerary.legs.get(multiLegItinerary.legs.size() - 1);
Leg multiItinBusLeg = multiLegItinerary.legs.get(multiLegItinerary.legs.size() - 2);
Coordinates multiItinFirstLegDestCoords = new Coordinates(multiItinFirstLeg.to);
Coordinates multiItinLastLegDestCoords = new Coordinates(multiItinLastLeg.to);
String ansleyMallPetShopDestinationName = multiItinLastLeg.to.name;

return Stream.of(
Arguments.of(
monitoredTrip,
createPoint(firstStepCoords, 1, NORTH_EAST_BEARING),
"IMMEDIATE: Head WEST on Adair Avenue Northeast",
new OnTrackInstruction(1, adairAvenueNortheastStep, locale).build(),
TripStatus.ON_SCHEDULE,
"Coords near first step should produce relevant instruction"
),
Arguments.of(
monitoredTrip,
createPoint(firstStepCoords, 4, NORTH_EAST_BEARING),
"UPCOMING: Head WEST on Adair Avenue Northeast",
new OnTrackInstruction(4, adairAvenueNortheastStep, locale).build(),
TripStatus.DEVIATED,
"Coords deviated but near first step should produce relevant instruction"
),
Arguments.of(
monitoredTrip,
createPoint(firstStepCoords, 30, NORTH_EAST_BEARING),
"Head to Adair Avenue Northeast",
new DeviatedInstruction(adairAvenueNortheastStep.streetName, locale).build(),
TripStatus.DEVIATED,
"Deviated coords near first step should produce instruction to head to first step #1"
),
Arguments.of(
monitoredTrip,
createPoint(firstStepCoords, 15, NORTH_WEST_BEARING),
"Head to Adair Avenue Northeast",
new DeviatedInstruction(adairAvenueNortheastStep.streetName, locale).build(),
TripStatus.DEVIATED,
"Deviated coords near first step should produce instruction to head to first step #2"
),
Arguments.of(
monitoredTrip,
createPoint(firstStepCoords, 20, WEST_BEARING),
NO_INSTRUCTION,
new ContinueInstruction(virginiaCircleNortheastStep, locale).build(),
TripStatus.ON_SCHEDULE,
"Coords along a step should produce no instruction"
"Coords along a step should produce a continue on street instruction"
),
Arguments.of(
monitoredTrip,
thirdStepCoords,
"IMMEDIATE: LEFT on Ponce de Leon Place Northeast",
new OnTrackInstruction(0, ponceDeLeonPlaceNortheastStep, locale).build(),
TripStatus.AHEAD_OF_SCHEDULE,
"Coords near a not-first step should produce relevant instruction"
),
Arguments.of(
monitoredTrip,
createPoint(thirdStepCoords, 30, NORTH_WEST_BEARING),
"Head to Ponce de Leon Place Northeast",
new DeviatedInstruction(ponceDeLeonPlaceNortheastStep.streetName, locale).build(),
TripStatus.DEVIATED,
"Deviated coords near a not-first step should produce instruction to head to step"
),
Arguments.of(
monitoredTrip,
createPoint(destinationCoords, 1, NORTH_WEST_BEARING),
"ARRIVED: Monroe Dr NE at Cooledge Ave NE",
new OnTrackInstruction(2, monroeDrDestinationName, locale).build(),
TripStatus.COMPLETED,
"Instructions for destination coordinate"
),
Arguments.of(
multiLegMonitoredTrip,
createPoint(multiItinFirstLegDestCoords, 1.5, WEST_BEARING),
// Time is in US Pacific time zone (instead of US Eastern) by configuration for other E2E tests.
"Wait 6 minutes for your bus, route 27, scheduled at 9:18 AM, on time",
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."
),
Arguments.of(
multiLegMonitoredTrip,
createPoint(multiItinLastLegDestCoords, 1, NORTH_WEST_BEARING),
"ARRIVED: Ansley Mall Pet Shop",
new OnTrackInstruction(1, ansleyMallPetShopDestinationName, locale).build(),
TripStatus.COMPLETED,
"Instructions for destination coordinate of multi-leg trip"
),
Arguments.of(
monitoredTrip,
createPoint(thirdStepCoords, 1000, NORTH_WEST_BEARING),
NO_INSTRUCTION,
TripStatus.DEVIATED,
"Deviated significantly from nearest step should produce no instruction"
)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.opentripplanner.middleware.otp.response.Place;
import org.opentripplanner.middleware.otp.response.Step;
import org.opentripplanner.middleware.testutils.CommonTestUtils;
import org.opentripplanner.middleware.triptracker.instruction.ContinueInstruction;
import org.opentripplanner.middleware.triptracker.instruction.DeviatedInstruction;
import org.opentripplanner.middleware.triptracker.instruction.OnTrackInstruction;
import org.opentripplanner.middleware.utils.ConfigUtils;
Expand Down Expand Up @@ -270,9 +271,9 @@ private static Stream<Arguments> createTurnByTurnTrace() {
walkLeg,
new TraceData(
createPoint(virginiaCircleNortheastCoords, 12, SOUTH_WEST_BEARING),
NO_INSTRUCTION,
new ContinueInstruction(ponceDeLeonPlaceNortheastStep, locale).build(),
false,
"On track approaching second step, but not close enough for instruction."
"On track approaching second step, provide continue instruction."
)
),
Arguments.of(
Expand Down Expand Up @@ -336,9 +337,9 @@ private static Stream<Arguments> createTurnByTurnTrace() {
walkLeg,
new TraceData(
createPoint(pointAfterTurn, 0, calculateBearing(pointAfterTurn, virginiaAvenuePoint)),
NO_INSTRUCTION,
new ContinueInstruction(virginiaAvenueNortheastStep, locale).build(),
false,
"After turn left on to Virginia Avenue should not produce turn instruction."
"After turn left on to Virginia Avenue should provide continue instruction."
)
),
Arguments.of(
Expand Down

0 comments on commit aab57b5

Please sign in to comment.