Skip to content

Commit 0d41bb7

Browse files
authored
Merge pull request opentripplanner#6715 from HSLdevcom/stop-in-service
Check if stops have services running in the future
2 parents 89cc39a + 224c5c3 commit 0d41bb7

5 files changed

Lines changed: 119 additions & 14 deletions

File tree

application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitRealtimeStopPropertyMapper.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@ protected Collection<KeyValue> map(RegularStop stop) {
3939
.findStopTimesInPattern(stop, serviceDate, ArrivalDeparture.BOTH, true)
4040
.stream()
4141
.anyMatch(stopTime -> stopTime.times.size() > 0);
42+
var inService = transitService.hasScheduledServicesAfter(LocalDate.now(), stop);
4243

4344
Collection<KeyValue> sharedKeyValues = getBaseKeyValues(stop, i18NStringMapper, transitService);
4445
return ListUtils.combine(
4546
sharedKeyValues,
4647
List.of(
4748
new KeyValue("closedByServiceAlert", noServiceAlert),
48-
new KeyValue("servicesRunningOnServiceDate", stopTimesExist)
49+
new KeyValue("servicesRunningOnServiceDate", stopTimesExist),
50+
new KeyValue("servicesRunningInFuture", inService)
4951
)
5052
);
5153
}

application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,4 +813,9 @@ public int compare(TripOnServiceDate t1, TripOnServiceDate t2) {
813813
}
814814
}
815815
}
816+
817+
@Override
818+
public boolean hasScheduledServicesAfter(LocalDate date, StopLocation stop) {
819+
return timetableRepositoryIndex.hasScheduledServicesAfter(date, stop);
820+
}
816821
}

application/src/main/java/org/opentripplanner/transit/service/TimetableRepositoryIndex.java

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class TimetableRepositoryIndex {
5050
private final Multimap<Route, TripPattern> patternsForRoute = ArrayListMultimap.create();
5151
private final Multimap<StopLocation, TripPattern> patternsForStop = ArrayListMultimap.create();
5252

53+
private Map<StopLocation, LocalDate> endOfServiceDateForStop = new HashMap<>();
5354
private final Map<LocalDate, TIntSet> serviceCodesRunningForDate = new HashMap<>();
5455
private final Map<TripIdAndServiceDate, TripOnServiceDate> tripOnServiceDateForTripAndDay =
5556
new HashMap<>();
@@ -102,7 +103,7 @@ class TimetableRepositoryIndex {
102103
);
103104
}
104105

105-
initalizeServiceCodesForDate(timetableRepository);
106+
initializeServiceData(timetableRepository);
106107

107108
if (OTPFeature.FlexRouting.isOn()) {
108109
flexIndex = new FlexIndex(timetableRepository);
@@ -150,6 +151,21 @@ Collection<Trip> getTripsForStop(StopLocation stop) {
150151
.collect(Collectors.toList());
151152
}
152153

154+
/**
155+
* Checks if the last scheduled service date for the stop is on or after the given date.
156+
* This does not include real-time updates, so it only checks the scheduled service dates.
157+
*
158+
* @param date the date to check against
159+
* @param stop the stop to check
160+
* @return true if the stop has scheduled services after the given date, false otherwise
161+
*/
162+
boolean hasScheduledServicesAfter(LocalDate date, StopLocation stop) {
163+
LocalDate endOfServiceDate = endOfServiceDateForStop.get(stop);
164+
return (
165+
endOfServiceDate != null && (endOfServiceDate.isAfter(date) || endOfServiceDate.isEqual(date))
166+
);
167+
}
168+
153169
Operator getOperatorForId(FeedScopedId operatorId) {
154170
return operatorForId.get(operatorId);
155171
}
@@ -196,7 +212,7 @@ FlexIndex getFlexIndex() {
196212
return flexIndex;
197213
}
198214

199-
private void initalizeServiceCodesForDate(TimetableRepository timetableRepository) {
215+
private void initializeServiceData(TimetableRepository timetableRepository) {
200216
CalendarService calendarService = timetableRepository.getCalendarService();
201217

202218
if (calendarService == null) {
@@ -215,13 +231,22 @@ private void initalizeServiceCodesForDate(TimetableRepository timetableRepositor
215231
// Reconstruct set of all dates where service is defined, keeping track of which services
216232
// run on which days.
217233
Multimap<LocalDate, FeedScopedId> serviceIdsForServiceDate = HashMultimap.create();
234+
Map<FeedScopedId, LocalDate> endOfServiceDateForService = new HashMap<>();
218235

219236
for (FeedScopedId serviceId : calendarService.getServiceIds()) {
220237
Set<LocalDate> serviceDatesForService = calendarService.getServiceDatesForServiceId(
221238
serviceId
222239
);
223240
for (LocalDate serviceDate : serviceDatesForService) {
224241
serviceIdsForServiceDate.put(serviceDate, serviceId);
242+
243+
// Save the last service date for each service.
244+
if (
245+
endOfServiceDateForService.get(serviceId) == null ||
246+
(serviceDate != null && serviceDate.isAfter(endOfServiceDateForService.get(serviceId)))
247+
) {
248+
endOfServiceDateForService.put(serviceId, serviceDate);
249+
}
225250
}
226251
}
227252
for (LocalDate serviceDate : serviceIdsForServiceDate.keySet()) {
@@ -231,6 +256,31 @@ private void initalizeServiceCodesForDate(TimetableRepository timetableRepositor
231256
}
232257
serviceCodesRunningForDate.put(serviceDate, serviceCodesRunning);
233258
}
259+
260+
initializeTheEndOfServiceDateForStop(endOfServiceDateForService);
261+
}
262+
263+
private void initializeTheEndOfServiceDateForStop(
264+
Map<FeedScopedId, LocalDate> endOfServiceDateForService
265+
) {
266+
Map<StopLocation, LocalDate> endOfServiceDates = new HashMap<>();
267+
for (StopLocation stop : patternsForStop.keySet()) {
268+
for (TripPattern pattern : patternsForStop.get(stop)) {
269+
pattern
270+
.scheduledTripsAsStream()
271+
.forEach(trip -> {
272+
LocalDate tripEndDate = endOfServiceDateForService.get(trip.getServiceId());
273+
LocalDate endOfServiceDate = endOfServiceDates.get(stop);
274+
if (
275+
tripEndDate != null &&
276+
(endOfServiceDate == null || tripEndDate.isAfter(endOfServiceDate))
277+
) {
278+
endOfServiceDates.put(stop, tripEndDate);
279+
}
280+
});
281+
}
282+
}
283+
endOfServiceDateForStop = Map.copyOf(endOfServiceDates);
234284
}
235285

236286
Collection<GroupOfRoutes> getAllGroupOfRoutes() {

application/src/main/java/org/opentripplanner/transit/service/TransitService.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,4 +418,10 @@ Collection<RegularStop> findRegularStopsByBoundingBox(
418418
* Returns a list of {@link StopLocation}s that match the filtering defined in the request.
419419
*/
420420
Collection<StopLocation> findStopLocations(FindStopLocationsRequest request);
421+
422+
/**
423+
* Returns boolean indicating if there are scheduled services on or after the given date.
424+
* This does not include real-time updates, so it only checks the scheduled service dates.
425+
*/
426+
boolean hasScheduledServicesAfter(LocalDate date, StopLocation stop);
421427
}

application/src/test/java/org/opentripplanner/transit/service/DefaultTransitServiceTest.java

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
44
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
56
import static org.opentripplanner.transit.model.basic.TransitMode.BUS;
67
import static org.opentripplanner.transit.model.basic.TransitMode.FERRY;
78
import static org.opentripplanner.transit.model.basic.TransitMode.RAIL;
@@ -47,7 +48,10 @@ class DefaultTransitServiceTest {
4748
.withParentStation(STATION)
4849
.build();
4950
private static final RegularStop STOP_B = TEST_MODEL.stop("B").withParentStation(STATION).build();
50-
51+
private static final RegularStop STOP_C = TEST_MODEL.stop("C").withVehicleType(BUS).build();
52+
private static final RegularStop STOP_ONE = TEST_MODEL.stop("Stop_1")
53+
.withVehicleType(TRAM)
54+
.build();
5155
private static final FeedScopedId SERVICE_ID = new FeedScopedId("FEED", "SERVICE");
5256
private static final int SERVICE_CODE = 0;
5357
private static final TripPattern FERRY_PATTERN = TEST_MODEL.pattern(FERRY).build();
@@ -90,6 +94,22 @@ class DefaultTransitServiceTest {
9094
.withServiceCode(SERVICE_CODE)
9195
.build();
9296

97+
static FeedScopedId CALENDAR_ID_TWO = TimetableRepositoryForTest.id("CAL_2");
98+
static Trip TRIP_TODAY = TimetableRepositoryForTest.trip("12345")
99+
.withHeadsign(I18NString.of("Trip Headsign"))
100+
.withServiceId(CALENDAR_ID_TWO)
101+
.build();
102+
private static final ScheduledTripTimes SCHEDULED_TRIP_TIMES_TODAY = ScheduledTripTimes.of()
103+
.withTrip(TRIP_TODAY)
104+
.withArrivalTimes(new int[] { 0, 1 })
105+
.withDepartureTimes(new int[] { 0, 1 })
106+
.withServiceCode(SERVICE_CODE)
107+
.build();
108+
private static final TripPattern BUS_PATTERN_TODAY = TEST_MODEL.pattern(BUS)
109+
.withStopPattern(REAL_TIME_STOP_PATTERN)
110+
.withScheduledTimeTableBuilder(builder -> builder.addTripTimes(SCHEDULED_TRIP_TIMES_TODAY))
111+
.build();
112+
93113
private static final LocalDate SERVICE_DATE = LocalDate.of(2024, 1, 1);
94114
private static final LocalDate NO_SERVICE_DATE = LocalDate.of(2024, 1, 2);
95115

@@ -107,43 +127,57 @@ static void setup() {
107127
var siteRepository = TEST_MODEL.siteRepositoryBuilder()
108128
.withRegularStop(STOP_A)
109129
.withRegularStop(STOP_B)
130+
.withRegularStop(STOP_C)
131+
.withRegularStop(STOP_ONE)
110132
.withStation(STATION)
111133
.build();
112134

113135
var deduplicator = new Deduplicator();
114-
var transitModel = new TimetableRepository(siteRepository, deduplicator);
136+
var timetableRepository = new TimetableRepository(siteRepository, new Deduplicator());
115137
var canceledStopTimes = TEST_MODEL.stopTimesEvery5Minutes(3, TRIP, "11:30");
116138
var canceledTripTimes = TripTimesFactory.tripTimes(TRIP, canceledStopTimes, deduplicator)
117139
.createRealTimeFromScheduledTimes()
118140
.cancelTrip()
119141
.build();
120-
transitModel.addTripPattern(RAIL_PATTERN.getId(), RAIL_PATTERN);
142+
timetableRepository.addTripPattern(RAIL_PATTERN.getId(), RAIL_PATTERN);
121143

122144
// Crate a calendar (needed for testing cancelled trips)
123145
CalendarServiceData calendarServiceData = new CalendarServiceData();
124146
var firstDate = LocalDate.of(2024, 8, 8);
125147
var secondDate = LocalDate.of(2024, 8, 9);
148+
var thirdDate = LocalDate.of(2025, 7, 2);
149+
126150
calendarServiceData.putServiceDatesForServiceId(
127151
CALENDAR_ID,
128-
List.of(firstDate, secondDate, SERVICE_DATE)
152+
List.of(firstDate, secondDate, thirdDate, SERVICE_DATE)
153+
);
154+
calendarServiceData.putServiceDatesForServiceId(
155+
CALENDAR_ID_TWO,
156+
List.of(firstDate, secondDate)
129157
);
130-
transitModel.getServiceCodes().put(CALENDAR_ID, 0);
131-
transitModel.updateCalendarServiceData(true, calendarServiceData, DataImportIssueStore.NOOP);
132158

133-
transitModel.index();
134-
var timetableRepository = new TimetableRepository(siteRepository, new Deduplicator());
135-
var calendar = new CalendarServiceData();
136-
calendar.putServiceDatesForServiceId(SERVICE_ID, List.of(SERVICE_DATE));
137159
var serviceCodes = timetableRepository.getServiceCodes();
138160
serviceCodes.put(SERVICE_ID, SERVICE_CODE);
139-
timetableRepository.updateCalendarServiceData(true, calendar, DataImportIssueStore.NOOP);
161+
serviceCodes.put(CALENDAR_ID, SERVICE_CODE);
162+
serviceCodes.put(CALENDAR_ID_TWO, 1);
163+
140164
timetableRepository.addTripPattern(RAIL_PATTERN.getId(), RAIL_PATTERN);
165+
timetableRepository.addTripPattern(BUS_PATTERN.getId(), BUS_PATTERN);
166+
timetableRepository.addTripPattern(BUS_PATTERN_TODAY.getId(), BUS_PATTERN_TODAY);
167+
168+
timetableRepository.updateCalendarServiceData(
169+
true,
170+
calendarServiceData,
171+
DataImportIssueStore.NOOP
172+
);
173+
141174
timetableRepository.index();
142175

143176
TimetableSnapshot timetableSnapshot = new TimetableSnapshot();
144177
TripTimes tripTimes = ScheduledTripTimes.of()
145178
.withTrip(TimetableRepositoryForTest.trip("123").build())
146179
.withDepartureTimes(new int[] { 0, 1 })
180+
.withServiceCode(SERVICE_CODE)
147181
.build();
148182
timetableSnapshot.update(new RealTimeTripUpdate(REAL_TIME_PATTERN, tripTimes, firstDate));
149183
timetableSnapshot.update(new RealTimeTripUpdate(RAIL_PATTERN, canceledTripTimes, firstDate));
@@ -273,4 +307,12 @@ void getRealtimeTripTimesForAddedTrip() {
273307
void getRealtimeTripTimesForAddedTripOnNoServiceDay() {
274308
assertEquals(Optional.empty(), service.getTripTimeOnDates(ADDED_TRIP, NO_SERVICE_DATE));
275309
}
310+
311+
@Test
312+
void hasTripsForStop() {
313+
assertTrue(service.hasScheduledServicesAfter(LocalDate.of(2025, 7, 1), STOP_ONE));
314+
assertTrue(service.hasScheduledServicesAfter(LocalDate.of(2025, 7, 2), STOP_ONE));
315+
assertFalse(service.hasScheduledServicesAfter(LocalDate.of(2025, 7, 3), STOP_ONE));
316+
assertFalse(service.hasScheduledServicesAfter(LocalDate.of(2025, 7, 1), STOP_C));
317+
}
276318
}

0 commit comments

Comments
 (0)