1616package org .mobilitydata .gtfsvalidator .validator ;
1717
1818import 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 ;
2023import javax .inject .Inject ;
2124import org .mobilitydata .gtfsvalidator .annotation .GtfsValidationNotice ;
2225import org .mobilitydata .gtfsvalidator .annotation .GtfsValidationNotice .FileRefs ;
3235 */
3336@ GtfsValidator
3437public 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}
0 commit comments