Skip to content

Commit 97dc096

Browse files
authored
Merge 8b5e21e into 3ba27d9
2 parents 3ba27d9 + 8b5e21e commit 97dc096

File tree

3 files changed

+150
-50
lines changed

3 files changed

+150
-50
lines changed

main/src/main/java/org/mobilitydata/gtfsvalidator/validator/TripAndShapeDistanceValidator.java

Lines changed: 96 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
package org.mobilitydata.gtfsvalidator.validator;
1717

1818
import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.ERROR;
19+
import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.WARNING;
20+
import static org.mobilitydata.gtfsvalidator.util.S2Earth.getDistanceMeters;
1921

22+
import java.util.Comparator;
2023
import javax.inject.Inject;
2124
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice;
2225
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice.FileRefs;
@@ -32,55 +35,75 @@
3235
*/
3336
@GtfsValidator
3437
public class TripAndShapeDistanceValidator extends FileValidator {
35-
3638
private final GtfsTripTableContainer tripTable;
37-
3839
private final GtfsStopTimeTableContainer stopTimeTable;
39-
40+
private final GtfsStopTableContainer stopTable;
4041
private final GtfsShapeTableContainer shapeTable;
42+
private final double DISTANCE_THRESHOLD = 11.1; // distance in meters
4143

4244
@Inject
4345
TripAndShapeDistanceValidator(
4446
GtfsTripTableContainer tripTable,
4547
GtfsStopTimeTableContainer stopTimeTable,
48+
GtfsStopTableContainer stopTable,
4649
GtfsShapeTableContainer shapeTable) {
4750
this.tripTable = tripTable;
4851
this.stopTimeTable = stopTimeTable;
4952
this.shapeTable = shapeTable;
53+
this.stopTable = stopTable;
5054
}
5155

5256
@Override
5357
public void validate(NoticeContainer noticeContainer) {
54-
shapeTable
55-
.byShapeIdMap()
58+
tripTable
59+
.getEntities()
5660
.forEach(
57-
(shapeId, shape) -> {
58-
double maxShapeDist =
61+
trip -> {
62+
String shapeId = trip.shapeId();
63+
// Get distance for trip
64+
int nbStopTimes = stopTimeTable.byTripId(trip.tripId()).size();
65+
if (nbStopTimes == 0) {
66+
return;
67+
}
68+
GtfsStopTime lastStopTime =
69+
stopTimeTable.byTripId(trip.tripId()).get(nbStopTimes - 1);
70+
GtfsStop stop = stopTable.byStopId(lastStopTime.stopId()).orElse(null);
71+
if (stop == null) {
72+
return;
73+
}
74+
double maxStopTimeDist = lastStopTime.shapeDistTraveled();
75+
76+
// Get max shape distance for trip
77+
GtfsShape maxShape =
5978
shapeTable.byShapeId(shapeId).stream()
60-
.mapToDouble(GtfsShape::shapeDistTraveled)
61-
.max()
62-
.orElse(Double.NEGATIVE_INFINITY);
63-
64-
tripTable
65-
.byShapeId(shapeId)
66-
.forEach(
67-
trip -> {
68-
double maxStopTimeDist =
69-
stopTimeTable.byTripId(trip.tripId()).stream()
70-
.mapToDouble(GtfsStopTime::shapeDistTraveled)
71-
.max()
72-
.orElse(Double.NEGATIVE_INFINITY);
73-
74-
if (maxStopTimeDist > maxShapeDist) {
75-
noticeContainer.addValidationNotice(
76-
new TripDistanceExceedsShapeDistanceNotice(
77-
trip.tripId(), shapeId, maxStopTimeDist, maxShapeDist));
78-
}
79-
});
79+
.max(Comparator.comparingDouble(GtfsShape::shapeDistTraveled))
80+
.orElse(null);
81+
if (maxShape == null) {
82+
return;
83+
}
84+
85+
double maxShapeDist = maxShape.shapeDistTraveled();
86+
double distanceInMeters =
87+
getDistanceMeters(maxShape.shapePtLatLon(), stop.stopLatLon());
88+
if (maxStopTimeDist > maxShapeDist) {
89+
if (distanceInMeters > DISTANCE_THRESHOLD) {
90+
noticeContainer.addValidationNotice(
91+
new TripDistanceExceedsShapeDistanceNotice(
92+
trip.tripId(), shapeId, maxStopTimeDist, maxShapeDist, distanceInMeters));
93+
} else if (distanceInMeters > 0) {
94+
noticeContainer.addValidationNotice(
95+
new TripDistanceExceedsShapeDistanceBellowThresholdNotice(
96+
trip.tripId(), shapeId, maxStopTimeDist, maxShapeDist, distanceInMeters));
97+
}
98+
}
8099
});
81100
}
82101

83-
/** The distance traveled by a trip should be less or equal to the max length of its shape. */
102+
/**
103+
* The distance traveled by a trip should be less or equal to the max length of its shape.
104+
*
105+
* <p>The distance is greater or equal to the 11.1m threshold.
106+
*/
84107
@GtfsValidationNotice(
85108
severity = ERROR,
86109
files = @FileRefs({GtfsTrip.class, GtfsStopTime.class, GtfsShape.class}))
@@ -98,15 +121,59 @@ static class TripDistanceExceedsShapeDistanceNotice extends ValidationNotice {
98121
/** The faulty record's shape max distance traveled. */
99122
private final double maxShapeDistanceTraveled;
100123

124+
/** The distance in meters between the shape and the stop. */
125+
private final double distance;
126+
101127
TripDistanceExceedsShapeDistanceNotice(
102128
String tripId,
103129
String shapeId,
104130
double maxTripDistanceTraveled,
105-
double maxShapeDistanceTraveled) {
131+
double maxShapeDistanceTraveled,
132+
double distance) {
133+
this.tripId = tripId;
134+
this.shapeId = shapeId;
135+
this.maxShapeDistanceTraveled = maxShapeDistanceTraveled;
136+
this.maxTripDistanceTraveled = maxTripDistanceTraveled;
137+
this.distance = distance;
138+
}
139+
}
140+
141+
/**
142+
* The distance traveled by a trip should be less or equal to the max length of its shape.
143+
*
144+
* <p>The distance is less than the 11.1m threshold.
145+
*/
146+
@GtfsValidationNotice(
147+
severity = WARNING,
148+
files = @FileRefs({GtfsTrip.class, GtfsStopTime.class, GtfsShape.class}))
149+
static class TripDistanceExceedsShapeDistanceBellowThresholdNotice extends ValidationNotice {
150+
151+
/** The faulty record's trip id. */
152+
private final String tripId;
153+
154+
/** The faulty record's shape id. */
155+
private final String shapeId;
156+
157+
/** The faulty record's trip max distance traveled. */
158+
private final double maxTripDistanceTraveled;
159+
160+
/** The faulty record's shape max distance traveled. */
161+
private final double maxShapeDistanceTraveled;
162+
163+
/** The distance in meters between the shape and the stop. */
164+
private final double distance;
165+
166+
TripDistanceExceedsShapeDistanceBellowThresholdNotice(
167+
String tripId,
168+
String shapeId,
169+
double maxTripDistanceTraveled,
170+
double maxShapeDistanceTraveled,
171+
double distance) {
106172
this.tripId = tripId;
107173
this.shapeId = shapeId;
108174
this.maxShapeDistanceTraveled = maxShapeDistanceTraveled;
109175
this.maxTripDistanceTraveled = maxTripDistanceTraveled;
176+
this.distance = distance;
110177
}
111178
}
112179
}

main/src/test/java/org/mobilitydata/gtfsvalidator/validator/NoticeFieldsTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ public void testNoticeClassFieldNames() {
194194
"value",
195195
"maxShapeDistanceTraveled",
196196
"maxTripDistanceTraveled",
197+
"distance",
197198
"fileNameA",
198199
"fileNameB");
199200
}

main/src/test/java/org/mobilitydata/gtfsvalidator/validator/TripAndShapeDistanceValidatorTest.java

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,16 @@ private static List<GtfsTrip> createTripTable(int rows) {
2929
return trips;
3030
}
3131

32-
private static List<GtfsShape> createShapeTable(int rows, double shapeDistTraveled) {
32+
private static List<GtfsShape> createShapeTable(
33+
int rows, double shapeDistTraveled, double lonLat) {
3334
ArrayList<GtfsShape> shapes = new ArrayList<>();
3435
for (int i = 0; i < rows; i++) {
3536
shapes.add(
3637
new GtfsShape.Builder()
3738
.setCsvRowNumber(i + 1)
3839
.setShapeId("s" + i)
39-
.setShapePtLat(1.0)
40-
.setShapePtLon(1.0)
40+
.setShapePtLat(lonLat)
41+
.setShapePtLon(lonLat)
4142
.setShapePtSequence(0)
4243
.setShapeDistTraveled(shapeDistTraveled + i)
4344
.build());
@@ -60,38 +61,69 @@ private static List<GtfsStopTime> createStopTimesTable(int rows, double shapeDis
6061
return stopTimes;
6162
}
6263

64+
private static List<GtfsStop> createStopTable(int rows) {
65+
ArrayList<GtfsStop> stops = new ArrayList<>();
66+
for (int i = 0; i < rows; i++) {
67+
stops.add(
68+
new GtfsStop.Builder()
69+
.setCsvRowNumber(i + 1)
70+
.setStopId("st" + i)
71+
.setStopLat(0.0)
72+
.setStopLon(0.0)
73+
.build());
74+
}
75+
return stops;
76+
}
77+
6378
private static List<ValidationNotice> generateNotices(
64-
List<GtfsTrip> trips, List<GtfsStopTime> stopTimes, List<GtfsShape> shapes) {
79+
List<GtfsTrip> trips,
80+
List<GtfsStopTime> stopTimes,
81+
List<GtfsShape> shapes,
82+
List<GtfsStop> stops) {
6583
NoticeContainer noticeContainer = new NoticeContainer();
6684
new TripAndShapeDistanceValidator(
6785
GtfsTripTableContainer.forEntities(trips, noticeContainer),
6886
GtfsStopTimeTableContainer.forEntities(stopTimes, noticeContainer),
87+
GtfsStopTableContainer.forEntities(stops, noticeContainer),
6988
GtfsShapeTableContainer.forEntities(shapes, noticeContainer))
7089
.validate(noticeContainer);
7190
return noticeContainer.getValidationNotices();
7291
}
7392

7493
@Test
7594
public void testTripDistanceExceedsShapeDistance() {
76-
assertThat(
77-
generateNotices(
78-
createTripTable(1), createStopTimesTable(1, 10.0), createShapeTable(1, 9.0)))
79-
.isNotEmpty();
80-
}
81-
82-
@Test
83-
public void testValidTripVsShapeDistance1() {
84-
assertThat(
85-
generateNotices(
86-
createTripTable(1), createStopTimesTable(1, 10.0), createShapeTable(1, 10.0)))
87-
.isEmpty();
95+
List<ValidationNotice> notices =
96+
generateNotices(
97+
createTripTable(2),
98+
createStopTimesTable(1, 10.0),
99+
createShapeTable(1, 9.0, 10.0),
100+
createStopTable(1));
101+
boolean found =
102+
notices.stream()
103+
.anyMatch(
104+
notice ->
105+
notice
106+
instanceof
107+
TripAndShapeDistanceValidator.TripDistanceExceedsShapeDistanceNotice);
108+
assertThat(found).isTrue();
88109
}
89110

90111
@Test
91-
public void testValidTripVsShapeDistance2() {
92-
assertThat(
93-
generateNotices(
94-
createTripTable(1), createStopTimesTable(1, 9.0), createShapeTable(1, 10.0)))
95-
.isEmpty();
112+
public void testTripDistanceExceedsShapeDistanceWarning() {
113+
List<ValidationNotice> notices =
114+
generateNotices(
115+
createTripTable(2),
116+
createStopTimesTable(1, 10.0),
117+
createShapeTable(1, 9.0, 0.000001),
118+
createStopTable(1));
119+
boolean found =
120+
notices.stream()
121+
.anyMatch(
122+
notice ->
123+
notice
124+
instanceof
125+
TripAndShapeDistanceValidator
126+
.TripDistanceExceedsShapeDistanceBellowThresholdNotice);
127+
assertThat(found).isTrue();
96128
}
97129
}

0 commit comments

Comments
 (0)