Skip to content

Commit

Permalink
feat: reformat trip and shape dist validator (#1676)
Browse files Browse the repository at this point in the history
  • Loading branch information
cka-y authored Mar 11, 2024
1 parent b537a1b commit 2f9fccd
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
package org.mobilitydata.gtfsvalidator.validator;

import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.ERROR;
import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.WARNING;
import static org.mobilitydata.gtfsvalidator.util.S2Earth.getDistanceMeters;

import java.util.Comparator;
import javax.inject.Inject;
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice;
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice.FileRefs;
Expand All @@ -32,55 +35,75 @@
*/
@GtfsValidator
public class TripAndShapeDistanceValidator extends FileValidator {

private final GtfsTripTableContainer tripTable;

private final GtfsStopTimeTableContainer stopTimeTable;

private final GtfsStopTableContainer stopTable;
private final GtfsShapeTableContainer shapeTable;
private final double DISTANCE_THRESHOLD = 11.1; // distance in meters

@Inject
TripAndShapeDistanceValidator(
GtfsTripTableContainer tripTable,
GtfsStopTimeTableContainer stopTimeTable,
GtfsStopTableContainer stopTable,
GtfsShapeTableContainer shapeTable) {
this.tripTable = tripTable;
this.stopTimeTable = stopTimeTable;
this.shapeTable = shapeTable;
this.stopTable = stopTable;
}

@Override
public void validate(NoticeContainer noticeContainer) {
shapeTable
.byShapeIdMap()
tripTable
.getEntities()
.forEach(
(shapeId, shape) -> {
double maxShapeDist =
trip -> {
String shapeId = trip.shapeId();
String tripId = trip.tripId();

// Get distance for trip
int nbStopTimes = stopTimeTable.byTripId(tripId).size();
if (nbStopTimes == 0) {
return;
}
GtfsStopTime lastStopTime = stopTimeTable.byTripId(tripId).get(nbStopTimes - 1);
GtfsStop stop = stopTable.byStopId(lastStopTime.stopId()).orElse(null);
if (stop == null) {
return;
}
double maxStopTimeDist = lastStopTime.shapeDistTraveled();

// Get max shape distance for trip
GtfsShape maxShape =
shapeTable.byShapeId(shapeId).stream()
.mapToDouble(GtfsShape::shapeDistTraveled)
.max()
.orElse(Double.NEGATIVE_INFINITY);

tripTable
.byShapeId(shapeId)
.forEach(
trip -> {
double maxStopTimeDist =
stopTimeTable.byTripId(trip.tripId()).stream()
.mapToDouble(GtfsStopTime::shapeDistTraveled)
.max()
.orElse(Double.NEGATIVE_INFINITY);

if (maxStopTimeDist > maxShapeDist) {
noticeContainer.addValidationNotice(
new TripDistanceExceedsShapeDistanceNotice(
trip.tripId(), shapeId, maxStopTimeDist, maxShapeDist));
}
});
.max(Comparator.comparingDouble(GtfsShape::shapeDistTraveled))
.orElse(null);
if (maxShape == null) {
return;
}

double maxShapeDist = maxShape.shapeDistTraveled();
double distanceInMeters =
getDistanceMeters(maxShape.shapePtLatLon(), stop.stopLatLon());
if (maxStopTimeDist > maxShapeDist) {
if (distanceInMeters > DISTANCE_THRESHOLD) {
noticeContainer.addValidationNotice(
new TripDistanceExceedsShapeDistanceNotice(
tripId, shapeId, maxStopTimeDist, maxShapeDist, distanceInMeters));
} else {
noticeContainer.addValidationNotice(
new TripDistanceExceedsShapeDistanceBelowThresholdNotice(
tripId, shapeId, maxStopTimeDist, maxShapeDist, distanceInMeters));
}
}
});
}

/** The distance traveled by a trip should be less or equal to the max length of its shape. */
/**
* The distance between the last shape point and last stop point is greater than or equal to the
* 11.1m threshold.
*/
@GtfsValidationNotice(
severity = ERROR,
files = @FileRefs({GtfsTrip.class, GtfsStopTime.class, GtfsShape.class}))
Expand All @@ -98,15 +121,57 @@ static class TripDistanceExceedsShapeDistanceNotice extends ValidationNotice {
/** The faulty record's shape max distance traveled. */
private final double maxShapeDistanceTraveled;

/** The distance in meters between the shape and the stop. */
private final double geoDistanceToShape;

TripDistanceExceedsShapeDistanceNotice(
String tripId,
String shapeId,
double maxTripDistanceTraveled,
double maxShapeDistanceTraveled) {
double maxShapeDistanceTraveled,
double geoDistanceToShape) {
this.tripId = tripId;
this.shapeId = shapeId;
this.maxShapeDistanceTraveled = maxShapeDistanceTraveled;
this.maxTripDistanceTraveled = maxTripDistanceTraveled;
this.geoDistanceToShape = geoDistanceToShape;
}
}

/**
* The distance between the last shape point and last stop point is less than the 11.1m threshold.
*/
@GtfsValidationNotice(
severity = WARNING,
files = @FileRefs({GtfsTrip.class, GtfsStopTime.class, GtfsShape.class}))
static class TripDistanceExceedsShapeDistanceBelowThresholdNotice extends ValidationNotice {

/** The faulty record's trip id. */
private final String tripId;

/** The faulty record's shape id. */
private final String shapeId;

/** The faulty record's trip max distance traveled. */
private final double maxTripDistanceTraveled;

/** The faulty record's shape max distance traveled. */
private final double maxShapeDistanceTraveled;

/** The distance in meters between the shape and the stop. */
private final double geoDistanceToShape;

TripDistanceExceedsShapeDistanceBelowThresholdNotice(
String tripId,
String shapeId,
double maxTripDistanceTraveled,
double maxShapeDistanceTraveled,
double geoDistanceToShape) {
this.tripId = tripId;
this.shapeId = shapeId;
this.maxShapeDistanceTraveled = maxShapeDistanceTraveled;
this.maxTripDistanceTraveled = maxTripDistanceTraveled;
this.geoDistanceToShape = geoDistanceToShape;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,16 @@ private static List<GtfsTrip> createTripTable(int rows) {
return trips;
}

private static List<GtfsShape> createShapeTable(int rows, double shapeDistTraveled) {
private static List<GtfsShape> createShapeTable(
int rows, double shapeDistTraveled, double lonLat) {
ArrayList<GtfsShape> shapes = new ArrayList<>();
for (int i = 0; i < rows; i++) {
shapes.add(
new GtfsShape.Builder()
.setCsvRowNumber(i + 1)
.setShapeId("s" + i)
.setShapePtLat(1.0)
.setShapePtLon(1.0)
.setShapePtLat(lonLat)
.setShapePtLon(lonLat)
.setShapePtSequence(0)
.setShapeDistTraveled(shapeDistTraveled + i)
.build());
Expand All @@ -60,38 +61,69 @@ private static List<GtfsStopTime> createStopTimesTable(int rows, double shapeDis
return stopTimes;
}

private static List<GtfsStop> createStopTable(int rows) {
ArrayList<GtfsStop> stops = new ArrayList<>();
for (int i = 0; i < rows; i++) {
stops.add(
new GtfsStop.Builder()
.setCsvRowNumber(i + 1)
.setStopId("st" + i)
.setStopLat(0.0)
.setStopLon(0.0)
.build());
}
return stops;
}

private static List<ValidationNotice> generateNotices(
List<GtfsTrip> trips, List<GtfsStopTime> stopTimes, List<GtfsShape> shapes) {
List<GtfsTrip> trips,
List<GtfsStopTime> stopTimes,
List<GtfsShape> shapes,
List<GtfsStop> stops) {
NoticeContainer noticeContainer = new NoticeContainer();
new TripAndShapeDistanceValidator(
GtfsTripTableContainer.forEntities(trips, noticeContainer),
GtfsStopTimeTableContainer.forEntities(stopTimes, noticeContainer),
GtfsStopTableContainer.forEntities(stops, noticeContainer),
GtfsShapeTableContainer.forEntities(shapes, noticeContainer))
.validate(noticeContainer);
return noticeContainer.getValidationNotices();
}

@Test
public void testTripDistanceExceedsShapeDistance() {
assertThat(
generateNotices(
createTripTable(1), createStopTimesTable(1, 10.0), createShapeTable(1, 9.0)))
.isNotEmpty();
}

@Test
public void testValidTripVsShapeDistance1() {
assertThat(
generateNotices(
createTripTable(1), createStopTimesTable(1, 10.0), createShapeTable(1, 10.0)))
.isEmpty();
List<ValidationNotice> notices =
generateNotices(
createTripTable(2),
createStopTimesTable(1, 10.0),
createShapeTable(1, 9.0, 10.0),
createStopTable(1));
boolean found =
notices.stream()
.anyMatch(
notice ->
notice
instanceof
TripAndShapeDistanceValidator.TripDistanceExceedsShapeDistanceNotice);
assertThat(found).isTrue();
}

@Test
public void testValidTripVsShapeDistance2() {
assertThat(
generateNotices(
createTripTable(1), createStopTimesTable(1, 9.0), createShapeTable(1, 10.0)))
.isEmpty();
public void testTripDistanceExceedsShapeDistanceWarning() {
List<ValidationNotice> notices =
generateNotices(
createTripTable(2),
createStopTimesTable(1, 10.0),
createShapeTable(1, 9.0, 0.000001),
createStopTable(1));
boolean found =
notices.stream()
.anyMatch(
notice ->
notice
instanceof
TripAndShapeDistanceValidator
.TripDistanceExceedsShapeDistanceBelowThresholdNotice);
assertThat(found).isTrue();
}
}

0 comments on commit 2f9fccd

Please sign in to comment.