Skip to content

Commit 6d17a01

Browse files
authored
Merge pull request opentripplanner#6621 from HSLdevcom/fix-paging-first-search-previous-page-edge-case
Fix issue with paging when going to the next page after initial arrive by search
2 parents 12c24b9 + 56111e0 commit 6d17a01

File tree

10 files changed

+268
-90
lines changed

10 files changed

+268
-90
lines changed

application/src/main/java/org/opentripplanner/model/plan/SortOrder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public enum SortOrder {
3232
STREET_AND_DEPARTURE_TIME;
3333

3434
/**
35-
* The itineraries are sorted with by arrival time with the earliest arrival time first. When
35+
* The itineraries are sorted by arrival time with the earliest arrival time first. When
3636
* paging we need to know which end of the list of itineraries we should crop. This method is used
3737
* to decide that together with the current page type (next/previous).
3838
* <p>

application/src/main/java/org/opentripplanner/model/plan/paging/cursor/PageCursorFactory.java

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@
1313

1414
public class PageCursorFactory {
1515

16+
/**
17+
* The search-window start and end is [inclusive, exclusive], so to calculate the start of the
18+
* search-window from the last time included in the search window we need to include one extra
19+
* minute at the end.
20+
* <p>
21+
* The value is set to a minute because raptor operates in one minute increments.
22+
*/
23+
private static final Duration SEARCH_WINDOW_END_EXCLUSIVITY_TIME_ADDITION = Duration.ofMinutes(1);
24+
1625
private final SortOrder sortOrder;
1726
private final Duration newSearchWindow;
1827
private PageType currentPageType;
@@ -22,6 +31,7 @@ public class PageCursorFactory {
2231
private boolean wholeSearchWindowUsed = true;
2332
private ItinerarySortKey itineraryPageCut = null;
2433
private PageCursorInput pageCursorInput = null;
34+
private Instant firstSearchLatestItineraryDeparture = null;
2535

2636
private PageCursor nextCursor = null;
2737
private PageCursor prevCursor = null;
@@ -33,17 +43,24 @@ public PageCursorFactory(SortOrder sortOrder, Duration newSearchWindow) {
3343

3444
/**
3545
* Set the original search earliest-departure-time({@code edt}), latest-arrival-time ({@code lat},
36-
* optional) and the search-window used.
46+
* optional) and the search-window used. Also resolve the page-type and
47+
* first-search-latest-itinerary-departure.
3748
*/
3849
public PageCursorFactory withOriginalSearch(
3950
@Nullable PageType pageType,
51+
@Nullable Instant firstItineraryResultDeparture,
4052
Instant edt,
4153
Instant lat,
4254
Duration searchWindow
4355
) {
4456
this.currentPageType = pageType == null
4557
? resolvePageTypeForTheFirstSearch(sortOrder)
4658
: pageType;
59+
this.firstSearchLatestItineraryDeparture = resolveFirstSearchLatestItineraryDeparture(
60+
pageType,
61+
firstItineraryResultDeparture,
62+
edt
63+
);
4764

4865
this.currentEdt = edt;
4966
this.currentLat = lat;
@@ -109,6 +126,26 @@ private static PageType resolvePageTypeForTheFirstSearch(SortOrder sortOrder) {
109126
return sortOrder.isSortedByAscendingArrivalTime() ? NEXT_PAGE : PREVIOUS_PAGE;
110127
}
111128

129+
/**
130+
* If the first search is an arrive by search (PREVIOUS_PAGE type), the current search window is
131+
* misleading. Instead of using the current search window to set the page cursor of the next
132+
* page, the departure time of the latest itinerary result is used to avoid missing itineraries.
133+
*/
134+
private Instant resolveFirstSearchLatestItineraryDeparture(
135+
@Nullable PageType pageType,
136+
@Nullable Instant firstItineraryResultDeparture,
137+
Instant edt
138+
) {
139+
if (pageType == null && resolvePageTypeForTheFirstSearch(sortOrder) == PREVIOUS_PAGE) {
140+
if (firstItineraryResultDeparture != null) {
141+
return firstItineraryResultDeparture;
142+
} else {
143+
return edt;
144+
}
145+
}
146+
return null;
147+
}
148+
112149
/** Create page cursor pair (next and previous) */
113150
private void createPageCursors() {
114151
if (currentEdt == null || nextCursor != null || prevCursor != null) {
@@ -128,10 +165,10 @@ private void createPageCursors() {
128165
prevEdt = edtBeforeNewSw();
129166
nextEdt = pageCursorInput.earliestRemovedDeparture();
130167
} else {
131-
// The search-window start and end is [inclusive, exclusive], so to calculate the start of the
132-
// search-window from the last time included in the search window we need to include one extra
133-
// minute at the end.
134-
prevEdt = pageCursorInput.latestRemovedDeparture().minus(newSearchWindow).plusSeconds(60);
168+
prevEdt = pageCursorInput
169+
.latestRemovedDeparture()
170+
.minus(newSearchWindow)
171+
.plus(SEARCH_WINDOW_END_EXCLUSIVITY_TIME_ADDITION);
135172
nextEdt = edtAfterUsedSw();
136173
}
137174
}
@@ -163,6 +200,15 @@ private Instant edtBeforeNewSw() {
163200
}
164201

165202
private Instant edtAfterUsedSw() {
166-
return currentEdt.plus(currentSearchWindow);
203+
Instant defaultEdt = currentEdt.plus(currentSearchWindow);
204+
if (firstSearchLatestItineraryDeparture != null) {
205+
Instant edtFromLatestItineraryDeparture = firstSearchLatestItineraryDeparture.plus(
206+
SEARCH_WINDOW_END_EXCLUSIVITY_TIME_ADDITION
207+
);
208+
if (edtFromLatestItineraryDeparture.isBefore(defaultEdt)) {
209+
return edtFromLatestItineraryDeparture;
210+
}
211+
}
212+
return defaultEdt;
167213
}
168214
}

application/src/main/java/org/opentripplanner/service/paging/PagingService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ private PageCursorFactory mapIntoPageCursorFactory(@Nullable PageType currentPag
183183

184184
factory = factory.withOriginalSearch(
185185
currentPageType,
186+
itineraries.size() > 0 ? itineraries.get(0).startTimeAsInstant() : null,
186187
earliestDepartureTime,
187188
latestArrivalTime,
188189
searchWindowUsed

application/src/test/java/org/opentripplanner/model/plan/paging/cursor/PageCursorFactoryTest.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ class PageCursorFactoryTest implements PlanTestConstants {
2727
private static final Instant T10_30 = time("10:30");
2828
private static final Instant T11_01 = time("11:01");
2929
private static final Instant T12_00 = time("12:00");
30-
private static final Instant T12_10 = time("12:10");
30+
private static final Instant T12_01 = time("12:01");
3131
private static final Instant T12_30 = time("12:30");
3232
private static final Instant T13_00 = time("13:00");
3333
private static final Instant T13_30 = time("13:30");
3434

3535
@Test
3636
public void sortArrivalAscending() {
3737
var factory = new PageCursorFactory(STREET_AND_ARRIVAL_TIME, D90M)
38-
.withOriginalSearch(null, T12_00, null, D1H)
38+
.withOriginalSearch(null, null, T12_00, null, D1H)
3939
.withPageCursorInput(new TestPageCursorInput());
4040

4141
var nextPage = factory.nextPageCursor();
@@ -48,7 +48,7 @@ public void sortArrivalAscending() {
4848
@Test
4949
public void sortArrivalAscendingCropSearchWindow() {
5050
var factory = new PageCursorFactory(STREET_AND_ARRIVAL_TIME, D90M)
51-
.withOriginalSearch(NEXT_PAGE, T12_00, null, D1H)
51+
.withOriginalSearch(NEXT_PAGE, null, T12_00, null, D1H)
5252
.withPageCursorInput(
5353
new TestPageCursorInput(
5454
newItinerary(A).bus(65, timeAsSeconds(T12_30), timeAsSeconds(T13_30), B).build(),
@@ -66,7 +66,7 @@ public void sortArrivalAscendingCropSearchWindow() {
6666
@Test
6767
public void sortArrivalAscendingPreviousPage() {
6868
var factory = new PageCursorFactory(STREET_AND_ARRIVAL_TIME, D90M)
69-
.withOriginalSearch(PREVIOUS_PAGE, T12_00, null, D1H)
69+
.withOriginalSearch(PREVIOUS_PAGE, null, T12_00, null, D1H)
7070
.withPageCursorInput(new TestPageCursorInput());
7171

7272
var nextPage = factory.nextPageCursor();
@@ -79,7 +79,7 @@ public void sortArrivalAscendingPreviousPage() {
7979
@Test
8080
public void sortArrivalAscendingCropSearchWindowPreviousPage() {
8181
var factory = new PageCursorFactory(STREET_AND_ARRIVAL_TIME, D90M)
82-
.withOriginalSearch(PREVIOUS_PAGE, T12_00, null, D1H)
82+
.withOriginalSearch(PREVIOUS_PAGE, null, T12_00, null, D1H)
8383
.withPageCursorInput(
8484
new TestPageCursorInput(
8585
newItinerary(A).bus(65, timeAsSeconds(T12_30), timeAsSeconds(T13_30), B).build(),
@@ -97,11 +97,11 @@ public void sortArrivalAscendingCropSearchWindowPreviousPage() {
9797
@Test
9898
public void sortDepartureDescending() {
9999
var factory = new PageCursorFactory(STREET_AND_DEPARTURE_TIME, D90M)
100-
.withOriginalSearch(null, T12_00, T13_30, D1H)
100+
.withOriginalSearch(null, null, T12_00, T13_30, D1H)
101101
.withPageCursorInput(new TestPageCursorInput());
102102

103103
var nextPage = factory.nextPageCursor();
104-
assertPageCursor(nextPage, T13_00, null, D90M, NEXT_PAGE, false, false);
104+
assertPageCursor(nextPage, T12_01, null, D90M, NEXT_PAGE, false, false);
105105

106106
var prevPage = factory.previousPageCursor();
107107
assertPageCursor(prevPage, T10_30, T13_30, D90M, PREVIOUS_PAGE, false, false);
@@ -110,7 +110,7 @@ public void sortDepartureDescending() {
110110
@Test
111111
public void sortDepartureDescendingCropSearchWindow() {
112112
var factory = new PageCursorFactory(STREET_AND_DEPARTURE_TIME, D90M)
113-
.withOriginalSearch(PREVIOUS_PAGE, T12_00, T13_30, D1H)
113+
.withOriginalSearch(PREVIOUS_PAGE, null, T12_00, T13_30, D1H)
114114
.withPageCursorInput(
115115
new TestPageCursorInput(
116116
newItinerary(A).bus(65, timeAsSeconds(T12_30), timeAsSeconds(T13_00), B).build(),
@@ -128,7 +128,7 @@ public void sortDepartureDescendingCropSearchWindow() {
128128
@Test
129129
public void sortDepartureDescendingNextPage() {
130130
var factory = new PageCursorFactory(STREET_AND_DEPARTURE_TIME, D90M)
131-
.withOriginalSearch(NEXT_PAGE, T12_00, T13_30, D1H)
131+
.withOriginalSearch(NEXT_PAGE, null, T12_00, T13_30, D1H)
132132
.withPageCursorInput(new TestPageCursorInput());
133133

134134
var nextPage = factory.nextPageCursor();
@@ -141,7 +141,7 @@ public void sortDepartureDescendingNextPage() {
141141
@Test
142142
public void sortDepartureDescendingCropSearchWindowNextPage() {
143143
var factory = new PageCursorFactory(STREET_AND_DEPARTURE_TIME, D90M)
144-
.withOriginalSearch(NEXT_PAGE, T12_00, T13_30, D1H)
144+
.withOriginalSearch(NEXT_PAGE, null, T12_00, T13_30, D1H)
145145
.withPageCursorInput(
146146
new TestPageCursorInput(
147147
newItinerary(A).bus(65, timeAsSeconds(T12_30), timeAsSeconds(T13_00), B).build(),
@@ -159,7 +159,7 @@ public void sortDepartureDescendingCropSearchWindowNextPage() {
159159
@Test
160160
public void testGeneralizedCostMaxLimit() {
161161
var factory = new PageCursorFactory(STREET_AND_DEPARTURE_TIME, D90M)
162-
.withOriginalSearch(NEXT_PAGE, T12_00, T13_30, D1H)
162+
.withOriginalSearch(NEXT_PAGE, null, T12_00, T13_30, D1H)
163163
.withPageCursorInput(
164164
new TestPageCursorInput(
165165
newItinerary(A).bus(65, timeAsSeconds(T12_30), timeAsSeconds(T13_00), B).build(),

application/src/test/java/org/opentripplanner/service/paging/PS1_LegacyMetaDataTest.java

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,10 @@ void testCreateTripSearchMetadataDepartAfterNormalSearchWindow() {
5151
var subject = testDriver.pagingService();
5252

5353
assertEquals(D30m, subject.createTripSearchMetadata().searchWindowUsed);
54-
assertEquals(
55-
"08:15",
56-
TestPagingUtils.cleanStr(subject.createTripSearchMetadata().prevDateTime)
57-
);
54+
assertEquals("8:15", TestPagingUtils.cleanStr(subject.createTripSearchMetadata().prevDateTime));
5855
// 12:11 will drop results, the solution is to use the complete sort-vector.
5956
// The cursor implementation does that
60-
assertEquals(
61-
"09:15",
62-
TestPagingUtils.cleanStr(subject.createTripSearchMetadata().nextDateTime)
63-
);
57+
assertEquals("9:15", TestPagingUtils.cleanStr(subject.createTripSearchMetadata().nextDateTime));
6458
}
6559

6660
@Test
@@ -89,13 +83,7 @@ void testCreateTripSearchMetadataArriveByWithNormalSearchWindow() {
8983
var subject = testDriver.pagingService();
9084

9185
assertEquals(D30m, subject.createTripSearchMetadata().searchWindowUsed);
92-
assertEquals(
93-
"08:45",
94-
TestPagingUtils.cleanStr(subject.createTripSearchMetadata().prevDateTime)
95-
);
96-
assertEquals(
97-
"09:45",
98-
TestPagingUtils.cleanStr(subject.createTripSearchMetadata().nextDateTime)
99-
);
86+
assertEquals("8:45", TestPagingUtils.cleanStr(subject.createTripSearchMetadata().prevDateTime));
87+
assertEquals("9:45", TestPagingUtils.cleanStr(subject.createTripSearchMetadata().nextDateTime));
10088
}
10189
}

0 commit comments

Comments
 (0)