Skip to content

Commit

Permalink
Merge 60c32d7 into 88cee5b
Browse files Browse the repository at this point in the history
  • Loading branch information
qcdyx authored Jan 28, 2025
2 parents 88cee5b + 60c32d7 commit 4bb4dd7
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package org.mobilitydata.gtfsvalidator.validator;

import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.ERROR;

import javax.inject.Inject;
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice;
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidator;
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
import org.mobilitydata.gtfsvalidator.table.*;
import org.mobilitydata.gtfsvalidator.type.GtfsTime;

/**
* Validates that if `routes.continuous_pickup` or `routes.continuous_drop_off` are included, then
* `stop_times.start_pickup_drop_off_window` or `stop_times.end_pickup_drop_off_window` are not
* defined for any trip of this route.
*
* <p>Generated notice: {@link ForbiddenContinuousPickupDropOffNotice}.
*/
@GtfsValidator
public class ContinuousPickupDropOffValidator extends FileValidator {
private final GtfsRouteTableContainer routeTable;
private final GtfsTripTableContainer tripTable;
private final GtfsStopTimeTableContainer stopTimeTable;

@Inject
public ContinuousPickupDropOffValidator(
GtfsRouteTableContainer routeTable,
GtfsTripTableContainer tripTable,
GtfsStopTimeTableContainer stopTimeTable) {
this.routeTable = routeTable;
this.tripTable = tripTable;
this.stopTimeTable = stopTimeTable;
}

@Override
public void validate(NoticeContainer noticeContainer) {
for (GtfsRoute route : routeTable.getEntities()) {
boolean continuous =
(route.continuousPickup() == GtfsContinuousPickupDropOff.ALLOWED
|| route.continuousPickup() == GtfsContinuousPickupDropOff.MUST_PHONE
|| route.continuousPickup() == GtfsContinuousPickupDropOff.ON_REQUEST_TO_DRIVER)
|| (route.continuousDropOff() == GtfsContinuousPickupDropOff.ALLOWED
|| route.continuousDropOff() == GtfsContinuousPickupDropOff.MUST_PHONE
|| route.continuousDropOff() == GtfsContinuousPickupDropOff.ON_REQUEST_TO_DRIVER);
if (!continuous) {
continue;
}
for (GtfsTrip trip : tripTable.byRouteId(route.routeId())) {
for (GtfsStopTime stopTime : stopTimeTable.byTripId(trip.tripId())) {
if (stopTime.hasStartPickupDropOffWindow() || stopTime.hasEndPickupDropOffWindow()) {
noticeContainer.addValidationNotice(
new ForbiddenContinuousPickupDropOffNotice(
route.csvRowNumber(),
trip.tripId(),
stopTime.csvRowNumber(),
stopTime.startPickupDropOffWindow(),
stopTime.endPickupDropOffWindow()));
}
}
}
}
}

@Override
public boolean shouldCallValidate() {
if (routeTable != null && stopTimeTable != null) {
return routeTable.hasColumn(GtfsRoute.CONTINUOUS_PICKUP_FIELD_NAME)
|| routeTable.hasColumn(GtfsRoute.CONTINUOUS_DROP_OFF_FIELD_NAME)
&& (stopTimeTable.hasColumn(GtfsStopTime.START_PICKUP_DROP_OFF_WINDOW_FIELD_NAME)
|| stopTimeTable.hasColumn(GtfsStopTime.END_PICKUP_DROP_OFF_WINDOW_FIELD_NAME));
} else {
return false;
}
}

/**
* Continuous pickup or drop-off are forbidden when routes.continuous_pickup or
* routes.continuous_drop_off are 0, 2 or 3 and stop_times.start_pickup_drop_off_window or
* stop_times.end_pickup_drop_off_window are defined for any trip of this route.
*/
@GtfsValidationNotice(severity = ERROR)
public static class ForbiddenContinuousPickupDropOffNotice extends ValidationNotice {
/** The row number of the route in the `routes.txt` file. */
private final int routeCsvRowNumber;

/** The ID of the trip. */
private final String tripId;

/** The row number of the stop time in the `stop_times.txt` file. */
private final int stopTimeCsvRowNumber;

/** The start time of the pickup/drop-off window. */
private final GtfsTime startPickupDropOffWindow;

/** The end time of the pickup/drop-off window. */
private final GtfsTime endPickupDropOffWindow;

public ForbiddenContinuousPickupDropOffNotice(
int routeCsvRowNumber,
String tripId,
int stopTimesCsvRowNumber,
GtfsTime startPickupDropOffWindow,
GtfsTime endPickupDropOffWindow) {
this.routeCsvRowNumber = routeCsvRowNumber;
this.tripId = tripId;
this.stopTimeCsvRowNumber = stopTimesCsvRowNumber;
this.startPickupDropOffWindow = startPickupDropOffWindow;
this.endPickupDropOffWindow = endPickupDropOffWindow;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package org.mobilitydata.gtfsvalidator.validator;

import static com.google.common.truth.Truth.assertThat;

import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
import org.mobilitydata.gtfsvalidator.table.*;
import org.mobilitydata.gtfsvalidator.type.GtfsTime;

@RunWith(JUnit4.class)
public class ContinuousPickupDropOffValidatorTest {

private static List<ValidationNotice> generateNotices(
List<GtfsRoute> routes, List<GtfsTrip> trips, List<GtfsStopTime> stopTimes) {
NoticeContainer noticeContainer = new NoticeContainer();
GtfsRouteTableContainer routeTable =
GtfsRouteTableContainer.forEntities(routes, noticeContainer);
GtfsTripTableContainer tripTable = GtfsTripTableContainer.forEntities(trips, noticeContainer);
GtfsStopTimeTableContainer stopTimeTable =
GtfsStopTimeTableContainer.forEntities(stopTimes, noticeContainer);
new ContinuousPickupDropOffValidator(routeTable, tripTable, stopTimeTable)
.validate(noticeContainer);
return noticeContainer.getValidationNotices();
}

@Test
public void continuousPickupWithPickupDropOffWindowShouldGenerateNotice() {
List<ValidationNotice> notices =
generateNotices(
List.of(
new GtfsRoute.Builder()
.setCsvRowNumber(1)
.setRouteId("route1")
.setContinuousPickup(2)
.build()),
List.of(
new GtfsTrip.Builder()
.setCsvRowNumber(2)
.setTripId("trip1")
.setRouteId("route1")
.build()),
List.of(
new GtfsStopTime.Builder()
.setCsvRowNumber(3)
.setTripId("trip1")
.setStartPickupDropOffWindow(GtfsTime.fromString("08:00:00"))
.setEndPickupDropOffWindow(GtfsTime.fromString("09:00:00"))
.build()));
assertThat(notices)
.containsExactly(
new ContinuousPickupDropOffValidator.ForbiddenContinuousPickupDropOffNotice(
1, "trip1", 3, GtfsTime.fromString("08:00:00"), GtfsTime.fromString("09:00:00")));
}

@Test
public void noContinuousPickupOrDropOffShouldNotGenerateNotice() {
List<ValidationNotice> notices =
generateNotices(
List.of(new GtfsRoute.Builder().setCsvRowNumber(1).setRouteId("route1").build()),
List.of(
new GtfsTrip.Builder()
.setCsvRowNumber(2)
.setTripId("trip1")
.setRouteId("route1")
.build()),
List.of(
new GtfsStopTime.Builder()
.setCsvRowNumber(3)
.setTripId("trip1")
.setStartPickupDropOffWindow(GtfsTime.fromString("08:00:00"))
.build()));
assertThat(notices).isEmpty();
}

@Test
public void continuousPickupAndDropOffWithoutPickupDropOffWindowShouldNotGenerateNotice() {
List<ValidationNotice> notices =
generateNotices(
List.of(
new GtfsRoute.Builder()
.setCsvRowNumber(1)
.setRouteId("route1")
.setContinuousPickup(1)
.setContinuousDropOff(1)
.build()),
List.of(
new GtfsTrip.Builder()
.setCsvRowNumber(2)
.setTripId("trip1")
.setRouteId("route1")
.build()),
List.of(new GtfsStopTime.Builder().setCsvRowNumber(3).setTripId("trip1").build()));
assertThat(notices).isEmpty();
}
}

0 comments on commit 4bb4dd7

Please sign in to comment.