All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.conveyal.gtfs.validator.NamesValidator Maven / Gradle / Ivy

package com.conveyal.gtfs.validator;

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 java.util.HashMap;
import java.util.Map;

import static com.conveyal.gtfs.error.NewGTFSErrorType.*;

public class NamesValidator extends FeedValidator {

    public NamesValidator(Feed feed, SQLErrorStorage errorStorage) {
        super(feed, errorStorage);
    }

    @Override
    public void validate() {
        // Check routes
        for (Route route : feed.routes) {
            String shortName = normalize(route.route_short_name);
            String longName = normalize(route.route_long_name);
            String desc = normalize(route.route_desc);
            // At least one of route_long_name and route_short_name must be supplied.
            // According to the GTFS spec these fields are required, but the logic is more complicated than for other fields.
            if (longName.isEmpty() && shortName.isEmpty()) {
                registerError(route, ROUTE_SHORT_AND_LONG_NAME_MISSING);
            }
            // Route_short_name should be really short, so it fits in a compact display e.g. on a mobile device.
            if (shortName.length() > 6) {
                registerError(route, ROUTE_SHORT_NAME_TOO_LONG, shortName);
            }
            // The long name should not contain the short name, it should contain different information.
            if (!longName.isEmpty() && !shortName.isEmpty() && longName.contains(shortName)) {
                registerError(route, ROUTE_LONG_NAME_CONTAINS_SHORT_NAME, longName);
            }
            // If provided, the description of a route should be more informative than its names.
            if (!desc.isEmpty() && (desc.equals(shortName) || desc.equals(longName))) {
                registerError(route, ROUTE_DESCRIPTION_SAME_AS_NAME, desc);
            }
            // Special range check for route_type.
            if (route.route_type < 0 || route.route_type > 7){
                // TODO we want some additional checking for extended route types.
            }
        }
        // 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);
            }
        }
        // Place routes into a map for quick access while validating trip names.
        Map routesForId = new HashMap<>();
        for (Route route : feed.routes) {
            routesForId.put(route.route_id, route);
        }
        // Check trip names (headsigns and TODO short names)
        for (Trip trip : feed.trips) {
            String headsign = normalize(trip.trip_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: check trip short name?
//            String shortName = normalize(trip.trip_short_name);
            Route route = routesForId.get(trip.route_id);
            // Skip route name/headsign check if the trip has a bad reference to its route.
            if (route == null) continue;
            String routeShortName = normalize(route.route_short_name);
            String 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);
            }
        }
        // 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 */
    private String normalize (String string) {
        if (string == null) return "";
        return string.trim().toLowerCase();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy