Skip to content

Commit

Permalink
Merge pull request #6492 from Skanetrafiken/siri-illustrate-interpola…
Browse files Browse the repository at this point in the history
…tion

Illustrate current behaviour of siri time interpolation
  • Loading branch information
habrahamsson-skanetrafiken authored Feb 28, 2025
2 parents caf673b + 769366d commit 71f7369
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -159,59 +159,68 @@ private void applyUpdates(RealTimeTripTimes newTimes) {

int departureFromPreviousStop = 0;
int lastDepartureDelay = 0;
List<StopLocation> stops = pattern.getStops();
for (int callCounter = 0; callCounter < stops.size(); callCounter++) {
StopLocation stop = stops.get(callCounter);
boolean foundMatch = false;
List<StopLocation> stopsInPattern = pattern.getStops();
for (int stopIndex = 0; stopIndex < stopsInPattern.size(); stopIndex++) {
StopLocation stopInPattern = stopsInPattern.get(stopIndex);
CallWrapper matchingCall = null;

for (CallWrapper call : calls) {
if (alreadyVisited.contains(call)) {
continue;
}
//Current stop is being updated
RegularStop stopPoint = entityResolver.resolveQuay(call.getStopPointRef());
foundMatch = stop.equals(stopPoint) || stop.isPartOfSameStationAs(stopPoint);
if (foundMatch) {
TimetableHelper.applyUpdates(
startOfService,
newTimes,
callCounter,
callCounter == (stops.size() - 1),
predictionInaccurate,
call,
occupancy
);

alreadyVisited.add(call);

lastDepartureDelay = newTimes.getDepartureDelay(callCounter);
if (stopInPattern.equals(stopPoint) || stopInPattern.isPartOfSameStationAs(stopPoint)) {
matchingCall = call;
break;
}
}
if (!foundMatch) {

if (matchingCall != null) {
TimetableHelper.applyUpdates(
startOfService,
newTimes,
stopIndex,
stopIndex == (stopsInPattern.size() - 1),
predictionInaccurate,
matchingCall,
occupancy
);

alreadyVisited.add(matchingCall);

lastDepartureDelay = newTimes.getDepartureDelay(stopIndex);
} else {
// No update found in calls
if (pattern.isBoardAndAlightAt(callCounter, NONE)) {
if (pattern.isBoardAndAlightAt(stopIndex, NONE)) {
// When newTimes contains stops without pickup/dropoff - set both arrival/departure to previous stop's departure
// This necessary to accommodate the case when delay is reduced/eliminated between to stops with pickup/dropoff, and
// multiple non-pickup/dropoff stops are in between.
newTimes.updateArrivalTime(callCounter, departureFromPreviousStop);
newTimes.updateDepartureTime(callCounter, departureFromPreviousStop);
newTimes.updateArrivalTime(stopIndex, departureFromPreviousStop);
newTimes.updateDepartureTime(stopIndex, departureFromPreviousStop);

LOG.info(
"Siri non-pickup/dropoff stop time interpolation for tripId: {}",
newTimes.getTrip().getId()
);
} else {
int arrivalDelay = lastDepartureDelay;
int departureDelay = lastDepartureDelay;

if (lastDepartureDelay == 0) {
//No match has been found yet (i.e. still in RecordedCalls) - keep existing delays
arrivalDelay = existingTripTimes.getArrivalDelay(callCounter);
departureDelay = existingTripTimes.getDepartureDelay(callCounter);
arrivalDelay = existingTripTimes.getArrivalDelay(stopIndex);
departureDelay = existingTripTimes.getDepartureDelay(stopIndex);
}

newTimes.updateArrivalDelay(callCounter, arrivalDelay);
newTimes.updateDepartureDelay(callCounter, departureDelay);
newTimes.updateArrivalDelay(stopIndex, arrivalDelay);
newTimes.updateDepartureDelay(stopIndex, departureDelay);

LOG.info("Siri stop time interpolation for tripId: {}", newTimes.getTrip().getId());
}
}

departureFromPreviousStop = newTimes.getDepartureTime(callCounter);
departureFromPreviousStop = newTimes.getDepartureTime(stopIndex);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package org.opentripplanner.updater.trip.siri.moduletests;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;
import org.opentripplanner.updater.trip.RealtimeTestConstants;
import org.opentripplanner.updater.trip.RealtimeTestEnvironment;
import org.opentripplanner.updater.trip.TripInput;
import org.opentripplanner.updater.trip.siri.SiriEtBuilder;

class InterpolationTest implements RealtimeTestConstants {

private static final TripInput TRIP_INPUT = TripInput
.of(TRIP_1_ID)
.addStop(STOP_A1, "0:00:10", "0:00:11")
.addStop(STOP_B1, "0:00:20", "0:00:21")
.addStop(STOP_C1, "0:00:40", "0:00:41")
.build();

/**
* This test shows that we accept a Siri message containing incomplete data.
* The ET message contains only departure times for one of three calls. The current logic
* interpolates the times for the omitted calls.
*
* This message is not valid according to the Siri nordic profile and we would probably want to
* reject it instead.
*/
@Test
void testInterpolation() {
var env = RealtimeTestEnvironment.of().addTrip(TRIP_INPUT).build();

var updates = new SiriEtBuilder(env.getDateTimeHelper())
.withDatedVehicleJourneyRef(TRIP_1_ID)
.withEstimatedCalls(builder ->
builder.call(STOP_B1).departAimedExpected("00:00:21", "00:00:25")
)
.buildEstimatedTimetableDeliveries();

var result = env.applyEstimatedTimetable(updates);

assertEquals(1, result.successful());
// The times for A1 are unchanged
// The times for C1 are interpolated from the delay at B1
assertEquals(
"UPDATED | A1 0:00:10 0:00:11 | B1 0:00:20 0:00:25 | C1 0:00:44 0:00:45",
env.getRealtimeTimetable(TRIP_1_ID)
);
}

/**
* This test illustrates that we use the previous realtime information for missing calls.
*
* This message is not valid according to the Siri nordic profile and we would probably want to
* reject it instead.
*/
@Test
void testInterpolationWithPreviousRealtime() {
var env = RealtimeTestEnvironment.of().addTrip(TRIP_INPUT).build();

var updates = new SiriEtBuilder(env.getDateTimeHelper())
.withDatedVehicleJourneyRef(TRIP_1_ID)
.withEstimatedCalls(builder ->
builder
.call(STOP_A1)
.departAimedExpected("00:00:11", "00:00:15")
.call(STOP_B1)
.arriveAimedExpected("00:00:20", "00:00:25")
.departAimedExpected("00:00:21", "00:00:26")
.call(STOP_C1)
.arriveAimedExpected("00:00:40", "00:00:45")
.departAimedExpected("00:00:41", "00:00:46")
)
.buildEstimatedTimetableDeliveries();
var result = env.applyEstimatedTimetable(updates);
assertEquals(1, result.successful());

var updates2 = new SiriEtBuilder(env.getDateTimeHelper())
.withDatedVehicleJourneyRef(TRIP_1_ID)
.withEstimatedCalls(builder ->
builder.call(STOP_B1).departAimedExpected("00:00:21", "00:00:31")
)
.buildEstimatedTimetableDeliveries();
var result2 = env.applyEstimatedTimetable(updates2);
assertEquals(1, result2.successful());

// We use the previous realtime times for A1
// We interpolate the times for C1
assertEquals(
"UPDATED | A1 0:00:15 0:00:15 | B1 0:00:20 0:00:31 | C1 0:00:50 0:00:51",
env.getRealtimeTimetable(TRIP_1_ID)
);
}
}

0 comments on commit 71f7369

Please sign in to comment.