Skip to content

Commit acbedaf

Browse files
committed
service end date for stops for checking the status of a stop
1 parent 4738e85 commit acbedaf

8 files changed

Lines changed: 134 additions & 12 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.isStopInService(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/apis/gtfs/datafetchers/StopImpl.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,18 @@ public DataFetcher<String> vehicleMode() {
474474
};
475475
}
476476

477+
@Override
478+
public DataFetcher<Boolean> hasFutureServices() {
479+
return environment -> {
480+
TransitService transitService = getTransitService(environment);
481+
return getValue(
482+
environment,
483+
transitService::isStopInService,
484+
transitService::isStationInService
485+
);
486+
};
487+
}
488+
477489
@Deprecated
478490
@Override
479491
public DataFetcher<Integer> vehicleType() {

application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,8 @@ public interface GraphQLStop {
10801080

10811081
public DataFetcher<String> gtfsId();
10821082

1083+
public DataFetcher<Boolean> hasFutureServices();
1084+
10831085
public DataFetcher<graphql.relay.Relay.ResolvedGlobalId> id();
10841086

10851087
public DataFetcher<Double> lat();

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,4 +813,19 @@ public int compare(TripOnServiceDate t1, TripOnServiceDate t2) {
813813
}
814814
}
815815
}
816+
817+
@Override
818+
public boolean isStopInService(StopLocation stop) {
819+
return timetableRepositoryIndex.isStopInService(stop);
820+
}
821+
822+
@Override
823+
public boolean isStationInService(Station station) {
824+
for (StopLocation stop : station.getChildStops()) {
825+
if (isStopInService(stop)) {
826+
return true;
827+
}
828+
}
829+
return false;
830+
}
816831
}

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

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

53+
private final Map<StopLocation, LocalDate> endOfServiceDateForStop = new HashMap<>();
54+
private final Map<FeedScopedId, LocalDate> endOfServiceDateForService = new HashMap<>();
5355
private final Map<LocalDate, TIntSet> serviceCodesRunningForDate = new HashMap<>();
5456
private final Map<TripIdAndServiceDate, TripOnServiceDate> tripOnServiceDateForTripAndDay =
5557
new HashMap<>();
@@ -103,6 +105,7 @@ class TimetableRepositoryIndex {
103105
}
104106

105107
initalizeServiceCodesForDate(timetableRepository);
108+
initializeTheEndOfServiceDateForStop();
106109

107110
if (OTPFeature.FlexRouting.isOn()) {
108111
flexIndex = new FlexIndex(timetableRepository);
@@ -150,6 +153,14 @@ Collection<Trip> getTripsForStop(StopLocation stop) {
150153
.collect(Collectors.toList());
151154
}
152155

156+
boolean isStopInService(StopLocation stop) {
157+
LocalDate endOfServiceDate = endOfServiceDateForStop.get(stop);
158+
return (
159+
endOfServiceDate != null &&
160+
(endOfServiceDate.isAfter(LocalDate.now()) || endOfServiceDate.isEqual(LocalDate.now()))
161+
);
162+
}
163+
153164
Operator getOperatorForId(FeedScopedId operatorId) {
154165
return operatorForId.get(operatorId);
155166
}
@@ -222,6 +233,14 @@ private void initalizeServiceCodesForDate(TimetableRepository timetableRepositor
222233
);
223234
for (LocalDate serviceDate : serviceDatesForService) {
224235
serviceIdsForServiceDate.put(serviceDate, serviceId);
236+
237+
// Save the last service date for each service.
238+
if (
239+
endOfServiceDateForService.get(serviceId) == null ||
240+
(serviceDate != null && serviceDate.isAfter(endOfServiceDateForService.get(serviceId)))
241+
) {
242+
endOfServiceDateForService.put(serviceId, serviceDate);
243+
}
225244
}
226245
}
227246
for (LocalDate serviceDate : serviceIdsForServiceDate.keySet()) {
@@ -233,6 +252,25 @@ private void initalizeServiceCodesForDate(TimetableRepository timetableRepositor
233252
}
234253
}
235254

255+
private void initializeTheEndOfServiceDateForStop() {
256+
for (StopLocation stop : patternsForStop.keySet()) {
257+
for (TripPattern pattern : patternsForStop.get(stop)) {
258+
pattern
259+
.scheduledTripsAsStream()
260+
.forEach(trip -> {
261+
LocalDate tripEndDate = endOfServiceDateForService.get(trip.getServiceId());
262+
LocalDate endOfServiceDate = endOfServiceDateForStop.get(stop);
263+
if (
264+
tripEndDate != null &&
265+
(endOfServiceDate == null || tripEndDate.isAfter(endOfServiceDate))
266+
) {
267+
endOfServiceDateForStop.put(stop, tripEndDate);
268+
}
269+
});
270+
}
271+
}
272+
}
273+
236274
Collection<GroupOfRoutes> getAllGroupOfRoutes() {
237275
return Collections.unmodifiableCollection(groupOfRoutesForId.values());
238276
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,4 +418,14 @@ 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 trips scheduled for a stop in the future.
424+
*/
425+
boolean isStopInService(StopLocation stop);
426+
427+
/**
428+
* Returns boolean indicating if there are trips scheduled for any child stop of the station in the future.
429+
*/
430+
boolean isStationInService(Station station);
421431
}

application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2149,6 +2149,8 @@ type Stop implements Node & PlaceInterface {
21492149
geometries: StopGeometries
21502150
"ÌD of the stop in format `FeedId:StopId`"
21512151
gtfsId: String!
2152+
"Stop has services running in the future"
2153+
hasFutureServices: Boolean
21522154
"Global object ID provided by Relay. This value can be used to refetch this object using **node** query."
21532155
id: ID!
21542156
"Latitude of the stop (WGS 84)"

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

Lines changed: 52 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") // added automatically to pattern by timetableRepositoryTest (same name)
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 today = LocalDate.now();
149+
126150
calendarServiceData.putServiceDatesForServiceId(
127151
CALENDAR_ID,
128-
List.of(firstDate, secondDate, SERVICE_DATE)
152+
List.of(firstDate, secondDate, today, 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,11 @@ 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.isStopInService(STOP_ONE));
314+
assertFalse(service.isStopInService(STOP_A));
315+
assertFalse(service.isStopInService(STOP_C));
316+
}
276317
}

0 commit comments

Comments
 (0)