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+ String tripId = trip .tripId ();
64+
65+ // Get distance for trip
66+ int nbStopTimes = stopTimeTable .byTripId (tripId ).size ();
67+ if (nbStopTimes == 0 ) {
68+ return ;
69+ }
70+ GtfsStopTime lastStopTime = stopTimeTable .byTripId (tripId ).get (nbStopTimes - 1 );
71+ GtfsStop stop = stopTable .byStopId (lastStopTime .stopId ()).orElse (null );
72+ if (stop == null ) {
73+ return ;
74+ }
75+ double maxStopTimeDist = lastStopTime .shapeDistTraveled ();
76+
77+ // Get max shape distance for trip
78+ GtfsShape maxShape =
5979 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- });
80+ .max ( Comparator . comparingDouble ( GtfsShape ::shapeDistTraveled ) )
81+ .orElse ( null );
82+ if ( maxShape == null ) {
83+ return ;
84+ }
85+
86+ double maxShapeDist = maxShape . shapeDistTraveled ();
87+ double distanceInMeters =
88+ getDistanceMeters ( maxShape . shapePtLatLon (), stop . stopLatLon ());
89+ if ( maxStopTimeDist > maxShapeDist ) {
90+ if ( distanceInMeters > DISTANCE_THRESHOLD ) {
91+ noticeContainer . addValidationNotice (
92+ new TripDistanceExceedsShapeDistanceNotice (
93+ tripId , shapeId , maxStopTimeDist , maxShapeDist , distanceInMeters ));
94+ } else if (distanceInMeters > 0 ) {
95+ noticeContainer .addValidationNotice (
96+ new TripDistanceExceedsShapeDistanceBelowThresholdNotice (
97+ tripId , shapeId , maxStopTimeDist , maxShapeDist , distanceInMeters ));
98+ }
99+ }
80100 });
81101 }
82102
83- /** The distance traveled by a trip should be less or equal to the max length of its shape. */
103+ /**
104+ * The distance between the last shape point and last stop point is greater than or equal to the
105+ * 11.1m threshold.
106+ */
84107 @ GtfsValidationNotice (
85108 severity = ERROR ,
86109 files = @ FileRefs ({GtfsTrip .class , GtfsStopTime .class , GtfsShape .class }))
@@ -98,15 +121,57 @@ 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 geoDistanceToShape ;
126+
101127 TripDistanceExceedsShapeDistanceNotice (
102128 String tripId ,
103129 String shapeId ,
104130 double maxTripDistanceTraveled ,
105- double maxShapeDistanceTraveled ) {
131+ double maxShapeDistanceTraveled ,
132+ double geoDistanceToShape ) {
133+ this .tripId = tripId ;
134+ this .shapeId = shapeId ;
135+ this .maxShapeDistanceTraveled = maxShapeDistanceTraveled ;
136+ this .maxTripDistanceTraveled = maxTripDistanceTraveled ;
137+ this .geoDistanceToShape = geoDistanceToShape ;
138+ }
139+ }
140+
141+ /**
142+ * The distance between the last shape point and last stop point is less than the 11.1m threshold.
143+ */
144+ @ GtfsValidationNotice (
145+ severity = WARNING ,
146+ files = @ FileRefs ({GtfsTrip .class , GtfsStopTime .class , GtfsShape .class }))
147+ static class TripDistanceExceedsShapeDistanceBelowThresholdNotice extends ValidationNotice {
148+
149+ /** The faulty record's trip id. */
150+ private final String tripId ;
151+
152+ /** The faulty record's shape id. */
153+ private final String shapeId ;
154+
155+ /** The faulty record's trip max distance traveled. */
156+ private final double maxTripDistanceTraveled ;
157+
158+ /** The faulty record's shape max distance traveled. */
159+ private final double maxShapeDistanceTraveled ;
160+
161+ /** The distance in meters between the shape and the stop. */
162+ private final double geoDistanceToShape ;
163+
164+ TripDistanceExceedsShapeDistanceBelowThresholdNotice (
165+ String tripId ,
166+ String shapeId ,
167+ double maxTripDistanceTraveled ,
168+ double maxShapeDistanceTraveled ,
169+ double geoDistanceToShape ) {
106170 this .tripId = tripId ;
107171 this .shapeId = shapeId ;
108172 this .maxShapeDistanceTraveled = maxShapeDistanceTraveled ;
109173 this .maxTripDistanceTraveled = maxTripDistanceTraveled ;
174+ this .geoDistanceToShape = geoDistanceToShape ;
110175 }
111176 }
112177}
0 commit comments