From 57566ec8876faf2bf5b60b9923381657d2034709 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Wed, 19 Dec 2018 16:49:12 -0500 Subject: [PATCH] feat(validator): validate stop names and trip headsigns refs #167 --- .../conveyal/gtfs/error/NewGTFSErrorType.java | 4 ++ .../gtfs/validator/NamesValidator.java | 38 ++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/conveyal/gtfs/error/NewGTFSErrorType.java b/src/main/java/com/conveyal/gtfs/error/NewGTFSErrorType.java index 02fc5075d..b35037642 100644 --- a/src/main/java/com/conveyal/gtfs/error/NewGTFSErrorType.java +++ b/src/main/java/com/conveyal/gtfs/error/NewGTFSErrorType.java @@ -44,10 +44,14 @@ public enum NewGTFSErrorType { SERVICE_NEVER_ACTIVE(Priority.MEDIUM, "A service code was defined, but is never active on any date."), SERVICE_UNUSED(Priority.MEDIUM, "A service code was defined, but is never referenced by any trips."), SHAPE_DIST_TRAVELED_NOT_INCREASING(Priority.MEDIUM, "Shape distance traveled must increase with stop times."), + STOP_DESCRIPTION_SAME_AS_NAME(Priority.LOW, "The description of a stop is identical to its name, so does not add any information."), STOP_LOW_POPULATION_DENSITY(Priority.HIGH, "A stop is located in a geographic area with very low human population density."), + STOP_NAME_MISSING(Priority.MEDIUM, "A stop does not have a name."), STOP_GEOGRAPHIC_OUTLIER(Priority.HIGH, "This stop is located very far from the middle 90% of stops in this feed."), STOP_UNUSED(Priority.MEDIUM, "This stop is not referenced by any trips."), TRIP_EMPTY(Priority.HIGH, "This trip is defined but has no stop times."), + TRIP_HEADSIGN_CONTAINS_ROUTE_NAME(Priority.LOW, "A trip headsign contains the route name, but should only contain information to distinguish it from other trips for the route."), + TRIP_HEADSIGN_SHOULD_DESCRIBE_DESTINATION_OR_WAYPOINTS(Priority.LOW, "A trip headsign begins with 'to' or 'towards', but should begin with destination or direction and optionally include waypoints with 'via'"), TRIP_NEVER_ACTIVE(Priority.MEDIUM, "A trip is defined, but its service is never running on any date."), ROUTE_UNUSED(Priority.HIGH, "This route is defined but has no trips."), TRAVEL_DISTANCE_ZERO(Priority.MEDIUM, "The vehicle does not cover any distance between the last stop and this one."), diff --git a/src/main/java/com/conveyal/gtfs/validator/NamesValidator.java b/src/main/java/com/conveyal/gtfs/validator/NamesValidator.java index 913b56453..889a3679b 100644 --- a/src/main/java/com/conveyal/gtfs/validator/NamesValidator.java +++ b/src/main/java/com/conveyal/gtfs/validator/NamesValidator.java @@ -3,6 +3,8 @@ import com.conveyal.gtfs.error.SQLErrorStorage; import com.conveyal.gtfs.loader.Feed; import com.conveyal.gtfs.model.Route; +import com.conveyal.gtfs.model.Stop; +import com.conveyal.gtfs.model.Trip; import static com.conveyal.gtfs.error.NewGTFSErrorType.*; @@ -41,7 +43,41 @@ public void validate() { // TODO we want some additional checking for extended route types. } } - // TODO Check trips and all other tables. + // Check stops + for (Stop stop : feed.stops) { + String name = normalize(stop.stop_name); + String desc = normalize(stop.stop_desc); + // Stops must be named. + if (name.isEmpty()) { + registerError(stop, STOP_NAME_MISSING); + } + // If provided, the description of a stop should be more informative than its name. + if (!desc.isEmpty() && desc.equals(name)) { + registerError(stop, STOP_DESCRIPTION_SAME_AS_NAME, desc); + } + } + // Check trips + for (Trip trip : feed.trips) { + String headsign = normalize(trip.trip_headsign); + // TODO: check trip short name? +// String shortName = normalize(trip.trip_short_name); + Route route = feed.routes.get(trip.route_id); + String routeShortName = "", routeLongName = ""; + if (route != null) { + routeShortName = normalize(route.route_short_name); + routeLongName = normalize(route.route_long_name); + } + // Trip headsign should not duplicate route name. + if (!headsign.isEmpty() && (headsign.contains(routeShortName) || headsign.contains(routeLongName))) { + registerError(trip, TRIP_HEADSIGN_CONTAINS_ROUTE_NAME, headsign); + } + // Trip headsign should not begin with "to" or "towards" (note: headsign normalized to lowercase). Headsigns + // should follow one of the patterns defined in the best practices: http://gtfs.org/best-practices#tripstxt + if (headsign.startsWith("to ") || headsign.startsWith("towards ")) { + registerError(trip, TRIP_HEADSIGN_SHOULD_DESCRIBE_DESTINATION_OR_WAYPOINTS, headsign); + } + } + // TODO Are there other tables we're not checking? } /** @return a non-null String that is lower case and has no leading or trailing whitespace */